Linux内核源代码情景分析-虚拟文件系统

我们先来看两张图:

第一张是VFS与具体文件系统的关系示意图:

第二张是Linux文件系统的层次结构:

特殊文件:用来实现”管道“的文件,特别是"命名管道"的FIFO文件,还有Unix域的socket,也都属于特殊文件;还有在/proc目录下的一系列文件。

磁盘文件:就是存在硬盘上的文件。

设备文件:sudo mount -t ext2 /dev/sdb1 /mnt/sdb,这里的/dev/sdb1就是设备文件。如果硬盘上的节点raw_inode->i_block[block],如果得到是目录节点的inode,那么i_block[]存储着目录项的位置。如果是文件节点的inode,那么i_block[]存储着真正数据的位置,现在设备节点的inode存储着设备号(包含了主设备号和次设备号)。

区别的他们的代码如下,ext2_read_inode代码如下:

if (inode->i_ino == EXT2_ACL_IDX_INO ||
	    inode->i_ino == EXT2_ACL_DATA_INO)
		/* Nothing to do */ ;
	else if (S_ISREG(inode->i_mode)) {//硬盘文件,普通文件
		inode->i_op = &ext2_file_inode_operations;
		inode->i_fop = &ext2_file_operations;
		inode->i_mapping->a_ops = &ext2_aops;
	} else if (S_ISDIR(inode->i_mode)) {//硬盘文件,目录文件
		inode->i_op = &ext2_dir_inode_operations;
		inode->i_fop = &ext2_dir_operations;
	} else if (S_ISLNK(inode->i_mode)) {//硬盘文件,链接文件
		if (!inode->i_blocks)
			inode->i_op = &ext2_fast_symlink_inode_operations;
		else {
			inode->i_op = &page_symlink_inode_operations;
			inode->i_mapping->a_ops = &ext2_aops;
		}
	} else
		init_special_inode(inode, inode->i_mode,
				   le32_to_cpu(raw_inode->i_block[0]));
void init_special_inode(struct inode *inode, umode_t mode, int rdev)
{
	inode->i_mode = mode;
	if (S_ISCHR(mode)) {//设备文件,字符设备
		inode->i_fop = &def_chr_fops;
		inode->i_rdev = to_kdev_t(rdev);
	} else if (S_ISBLK(mode)) {//设备文件,块设备
		inode->i_fop = &def_blk_fops;
		inode->i_rdev = to_kdev_t(rdev);
		inode->i_bdev = bdget(rdev);
	} else if (S_ISFIFO(mode))//特殊文件,命名管道
		inode->i_fop = &def_fifo_fops;
	else if (S_ISSOCK(mode))//特殊文件,socket文件
		inode->i_fop = &bad_sock_fops;
	else
		printk(KERN_DEBUG "init_special_inode: bogus imode (%o)\n", mode);
}

一、我们对比下open对特殊文件,命名管道和硬盘文件,普通文件来做对比。

特殊文件,命名管道的打开请参考Linux内核源代码情景分析-进程间通信-命名管道

硬盘文件,普通文件的打开请参考Linux内核源代码情景分析-文件的打开。f->f_op已经指向了ext2_file_operations。

两者不同之处在于dentry_open()时,f->f_op->open,普通文件指向ext2_open_file;而命名管道指向fifo_open。

f->f_op = fops_get(inode->i_fop);//f->f_op被赋值为inode_i_fop
if (inode->i_sb)
    file_move(f, &inode->i_sb->s_files);//将其从中间队列脱链而挂入该文件所在设备的super_block结构中的file结构队列s_files
if (f->f_op && f->f_op->open) {
    error = f->f_op->open(inode,f);//普通文件指向ext2_open_file;而命名管道指向fifo_open
    if (error)
        goto cleanup_all;
}   

另外在open中调用path_walk指向的过程中也会因为dentry->d_op和inode->i_op的不同执行的代码也不同。

二、对比普通文件的读和管道文件的读的不同。

普通文件的读,read映射到内核是sys_read,代码如下:

