分享

vfs2

 木芙蓉的图书馆 2011-05-11
四) VFS 数据结构分析 
现在我们已经大致了解了VFS操作的基本过程。下面我们分析一下在VFS中使用的几个重要的数据结构,它们是VFS实现的核心,更是与逻辑文件系统交互的接口,因此必须进行详细的分析。 

1 VFS超级块及其操作 
许多逻辑文件系统都有超级块结构,超级块是这些文件系统中最重要的数据结构,用来描述整个文件系统的信息,是一个全局的数据结构。MINIX、EXT2等 都有自己的超级块,VFS也有超级块,但和逻辑文件系统的超级块不同,VFS超级块是存在于内存中的结构,它在逻辑文件系统安装时建立,并且在文件系统卸 载时自动删除,因此,VFS对于每一个逻辑文件系统,都有一个对应的VFS超级块。 

VFS超级块在include/fs/fs.h中定义,即数据结构super_block,该结构主要定义如下: 
struct super_block { 
struct list_head s_list; /* Keep this first */ 
kdev_t s_dev; 
unsigned long s_blocksize; 
unsigned char s_blocksize_bits; 
unsigned char s_lock; 
unsigned char s_dirt; 
unsigned long long s_maxbytes; /* Max file size */ 
struct file_system_type *s_type; 
struct super_operations *s_op; 
struct dquot_operations *dq_op; 
unsigned long s_flags; 
unsigned long s_magic; 
struct dentry *s_root; 
wait_queue_head_t s_wait; 
struct list_head s_dirty; /* dirty inodes */ 
struct list_head s_files; 
struct block_device *s_bdev; 
struct list_head s_mounts; /* vfsmount(s) of this one */ struct quota_mount_options s_dquot; /*Diskquota specific options */ 
union { 
struct minix_sb_info minix_sb; 
struct ext2_sb_info ext2_sb; 
…… 
…… 
void *generic_sbp; 
} u; 
struct semaphore s_vfs_rename_sem; /*Kludge */ 
struct semaphore s_nfsd_free_path_sem; 
}; 

下面对该结构的主要域进行一个简单的分析: 
s_list:所有已装载文件系统的双向链表(参考 linux/list.h)。 

s_dev:装载该文件系统的设备(可以是匿名设备)标识号,举例来说,对于/dev/hda1,其设备标识号为ox301。
s_blocksize:该文件系统的基本数据块的大小。以字节为单位,并且必须是2的n次方。 

s_blocksize_bits:块大小所占的位数,即log2(s_blocksize)。 

s_lock:用来指出当前超级块是否被锁住。 

s_wait:这是一个等待队列,其中的进程都在等待该超级块的s_lock。

s_dirt:这是一个标志位。当超级块被改变时,将置位;当超级块被写入设备时,将清位。(当文件系统被卸载或者调用sync 时,有可能会将超级块写入设备。) 

s_type:指向文件系统的file_system_type结构。 

s_op:指向一个超级块操作集super_operations,我们将在后面进行讨论。

dq_op:指向一个磁盘限额(DiscQuota)操作集。 

s_flags:这是一组操作权限标志,它将与索引节点的标志进行逻辑或操作,从而确定某一特定的行为。这里有一个标志,可以应用于整个文件系统,就是 MS_RDONLY。一个设置了如此标志的文件系统将被以只读的方式装载,任何直接或者间接的写操作都被禁止,包括超级块中装载时间和文件访问时间的改变 等等。 

s_root:这是一个指向dentry结构的指针。它指向该文件系统的根。通常它是由装载文件系统的根结点(root inode)时创建的,并将它传递给d_alloc_root。这个dentry将被mount命令加入到dcache中。

s_dirty:“脏”索引节点的链表。当一个索引节点被mark_inode_dirty标志为“脏”时,该索引节点将被放入到这个链表中;当sync_inode被调用时,这个链表中的所有索引节点将被传递给该文件系统的write_inode方法。

s_files:该文件系统所有打开文件的链表。

u.generic_sbp:在联合结构u中,包括了一个文件系统特定的超级块信息,在上面的结构中,我们可以看到有minix_sb 和ext2_sb 等等结构。这些信息是编译时可知的信息,对于那些当作模块装载的文件系统,则必须分配一个单独的结构,并且将地址放入u.generic_sbp中。

