接着上次的来,我今天讲虚拟文件系统剩下的一点知识.
3.目录项对象.目录项的概念上节已经说了,我就不多说.目录项中也可包括安装点.在路径/mnt/cdrom/foo中,/,mnt,cdrom都属于目录项对象。目录项由dentry结构体表示,定义在文件linux/dcache.h中,描述如下:
struct dentry { atomic_t d_count; /* usage count */ unsigned long d_vfs_flags; /* dentry cache flags */ spinlock_t d_lock; /* per-dentry lock */ struct inode *d_inode; /* associated inode */ struct list_head d_lru; /* unused list */ struct list_head d_child; /* list of dentries within */ struct list_head d_subdirs; /* subdirectories */ struct list_head d_alias; /* list of alias inodes */ unsigned long d_time; /* revalidate time */ struct dentry_operations *d_op; /* dentry operations table */ struct super_block *d_sb; /* superblock of file */ unsigned int d_flags; /* dentry flags */ int d_mounted; /* is this a mount point? */ void *d_fsdata; /* filesystem-specific data */ struct rcu_head d_rcu; /* RCU locking */ struct dcookie_struct *d_cookie; /* cookie */ struct dentry *d_parent; /* dentry object of parent */ struct qstr d_name; /* dentry name */ struct hlist_node d_hash; /* list of hash table entries */ struct hlist_head *d_bucket; /* hash bucket */ unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* short name */ };
由于目录项并非真正保存在磁盘上,所有目录项没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它,目录项结构体也没有是否被修改的标志。目录项对象有三种状态:被使用,未被使用和负状态。一个被使用的目录项对应一个有效的索引节点(即d_inode指向相应的索引节点)并且该对象存在一个或多个使用者(即d_count为正值)。一个未被使用的目录项对应一个有效的索引节点(d_inode指向一个索引节点),但是VFS当前并未使用它(d_count为0)。该目录项对象仍然指向一个有效对象,而且被保留在内存中以便需要时再使用它。显然这样要比重新创建要效率高些。一个负状态的目录项没有对应的有效索引节点(d_inode为NULL).因为索引节点已被删除了,或路径不再正确了,但是目录项仍然保留,以便快速解析以后的路径查询。虽然负的状态目录项有些用处,但如果需要的话话,还是可以删除的,可以销毁它。
结构体dentry_operation指明了VFS操作目录的所有方法,如下:
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 *); };
其实,如果VFS遍历路径名中所有的元素并将它们逐个地解析成目录项对象,将是一件非常耗时的事情。所以内核将目录项对象缓存在目录项缓存(dcache)中,目录项缓存包括三个主要部分:
1.“被使用的”目录项链表,该链表通过索引节点对象中的i_dentry项连接相关的索引节点,因为一个给定的索引节点可能有多个链接,所以就可能有多 个目录项对象,因此用一个链表来连接它们。 2.“最近被使用的”双向链表。该链表包含未被使用的和负状态的目录项对象。该链表是按时间插入的。 3. 哈希表和相应的哈希函数用来快速地将给定路径解析为相关目录项对象。 |
哈希表有数组dentry_hashtable表示,其中每一个元素都是一个指向具有相同键值的目录项对象链表的指针。数组的大小取决于系统中物理内存的大小。实际的哈希值由d_hash()计算,它是内核提供给文件系统的唯一的一个哈希函数。查找哈希表要通过d_lookup()函数,如果该函数在dcache中发现了与其相匹配的目录项对像,则匹配对象被返回;否则,返回NULL指针。dcache在一定意义上也提供了对索引节点的缓存。和目录项对象相关的索引节点对象不会被释放,因为目录项会让相关索引节点的使用计数为正,这样就可以确保索引节点留在内存中。只要目录项被缓存,其相应的索引节点也就被缓存了。
4.文件对象:文件对象表示进程以打开的文件。文件对象仅仅在进程观点上代表已打开文件,它反过来指向目录项对象(反过来指向索引节点),其实只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项对象无疑是唯一的。文件对象由file结构表示,定义在文件linux/fs.h中,如下:
struct file { struct list_head f_list; /* list of file objects */ struct dentry *f_dentry; /* associated dentry object */ struct vfsmount *f_vfsmnt; /* associated mounted fs */ struct file_operations *f_op; /* file operations table */ atomic_t f_count; /* file object's usage count */ unsigned int f_flags; /* flags specified on open */ mode_t f_mode; /* file access mode */ loff_t f_pos; /* file offset (file pointer) */ struct fown_struct f_owner; /* owner data for signals */ unsigned int f_uid; /* user's UID */ unsigned int f_gid; /* user's GID */ int f_error; /* error code */ struct file_ra_state f_ra; /* read-ahead state */ unsigned long f_version; /* version number */ void *f_security; /* security module */ void *private_data; /* tty driver hook */ struct list_head f_ep_links; /* list of eventpoll links */ spinlock_t f_ep_lock; /* eventpoll lock */ struct address_space *f_mapping; /* page cache mapping */ };
文件对象的操作有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 (*aio_read) (struct kiocb *, char *, size_t, loff_t); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); ssize_t (*aio_write) (struct kiocb *, 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); int (*aio_fsync) (struct kiocb *, int); 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 *); ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags) (int flags); int (*dir_notify) (struct file *filp, unsigned long arg); int (*flock) (struct file *filp, int cmd, struct file_lock *fl); };
最后,除了以上几种VFS基础对象外,内核还使用了另外一些数据结构来管理文件系统的其它相关数据,如下:
1.file_system_type:因为linux支持众多的文件系统,所以内核必有由一个特殊的结构来描述每种文件系统的功能和行为:
struct file_system_type { const char *name; /* filesystem's name */ struct subsystem subsys; /* sysfs subsystem object */ int fs_flags; /* filesystem type flags */ /* the following is used to read the superblock off the disk */ struct super_block *(*get_sb) (struct file_system_type *, int,char *, void *); /* the following is used to terminate access to the superblock */ void (*kill_sb) (struct super_block *); struct module *owner; /* module owning the filesystem */ struct file_system_type *next; /* next file_system_type in list */ struct list_head fs_supers; /* list of superblock objects */ };
其中,get_sb()函数从磁盘上读取超级块,并且在文件系统被安装时,在内存中组装超级块对象,剩余的函数描述文件系统的属性。每种文件系统,不管有多少个实力安装到系统中,还是根本就没有安装到系统中,都只有一个file_system_type结构。更有趣的是,当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建。该结构体被用来代表文件系统的实例----换句话说,代表一个安装点.
2.vfsmount结构被定义在linux/mount.h中,下面是具体结构:
struct vfsmount { struct list_head mnt_hash; /* hash table list */ struct vfsmount *mnt_parent; /* parent filesystem */ struct dentry *mnt_mountpoint; /* dentry of this mount point */ struct dentry *mnt_root; /* dentry of root of this fs */ struct super_block *mnt_sb; /* superblock of this filesystem */ struct list_head mnt_mounts; /* list of children */ struct list_head mnt_child; /* list of children */ atomic_t mnt_count; /* usage count */ int mnt_flags; /* mount flags */ char *mnt_devname; /* device file name */ struct list_head mnt_list; /* list of descriptors */ struct list_head mnt_fslink; /* fs-specific expiry list */ struct namespace *mnt_namespace /* associated namespace */ };
vfs中维护的各种链表是为了跟踪文件系统和所有其他安装点的关系,mnt_flags保存了安装时指定的标志信息,下表给出了标准的安装标志:
安装那些管理不充分信任的移动设备时,这些标志很有用处。
系统中每一个进程都有自己的一组打开的文件,有三个数据结构将VFS层和文件的进程紧密联系在一起,它们分别是file_struct,fs_struct和namespace.
1.file_struct:该结构体有进程描述符中的files域指向,如下:
struct files_struct { atomic_t count; /* structure's usage count */ spinlock_t file_lock; /* lock protecting this structure */ int max_fds; /* maximum number of file objects */ int max_fdset; /* maximum number of file descriptors */ int next_fd; /* next file descriptor number */ struct file **fd; /* array of all file objects */ fd_set *close_on_exec; /* file descriptors to close on exec() */ fd_set *open_fds; /* pointer to open file descriptors */ fd_set close_on_exec_init; /* initial files to close on exec() */ fd_set open_fds_init; /* initial set of file descriptors */ struct file *fd_array[NR_OPEN_DEFAULT]; /* default array of file objects */ };fd数组指针指向以打开的文件对象链表,默认情况下,指向fd_arrar数组。NR_OPEN_DEFAULT默认是32,所以该数组可以容纳32个文件对象。如果一个进程所打开的文件对象超过32个,内核将分配一个新数组,并且将fd指针指向它。这个值也是可以调整的。
2.第二个结构体是fs_struct:由进程描述符的fs域指向。它包含文件系统和进程相关的信息,在linux/fs_struct.h中,如下:
struct fs_struct { atomic_t count; /* structure usage count */ rwlock_t lock; /* lock protecting structure */ int umask; /* default file permissions*/ struct dentry *root; /* dentry of the root directory */ struct dentry *pwd; /* dentry of the current directory */ struct dentry *altroot; /* dentry of the alternative root */ struct vfsmount *rootmnt; /* mount object of the root directory */ struct vfsmount *pwdmnt; /* mount object of the current directory */ struct vfsmount *altrootmnt; /* mount object of the alternative root */ };该结构包含了当前进程的当前工作目录和根目录。
3.最后一个是namespace:由进程描述符namespace域指向,定义在linux/namespace.h中,如下:
struct namespace { atomic_t count; /* structure usage count */ struct vfsmount *root; /* mount object of root directory */ struct list_head list; /* list of mount points */ struct rw_semaphore sem; /* semaphore protecting the namespace */ };list域是连接已安装文件系统的双向链表,它包含的元素组成了全体命令空间。 上述这些数据结构都是通过进程描述符连接起来的。对多数进程来说,它们的描述符都指向唯一的files_struct和fs_struct结构体。但是,对于那些使用克隆标志CLONE_FILES或CLONE_FS创建的进程,会共享这两个结构体。所以多个进程描述符可能指向同一个files_struct或fs_struct结构体。每个结构体都维护一个count域作为引用计数,它防止进程正使用该结构时,该结构被销毁。而namespace却不是这样,默认情况下,所有的进程共享同样的命名空间,也就是说,它们都看到同一个文件层层结构。只有在进行clone()操作时使用CLONE_NEWS标志,才会给进程一个另外的命名空间结构体的拷贝。