VFS中的基本数据结构

本文涉及VFS中的数据结构有:struct super_block;struct inode;struct dentry;struct file;

Linux中的VFS(关于VFS更加全面的解说可以看这里)以一组通用的数据结构来描述各种文件系统。这些数据结构分别是超级块、索引结点、目录项和文件。下面将分别对这些结构进行说明。

超级块结构体

超级块结构代表一个已经安装了的文件系统,其存储该文件系统的有关信息。对于一个基于磁盘的文件系统来说,这类对象存放于磁盘的特定扇区中;对于非基于磁盘的文件系统,它会在该文件系统的使用现场创建超级块结构并存放在内存中。
在yoursource/include/linux/fs.h中有这个结构体的定义。下面对该结构的部分字段进行说明:

01 1318struct super_block {
02 1319        struct list_head        s_list;         /* Keep this first */
03 1320        dev_t                   s_dev;          /* search index; _not_ kdev_t */
04 1321        unsigned char           s_dirt;
05 1322        unsigned char           s_blocksize_bits;
06 1323        unsigned long           s_blocksize;
07 1324        loff_t                  s_maxbytes;     /* Max file size */
08 1325        struct file_system_type *s_type;
09 1326        const struct super_operations   *s_op;
10 …… ……
11 1330        unsigned long           s_flags;
12 1332        struct dentry           *s_root;
13 1335        int                     s_count;
14 1342        struct list_head        s_inodes;       /* all inodes */
15 1347        struct list_head        s_files;
16 1353        struct block_device     *s_bdev;
17 1357        struct quota_info       s_dquot;        /* Diskquota specific options */
18 1362        char s_id[32];                          /* Informational name */
19 …… ……
20 1388};

s_list:所以的超级块形成一个双联表,s_list.prev和s_list.next分别指向与当前超级块相邻的前一个元素和后一个元素。通常我们通过list_entry宏来获取s_list所在超级块结构体的地址。超级块链表的头结点是变量super_blocks;
s_dev:超级块所描述的文件系统所在设备的设备号。比如,ext2文件系统所在设备为磁盘,则该设备号即为该磁盘在系统中的设备号;
s_dirt:超级块在内存中被修改后,该标志为1,则修改后的超级块必须写回磁盘。
s_dirty:所有脏inode链接在一起所形成的指针;
s_blocksize:以字节为单位表示块的大小。系统对文件的存取操作是以块为单位的,该值即代表这个块的具体大小;
s_blocksize_bits:以位来表示块的大小。比如,一个块的大小为1024字节,则该值为10;
s_maxbytes:该超级块所属文件系统中文件的最大长度;
s_type:指向具体的文件系统类型;
s_op:指向对超级块操作的函数指针结构体;
s_flags:安装文件系统时的标志,记录比如只读或可擦写等这样的标志;
s_root:该文件系统根目录的目录项结构指针。利用该根目录项,可以访问到这个文件系统中的任何一个文件;
s_count:对该超级块的引用计数;
s_inodes:该文件系统中所有的索引结点形成一个双联表,该字段存放这个链表的头结点;
s_files:该文件系统中所有已被打开的文件形成一个双联表,该字段存放这个链表的头结点;
s_instances:某个具体文件系统中所有超级块会组成一个双联表。这个链表的头结点为super_block,头结点定义在该文件系统对应的file_system_type结构体中;

s_id[32]:文件系统的名称。比如ext3文件系统,该值为“ext3”;

超级块操作结构体

从上面的字段说明可以知道,超级块中有一个s_op字段,它指向与该操作块相关的操作。该字段的类型为struct super_operations,在yoursource/include/linux/fs.h中有这个结构体的定义。下面对该结构的部分字段进行说明:

01 1560struct super_operations {
02 1561        struct inode *(*alloc_inode)(struct super_block *sb);
03 1562        void (*destroy_inode)(struct inode *);
04 …… ……
05 1564        void (*dirty_inode) (struct inode *);
06 1565        int (*write_inode) (struct inode *, struct writeback_control *wbc);
07 1568        void (*put_super) (struct super_block *);
08 1569        void (*write_super) (struct super_block *);
09 …… ……
10 1584};

alloc_inode:创建和初始化一个新的索引结点;
destroy_inode:释放指定的索引结点;
put_super:释放指定的超级块,文件系统被卸载时使用;
write_super:如果该超级块被修改,即s_dirt为1时,则要将超级块写回磁盘,同时还要将s_dirt重设为0;
write_inode:将指定的inode写回磁盘,用于指定inode的更新;
drop_inode:释放指定的inode,与write_inode成对出现;

当文件系统需要对其所对应的超级块进行操作时,就应该使用超级块操作类中的具体函数。比如,定义sb为指向某个超级块的指针,如果该超级块需要将自己写回磁盘,则应该这么调用:

sb->s_op->write_super(sb);

可以看到,虽然write_super函数是由sb所指的超级块所调用的,但是仍然将sb传递给write_super函数。

索引结点结构体

索引结点结构体用来描述存放在磁盘上的文件信息。每当内核对磁盘上的文件进行操作时,就会将该文件的信息填充到一个索引结点可以代表一个普通的文件,也可以代表管道或者设备文件等这样的特殊文件。因此,在索引结点结构中,会包含针对这些特殊文件的一些属性。该结构体定义于在yoursource/include/linux/fs.h中有这个结构体的定义。下面对该结构的部分字段进行说明:

01 725struct inode {
02  726        struct hlist_node       i_hash;
03  727        struct list_head        i_list;         /* backing dev IO list */
04  729        struct list_head        i_dentry;
05  730        unsigned long           i_ino;
06  731        atomic_t                i_count;
07  732        unsigned int            i_nlink;
08  733        uid_t                   i_uid;
09  734        gid_t                   i_gid;
10  735        dev_t                   i_rdev;
11  736        unsigned int            i_blkbits;
12  737        u64                     i_version;
13  738        loff_t                  i_size;
14 …… ……
15  742        struct timespec         i_atime;
16  743        struct timespec         i_mtime;
17  744        struct timespec         i_ctime;
18  745        blkcnt_t                i_blocks;
19  746        unsigned short          i_bytes;
20  747        umode_t                 i_mode;
21  …… ……
22  751        const struct inode_operations   *i_op;
23  752        const struct file_operations    *i_fop; /* former ->i_op->default_file_ops */
24  753        struct super_block      *i_sb;
25  760        struct list_head        i_devices;
26  761        union {
27  762                struct pipe_inode_info  *i_pipe;
28  763                struct block_device     *i_bdev;
29  764                struct cdev             *i_cdev;
30  765        };
31  …… ……
32  788};

i_hash:为了提高查找正在被使用的inode的效率,每一个inode都会有一个hash值,所有hash值相同的inode形成一个双链表。该字段包含prev和next两个指针,分别指向上述链表的前一个元素和后一个元素;
i_list:VFS中使用四个链表来管理不同状态的inode结点。inode_unused将当前未使用的inode链接起来,inode_in_use将当前正在被使用的inode链接起来,超级块中的s_dirty将所有脏inode链接起来,i_hash将所有hash值相同的inode链接起来。i_list中包含prev和next两个指针,分别指向与当前inode处于同一个状态链表的前后两个元素。
i_sb_list:每个文件系统中的inode都会形成一个双联表,这个双链表的头结点存放在超级块的s_inodes中。而该字段中的prev和next指针分别指向在双链表中与其相邻的前后两个元素;
i_dentry:所有引用该inode的目录项将形成一个双联表,该字段即为这个双联表的头结点;
i_ino:索引结点号。通过ls -l命令可以查看文件的索引节点号;
i_count:引用计数;
i_nlink:硬链接数。当该inode描述一个目录时,这个值至少为2,代表.和..的数目;
i_uid:inode所属文件的拥有者的id,通过ls -n可查看拥有者id;
i_gid:inode所属文件所在组的id,通过ls -n可查看组id;
i_rdev:如果该inode描述的是一个设备文件,此值为设备号;
i_blkbits:以位为单位的块大小;
i_atime:文件最近一次被访问的时间。通过ls -lu可查看该时间;
i_mtime:文件最近一次被修改的时间,这里的修改只文件内容被修改。通过ls -l可查看该时间;
i_ctime:文件最近一次被修改的时间,这里的修改除了指文件内容被修改外,更强调的是文件的属性被修改。通过ls -lc可查看该时间;
i_blocks:文件使用块的个数,通过ls -s可以查看该某个文件的块使用数目;
i_mode:文件的访问权限;
i_op:指向索引结点操作结构体的指针;
i_fop:指向文件操作结构体的指针,这个字段用来初始化文件结构体(struct file)中的f_op字段;
i_sb:指向inode所属文件系统的超级块的指针;
i_pipe:如果inode所代表的文件是一个管道,则使用该字段;
i_bdev:如果inode所代表的文件是一个块设备,则使用该字段;
i_cdev:如果inode所代表的文件是一个字符设备,则使用该字段;

索引结点操作结构体

在inode结构体中,有一个i_op字段,该字段指向与索引结点相关的操作。这个字段的类型为struct inode_operations,在yoursource/include/linux/fs.h中有这个结构体的定义。下面只列出部分字段的说明。