s_vfs_rename_sem:这个信号量可以在整个文件系统的范围内使用,当重命名一个目录的时候,将使用它来进行锁定。这是为了防止把一个目录重命名为它自己的子目录。当重命名的目标不是目录时,则不使用该信号量。 

针对上面的超级块,定义了一组方法,也叫作操作,在结构super_operations中: 
struct super_operations { 
void (*read_inode) (struct inode *); 
void (*read_inode2) (struct inode *, void *) ; 
void (*dirty_inode) (struct inode *); 
void (*write_inode) (struct inode *, int); 
void (*put_inode) (struct inode *); 
void (*delete_inode) (struct inode *); 
void (*put_super) (struct super_block *); 
void (*write_super) (struct super_block *); 
void (*write_super_lockfs) (struct super_block *); 
void (*unlockfs) (struct super_block *); 
int (*statfs) (struct super_block *, struct statfs *); 
int (*remount_fs) (struct super_block *, 
int *, char *); 
void (*clear_inode) (struct inode *); 
void (*umount_begin) (struct super_block *); 
}; 

因此在实现实现自己的逻辑文件系统时,我们必须提供一套自己的超级块操作函数。对这些函数的调用都来自进程正文(process context),而不是来自在中断例程或者bottom half,并且所有的方法调用时,都会使用内核锁,因此,操作可以安全地阻塞,但我们也要避免并发地访问它们。

根据函数的名字,我们可以大概地了解其功能,下面简单地介绍一下: 
read_inode:该方法是从一个装载的文件系统中读取一个指定的索引节点。它由get_new_inode调用,而get_new_inode则由fs/inode.c中的iget调用。一般来说,文件系统使用iget来读取特定的索引节点。 

write_inode:当一个文件或者文件系统要求sync时,该方法会被由mark_inode_dirty标记为“脏”的索引节点调用,用来确认所有信息已经写入设备。 

put_inode:如果该函数被定义了,则每当一个索引节点的引用计数减少时,都会被调用。这并不意味着该索引节点已经没人使用了,仅仅意味着它减少了 一个用户。要注意的是,put_inode在i_count减少之前被调用,所以,如果put_inode想要检查是否这是最后一个引用,则应检查 i_count是否为1。大多数文件系统都会定义该函数,用来在一个索引节点的引用计数减少为0之前做一些特殊的工作。 

delete_inode:如果被定义,则当一个索引节点的引用计数减少至0,并且链接计数(i_nlink)也是0的时候,便调用该函数。以后,这个函数有可能会与上一个函数合并。 

notify_change:当一个索引节点的属性被改变时,会调用该函数。它的参数struct iattr *指向一个新的属性组。如果一个文件系统没有定义该方法(即NULL),则VFS会调用例程fs/iattr.c:inode_change_ok,该方 法实现了一个符合POSIX标准的属性检验,然后VFS会将该索引节点标记为“脏”。如果一个文件系统实现了自己的notify_change方法,则应 该在改变属性后显式地调用mark_inode_dirty(inode)方法。 

put_super:在umount(2)系统调用的最后一步,即将入口从vfsmntlist中移走之前,会调用该函数。该函数调用时,会对 super_block上锁。一般来说,文件系统会针对这个装载实例,释放特有的私有资源,比如索引节点位图,块位图。如果该文件系统是由动态装载模块实 现的,则一个buffer header将保存该super_block,并且减少模块使用计数。 

write_super:当VFS决定要将超级块写回磁盘时,会调用该函数。有三个地方会调用它:fs/buffer.c:fs_fsync,fs/super.c:sync_supers和fs/super.c:do_umount,显然只读文件系统不需要这个函数。 

statfs:这个函数用来实现系统调用statfs(2),并且如果定义了该函数,会被fs/open.c:sys_statfs调用,否则将返回ENODEV错误。 

remountfs:当文件系统被重新装载时,也就是说,当mount(2)系统调用的标志MS_REMOUNT被设置时,会调用该函数。一般用来在不卸载文件系统的情况下,改变不同的装载参数。比如,把一个只读文件系统变成可写的文件系统。 

