zoukankan      html  css  js  c++  java
  • Linux13-虚拟文件系统

    Linux内核第13章

    通过虚拟文件系统VFS,程序可以利用标准的Unix系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作。

    13.1 通用文件系统接口

    VFS使得用户可以直接使用open()、rea()和write()这样的系统调用而无需考虑具体文件系统和实际物理介质。

    13.2 文件系统抽象层

    之所以可以使用这种通用接口对所有类型的文件系统进行操作,是因为内核在它的底层文件系统接口上建立了一个抽象层。为了支持多文件系统,VFS提供了一个通用文件系统模型,该模型囊括了任何文件系统的常用功能集和行为。

    VFS抽象层之所以能衔接各种各样的文件系统,是因为它定义了所有文件系统都支持的、基本的、概念上的接口和数据结构。

    例如:

    ret=write(fd, buf, len);

    该系统调用将buf指针指向的长度为len字节的数据写入文件描述符fd对应的文件的当前位置。这个系统调用首先被一个通用系统调用sys_write()处理,sys_write()函数要找到fd所在的文件系统实际给出的是哪个写操作,然后再执行该操作。实际文件系统的写方法是文件系统实现的一部分,数据最终通过该操作写入介质(或执行这个文件系统想要完成的动作)。

    13.3 Unix文件系统

    Unix使用了四种和文件系统相关的抽象概念:文件、目录项、索引节点和安装点(mount point)。

    在Unix中,文件系统被安装在一个特定的安装点上,该安装点在全局层次结构中被称作命名空间,所有的已安装文件系统都作为根文件系统树的枝叶出现在系统中。

    文件可以看作一个有序字节串,字节串中第一个字节是文件的头,最后一个字节是文件的尾。每一个文件分配一个便于理解的名字。典型的文件操作有读、写、创建、删除等。

    文件通过目录组织起来。文件目录好比一个文件夹,用来容纳相关文件。因为目录也可以包含其它目录,即子目录,所以目录可以层层嵌套,形成文件路径。路径中的每一部分都被称作目录条目。在Unix中,目录属于普通文件,它列出包含在其中的所有文件。由于VFS把目录当作文件对待,所以可以对目录执行和文件相同的操作。

    Unix系统将文件的相关信息和文件本身这两个概念加以区分,例如访问控制权限、大小、拥有者、创建时间等信息。文件相关信息,也称作文件的元数据(文件相关的数据),被存储在一个单独的数据结构中,该结构称为索引节点inode(index node)。

    文件系统的控制信息存储在超级块中,超级块是一种包含文件系统信息的数据结构。有时,把这些收集起来的信息称为文件系统数据元,它集单独文件信息和文件系统的信息于一身。

    13.4 VFS对象及其数据结构

    VFS其实采用的是面向对象的设计思路,使用一组数据结构来代表通用文件对象。

    VFS中有四个主要的对象类型,它们分别是:

    -超级块对象,它代表一个具体的已安装文件系统。

    -索引节点对象,它代表一个具体文件。

    -目录项对象,它代表一个目录项,是路径的一个组成部分。

    -文件对象,它代表由进程打开的文件。

    VFS将目录作为文件,所以没有目录对象。

    每个主要对象中都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法:

    -super_operations对象,其中包含内核针对特定文件系统所能调用的方法,比如write_inode()和sys_fs()等方法。

    -inode_operations对象,其中包含内核针对特定文件所能调用的方法,比如create()和link()等方法。

    -dentry_operations对象,其中包括内核针对特定目录所能调用的方法,比如d_compare()和d_delete()等方法。

    -file_operations对象,其中包括进程针对已打开文件所能调用的方法,比如read()和write()等方法。

    操作对象作为一个结构体指针来实现,此结构体中包含指向操作其父对象的函数指针。对于其中许多方法来说,可以继承使用VFS提供的通用函数,如果通用函数提供的基本功能无法满足需要那么就必须使用实际文件系统的独有方法填充这些函数指针,使其指向文件系统实例。

    13.5 超级块对象

    各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块。对于并非基于磁盘的文件系统,它们会在使用现场创建超级块并将其保存到内存中。

    超级块对象由super_block结构体表示,定义在文件linux/fs.h中,下面给出它的结构和各个域的描述:

    struct super_block{

      struct list_head  s_list;  //指向所有超级块的链表

      dev_t  s_dev;  //设备标识符

      unsigned long  s_blocksize;  //以字节为单位的块大小

      unsigned char  

      。。。。。

    };//书上214页

    创建、管理和撤销超级块对象的代码位于fs/super.c中。超级块对象通过alloc_super()函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。

    13.6 超级块对象中最重要的一个域是s_op,它指向超级块的操作函数表。超级块操作函数表由super_operations结构体表示,定义在linux/fs.h中,其形式如下:

    struct super_operations{

      。。。

    };

    该结构体中的每一项都是一个指向超级块操作函数的指针,超级块操作函数执行文件系统和索引节点的低层操作。

    在C语言中无法直接得到操作函数的父对象,所以必须将父对象以参数形式传给操作函数。

    在给定的超级块下创建和初始化一个新的索引节点对象:

    struct inode *alloc_inode(struct super_block *sb)

    释放给定的索引节点:

    void destroy_inode(struct inode *inode)

    VFS在索引节点脏(被修改)时调用函数,日志文件系统(如ext3、ext4)执行该函数进行日志更新:

    void dirty_inode(struct inode *inode)

    将给定的索引节点写入磁盘,wait参数指明写操作是否需要同步:

    void write_inode( struct inode *inode, int wait)

    在最后一个指向索引节点的引用被释放后,VFS会调用该函数。VFS只需要简单地删除这个索引节点后,普通Unix文件系统就不会定义这个函数了:

    void drop_inode(struct inode*inode)

    从磁盘上删除给定的索引节点:

    void delete_inode(struct inode *inode)

    在卸载文件系统时由VFS调用,用来释放超级块。调用者必须一直持有s_lock锁:

    void put_super(struct super_block *sb)

    用给定的超级块更新磁盘上的超级块。VFS通过该函数对内存中的超级块和磁盘中的超级块进行同步。调用者必须一直持有s_lock锁。

    void write_super(struct super_block *sb)

    使文件系统的数据元与磁盘上的文件系统同步。wait参数指定操作是否同步:

    int sync_fs( struct super_block *sb, int wait)

    首先禁止对文件系统做改变,再使用给定的超级块更新磁盘上的超级块。目前LVM逻辑卷标管理会调用该函数:

    void write_super_lockfs( struct super_block *sb)

    对文件系统解除锁定,它是write_super_lockfs()的逆操作:

    void unlockfs( struct super_block *sb)

    VFS通过调用该函数获取文件系统状态。指定文件系统相关的统计信息将放置在statfs中:

    int statfs( struct super_block *sb, struct statfs *statfs)

    当制定新的安装选项重新安装文件系统时,VFS会调用该函数。调用者必须一直持有s_lock锁:

    int remount_fs( struct super_block *sb, int *flags, char *data)

    VFS调用该函数释放索引节点,并清空包含相关数据的所有页面:

    void clear_inode( struct inode *inode)

    VFS调用该函数中断安装操作。该函数被网络文件系统使用,如NFS:

    void mount_begin( struct super_block *sb)

    所有以上函数都是由VFS在进程上下文中调用。除了dirty_inode(),其它函数在必要时都可以阻塞。

    在超级块操作表中,文件系统可以将不需要的函数指针设置为NULL。如果VFS发现操作函数指针是NULL,那它要么就会调用通过函数指向相应操作,要么什么也不做,如何选择取决于具体操作。

    13.7 索引节点对象

    索引节点对象包含了内核在操作文件或目录时需要的全部信息。对于Unix风格的文件系统来说,这些信息可以从磁盘索引节点直接读入。没有索引节点的文件系统通常将文件的描述信息作为文件的一部分来存放。不管哪种方式,索引节点对象必须在内存中创建,以便于文件系统使用。

    索引节点对象由inode结构体表示,定义在文件linux/fs.h中:成员中有对应的超级块链表

    struct inode{

      。。。

    };

    一个索引节点代表文件系统中(但是索引节点仅当文件被访问时,才在内存中创建)的一个文件,它也可以是设备或管道这样的特殊文件。因此索引节点结构体中有一些和特殊文件相关的项,比如i_pipe项就指向一个代表有名管道的数据结构,i_bdev指向块设备结构体,i_cdev指向字符设备结构体。这三个指针被存放在一个共用体中,因为一个给定的索引节点每次只能表示三者之一(或三者均不)。

    13.8 索引节点对象

    和超级块操作一样,索引节点对象中的inode_operations项也非常重要,它描述了VFS用以操作索引节点对象的所有方法,这些方法由文件系统实现。

    struct inode_operations{

      。。。

    };

    int create(struct inode*dir, struct dentry *dentry, int mode)

    VFS通过系统调用create()和open()来调用该函数,从而为dentry对象创建一个新的索引节点。在创建时使用mode指定的初始模式。

    struct dentry *lookup( struct inode *dir, struct dentry *dentry)

    该函数在特定目录下寻找索引节点,该索引节点要对应于dentry中给出的文件名。

    int link( struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)

    该函数被系统调用link()调用,用来创建硬连接。硬连接由dentry参数指定,连接对象是dir目录中old_dentry目录项所代表的文件。

    int unlink(struct inode *dir, struct dentry *dentry)

    该函数被系统调用unlink调用,从目录dir中删除由目录项dentry指定的索引节点对象。

    int symlink(struct inode *dir, struct dentry *dentry, const char *symname)

    该函数被系统调用symlink调用,创建符号连接。该符号连接名称由symname指定,连接对象是dir目录中的dentry目录项。

    int mkdir(struct inode *dir, struct dentry *dentry, int mode)

    该函数被系统调用mkdir()调用,创建一个新目录。创建时使用mode指定的初始模式。

    int rmdir( struct inode *dir, struct dentry *dentry)

    该函数被系统调用rmdir()调用,删除dir目录中的dentry目录项代表的文件。

    int mknode(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev)

    该函数被系统调用mknod()调用,创建特殊文件(设备文件、命名管道或套接字)。要创建的文件放在dir目录中,其目录项为dentry,关联的设备为rdev,初始权限由mode指定。

    int rename( struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry)

    VFS调用该函数来移动文件。文件源路径在old_dir目录中,源文件由old_dentry目录项指定,目标路径在new_dir目录中,目标文件由new_dentry指定。

    int readlink(struct dentry *dentry, char *buffer, int buflen)

    该函数被系统调用readlink()调用,拷贝数据到特定的缓冲buffer中。拷贝的数据来自dentry指定的符号链接,拷贝大小最大可达buflen字节。

    int follow_link(struct dentry *dentry, struct nameidata *nd)

    该函数由VFS调用,从一个符号连接查找它指向的索引节点。由dentry指向的连接被解析,其结果存放在由nd指向的nameidata结构体中。

    int put_link(struct dentry *dentry, struct nameidata *nd)

    在follow_link()调用之后,该函数由VFS调用进行清除工作。

    void truncate(struct inode *inode)

    该函数由VFS调用,修改文件的大小。在调用前,索引节点的i_size项必须设置为预期的大小。

    int permission(struct inode *inode, int mask)

    该函数用来检查给定的inode所代表的文件是否允许特定的访问模式。如果允许特定的访问模式,返回0,否则返回负值的错误码。多数文件系统都将此区域设置为NULL,使用VFS提供的通用方法进行检查。这种检查操作仅仅比较索引节点对象中的访问模式位是否和给定的mask一致。比较复杂的系统,需要使用特殊的permission()方法。

    int setattr(struct dentry *dentry, struct iattr *attr)

    该函数被notify_change()调用,在修改索引节点后,通知发生了“改变事件”。

    int getattr( struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)

    在通知索引节点需要从磁盘中更新时,VFS会调用该函数。

    扩展属性允许key/value这样的一对值与文件相关联。

    int setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags)

    该函数由VFS调用,给dentry指定的文件设置扩展属性。属性名为name,值为value。

    ssize_t getxattr( struct dentry *dentry, const char *name, void *value, size_t size)

    该函数由VFS调用,向value中拷贝给定文件的扩展属性name对应的数值。

    ssize_t listxattr(struct dentry *dentry, char *list, size_t size)

    该函数将特定文件的所有属性列表拷贝到一个缓冲列表中。

    int removexattr( struct dentry *dentry, const char *name)

    该函数从给定文件中删除指定的属性。

    13.9 目录项对象

    VFS把目录当作文件对待,所以在路径/bin/vi中,bin和vi都属于文件----bin特殊的目录文件而vi是一个普通文件,路径中的每个组成部分都由一个索引节点对象表示。路径名查找需要解析路径中的每一个组成部分,不但要确保它有效,而且还需要再进一步寻找路径中的下一个部分。

    为了方便查找操作,VFS引入了目录项的概念。每个dentry代表路径中的一个特定部分。对前一个例子来说,/、bin和vi都属于目录项对象。前两个是目录,最后一个是普通文件。必须明确一点:在路劲中(包括普通文件在内),每一个部分都是目录项对象。

    目录项也可以包括安装点。在路径/mnt/cdrom/foo中,构成元素/、mnt、cdrom和foo都属于目录项对象。VFS在执行目录操作时(如果需要的话)会现场创建目录项对象。

    目录项对象由dentry结构体表示,定义在文件linux/dcache.h中:

    struct dentry{

      atomic_t  d_count;  //使用计数

      unsigned int  d_flags;  //目录项标识

      spinlock_t  d_lock;  //单目录项锁

      int  d_mounted;  //是登录点的目录项吗?

      struct inode  *d_inode;  //相关联的索引节点

      struct hlist_node  d_hash;  //散列表

      struct dentry  *d_parent;  //父目录的目录项对象

      struct qstr  d_name;  //目录项名称

      struct list_head  d_lru;  //未使用的链表

      union{

        struct list_head  d_child;  //目录项内部形成的链表

        struct rcu_head  d_rcu;  //RCU加锁

      }d_u;

      struct list_head  d_subdirs;  //子目录链表

      struct list_head  a_alias;  //索引节点别名链表

      unsigned long  d_time;  //重置时间

      struct dentry_operations  *d_op;  //目录项操作指针

      struct super_block  *d_sb;  //文件的超级块

      void  *d_fsdata;  //文件系统特有数据

      unsigned char  d_iname[DNAME_INLINE_LEN_MIN];  //短文件名

    };

    与前两个对象不同,目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它。而且由于目录项对象并非真正保存在磁盘上,所以目录想结构体没有是否被修改的标志(也就是是否为脏、是否需要写回磁盘的标志)。

    13.9.1 目录项状态

    目录项有三种有效状态:被使用、未被使用和负状态。

    一个被使用的目录项对应一个有效的索引节点(即d_inode指向相应的索引节点)并且表明该对象存在一个或多个使用者(即d_count为正值)。一个目录项处于被使用状态,意味着它正被VFS使用并且指向有效的数据,因此不能被丢弃。

    一个未被使用的目录项对应一个有效的索引节点(d_inode指向一个索引节点),但是应指明VFS当前并未使用它(d_count为0)。该目录项对象仍然指向一个有效对象,而且被保留在缓存中以便需要时再使用它。由于该目录项不会过早地被撤销,所以以后再需要它时,不必重新创建,与未缓存的目录项相比,这样使路径查找更迅速。但是如果要回收内存的话,可以撤销未使用的目录项。

    一个负状态的目录项没有对应的有效索引节点(d_inode为NULL),因为索引节点已被删除了,或路径不在正确了,但是目录项仍然保留,以便快速解析以后的路径查询。比如,一个守护进程不断试图打开并读取一个不存在的配置文件。open()系统调用不断地返回ENOENT,直到内核构建了这个路径、遍历磁盘上的目录结构体并检查这个文件的确不存在为止。即便这个失败的查找很浪费资源,但是将负状态缓存起来还是非常值得的。虽然负状态的目录项有些用处,但是如果有需要,可以撤销它,实际上很少使用它。

    13.9.2 目录项缓存dcache

    如果VFS层遍历路径名中所有的元素并将它们逐个地解析成目录对象,还要到达最深层目录,将是一件非常费力的工作,会浪费大量的时间。所以内核将目录项对象缓存在目录项缓存(简称dcache中)。

    目录项缓存包括三个主要部分:

    1)“被使用的”目录项链表。该链表通过索引节点对象中的i_dentry项连接相关的索引节点,因为一个给定的索引节点可能有多个连接,所以就可能有多个目录项对象,因此用一个链表来连接它们。

    2)“最近被使用的”双向链表。该链表含有未被使用的和负状态的目录项对象。由于该链总是在头部插入目录项,所以链头节点的数据总比链尾的数据要新。当内核必须通过删除节点项回收内存时,会从链尾删除节点项,因为尾部的节点最旧,所以它们在近期再次被使用的可能性最小。

    3)散列表和相应的散列函数用来快速地将给定路径解析为相关目录项对象。

    散列表由数组dentry_hashtable表示,其中每一个元素都是一个指向具有相同键值的目录项对象链表的指针。数组的大小取决于系统中物理内存的大小。

    实际的散列值由d_hash()函数计算,它是内核提供给文件系统的唯一的一个散列函数。

    查找散列表要通过d_lookup()函数,如果该函数在dcache中发现了与其匹配的目录项对象,则匹配的对象被返回;否则,返回NULL指针。

    dcache在一定意义上也提供对索引节点的缓存,也就是icache。和目录项对象相关的索引节点对象不会被释放,因为目录项会让相关索引节点的使用计数为正,这样就可以确保索引节点留在内存中。只要目录项被缓存,其相应的索引节点肯定也在内存中缓存着。只要路径名在缓存中找到了,那么相应的索引节点肯定也在内存中缓存着。

    因为文件访问呈现空间和时间的局部性,所以对目录项和索引节点进行缓存非常有益。

    13.10 目录项操作

    dentry_operations结构体指明了VFS操作目录项的所有办法,定义在linux/dcache.h中:

    struct dentry_operations{

      ....

    };

    下面给出函数的具体用法。

    int d_revalidate(struct dentry *dentry, struct nameidata *);

    该函数判断目录项对象是否有效。VFS准备从dcache中使用一个目录项时,会调用该函数。大部分文件系统将该方法置NULL,因为它们认为dcache中的目录项对象总是有效的。

    int d_hash(struct dentry *dentry, struct qstr *name)

    该函数为目录项生成散列值,当目录项需要加入到散列表中时,VFS调用该函数。

    int d_compare(struct dentry *dentry, struct qstr *name1, struct qstr *name2)

    VFS调用该函数来比较name1和name2这两个文件名。多数文件系统使用VFS默认的操作,仅仅作字符串比较。注意使用该函数时需要加dcache_lock锁。

    int d_delete( struct dentry *dentry)

    当目录项对象的d_count计数值等于0时,VFS调用该函数。注意使用该函数需要加dcache_lock锁和目录项的d_lock。

    void d_release( struct dentry *dentry)

    当目录项对象要被释放时,VFS调用该函数,默认情况下,它什么也不做。

    void d_iput( struct dentry *dentry, struct inode *inode)

    当一个目录项对象丢失了其相关的索引节点时(也就是说磁盘索引节点被删除了),VFS调用该函数。默认情况下VFS会调用iput()函数释放索引节点。如果文件系统重载了该函数,那么除了执行此文件系统特殊的工作外,还必须调用iput()函数。

    13.11 文件对象

    文件对象表示进程已打开的文件。文件对象包含我们非常熟悉的信息(如访问模式,当前偏移等),同样道理,文件操作和我们非常熟悉的系统调用read()和write()等也很类似。

    文件对象是已打开的文件在内存中的表示。该对象(不是物理文件)由相应的open()系统调用创建,由close()系统调用撤销,所有这些文件相关的调用实际上都是文件操作表中定义的方法。因为多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已打开文件,它反过来指向目录项对象(反过来指向索引节点),其实只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项对象是唯一的。

    文件对象由file结构体表示,定义在文件linux/fs.h中,以下是该结构体各项描述:

    struct file{

      union{

        struct list_head  fu_list;  //文件对象链表

        struct rcu_head  fu_rcuhead;  //释放之后的RCU链表

      }f_u;

      struct path  f_path;  //包含目录项    f_path.dentry;   f_path.mnt;   从这里指向目录项

      struct file_operations  *f_op;  //文件操作表

      spinlock_t  f_lock;  //单个文件结构锁

      atomic_t  f_count;  //文件对象的使用计数

      unsigned int  f_flags;  //当打开文件时所指定的标志

      mode_t  f_mode;  //文件的访问模式

      loff_t  f_pos;  //文件当前的位移量(文件指针)

      struct fown_struct  f_owner;  //拥有者通过信号进行异步I/O数据的传送

      const struct cred  *f_cred;  //文件的信任状

      struct file_ra_state  f_ra;  //预读状态

      u64  f_version;  //版本号

      void  *f_security;  //安全模板

      void  *private_data;  //tty设备驱动的钩子

      struct list_head  f_ep_links;  //事件池链表

      spinlock_t  f_ep_lock;  //事件池锁

      struct address_space  *f_mapping;  //页缓存映射

      unsigned long  f_mnt_write_stata;  //调试状态

    };

    类似于目录项对象,文件对象实际上没有对应的磁盘数据。所以在结构体中没有代表其对象是否在脏、是否需要写回磁盘的标志。文件对象通过f_dentry指针指向相关的目录项对象。目录项会指向相关的索引节点,索引节点会记录文件是否是脏的。

    13.12 文件操作

    文件对象的操作由file_operations结构体表示,定义在文件linux/fs.h中:

    struct file_operations{

      ......

    };

    具体的文件系统可以为每一种操作做专门的实现,或者如果存在通用操作,也可以使用通用操作。对不感兴趣的操作可以设为NULL。

    loff_t lleek( struct file *file, loff_t offset, int origin)

    该函数用于更新偏移量指针,由系统调用lleek()调用它。

    ssize_t read( struct file *file, char *buf, size_t count, loff_t *offset)

    该函数从给定文件的offset偏移处读取count字节的数据到buf中,同时更新文件指针。由系统调用read()调用它。

    ssize_t aio_read(struct kiocb *iocb, char *buf, size_t count, loff_t offset)

    该函数从iocb描述的文件里,以同步方式读取count字节的数据到buf中。由系统调用aio_read()调用它。

    ssize_t write( struct file *file, const char *buf, size_t count, loff_t *offset)

    该函数从给定的buf中取出count字节的数据,写入给定文件的offset偏移处,同时更新文件指针。由系统调用write()调用它。

    ssize_t aio_write( struct kiocb *iocb, const char *buf, size_t count, loff_t offset)

    该函数以同步方式从给定的buf中取出count字节的数据。写入由iocb描述的文件中。由系统调用aio_write()调用它。

    int readdir( struct file *file, void *dirent, filldir_t filldir)

    该函数返回目录列表的下一个目录。由系统调用readdir()调用它。

    unsigned int poll( struct file *file, struct poll_table_struct *poll_table)

    该函数睡眠等待给定文件活动。由系统调用poll()调用它。

    int ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

    该函数用来给设备发送命令参数对。当文件是一个被打开的设备节点时,可以通过它进行设置操作。由系统调用ioctl()调用它。调用者必须持有BKL。

    int unlocked_ioctl( struct file *file, unsigned int cmd, unsigned long arg)

    其实现与ioctl()有类似的功能,只不过不需要调用者持有KBL。如果用户空间调用ioctl()系统调用,VFS便可以调用unlocked_ioctl()。因此文件系统只需要实现其中的一个,一般优先实现unlocked_ioctl()。

    int compat_ioctl (struct file *file, unsigned int cmd, unsigned long arg)

    该函数是ioctl()函数的可移植变种,被32位应用程序用在64位系统上。不需要调用者持有BKL。

    int mmao( struct file *file, struct vm_area_struct *vma)

    该函数将给定的文件映射到指定的地址空间上。由系统调用mmap()调用它。

    int open( struct inode *inode, struct file *file)

    该函数创建一个新的文件对象,并将它和相应的索引节点对象关联起来。由系统调用open()调用它。

    int flush( struct file *file)

    当已打开文件的引用计数减少时,该函数被VFS调用。它的作用根据具体文件系统而定。

    int release( struct inode *inode, struct file *file)

    当文件的最后一个引用被注销时(比如,当最后一个共享文件描述符的进程调用了close()或退出时),该函数会被VFS调用。它的作用根据具体文件系统而定。

    int fsync( struct file *file, struct dentry *dentry, int datasync)

    将给定文件的所有被缓存数据写回磁盘。由系统调用fsync()调用它。

    int aio_fsync( struct kiocb *iocb, int datasync)

    将iocb描述的文件的所有被缓存数据写回到磁盘。由系统调用aio_fsync()调用它。

    int fasync( int fd, struct file *file, int on)

    该函数用于打开或关闭异步I/O的通告信号。

    int lock( struct file *file, int cmd, struct file_lock *lock)

    该函数用于给指定文件上锁。

    ssize_t readv(struct file *file, const struct iovec *vector, unsigned long count, loff_t *offset)

    该函数从给定文件中读取数据,并将其写入由vector描述的count个缓冲中去,同时增加文件的偏移量。由系统调用readv()调用它。

    ssize_t writev( struct file *file, const struct iovec *vector, unsigned long count, loff_t *offset)

    该函数将由vector描述的count个缓冲中的数据写入到由file指定的文件中区=去,同时减小文件的偏移量。由系统调用writev()调用它。

    ssize_t sendfile(struct file *file, loff_t *offset, size_t size, read_actor_t actor, void *target)

    该函数用于从一个文件拷贝数据到另一个文件中,它执行的拷贝操作完全在内核中完成,避免了向用户空间进行不必要的拷贝。由系统调用sendfile()调用它。

    ssize_t sendpage( struct file *file, struct page *page, int offset, size_t size, loff_t *pos, int more)

    该函数用来从一个文件向另一个文件发送数据。

    unsigned long get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, unsigned long offset, unsigned long flags)

    该函数用于获取未使用的地址空间来映射给定的文件。

    int check_flags(int flags)

    当给出SETFL命令时,这个函数用来检查传递给fcntl()系统调用的flags的有效性。

    int flock(struct file *file, int cmd, struct file_lcok *fl)

    这个函数用来实现flock()系统调用,该调用提供忠告锁。

    13.13 和文件系统相关的数据结构

    除了以上几种VFS基础对象外,内核还使用了另外一些标准数据结构来管理文件系统的其它相关数据。第一个对象是file_system_type,用来描述各种特定文件系统类型,比如ext3、ext4或UDF。第二个结构体是vfsmount,用来描述一个安装文件系统的实例。

    因为Linux支持众多的文件系统,所以内核使用一个特殊的结构来描述每种文件系统的功能和行为。file_system_type结构体被定义在linux/fs.h中,具体实现如下

    struct file_system_type{

      const char  *name;  //文件系统的名字

      int  fs_flags;  //文件系统类型标志

      struct super_block *(*get_sb) (struct file_system_type *, int, char *, void *);  //用来从键盘中读取超级块

      void (*kill_sb) (struct super_block *);  //终止访问超级块

      struct module  *owner;  //文件系统模块

      struct file_system_type  *next;  //链表中下一个文件系统类型

      struct list_head  fs_supers;  //超级块对象链表

      //剩下的几个字段运行时使锁生效

      struct lock_class_key  s_lock_key;

      struct lock_class_key  s_mount_key;

      struct lock_class_key  i_lock_key;

      struct lock_class_key  i_mutex_key;

      struct lock_class_key  i_mutex_dir_key;

      struct lock_class_key  i_alloc_sem_key;

    };

    get_sb()函数从磁盘上读取超级块,并且在文件系统被安装时,在内存中组装超级块对象。

    每种文件系统,不管有多少个实例安装到系统中,还是根本就没有安装到系统中。都只有一个file_system_type结构。

    当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建。该结构体用来代表文件系统的实例-----代表一个安装点。

    vfsmount结构如下:

    struct vfsmount{

      struct list_head  mnt_hash;  //散列表

      struct vfsmount  *mnt_parent;  //父文件系统

      struct dentry  *mnt_mountpoint;  //安装点的目录项

      struct dentry  *mnt_root;  //该文件系统的根目录想

      struct super_block  *mnt_sb;  //该文件系统的超级块

      struct list_head  mnt_mounts;  //子文件系统链表

      struct list_head  mnt_child;  //子文件系统链表;

      int  mnt_flags;  //安装标志

      char  *mnt_devname;  //设备文件名

      struct list_head  mnt_list;  //描述符链表

      struct list_head  mnt_expire;  //在到期链表的入口处

      struct list_head  mnt_share;  //在共享链表的入口处

      struct list_head  mnt_slave_list;  //从安装链表

      struct list_head  mnt_slave;  //从安装链表的入口

      struct vfsmount  *mnt_master;  //从安装链表的主人

      struct mnt_namespace  *mnt_namespace;  //相关的命名空间

      int  mnt_id;  //安装标识符

      int  mnt_group_id;  //组标识符

      atomic_t  mnt_count;  //使用计数

      int  mnt_expire_mask;  //如果标记为到期,则值为真

      int   mnt_pinned;  //钉住进程计数

      int  mnt_ghosts:  //镜像引用计数

      atomic_t  __mnt_writers;  //写者引用计数

    };

    。。

    13.14 和进程相关的数据结构

    系统中的每一个进程都有自己的一组打开的文件,像根文件系统、当前工作目录、安装点等。有三个数据结构将VFS层和系统的进程紧密联系在一起,它们分别是:file_struct、fs_struct和namespace结构体。

    file_struct结构体由进程描述符中的files目录项指向,所有与单个进程相关的信息(如打开的文件及文件描述符)都包含在其中/其结构和描述如下:

    struct file_struct{

      atomic_t  count;  //结构的使用计数

      struct fdtable  *fdt;  //指向其它fd表的指针

      struct fdtable  fdtab;  //基fd表

      spinlock_t  file_lock;  //单个文件的锁

      int  next_fd;  //缓存下一个可用的fd

      struct embedded_fd_set  close_on_exec_init;  //exec()时关闭的文件描述符链表

      struct embedded_fd_set  open_fds_init;  //打开的文件描述符链表

      struct file  *fd_array[NR_OPEN_DEFAULT];  //缺省的文件对象数组

    };

    fd_array数组指针指向已打开的文件对象。因为NR_OPEN_DEFAULT等于BITS_PER_LONG,在64位机器体系结构中这个宏的值为64,所以该数组可以容纳64个文件对象。如果一个进程所打开的文件对象超过64个,内核将分配一个新数组,并且将fdt指针指向它。

    如果一个进程打开的文件数量过多,那么内核就需要建立新数组。所以如果系统中有大量的进程都要打开超过64个文件。为了优化性能,管理员可以适当增大NR_OPEN_DEFAULT的预定义值。

    和进程相关的第二个结构体是fs_struct。该结构体由进程描述符的fs域指向。它包含文件系统和进程相关的信息。以下是其具体结构体各项描述:

    struct fs_struct{

      int  users;  //用户数组

      rwlock_t  lock;  //保护该结构体的锁

      int  umask;  //掩码

      int  in_exec;  //当前正在执行的文件

      struct path  root;  //根目录路径

      struct path  root;  //当前工作目录的路径

    };

    该结构包含了当前进程的当前工作目录(pws)和根目录。

    第三个进程相关结构体是namespace结构体,由进程描述符中的mmt_namespace域指向。2.4内核以后,单进程命名空间被加入到内核中,它使得每一个进程在系统中都看到唯一的安装文件系统----不仅是唯一的根目录,而且是唯一的文件系统层次结构。

    struct mnt_namespace{

      atomic_t  count;  //结构的使用计数

      struct vfsmount  *root;  //根目录的安装点对象

      struct list_head  list;  //安装点链表

      wait_queue_head_t  poll;  //轮询的等待队列

      int  event;  //事件计数

    };

    list域是连接已安装文件系统的双向链表,它包含的元素组成了全体命名空间。

    上述这些数据结构都是通过进程描述符连接起来的。对多数进程来说,它们的描述符都指向唯一的file_struct和fs_struct结构体。但是,对于那些使用克隆标志CLONE_FILES或CLONE_FS创建的进程,会共享这两个结构体。所以多个进车那个描述符可能指向同一个files_struct或fs_struct结构体。每个结构体都维护一个count域作为引用计数,它防止在进程正使用该结构时,该结构被撤销。

    namespace结构体的使用方法与前两种不同,默认情况下,所有的进程共享同样的命名空间。只有在进行clone()操作时使用CLONE_NEWS标志,才会给进程唯一的命名空间结构体的拷贝。因为大多数进程不提供这个标志,所有进程都继承其父进程的命名空间。

    PS.线程通常在创建时使用CLONE_FILES和CLONE_FS标志,所以多个线程共享一个file_struct结构体和fs_struct结构体。但另一方面,普通进程没有指定这些标志,所以它们有自己的文件系统信息和打开文件表。

  • 相关阅读:
    Qt实战6.万能的无边框窗口(FramelessWindow)
    Qt实战5.如何获取USB设备信息?
    Qt实战4.简单封装的文件监控
    Qt实战3.Qt仿Win10风格界面
    Linux下使用脚本让程序顺序启动
    项目经验1.软件的开发过程
    Qt实战2.老生常谈的文件传输
    mysql导入txt文件
    linux离线安装python3
    mysql知识点
  • 原文地址:https://www.cnblogs.com/cjj-ggboy/p/12363047.html
Copyright © 2011-2022 走看看