文件系统体系结构
从两个角度考察Linux文件系统层的体系结构,首先是高层体系结构的角度。然后进行深层次讨论,介绍实现文件系统层的主要结构。
高层体系结构
尽管大多数文件系统代码在内核中(后面讨论的用户空间文件系统除外),但是图 1 所示的体系结构显示了用户空间和内核中与文件系统相关的主要组件之间的关系。
用户空间包含一些应用程序(例如,文件系统的使用者)和 GNU C 库(glibc),它们为文件系统调用(打开、读取、写和关闭)提供用户接口。系统调用接口的作用就像是交换器,它将系统调用从用户空间发送到内核空间中的适当端点。图 1.Linux文件系统组件的体系结构
VFS 是底层文件系统的主要接口。这个组件导出一组接口,然后将它们抽象到各个文件系统,各个文件系统的行为可能差异很大。有两个针对文件系统对象的缓存(inode 和 dentry)。它们缓存最近使用过的文件系统对象。
VFS下面挂着个性差异的具体文件系统(Individual file system),比如 ext2、JFS 等等,尽管这些文件系统内部有所差异,但从中导出的是一组通用接口,供 VFS 使用。
缓冲区缓存会缓存文件系统和相关块设备之间的请求。例如,对底层设备驱动程序的读写请求会通过缓冲区缓存来传递。这就允许在其中缓存请求,减少访问物理设备的次数,加快访问速度。以最近最久未使用(LRU)列表的形式管理缓冲区缓存。注意,可以使用 sync 命令将缓冲区缓存中的请求发送到存储媒体(迫使所有未写的数据发送到设备驱动程序,进而发送到存储设备)。
这就是 VFS 和文件系统组件的高层情况。现在,讨论实现这个子系统的主要结构。
主要结构
Linux 以一组通用对象的角度看待所有文件系统。这些对象是超级块(superblock)、inode、dentry 和文件(file)。超级块在每个文件系统的根上,超级块描述和维护文件系统的状态。文件系统中管理的每个对象(文件或目录)在Linux中表示为一个inode。inode 包含管理文件系统中的对象所需的所有元数据(包括可以在对象上执行的操作)。另一组结构称为 dentry,它们用来实现名称和 inode 之间的映射,有一个目录缓存用来保存最近使用的 dentry。dentry 还维护目录和文件之间的关系,从而支持在文件系统中移动。最后,文件(file)表示一个打开的文件(保存打开的文件的状态,比如写偏移量等等)。
虚拟文件系统层
VFS 作为文件系统接口的根层。VFS 记录当前支持的文件系统以及当前挂装的文件系统。
当内核被编译时,就已经确定了可以支持哪些文件系统,这些文件系统在系统引导时,在 VFS 中进行注册。如果文件系统是作为内核可装载的模块,则在实际安装时进行注册,并在模块卸载时注销。每个文件系统都有一个初始化例程,它的作用就是在 VFS 中进行注册,即填写一个叫做 file_system_type的数据结构,该结构包含了文件系统的名称以及一个指向对应的超级块读取例程的地址,所有已注册的文件系统的file_system_type结构形成一个链表,为区别后面将要说到的已安装的文件系统形成的另一个链表,我们把这个链表称为注册链表。
文件系统注册后,还可以撤消这个注册,即从注册链表中删除一个file_system_type 结构,此后系统不再支持该种文件系统。fs/super.c中的unregister_filesystem()函数就是起这个作用的,它在执行成功后返 回0,如果注册链表中本来就没有指定的要删除的结构,则返回-1.
可以使用一组注册函数在Linux中动态地添加或删除所支持的文件系统。内核保存当前支持的文件系统的列表,可以通过 /proc 下 filesystems 文件在用户空间中查看这个列表。这个虚拟文件还显示当前与这些文件系统相关联的设备。在Linux中添加新文件系统的方法是调用 register_filesystem。这个函数的参数定义一个文件系统结构(file_system_type)的引用,这个结构定义文件系统的名称、一组属性和两个超级块函数。
在注册新的文件系统时,会把这个文件系统和它的相关信息添加到 file_systems 列表中。这个列表定义可以支持的文件系统。在命令行上输入 cat /proc/filesystems,就可以查看这个列表。
图 2. 向内核注册的文件系统
VFS 中维护的另一个结构是挂装的文件系统。这个结构提供当前挂装的文件系统(见 linux/include/linux/fs.h)。它链接下面讨论的超级块结构。
要使用一个文件系统,仅仅注册是不行的,还必须安装这个文件系统。在安装Linux时,硬盘上已经有一个分区安装了 Ext4文件系统,它是作为根文件系统的,根文件系统在启动时自动安装。其实,在系统启动后你所看到的文件系统,都是在启动时安装的。如果你需要自己(一般是超级用户)安装文件系统,则需要指定三种信息:文件系统的名称、包含文件系统的物理块设备、文件系统在已有文件系统中的安装点。(eg. mount -t /dev/sdb /mnt/usb1)
缓冲区缓存图 3. 挂装的文件系统列表
除了各个文件系统实现(可以在 ./linux/fs 中找到)之外,文件系统层的底部是缓冲区缓存。这个组件跟踪来自文件系统实现和物理设备(通过设备驱动程序)的读写请求。为了提高效率,Linux 对请求进行缓存,避免将所有请求发送到物理设备。缓存中缓存最近使用的缓冲区(页面),这些缓冲区可以快速提供给各个文件系统。
主要数据对象:
1: 超级块
超级块结构表示一个文件系统。它包含管理文件系统所需的信息,包括文件系统名称(比如 ext4)、文件系统的大小和状态、块设备的引用和元数据信息(比如空闲列表等等)。超级块通常存储在存储媒体上,但是如果超级块不存在,也可以实时创建它。可以在./linux/include/linux/fs.h 中找到超级块结构。
图 4. 超级块结构和 inode 操作
超级块中的一个重要元素是超级块操作的定义。这个结构定义一组用来管理这个文件系统中的 inode 的函数。例如,可以用 alloc_inode 分配 inode,用 destroy_inode 删除 inode。可以用 read_inode 和 write_inode 读写 inode,用 sync_fs 执行文件系统同步。可以在 ./linux/include/linux/fs.h 中找到 super_operations 结构。每个文件系统提供自己的 inode 方法,这些方法实现操作并向 VFS 层提供通用的抽象。
一个超级块对应一个文件系统(已经安装的文件系统类型如ext4,此处是实际的文件系统,不是VFS)。文件系统用于管理这些文件的数据格式和操作,系统文件有系统文件自己的文件系统,同时对于不同的磁盘分区也可以有不同的文件系统。那么一个超级块对于一个独立的文件系统,保存文件系统的类型、大小、状态等等。
(“文件系统”和“文件系统类型”不一样!一个文件系统类型下可以包括很多文件系统即很多的super_block)
既然对于不同的文件系统有不同的super_block,那么对于不同的super_block的操作肯定也是不同的,所以在下面的 super_block结构中可以看到上面说的抽象的struct结构(例如下面的:struct super_operations):(linux内核2.4.37)
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_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;
struct quotactl_ops * s_qcop;
unsigned long s_flags;
unsigned long s_magic;
struct dentry * s_root;
struct rw_semaphore s_umount;
struct semaphore s_lock;
int s_count;
atomic_t s_active;
struct list_head s_dirty; /* dirty inodes */
struct list_head s_locked_inodes;/* inodes being synced */
struct list_head s_files;
struct block_device * s_bdev;
struct list_head s_instances;
struct quota_info s_dquot; /* Diskquota specific options */
union {
struct minix_sb_info minix_sb;
struct ext2_sb_info ext2_sb;
struct ext3_sb_info ext3_sb;
struct hpfs_sb_info hpfs_sb;
struct ntfs_sb_info ntfs_sb;
struct msdos_sb_info msdos_sb;
struct isofs_sb_info isofs_sb;
struct nfs_sb_info nfs_sb;
struct sysv_sb_info sysv_sb;
struct affs_sb_info affs_sb;
struct ufs_sb_info ufs_sb;
struct efs_sb_info efs_sb;
struct shmem_sb_info shmem_sb;
struct romfs_sb_info romfs_sb;
struct smb_sb_info smbfs_sb;
struct hfs_sb_info hfs_sb;
struct adfs_sb_info adfs_sb;
struct qnx4_sb_info qnx4_sb;
struct reiserfs_sb_info reiserfs_sb;
struct bfs_sb_info bfs_sb;
struct udf_sb_info udf_sb;
struct ncp_sb_info ncpfs_sb;
struct usbdev_sb_info usbdevfs_sb;
struct jffs2_sb_info jffs2_sb;
struct cramfs_sb_info cramfs_sb;
void * generic_sbp;
}
解释字段:
s_list:指向超级块链表的指针,这个struct list_head是很熟悉的结构了,里面其实就是用于连接关系的prev和next字段。
内核中的结构处理都是有讲究的(内核协议栈中也说过),内核单独使用一个简单的结构体将所有的super_block都链接起来,但是这个结构不是super_block本身,因为本身数据结构太大,效率不高,所有仅仅使用
struct
{
list_head prev;
list_head next;
}
这样的结构来将super_block中的s_list链接起来,那么遍历到s_list之后,直接读取super_block这么长的一个内存块,就可以将这个super_block直接读进来!这样就很快捷方便!这也是为什么s_list必须放在第一个字段的原因。
s_dev:包含该具体文件系统的块设备标识符。例如,对于 /dev/hda1,其设备标识符为 0x301
s_blocksize:文件系统中数据块大小,以字节单位
s_blocksize_bits:上面的size大小占用位数,例如512字节就是9 bits
s_dirt:脏位,标识是否超级块被修改
s_maxbytes:允许的最大的文件大小(字节数)
struct file_system_type *s_type:文件系统类型(也就是当前这个文件系统属于哪个类型?ext2还是fat32)
struct super_operations *s_op:指向某个特定的具体文件系统的用于超级块操作的函数集合(inode)
struct dquot_operations *dq_op:指向某个特定的具体文件系统用于限额操作的函数集合
struct quotactl_ops *s_qcop:用于配置磁盘限额的的方法,处理来自用户空间的请求
s_flags:安装标识
s_magic:区别于其他文件系统的标识
s_root:指向该具体文件系统安装目录的目录项
s_umount:对超级块读写时进行同步
s_lock:锁标志位,若置该位,则其它进程不能对该超级块操作
s_count:对超级块的使用计数
s_active:引用计数
s_dirty:已修改的索引节点inode形成的链表,一个文件系统中有很多的inode,有些inode节点的内容会被修改,那么会先被记录,然后写回磁盘。
s_locked_inodes:要进行同步的索引节点形成的链表
s_files:所有的已经打开文件的链表,这个file和进程相关的
s_bdev:指向文件系统被安装的块设备
u:u 联合体域包括属于具体文件系统的超级块信息
s_instances:同一类型的文件系统通过这个项将所有的super_block连接起来
s_dquot:磁盘限额相关选项
2: inode
inode 表示文件系统中的一个对象,它具有惟一标识符。各个文件系统提供将文件名映射为惟一 inode 标识符和 inode 引用的方法。图 5 显示 inode 结构的一部分以及两个相关结构。请特别注意 inode_operations 和 file_operations。这些结构表示可以在这个 inode 上执行的操作。inode_operations 定义直接在 inode 上执行的操作,而 file_operations 定义与文件和目录相关的方法(标准系统调用)。
inode和目录缓存分别保存最近使用的 inode 和 dentry。注意,对于 inode 缓存中的每个 inode,在目录缓存中都有一个对应的dentry。可以在 ./linux/include/linux/fs.h 中找到 inode 和 dentry 结构。图 5. inode 结构和相关联的操作
索引节点inode保存的其实是实际的数据的一些信息,这些信息称为“元数据”(也就是对文件属性的描述)。例如:文件大小,设备标识符,用户标识符,用户组标识符,文件模式,扩展属性,文件读取或修改的时间戳,链接数量,指向存储该内容的磁盘区块的指针,文件分类等等。
同时注意:inode有两种,一种是VFS的inode,一种是具体文件系统的inode。前者在内存中,后者在磁盘中。所以每次其实是将磁盘中的inode调进填充内存中的inode,这样才是算使用了磁盘文件inode。
注意inode怎样生成的:每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定(现代OS可以动态变化),一般每2KB就设置一个inode。一般文件系统中很少有文件小于2KB的,所以预定按照2KB分,一般inode是用不完的。所以inode在文件系统安装的时候会有一个默认数量,后期会根据实际的 需要发生变化。
注意inode号:inode号是唯一的,表示不同的文件。其实在Linux内部的时候,访问文件都是通过inode号来进行的,所谓文件名仅仅是给用户容易使用的。当我们打开一个文件的时候,首先,系统找到这个文件名对应的inode号;然后,通过inode号,得到inode信息,最后,由inode找到文件数据所在的block,就可以处理文件数据了。
inode和文件的关系:当创建一个文件的时候,就给文件分配了一个inode。一个inode只对应一个实际文件,一个文件也会只有一个inode。inodes最大数量就是文件的最大数量。
Inode结构:
struct inode {
struct list_head i_hash;
struct list_head i_list;
struct list_head i_dentry;
struct list_head i_dirty_buffers;
struct list_head i_dirty_data_buffers;
unsigned long i_ino;
atomic_t i_count;
kdev_t i_dev;
umode_t i_mode;
unsigned int 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 int i_blkbits;
unsigned long i_blksize;
unsigned long i_blocks;
unsigned long i_version;
unsigned short i_bytes;
struct semaphore i_sem;
struct rw_semaphore i_alloc_sem;
struct semaphore i_zombie;
struct inode_operations *i_op;
struct file_operations *i_fop; /* former ->i_op->default_file_ops */
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];
/* These three should probably be a union */
struct list_head i_devices;
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct char_device *i_cdev;
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 ext3_inode_info ext3_i;
struct hpfs_inode_info hpfs_i;
struct ntfs_inode_info ntfs_i;
struct msdos_inode_info msdos_i;
struct umsdos_inode_info umsdos_i;
struct iso_inode_info isofs_i;
struct nfs_inode_info nfs_i;
struct sysv_inode_info sysv_i;
struct affs_inode_info affs_i;
struct ufs_inode_info ufs_i;
struct efs_inode_info efs_i;
struct romfs_inode_info romfs_i;
struct shmem_inode_info shmem_i;
struct coda_inode_info coda_i;
struct smb_inode_info smbfs_i;
struct hfs_inode_info hfs_i;
struct adfs_inode_info adfs_i;
struct qnx4_inode_info qnx4_i;
struct reiserfs_inode_info reiserfs_i;
struct bfs_inode_info bfs_i;
struct udf_inode_info udf_i;
struct ncp_inode_info ncpfs_i;
struct proc_inode_info proc_i;
struct socket socket_i;
struct usbdev_inode_info usbdev_i;
struct jffs2_inode_info jffs2_i;
void *generic_ip;
}
解释一些字段:
i_hash:指向hash链表指针,用于inode的hash表
i_list:指向索引节点链表指针,用于inode之间的连接
i_dentry:指向目录项链表指针,注意一个inodes可以对应多个dentry,因为一个实际的文件可能被链接到其他的文件,那么就会有另一个dentry,这个链表就是将所有的与本inode有关的dentry都连在一起。
i_dirty_buffers和i_dirty_data_buffers:脏数据缓冲区
i_ino:索引节点号,每个inode都是唯一的
i_count:引用计数
i_mode:文件的类型和访问权限
i_nlink:与该节点建立链接的文件数(硬链接数)
i_uid:文件拥有者标号
i_gid:文件所在组标号
i_dev:如果inode代表设备,那么就是设备号
i_rdev:实际的设备标识
注意i_dev和i_rdev之间区别:如果是普通的文件,例如磁盘文件,存储在某块磁盘上,那么i_dev代表的就是保存这个文件的磁盘号,但是如果此处是特殊文件例如就是磁盘本身(因为所有的设备也看做文件处理),那么i_rdev就代表这个磁盘实际的磁盘号。
i_size:inode所代表的的文件的大小,以字节为单位
i_atime:文件最后一次访问时间
i_mtime:文件最后一次修改时间
i_ctime:inode最后一次修改时间
i_blkbits:块大小,字节单位
i_blksize:块大小,bit单位
i_blocks:文件所占块数
i_version:版本号
i_bytes:文件中最后一个块的字节数
i_sem:指向用于同步操作的信号量结构
i_alloc_sem:保护inode上的IO操作不被另一个打断
i_zombie:僵尸inode信号量
i_op:索引节点操作
i_fop:文件操作
i_sb:inode所属文件系统的超级块指针
i_wait:指向索引节点等待队列指针
i_flock:文件锁链表
注意下面:address_space不是代表某个地址空间,而是用于描述页高速缓存中的页面的。一个文件对应一个address_space,一个address_space和一个偏移量可以确定一个页高速缓存中的页面。
i_mapping:表示向谁请求页面
i_data:表示被inode读写的页面
i_dquot:inode的磁盘限额
关于磁盘限额:在多任务环境下,对于每个用户的磁盘使用限制是必须的,起到一个公平性作用。
磁盘限额分为两种:block限额和inode限额,而且对于一个特文件系统来说,使用的限额机制都是一样的,所以限额的操作函数
放在super_block中就可以了。
i_devices:设备链表。共用同一个驱动程序的设备形成的链表。
i_pipe:指向管道文件(如果文件是管道文件时使用)
i_bdev:指向块设备文件指针(如果文件是块设备文件时使用)
i_cdev:指向字符设备文件指针(如果文件是字符设备时使用)
i_dnotify_mask:目录通知事件掩码
i_dnotify:用于目录通知
i_state:索引节点的状态标识:I_NEW,I_LOCK,I_FREEING
i_flags:索引节点的安装标识
i_sock:如果是套接字文件则为True
i_write_count:记录多少进程以写模式打开此文件
i_attr_flags:文件创建标识
i_generation:保留
u:具体的inode信息
注意管理inode的四个链表:
inode_unused:将目前还没有使用的inode链接起来(通过i_list域链接)
inode_in_use:目前正在使用的inode链接起来(通过i_list域链接)
super_block中的s_dirty:将所有修改过的inode链接起来,这个字段在super_block中(通过i_list域链接起来)
inode_hashtable:注意为了加快inode的查找效率,将正在使用的inode和脏inode也会放在inode_hashtable这样一个hash结构中,
但是,不同的inode的hash值可能相等,所以将hash值相等的这些inode通过这个i_hash字段连接起来。
3: 目录项Dentry是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。注意不管是目录还是最终的文件,都是属于目录项,所有的目录项在一起构成一棵庞大的目录树。例如:打开一个文件/home/xxx/yyy.txt,那么/、 home、xxx、yyy.txt都是一个目录项,VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作 就可以找到最终的文件。
注意:目录也是一种文件(所以也存在对应的inode,是Dentry最初的来源)。打开目录,实际上就是打开目录文件。
Dentry结构:
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_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 */
int d_mounted;
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_vfs_flags;
void * d_fsdata; /* fs-specific data */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
};
解释一些字段:
d_count:引用计数
d_flags:目录项缓存标识,可取DCACHE_UNUSED、 DCACHE_REFERENCED等
d_inode:与该目录项关联的inode
d_name:目录项名称
d_parent:父目录的目录项
d_hash:内核使用dentry_hashtable对dentry进行管理,dentry_hashtable是由list_head组成的链表,一个dentry创建之后,就通过d_hash链接进入对应的hash值的链表中。
d_lru:最近未使用的目录项的链表
d_child:目录项通过这个加入到父目录的d_subdirs中
d_subdirs:本目录的所有孩子目录链表头
d_alias:一个有效的dentry必然与一个inode关联,但是一个inode可以对应多个dentry,因为一个文件可以被链接到其他文件,所以,本dentry通过这个字段链接到属于自己的inode结构中的i_dentry链表中的。(inode中讲过)
d_mounted:安装在该目录的文件系统的数量(该目录作为安装点时)。注意一个文件目录下可以有不同的文件系统!
d_time:重新变为有效的时间!注意只要操作成功这个dentry就是有效的,否则无效。
d_op:目录项操作
d_sb:这个目录项所属的文件系统的超级块
d_vfs_flags:一些标志
d_fsdata:文件系统私有数据
d_iname:存放短的文件名
一些解释:一个有效的dentry结构必定有一个inode结构,这是因为一个目录项要么代表着一个文件,要么代表着一个目录,而目录实际上也是文件。所以,只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。但是inode却可以对应多个dentry。
注意:整个结构其实就是一棵树。
4: 文件对象
注意文件对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开,所以一个文件可以存在多个文件对象。但是由于文件是唯一的,那么inode是唯一的。
进程其实是通过文件描述符来操作文件的,注意每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置。一般情况下打开文件后,打开位置都是从0开始,除非一些特殊情况。Linux用file结构体来保存打开的文件的位置,所以file称为打开的文件描述。file结构形成一个双链表,称为系统打开文件表。
file结构:
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;
size_t f_maxcount;
unsigned long f_version;
/* needed for tty driver, and maybe others */
void *private_data;
/* preallocated helper kiobuf to speedup O_DIRECT */
struct kiobuf *f_iobuf;
long f_iobuf_lock;
};
解释一些字段:
f_list:所有的打开的文件形成的链表!注意一个文件系统所有的打开的文件都通过这个链接到super_block中的s_files链表中!
f_dentry:与该文件相关的dentry
f_vfsmnt:该文件在这个文件系统中的安装点
f_op:文件操作,当进程打开文件的时候,这个文件的关联inode中的i_fop文件操作会初始化这个f_op字段
f_count:引用计数
f_flags:打开文件时候指定的标识
f_mode:文件的访问模式
f_pos:目前文件的相对开头的偏移
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin:预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数
f_owner:记录一个进程ID,以及当某些事发送的时候发送给该ID进程的信号
f_uid:用户ID
f_gid:组ID
f_error:写操作错误码
f_version:版本号,当f_pos改变时候,version递增
private_data:私有数据( 文件系统和驱动程序使用 )
重点解释一些重要字段:
首先,f_flags、f_mode和f_pos代表的是这个进程当前操作这个文件的控制信息。这个非常重要,因为对于一个文件,可以被多个进程同时打开,那么对于每个进程来说,操作这个文件是异步的,所以这个三个字段就很重要了。
第二:对于引用计数f_count,当我们关闭一个进程的某一个文件描述符时候,其实并不是真正的关闭文件,仅仅是将f_count减一,当f_count=0时候,才会真的去关闭它。对于dup,fork这些操作来说,都会使得f_count增加,具体的细节,以后再说。
第三:f_op也是很重要的!是涉及到所有的文件的操作结构体。例如:用户使用read,最终都会调用file_operations中的读操作,而file_operations结构体是对于不同的文件系统不一定相同。里面一个重要的操作函数式release函数,当用户执行close时候,其实在内核中是执行release函数,这个函数仅仅将f_count减一,这也就解释了上面说的,用户close一个文件其实是将f_count减一。只有引用计数减到0才关闭文件。
注意:对于“正在使用”和“未使用”的文件对象分别使用一个双向链表进行管理。
注意上面的file只是对一个文件而言,对于一个进程(用户)来说,可以同时处理多个文件,所以需要另一个结构来管理所有的files。
即:用户打开文件表--->files_struct
struct files_struct {
atomic_t count;
rwlock_t file_lock; /* Protects all the below members. Nests inside tsk->alloc_lock */
int max_fds;
int max_fdset;
int next_fd;
struct file ** fd; /* current fd array */
fd_set *close_on_exec;
fd_set *open_fds;
fd_set close_on_exec_init;
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];
};
解释一些字段:
count:引用计数
file_lock:锁,保护下面的字段
max_fds:当前文件对象的最大的数量
max_fdset:文件描述符最大数
next_fd:已分配的最大的文件描述符+1
fd:指向文件对象指针数组的指针,一般就是指向最后一个字段fd_arrray,当文件数超过NR_OPEN_DEFAULT时候,就会重新分配一个数组,然后指向这个新的数组指针!
close_on_exec:执行exec()时候需要关闭的文件描述符
open_fds:指向打开的文件描述符的指针
close_on_exec_init:执行exec()时候需要关闭的文件描述符初始化值
open_fds_init:文件描述符初值集合
fd_array:文件对象指针的初始化数组
注意上面的file和files_struct记录的是与进程相关的文件的信息,但是对于进程本身来说,自身的一些信息用什么表示,这里就涉及到fs_struct结构体。
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:用户设置的替换根目录
注意:实际运行时,这三个目录不一定都在同一个文件系统中。例如,进程的根目录通常是安装于“/”节点上的ext文件系统,而当前工作目录可能是安装于/etc的一个文件系统,替换根目录也可以不同文件系统中。
rootmnt,pwdmnt,altrootmnt:对应于上面三个的安装点。