clear_inode:可选方法。当VFS清除索引节点的时候,会调用该方法。当一个文件系统使用了索引节点结构中的generic_ip域,向索引节点增加了特别的(使用kmalloc动态分配的)数据时,便需要此方法来做相应的处理。 

2 VFS的文件及其操作 
文件对象使用在任何需要读写的地方,包括通过文件系统,或者管道以及网络等进行通讯的对象。文件对象和进程关系紧密,进程通过文件描述符(file descriptors)来访问文件。文件描述符是一个整数,linux通过fs.h中定义的NR_OPEN来规定每个进程最多同时使用的文件描述符个数: 
#define NR_OPEN (1024*1024) 

一共有三个与进程相关的结构,第一个是files_struct,在include/linux/sched.h中定义,主要是一个fd数组,数组的下标是文件描述符,其内容就是对应的下面将要介绍的file结构。

另外一个结构是fs_struct,主要有两个指针,pwd指向当前工作目录的索引节点;root指向当前工作目录所在文件系统的根目录的索引节点。 

最后一个结构是file结构,定义它是为了保证进程对文件的私有记录,以及父子进程对文件的共享,这是一个非常巧妙的数据结构。我们将在下面进行详细的分析。 

仔细分析其联系,对于我们理解进程对文件的访问操作很有帮助。 
结构file定义在linux/fs.h中: 
struct file { 
struct list_head f_list; 
struct dentry *f_dentry; 
struct vfsmount *f_vfsmnt; 
struct file_operations *f_op; 
atomic_t f_count; 
unsigned int f_flags; 
mode_t f_mode; 
loff_t f_pos; 
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; 
struct fown_struct f_owner; 
unsigned int f_uid, f_gid; 
int f_error; 
unsigned long f_version; 
/* needed for tty driver, and maybe others */ 
void *private_data; 
}; 
下面对其作一个简单的分析: 
f_list:该域将文件链接到打开的文件链表中,链表由超级块中的s_files开始。

f_dentry:该域指向该文件的索引节点的dcache入口。如果文件的索引节点不在普通的文件系统中,而是诸如管道pipe之类的对象,那么,dentry将是一个由d_alloc_root创建的root dentry。 

f_vfsmnt:该域指向该文件所在文件系统的vfsmount结构。 

f_op:指向应用于文件的操作集。 

f_count:引用该文件的计数。是用户进程的引用数加上内部的引用数。 

f_flags:该域存储了进程对该文件的访问类型,比如O_NONBLOCK,O_APPEND等等。有些标志比如O_EXCL,O_CREAT等等,只在打开文件的时候使用,因此并不存储在f_flags中。

f_mode:对文件的操作标志,只读,只写,以及读写。 

f_pos:该域存储了文件的当前位置。 

f_reada, f_ramax, f_raend, f_ralen, f_rawin:这五个域用来跟踪对文件的连续访问,并决定预读多少内容。 

f_owner:该结构存储了一个进程id,以及当特定事件发生在该文件时发送的一个信号,比如当有新数据到来的时候等等。 

f_uid, f_gid:打开该文件的进程的uid和gid,没有实际的用途。 

f_version:用来帮助底层文件系统检查cache的状态是否合法。当f_pos变化时,它的值就会发生变化。 
private_data:这个域被许多设备驱动所使用,有些特殊的文件系统为每一个打开的文件都保存一份额外的数据(如coda),也会使用这个域。 

下面我们看一看针对文件的操作,在file结构中,有一个指针指向了一个文件操作集file_operations,它在linux/fs.h中被定义:
struct file_operations { 
struct module *owner; 
loff_t (*llseek) (struct file *, loff_t, int); 
ssize_t (*read) (struct file *, char *, size_t, loff_t *); 
ssize_t (*write) (struct file *, const char *, size_t, loff_t *); 
int (*readdir) (struct file *, void *, filldir_t); 
unsigned int (*poll) (struct file *, struct poll_table_struct *); 
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 
int (*mmap) (struct file *, struct vm_area_struct *); 
int (*open) (struct inode *, struct file *); 
int (*flush) (struct file *); 
int (*release) (struct inode *, struct file *); 
int (*fsync) (struct file *, struct dentry *, int datasync); 
int (*fasync) (int, struct file *, int); 
int (*lock) (struct file *, int, struct file_lock *); 
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); 
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); 
}; 