01 1516struct inode_operations {
02 1517        int (*create) (struct inode *,struct dentry *,intstructnameidata *);
03 1518        struct dentry * (*lookup) (struct inode *,struct dentry *,struct nameidata *);
04 …… ……
05 1519        int (*link) (struct dentry *,struct inode *,struct dentry *);
06 1520        int (*unlink) (struct inode *,struct dentry *);
07 1521        int (*symlink) (struct inode *,struct dentry *,const char *);
08 1522        int (*mkdir) (struct inode *,struct dentry *,int);
09 1523        int (*rmdir) (struct inode *,struct dentry *);
10 1524        int (*mknod) (struct inode *,struct dentry *,int,dev_t);
11 …… ……
12 1544};

create:如果该inode描述一个目录文件,那么当在该目录下创建或打开一个文件时,内核必须为这个文件创建一个inode。VFS通过调用该inode的i_op->create()函数来完成上述新inode的创建。该函数的第一个参数为该目录的inode,第二个参数为要打开新文件的dentry,第三个参数是对该文件的访问权限。如果该inode描述的是一个普通文件,那么该inode永远都不会调用这个create函数;
lookup:查找指定文件的dentry;
link:用于在指定目录下创建一个硬链接。这个link函数最终会被系统调用link()调用。该函数的第一个参数是原始文件的dentry,第二个参数即为上述指定目录的inode,第三个参数是链接文件的dentry。
unlink:在某个目录下删除指定的硬链接。这个unlink函数最终会被系统调用unlink()调用。 第一个参数即为上述硬链接所在目录的inode,第二个参数为要删除文件的dentry。
symlink:在某个目录下新建
mkdir:在指定的目录下创建一个子目录,当前目录的inode会调用i_op->mkdir()。该函数会被系统调用mkdir()调用。第一个参数即为指定目录的inode,第二个参数为子目录的dentry,第三个参数为子目录权限;
rmdir:从inode所描述的目录中删除一个指定的子目录时,该函数会被系统调用rmdir()最终调用;
mknod:在指定的目录下创建一个特殊文件,比如管道、设备文件或套接字等。

目录项结构体

为了方便对目标文件的快速查找,VFS引入了目录项。目标文件路径中的每一项都代表一个目录项,比如/home/test.c中,/,home,test.c都分别是一个目录项。这些目录项都属于路径的一部分,并且每个目录项都与其对应的inode相联系。如果VFS得到了某个dentry,那么也就随之得到了这个目录项所对应文件的inode,这样就可以对这个inode所对应的文件进行相应操作。所以,依次沿着目标文件路径中各部分的目录项进行搜索,最终则可找到目标文件的inode。

与超级块和索引结点不同的是,目录项在磁盘上并没有对应的实体文件,它会在需要时候现场被创建。因此,在目录项结构体中并没有脏数据字段,因为目录项并不会涉及重写到磁盘。

目录项由struct dentry描述,在yoursource/include/linux/dcache.h中有这个结构的定义。下面只对部分字段进行说明。

01 89struct dentry {
02   90        atomic_t d_count;
03   91        unsigned int d_flags;           /* protected by d_lock */
04   92        spinlock_t d_lock;              /* per dentry lock */
05   93        int d_mounted;
06   94        struct inode *d_inode;          /* Where the name belongs to - NULL is
07   …… ……
08  100        struct hlist_node d_hash;       /* lookup hash list */
09  101        struct dentry *d_parent;        /* parent directory */
10  102        struct qstr d_name;
11  103
12  104        struct list_head d_lru;         /* LRU list */
13  108        union {
14  109                struct list_head d_child;       /* child of parent list */
15  110                struct rcu_head d_rcu;
16  111        } d_u;
17  112        struct list_head d_subdirs;     /* our children */
18  113        struct list_head d_alias;       /* inode alias list */
19  115        const struct dentry_operations *d_op;
20  116        struct super_block *d_sb;       /* The root of the dentry tree */
21 …… ……
22  120};

d_count:引用计数;
d_inode:与该目录项相关联的索引结点;
d_hash:内核使用哈希表对所有dentry进行管理,该字段使得当前dentry处于哈希表的某个冲突链表当中;
d_parent:指向父目录的目录项;
d_name:目录项的名称;
d_subdirs:如果当前目录项是一个目录,那么该目录下所有的子目录形成一个链表。该字段是这个链表的表头;
d_child:如果当前目录项是一个目录,那么该目录项通过这个字段加入到父目录的d_subdirs链表当中。这个字段中的next和prev指针分别指向父目录中的另外两个子目录;
d_alias:一个inode可能对应多个目录项,所有的目录项形成一个链表。inode结构中的i_dentry即为这个链表的头结点。当前目录项以这个字段处于i_dentry链表中。该字段中的prev和next指针分别指向与该目录项同inode的其他两个(如果有的话)目录项;
d_op:指向目录项操作结构体的指针;
d_sb:指向该目录项所属的文件系统对应的超级块;

