我们先来看两张图:
第一张是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);
}
特殊文件,命名管道的打开请参考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()等内部操作的跳转表。