这些操作用来将VFS对file结构的操作转化为逻辑文件系统处理相应操作的函数。因此,要了解一个逻辑文件系统,就要从这些接口函数入手。下面对这些操作进行一个简单的分析: 
llseek:该函数用来实现lseek系统调用。如果它没有定义,则缺省执行fs/read_write.c中的default_llseek函数。它将更新fs_pos域,并且,也有可能会改变f_reada和f_version域。 

read:该函数用来实现read系统调用,同时也支持其他诸如装载可执行文件等等操作。 

write:该方法用来写文件。但它并不关心数据是否真正写入到设备,只将数据放入队列中。 

readdir:该函数从一个假定为目录的文件读取目录结构,并且使用回调函数filldir_t将其返回。当readdir到达目录的结尾处时,它会返回0。 

poll:该函数用来实现select和poll系统调用。 

ioctl:该函数实现专门的ioctl功能。如果一个ioctl请求不在标准请求中(FIBMAP,FIGETBSZ,FIONREAD),那么该请求将传递给底层的文件实现。 

mmap:该例程用来实现文件的内存映射。它通常使用generic_file_map来实现。使用它的任务会被检验是否允许作映射,并且会设置vm_area_struct中的vm_ops。 

open:如果该方法被定义,那么当一个新的文件在索引节点上被打开时,会调用它。它可以做一些打开文件所必须的设置。在许多文件系统上,都不需要它。一个例外是coda,它需要在打开时试图获得本地缓存的文件。 

flush:当一个文件描述符被关闭时,会调用该函数。由于此时可能有其他的描述符在该文件上被打开,因此,它并不意味着该文件被最终关闭。目前在文件系统中,只有NFS的客户端定义了该方法。 

release:当文件的最后一个句柄被关闭时,release将被调用。它会做一些必要的清理工作。该函数不能向任何方面返回错误值,因此应该将其定义为void。

fsync:该方法用来实现fsync和fdatasync系统调用(它们一般是相同的)。它将一直等到所有对该文件挂起的写操作全部成功写到设备后才返 回。fsync可以部分地通过generic_buffer_fdatasync实现,这个函数将索引节点映射的页面中所有标记为脏的缓冲区,全部写回。 

fasync:该方法在一个文件的FIOASYNC标志被改变的时候被调用。它的int类型的参数包含了该标志位的新值。目前还没有文件系统实现该方法。

lock:该方法允许一个文件服务提供额外的POSIX锁。它不被FLOCK类型的锁使用,它对于网络文件系统比较有用。 

3 VFS索引节点及其操作 
Linux 维护了一个活动的及最近使用过的索引节点的高速缓存(cache)。有两种方法来访问这些索引节点。第一种是通过dcache,我们将在下一节介绍。在 dcache中的每一个dentry都指向一个索引节点,并且因此而将索引节点维护在缓存中。第二种方法是通过索引节点的哈希表。每一个索引节点都被基于该文件系统超级块的地址和索引节点的编号,被哈希为一个8位的数字。所有拥有同样哈希值的索引节点通过双项链表被链接在一起。 

通过哈希表访问是通过函数iget而实现的。iget只被个别的文件系统实现所调用(当索引节点不再dcache中而进行查找的时候)。 

