1.概述
我们主要对以下几个虚拟文件系统数据结构做详解:
struct fs_struct 、struct files_struct、struct file、struct dentry 、struct inode、struct super_block
他们之间的关系如下:
2.struct fs_struct
文件必须由进程打开,每个进程都有它自己当前的工作目录和它自己的根目录。task_struct的fs字段指向进程的fs_struct结构
struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
其中:
count:共享这个表的进程个数
lock:用于表中字段的读/写自旋锁
umask:当打开文件设置文件权限时所使用的位掩码
root:根目录的目录项
pwd:当前工作目录的目录项
altroot:模拟根目录的目录项(在80x86结构上始终为NULL)
rootmnt:根目录所安装的文件系统对象
pwdmnt:当前工作目录所安装的文件系统对象
altrootmnt:模拟根目录所安装的文件系统对象(在80x86结构上始终为NULL)
3.struct files_struct
task_struct的files字段指向进程的files_struct结构
struct files_struct {
atomic_t count;
struct fdtable *fdt;
struct fdtable fdtab;
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];
};
struct fdtable {
unsigned int max_fds;
int max_fdset;
struct file ** fd; /* current fd array */
fd_set *close_on_exec;
fd_set *open_fds;
struct rcu_head rcu;
struct files_struct *free_files;
struct fdtable *next;
};
#define NR_OPEN_DEFAULT BITS_PER_LONG
#define BITS_PER_LONG 32 /* asm-i386 */
fdtable结构嵌入在files_struct中,并且由它的fdt指向。
fdtable结构的fd字段指向文件对象的指针数组。该数组的长度存放在max_fds字段中。通常,fd字段指向files_struct结构的fd_array字段,该字段包括32个文件对象指针。如果进程打开的文件数目多于32,内核就分配一个新的、更大的文件指针数组,并将其地址存放在fd字段中,内核同时也更新max_fds字段的值。
对于在fd数组中所有元素的每个文件来说,数组的索引就是文件描述符(file descriptor)。通常,数组的第一个元素(索引为0)是进程的标准输入文件,数组的第二个元素(索引为1)是进程的标准输出文件,数组的第三个元素(索引为2)是进程的标准错误文件。请注意,借助于dup()、dup2()和fcntl()系统调用,两个文件描述符可以指向同一个打开的文件,也就是说,数组的两个元素可能指向同一个文件对象。当用户使用shell结构(如2>&1)将标准错误文件重定向到标准输出文件上时,用户也能看到这一点。
进程不能使用多于NR_OPEN(通常为1 048 576)个文件描述符。内核也在进程描述符的signal->rlim[RLIMIT_NOFILE]结构上强制动态限制文件描述符的最大数;这个值通常为1024,但是如果进程具有超级用户特权,就可以增大这个值。
3.struct file
文件对象描述进程怎样与一个打开的文件进行交互。文件对象是在文件被打开时创建的,由一个file结构组成。
类型 | 字段 | 说明 |
struct list_head | f_list | 用于通用文件对象链表的指针 |
struct dentry * | f_dentry | 与文件相关的目录项对象 |
struct vfsmount * | f_vfsmount | 含有该文件的已安装文件系统 |
struct file_operations * | f_op | 指向文件操作表的指针 |
atomic_t | f_count | 文件对象的引用计数器 |
unsigned int | f_flags | 当打开文件时所指定的标志 |
mode_t | f_mode | 进程的访问模式 |
int | f_error | 网络写操作的错误码 |
loff_t | f_pos | 当前的文件偏移量 |
struct fown_struct | f_owner | 通过信号进行 I/O事件通知的数据 |
unsigned int | f_uid | 用户的UID |
unsigned int | f_gid | 用户的GID |
struct file_ra_state | f_ra | 文件预读状态 |
size_t | f_maxcount | 一次单一的操作所能读或写的最大字节数(默认2的31次方-1) |
unsigend long | f_version | 版本号 |
void * | f_security | 指向文件对象的安全结构的指针 |
void * | private_data | ... |
struct list_head | f_ep_links | 文件的事件轮询等待者链表的头 |
spinlock_t | f_ep_lock | 保护f_ep_links链表的自旋锁 |
struct address_space* | f_mapping | 指向文件地址空间对象的指针 |
notes:
1.文件指针存放在文件对象而不是索引节点对象
2.分配的文件对象数目是有限的,由名为filp的slab高速缓存分配
3.“在使用”文件对象包含在由具体文件系统的超级块所确立的几个链表中。每个超级块对象把文件对象链表的头存放在s_files字段中;因此,属于不同文件系统的文件对象就包含在不同的链表中。链表中分别指向前一个元素和后一个元素的指针都存放在文件对象的f_list字段中。
4.文件对象的f_count字段是一个引用计数器:它记录使用文件对象的进程数(以CLONE_FILES标志创建的轻量级进程共享打开文件表,因此它们可以使用相同的文件对象)。当内核本身使用该文件对象时也要增加计数器的值-例如:把对象插入链表中或发出dup()系统调用
4.struct dentry
struct dentry {
atomic_t d_count; /* 目录项引用计数器 */
unsigned int d_flags; /* 目录项标志 */
struct inode * d_inode; /* 与文件名关联的索引节点 */
struct dentry * d_parent; /* 父目录的目录项 */
struct list_head d_hash; /* 目录项形成的哈希表 */
struct list_head d_lru; /*未使用的 LRU 链表 */
struct list_head d_child; /*父目录的子目录项所形成的链表 */
struct list_head d_subdirs; /* 该目录项的子目录所形成的链表*/
struct list_head d_alias; /* 索引节点别名的链表*/
int d_mounted; /* 目录项的安装点 */
struct qstr d_name; /* 目录项名(可快速查找) */
unsigned long d_time; /* 由 d_revalidate函数使用 */
struct dentry_operations *d_op; /* 目录项的函数集*/
struct super_block * d_sb; /* 目录项树的根 (即文件的超级块)*/
unsigned long d_vfs_flags;
void * d_fsdata; /* 具体文件系统的数据 */
unsigned char d_iname[DNAME_INLINE_LEN]; /* 短文件名 */
};
下面对dentry结构给出进一步的解释。
一个有效的dentry结构必定有一个inode结构,这是因为一个目录项要么代表着一个文件,要么代表着一个目录,而目录实际上也是文件。所以,只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。可是,反过来则不然,一个inode却可能对应着不止一个dentry结构;也就是说,一个文件可以有不止一个文件名或路径名。这是因为一个已经建立的文件可以被连接(link)到其他文件名。所以在inode结构中有一个队列i_dentry,凡是代表着同一个文件的所有目录项都通过其dentry结构中的d_alias域挂入相应inode结构中的i_dentry队列。
在内核中有一个哈希表dentry_hashtable ,是一个list_head的指针数组。一旦在内存中建立起一个目录节点的dentry 结构,该dentry结构就通过其d_hash域链入哈希表中的某个队列中。
内核中还有一个队列dentry_unused,凡是已经没有用户(count域为0)使用的dentry结构就通过其d_lru域挂入这个队列。
Dentry结构中除了d_alias 、d_hash、d_lru三个队列外,还有d_vfsmnt、d_child及d_subdir三个队列。其中d_vfsmnt仅在该dentry为一个安装点时才使用。另外,当该目录节点有父目录时,则其dentry结构就通过d_child挂入其父节点的d_subdirs队列中,同时又通过指针d_parent指向其父目录的dentry结构,而它自己各个子目录的dentry结构则挂在其d_subdirs域指向的队列中。
从上面的叙述可以看出,一个文件系统中所有目录项结构或组织为一个哈希表,或组织为一颗树,或按照某种需要组织为一个链表,这将为文件访问和文件路径搜索奠定下良好的基础。
目录项高速缓存(内核中一种散列表的使用情况)
由于从磁盘读入一个目录项并构造相应的目录项对象需要花费大量时间,所以,在完成对目录对象的操作后,需要把它仍然保留在内存中,为了最大限度提高处理这些目录项对象的效率,linux使用目录项高速缓存,主要由两种类型的数据结构组成:
- 目录项对象集合(包括正在使用的、未使用的或负状态的)
- 一个散列表(从中能够快速获取与给定的文件名和目录名对应的目录项对象)
5.struct inode
文件系统处理文件所需要的所有信息都放在一个名为索引节点的数据结构中。文件名可以随时修改,但索引节点对文件是唯一的,并且随文件的存在而存在。索引节点是指在许多类Unix文件系统中的一种数据结构。每个索引节点保存了文件系统中的一个文件系统对象的元信息数据,但不包括数据内容或者文件名。
notes:
1.每个索引节点对象都会复制磁盘索引节点包含的一些数据,比如分配给文件的磁盘块数。这个和超级块一样都有一个“脏”字段来处理一致性,比如i_state字段
2.每个索引节点对象总是出现在下列双向循环链表的某个链表中(i_list):
- 有效未使用的索引节点链表
- 正在使用的索引节点链表
- 脏索引节点的链表
3.此外,每个索引节点对象也包含在每个文件系统的双向循环链表中,链表的头存放在超级块对象的s_inodes字段中,索引节点对象的i_sb_list字段存放了指向相邻元素指针
4.索引节点对象也存放在一个称为inode_hashtable的散列表中。散列表加快了对索引节点对象的搜索。索引节点对象包含一个i_hash字段包含向前和向后的两个指针。
5.索引节点操作(inode_operations结构描述),存放在i_op字段中。
6.struct super_block