asmlinkage ssize_t sys_read(unsigned int fd, char * buf, size_t count)
{
	ssize_t ret;
	struct file * file;

	ret = -EBADF;
	file = fget(fd);
	if (file) {
		if (file->f_mode & FMODE_READ) {
			ret = locks_verify_area(FLOCK_VERIFY_READ, file->f_dentry->d_inode,
						file, file->f_pos, count);
			if (!ret) {
				ssize_t (*read)(struct file *, char *, size_t, loff_t *);
				ret = -EINVAL;
				if (file->f_op && (read = file->f_op->read) != NULL)
					ret = read(file, buf, count, &file->f_pos);//generic_file_read
			}
		}
		if (ret > 0)
			inode_dir_notify(file->f_dentry->d_parent->d_inode,
				DN_ACCESS);
		fput(file);
	}
	return ret;
}

硬盘文件,普通文件的打开请参考Linux内核源代码情景分析-文件的打开。f->f_op已经指向了ext2_file_operations。所以file->f_op->read指向generic_file_read。

管道文件的读请参考Linux内核源代码情景分析-进程间通信-管道。f->f_op指向pipe_read。

三、不同文件系统super_block结构中的指针s_op指向具体的super_operations数据结构。

参考Linux内核源代码情景分析-从路径名到目标节点,get_new_inode相关代码:

static struct inode * get_new_inode(struct super_block *sb, unsigned long ino, struct list_head *head, find_inode_t find_actor, void *opaque)
{
	struct inode * inode;

	inode = alloc_inode();
	if (inode) {
		struct inode * old;

		spin_lock(&inode_lock);
		/* We released the lock, so.. */
		old = find_inode(sb, ino, head, find_actor, opaque);//再一次在杂凑表队列中寻找
		if (!old) {//如果没有找到
			inodes_stat.nr_inodes++;
			list_add(&inode->i_list, &inode_in_use);
			list_add(&inode->i_hash, head);//加入到对应的hash表
			inode->i_sb = sb;//超级块结构
			inode->i_dev = sb->s_dev;//设备号
			inode->i_ino = ino;//节点号
			inode->i_flags = 0;
			atomic_set(&inode->i_count, 1);
			inode->i_state = I_LOCK;
			spin_unlock(&inode_lock);

			clean_inode(inode);
			sb->s_op->read_inode(inode);//根据不同的文件系统指向不同的代码

			/*
			 * This is special!  We do not need the spinlock
			 * when clearing I_LOCK, because we‘re guaranteed
			 * that nobody else tries to do anything about the
			 * state of the inode when it is locked, as we
			 * just created it (so there can be no old holders
			 * that haven‘t tested I_LOCK).
			 */
			inode->i_state &= ~I_LOCK;
			wake_up(&inode->i_wait);

			return inode;
		}

		/*
		 * Uhhuh, somebody else created the same inode under
		 * us. Use the old inode instead of the one we just
		 * allocated.
		 */
		__iget(old);//如果找到了inode结构
		spin_unlock(&inode_lock);
		destroy_inode(inode);
		inode = old;//使用找到的inode结构
		wait_on_inode(inode);
	}
	return inode;
}

sb->s_op->read_inode(inode);//根据不同的文件系统指向不同的代码。

总结:

我们把文件系统比喻作"接口卡",而把虚拟文件系统VFS比喻成一条插槽。因此,file结构中的指针f_op就可以看作插槽中的一个触点,并且在dentry、inode、super_operations数据结构中都有类似的触点。

我们在上文也看到了。主要是以下不同:

文件操作跳转表,即file_operations数据结构:file结构中的指针f_op指向具体的file_operations结构,这是open()、read()、write()等文件操作的跳转表。一种文件系统并不只限于一个file_operations结构,如ext2就有两个这样的数据结构,分别用于普通文件和目录文件。

目录项操作跳转表:即dentry_operations数据结构:dentry结构中的指针d_op指向具体的dentry_operations数据结构,这是内核中hash()、compare()等内部操作的跳转表。

索引节点操作跳转表,即inode_operations数据结构;inode结构中的指针i_op指向具体的inode_operations数据结构,lookup()、permissions()等内部函数的跳转表。

超级块操作跳转表,即super_operations数据结构:super_block结构中的指针s_op指向具体的super_operations数据结构,这是read_inode()、write_inode()、delete_inode()等内部操作的跳转表。

时间: 2024-11-10 16:15:20

Linux内核源代码情景分析-虚拟文件系统的相关文章

Linux内核源代码情景分析-特殊文件系统/proc-对/proc/self/cwd的访问