下面我们来分析索引节点inode的结构,在include/linux/fs.h中有inode的定义: 
struct inode { 
struct list_head i_hash; 
struct list_head i_list; 
struct list_head i_dentry; 
struct list_head i_dirty_buffers; 
unsigned long i_ino; 
atomic_t i_count; 
kdev_t i_dev; 
umode_t i_mode; 
nlink_t i_nlink; 
uid_t i_uid; 
gid_t i_gid; 
kdev_t i_rdev; 
loff_t i_size; 
time_t i_atime; 
time_t i_mtime; 
time_t i_ctime; 
unsigned long i_blksize; 
unsigned long i_blocks; 
unsigned long i_version; 
unsigned short i_bytes; 
struct semaphore i_sem; 
struct semaphore i_zombie; 
struct inode_operations *i_op; 
struct file_operations *i_fop; 
struct super_block * i_shadow; 
struct inode_shadow_operations * i_shadow_op; 
struct super_block *i_sb; 
wait_queue_head_t i_wait; 
struct file_lock *i_flock; 
struct address_space *i_mapping; 
struct address_space i_data; 
struct dquot *i_dquot[MAXQUOTAS]; 
struct pipe_inode_info *i_pipe; 
struct block_device *i_bdev; 
unsigned long i_dnotify_mask; /* Directory notify events */ 
struct dnotify_struct *i_dnotify; /* for directory notifications */ 
unsigned long i_state; 
unsigned int i_flags; 
unsigned char i_sock; 
atomic_t i_writecount; 
unsigned int i_attr_flags; 
__u32 i_generation; 
union { 
struct minix_inode_info minix_i; 
struct ext2_inode_info ext2_i; 
……… 
(略) 
struct proc_inode_info proc_i; 
struct socket socket_i; 
struct usbdev_inode_info usbdev_i; 
struct supermount_inode_info supermount_i; 
void *generic_ip; 
} u; 
}; 
下面我们对它所一个分析,在上面的结构中,大部分字段的意义都很明显,因此我们将对一些特殊的字段(针对linux)和一些特殊的地方进行分析。 

i_hash:i_hash将所有拥有相同哈希值的索引节点链接在一起。哈希值基于超级块结构的地址和索引节点的索引号。 

i_list:i_list用来将索引节点链接到不同的状态上。inode_in_use链表将正在使用的未改变的索引节点链接在一 起,inode_unused将未使用的索引节点链接在一起,而superblock->s_dirty维护指定文件系统内所有标记为“脏”的索引 节点。 

i_dentry:i_dentry链表中,链接了所有引用该索引节点的dentry结构。它们通过dentry中的d_alias链接在一起。 

i_version:它被文件系统用来记录索引节点的改变。一般来说,i_version被设置为全局变量event的值,然后event回自增。有时候文件系统的代码会把i_version的当前值分配给相关的file结构中的f_version,在随后file结构的应用中,它可以被用来告诉我们,inode是否被改变了,如果需要的话,在file结构中缓存的数据要被刷新。 

i_sem:这个信号灯用来保护对inode的改变。所有对inode的非原子操作代码,都要首先声明该信号灯。这包括分配和销毁数据块,以及通过目录进行查找等等操作。并且不能对只读操作声明共享锁。 

i_flock:它指向在该inode上加锁的file_lock结构链表。 

i_state:对于2.4内核来说,共有六种可能的inode状态:I_DIRTY_SYNC,I_DIRTY_DATASYNC, I_DIRTY_PAGES,I_LOCK,I_FREEING和 I_CLEAR。所有脏节点在相应超级块的s_dirty链表中,并且在下一次同步请求时被写入设备。在索引节点被创建,读取或者写入的时候,会被锁住,即I_LOCK状态。当一个索引节点的引用计数和链接计数都到0时,将被设置为I_CLEAR状态。 

i_flags:i_flags对应于超级块中的s_flags,有许多标记可以被系统范围内设置,也可以针对每个索引节点设置。 

i_writecount:如果它的值为正数,那么它就记录了对该索引节点有写权限的客户(文件或者内存映射)的个数。如果是负数,那么该数字的绝对值就是当前VM_DENYWRITE映射的个数。其他情况下,它的值为0。

i_attr_flags:未被使用。 

最后要注意的是,在linux 2.4中,inode结构中新增加了一项,就是struct file_operations *i_fop,它指向索引节点对应的文件的文件操作集,而原来它放在inode_operations中(即inode结构的另一个项目struct inode_operations *i_op之中),现在它已经从inode_operations中移走了,我们可以从下面对inode_operations结构的分析中看到这一点。