目录项操作结构体
1 134struct dentry_operations {
2 135        int (*d_revalidate)(struct dentry *, struct nameidata *);
3 136        int (*d_hash) (struct dentry *, struct qstr *);
4 137        int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
5 138        int (*d_delete)(struct dentry *);
6 139        void (*d_release)(struct dentry *);
7 140        void (*d_iput)(struct dentry *, struct inode *);
8 141        char *(*d_dname)(struct dentry *, char *, int);
9 142};
文件结构体

VFS使用struct file来描述一个被进程已经打开的文件。与上述三个结构体不同,文件结构体是进程直接处理的对象。因此,在该结构体中你可以看到我们熟悉的一些文件属性信息。文件对象是已打开文件在内存中的表示,因此,它在磁盘上并没有与之对应的数据。也就是说,文件对象只存在于内存中,所以这个结构也就不涉及脏数据字段和是否需要写回磁盘。

有上述可知,每当一个进程打开一个文件时,内存中有会有相应的file结构体。因此,当一个文件被多个进程打开时,这个文件就会有多个对应的文件结构体。但是,这些文件结构体对应的索引结点和目录项却是唯一的。在yoursource/include/linux/fs.h中有这个结构体的定义。下面只列出部分字段的说明。

01 909struct file {
02 …… ……
03  914        union {
04  915                struct list_head        fu_list;
05  916                struct rcu_head         fu_rcuhead;
06  917        } f_u;
07  918        struct path             f_path;
08  919#define f_dentry        f_path.dentry
09  920#define f_vfsmnt        f_path.mnt
10  921        const struct file_operations    *f_op;
11  922        spinlock_t              f_lock;  /* f_ep_links, f_flags, no IRQ */
12  923#ifdef CONFIG_SMP
13  924        int                     f_sb_list_cpu;
14  925#endif
15  926        atomic_long_t           f_count;
16  927        unsigned int            f_flags;
17  928        fmode_t                 f_mode;
18  929        loff_t                  f_pos;
19  930        struct fown_struct      f_owner;
20  935#ifdef CONFIG_SECURITY
21  936        void                    *f_security;
22  937#endif
23  938        /* needed for tty driver, and maybe others */
24  939        void                    *private_data;
25  940
26 …… ……
27  949};

fu_list:每个文件系统中以被打开的文件都会形成一个双联表,这个双联表的头结点存放在超级块的s_files字段中。该字段的prev和next指针分别指向在链表中与当前文件结构体相邻的前后两个元素;
f_dentry:与该文件对应的dentry;
f_vfsmnt:该文件所在文系统的安装点,与f_dentry相结合可以得到该文件的绝对路径;
f_op:指向与该文件相关的操作的结构体;
f_count:该文件的引用计数;
f_flags:进程打开该文件时候的标志,比如以只读,可读写等方式打开该文件;
f_mode:该文件的访问权限;
f_pos:当前该文件的偏移量,读写操作均从该偏移量开始;
f_security:指向文件安全数据结构struct file_security_struct的指针;

文件操作结构体

VFS使用struct file_operations来描述与文件相关的操作集合,文件结构体中的f_op字段就指向这种结构体类型。在这个操作结构体中,有许多我们所熟悉的函数指针,大多数与文件相关的系统调用最终会调用这里的函数。当一个进程打开某个文件时,该文件结构体中的f_op字段是通过该文件inode对象中的i_fop字段来初始化的。在yoursource/include/linux/fs.h中有这个结构体的定义。下面只列出部分字段的说明。

01 1488struct file_operations {
02 1489        struct module *owner;
03 1490        loff_t (*llseek) (struct file *, loff_t, int);
04 1491        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
05 1492        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
06 …… ……
07 1499        int (*mmap) (struct file *, struct vm_area_struct *);
08 1500        int (*open) (struct inode *, struct file *);
09 1502        int (*release) (struct inode *, struct file *);
10 …… ……
11 1514};

owner:用于指定拥有这个文件操作结构体的模块,通常取THIS_MODULE;
llseek:用于设置文件的偏移量。第一个参数指明要操作的文件,第二个参数为偏移量,第三个参数为开始偏移的位置(可取SEEK_SET,SEEK_CUR和SEEK_END之一)。
read:从文件中读数据。第一个参数为源文件,第二个参数为目的字符串,第三个参数指明欲读数据的总字节数,第四个参数指明从源文件的某个偏移量处开始读数据。由系统调用read()调用;
write:往文件里写数据。第一个参数为目的文件,第二个参数源字符串,第三个参数指明欲写数据的总字节数,第四个参数指明从目的文件的某个偏移量出开始写数据。由系统调用write()调用;
mmap:将指定文件映射到指定的地址空间上。由系统调用mmap()调用;
open:打开指定文件,并且将这个文件和指定的索引结点关联起来。由系统调用open()调用;
release:释放以打开的文件,当打开文件的引用计数(f_count)为0时,该函数被调用;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值