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()等内部操作的跳转表。

你可能感兴趣的:(Linux内核源代码情景分析-虚拟文件系统)