下面我们分析一下对于inode进行操作的函数。所有的方法都放在inode_operations结构中,它在include/linux/fs.h中被定义: 
struct inode_operations { 
int (*create) (struct inode *,struct dentry *,int); 
struct dentry * (*lookup) (struct inode *,struct dentry *); 
int (*link) (struct dentry *,struct inode *,struct dentry *); 
int (*unlink) (struct inode *,struct dentry *); 
int (*symlink) (struct inode *,struct dentry *,const char *); 
int (*mkdir) (struct inode *,struct dentry *,int); 
int (*rmdir) (struct inode *,struct dentry *); 
int (*mknod) (struct inode *,struct dentry *,int,int); 
int (*rename) (struct inode *, struct dentry *, 
struct inode *, struct dentry *); 
int (*readlink) (struct dentry *, char *,int); 
int (*follow_link) (struct dentry *, struct nameidata *); 
void (*truncate) (struct inode *); 
int (*permission) (struct inode *, int); 
int (*revalidate) (struct dentry *); 
int (*setattr) (struct dentry *, struct iattr *); 
int (*getattr) (struct dentry *, struct iattr *); 
}; 

同样,我们对这些方法做一个简单的分析。 
create:这个方法,以及下面的8个方法,都只在目录索引节点中被维护。 

当 VFS想要在给定目录创建一个给定名字(在参数dentry中)的新文件时,会调用该函数。VFS将提前确定该名字并不存在,并且作为参数的dentry 必须为负值(即其中指向inode的指针为NULL,根据include/dcache.h中的定义,其注释为“NULL is negative”)。 

如果create调用成功,将使用get_empty_inode从cache中得到一个新的空索引节点,填充它的内容,并使用 insert_inode_hash将其插入到哈希表中,使用mark_inode_dirty标记其为脏,并且使用d_instantiate将其在 dcache中实例化。 

int参数包含了文件的mode并指定了所需的许可位。 

lookup:该函数用来检查是否名字(由dentry提供)存在于目录(由inode提供)中,并且如果存在的话,使用d_add更新dentry。 

link:该函数用来将一个名字(由第一个dentry提供)硬链接到在在指定目录(由参数inode提供)中的另一个名字(由第二个dentry参数提供)。 

unlink:删除目录中(参数inode指定)的名字(由参数dentry提供)。 

symlink:创建符号链接。 

mkdir:根据给定的父节点,名字和模式,创建一个目录。 

rmdir:移除指定的目录(如果为空目录),并删除(d_delete)dentry。 

mknod:根据给定的父节点,名字,模式以及设备号,创建特殊的设备文件,然后使用d_instantiate将新的inode在dentry中实例化。 

rename:重命名。所有的检测,比如新的父节点不能是旧名字的孩子等等,都已经在调用前被完成。 

readlink:通过dentry参数,读取符号链接,并且将其拷贝到用户空间,最大长度由参数int指定。

permission:在该函数中,可以实现真正的权限检查,与文件本身的mode无关。 

4 VFS名字以及dentry 
根据我们上面的介绍,可以看出,文件和索引节点的联系非常紧密,而在文件和索引节点之间,是通过dentry结构来联系的。

VFS层处理了文件路径名的所有管理工作,并且在底层文件系统能够看到它们之前,将其转变为dcache中的入口(entry)。唯一的一个例外是对于符号链接的目标,VFS将不加改动地传递给底层文件系统,由底层文件系统对其进行解释。 

目录高速缓存dcache由许多dentry结构组成。每一个dentry都对应文件系统中的一个文件名,并且与之联系。每一个dentry的父节点都必须存在于dcache中。同时,dentry还记录了文件系统的装载关系。

dcache是索引节点高速缓存的管理者。不论何时,只要在dcache中存在一个入口,那么相应的索引节点一定在索引节点高速缓存中。换句话说,如果一个索引节点在高速缓存中,那么它一定引用dcache中的一个dentry。

