我们先来看两张图:
第一张是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()等内部操作的跳转表。