继上篇文章Linux内核源代码情景分析-特殊文件系统/proc,我们对/proc/loadavg访问后,这篇文章是对/proc/self/cwd的访问. int __user_walk(const char *name, unsigned flags, struct nameidata *nd) { char *tmp; int err; tmp = getname(name);//在系统空间分配一个页面,并从用户空间把文件名复制到这个页面 err = PTR_ERR(tmp); if (!IS

Linux内核源代码情景分析-设备文件系统devfs

我们以前多次讲过到,以主设备号/次设备号为基础的设备文件管理方式是有根本性的缺点的.这种从Unix早期一直沿用下来的方案一方面给设备号的管理带来了麻烦,一方面也破坏了/dev目录结构.Unix/Linux系统中的所有目录的结构都是层次的,惟独/dev目录是"平面"的.这不光是风格的问题,也直接影响着访问的效率和管理的方便与否. 那么理想中的/dev目录应该是什么样的呢?首先,它应该是层次的.树状的.其次,它的规模应该是可伸缩的,而且不受数量的限制(例如256个主设备号).还有,/dev

Linux内核源代码情景分析-特殊文件系统/proc

由于proc文件系统并不物理地存在于任何设备上,它的安装过程是特殊的.对proc文件系统不能直接通过mount()来安装,而要先由系统内核在内核初始化时自动地通过一个函数kern_mount()安装一次,然后再由处理系统初始化的进程通过mount()安装,实际上是"重安装". 一.在内核初始化时调用init_proc_fs(),代码如下: static DECLARE_FSTYPE(proc_fs_type, "proc", proc_read_super, FS_

Linux内核源代码情景分析-文件系统的安装

执行sudo mount -t ext2 /dev/sdb1 /mnt/sdb,将文件系统挂在到/mnt/sdb上.系统调用mount,映射到内核层执行的是sys_mount.假设/dev/sdb1和/mnt/sdb都位于ext2文件系统中. asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type, unsigned long flags, void * data)//dev_name指向了"/dev/sdb

Linux内核源代码情景分析-mmap后,文件与虚拟区间建立映射

一.文件映射的页面换入 在mmap后,mmap参考Linux内核源代码情景分析-系统调用mmap(),当这个区间的一个页面首次受到访问时,会由于见面无映射而发生缺页异常,相应的异常处理程序do_no_page(). static inline int handle_pte_fault(struct mm_struct *mm, struct vm_area_struct * vma, unsigned long address, int write_access, pte_t * pte) {

Linux内核源代码情景分析-交换分区

在Linux内核源代码情景分析-共享内存中,共享内存,当内存紧张时是换出到交换分区. 在Linux内核源代码情景分析-mmap后,文件与虚拟区间建立映射中,文件映射的页面,当内存紧张时是换出到硬盘上的文件中. 这里的交换分区,就是是swap分区,记得给电脑安装ubuntu时,就有一项是swap分区. 交换分区和文件的区别是: 文件是在一个具体的文件系统之下的,交换分区没有这个必要,它可能是一个裸分区,不需要文件系统. 需要说明一点,并不是所有从物理内存中交换出来的数据都会被放到Swap中(如果这

Linux内核源代码情景分析-fork()

父进程fork出子进程: fork经过系统调用,来到了sys_fork,详细过程请参考Linux内核源代码情景分析-系统调用. asmlinkage int sys_fork(struct pt_regs regs) { return do_fork(SIGCHLD, regs.esp, &regs, 0); } int do_fork(unsigned long clone_flags, unsigned long stack_start, //stack_start为用户空间堆栈指针 str

Linux内核源代码情景分析系列

http://blog.sina.com.cn/s/blog_6b94d5680101vfqv.html Linux内核源代码情景分析---第五章 文件系统 5.1 概述 构成一个操作系统最重要的就是 进程管理 与 文件系统: 有些操作系统有进程管理而没有文件系统,有些操作系统有文件系统而没有进程管理(MSDOS):两者都没有那就不是操作系统了: 狭义的文件:指磁盘文件,进入指可以是有序地存储在任何介质中(包括内存)的一组信息. 广义的文件:(unix把外部设备也当成文件)凡是可以产生或消耗信息

Linux内核源代码情景分析-系统调用mknod

普通文件可以用open或者create创建,FIFO文件可以用pipe创建,mknod主要用于设备文件的创建. 在内核中,mknod是由sys_mknod实现的,代码如下: asmlinkage long sys_mknod(const char * filename, int mode, dev_t dev) //比如filename为/tmp/server_socket,dev是设备号 { int error = 0; char * tmp; struct dentry * dentry;