下面我们来分析一下dentry的结构,以及在dentry上的操作。在include/linux/dcache.h中,由其定义: 
struct dentry { 
atomic_t d_count; 
unsigned int d_flags; 
struct inode * d_inode; /* Where the name belongs to - NULL is negative */ 
struct dentry * d_parent; /* parent directory */ 
struct list_head d_vfsmnt; 
struct list_head d_hash; /* lookup hash list */ 
struct list_head d_lru; /* d_count = 0 LRU list */ 
struct list_head d_child; /* child of parent list */ 
struct list_head d_subdirs; /* our children */ 
struct list_head d_alias; /* inode alias list */ 
struct qstr d_name; 
unsigned long d_time; /* used by d_revalidate */ 
struct dentry_operations *d_op; 
struct super_block * d_sb; /* The root of the dentry tree */ 
unsigned long d_reftime; /* last time referenced */ 
void * d_fsdata; /* fs-specific data */ 
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */ 
}; 

在该结构的注释中,大部分域的含义已经非常的清楚,下面我再简单地介绍一下。 
d_flags:在目前,只有两个可取值,而且都是给特殊的文件系统使用的,它们是DCACHE_AUTOFS_PENDING和DCACHE_NFSFS_RENAMED,因此,在这里我们可以暂时忽略它。 

d_inode:它简单地指向与该名字联系的索引节点。这个域可以是NULL,它标明这是一个负入口(negative entry),暗示着该名字并不存在。 

d_hash:这是一个双向链表,将所有拥有相同哈希值的入口链接在一起。 

d_lru:它提供了一个双向链表,链接高速缓存中未被引用的叶节点。这个链表的头是全局变量dentry_unused,按照最近最少使用的顺序存储。 

d_child:这是一个容易让人误会的名字,其实该链表链接d_parent的所有子节点,因此把它称为d_sibling(同胞)更恰当一些。 

d_subdirs:该链表将该dentry的所有子节点链接在一起,所以,它实际上是它子节点的d_child链表的链表头。这个名字也容易产生误会,因为它的子节点不仅仅包括子目录,也可以是文件。 

d_alias:由于文件(以及文件系统的其他一些对象)可能会通过硬链接的方法,拥有多个名字,因此有可能会有多个dentry指向同一个索引节点。在这种情况下,这些dentry将通过d_alias链接在一起。而inode的i_dentry就是该链表的头。 

d_name:该域包含了这个入口的名字,以及它的哈希值。它的子域name有可能会指向该dentry的d_iname域(如果名字小于等于16个字符),否则的话,它将指向一个单独分配出来的字符串。

d_op:指向dentry的操作函数集。 

d_sb:指向该dentry对应文件所在的文件系统的超级块。使用d_inode->i_sb有相同的效果。 

d_iname:它存储了文件名的前15个字符,目的是为了方便引用。如果名字适合,d_name.name将指向这里。 
下面我们再看一下对dentry的操作函数,同样在include/linux/dcache.h中有dentry_operations的定义:
struct dentry_operations { 
int (*d_revalidate)(struct dentry *, int); 
int (*d_hash) (struct dentry *, struct qstr *); 
int (*d_compare) (struct dentry *, struct qstr *, struct qstr *); 
int (*d_delete)(struct dentry *); 
void (*d_release)(struct dentry *); 
void (*d_iput)(struct dentry *, struct inode *); 
}; 

我们再简单地介绍一下: 
d_revalidate:这个方法在entry在dcache中做路径查找时调用,目的是为了检验这个entry是否依然合法。如果它依旧可以被信赖,则返回1,否则返回0。

d_hash:如果文件系统没有提供名字验证的规则,那么这个例程就被用来检验并且返回一个规范的哈希值。 

d_compare:它被用来比较两个qstr,来看它们是否是相同的。 

d_delete:当引用计数到0时,在这个dentry被放到dentry_unused链表之前,会调用该函数。

d_release:在一个dentry被最终释放之前,会调用该函数。 

_iput:如果定义了该函数,它就被用来替换iput,来dentry被丢弃时,释放inode。它被用来做iput的工作再加上其他任何想要做的事情。 

(五) 总结 
在上面的部分中,我们对VFS进行了一个大概的分析。了解了文件系统的注册,安装,以及卸载的过程,并对VFS对逻辑文件系统的管理,尤其是接口部分的数据结构,进行了详细的分析。 

proc文件系统作为一个特殊的逻辑文件系统,其实现也遵循VFS接口,因此根据上面对VFS的分析,我们可以基本确定对proc文件系统进行分析的步骤。 





    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多