Linux 内核编程之文件系统(二)

VFS中的目录项对象和文件对象

 

 

.VFS 中的目录项对象

1.为了方便查找,VFS引入了 目录 项,每个dentry代表路径中的一个特定部分。目录项也可包括安装点。
2.目录项对象由dentry结构体表示 ,定义在文件linux/dcache.h 头文件中。
  89struct dentry {
  90        atomic_t d_count;                //使用计数
  91        unsigned int d_flags;           //目录项标时
  92        spinlock_t d_lock;                //单目录锁
  93        int d_mounted;                    //目录项的安装点
  94        struct inode *d_inode;        //与该目录项相关联的索引节点
  95                                       
  96        /*
  97         * The next three fields are touched by __d_lookup.  Place them here
  98         * so they all fit in a cache line.
  99         */
 100        struct hlist_node d_hash;        //散列表
 101        struct dentry *d_parent;          //父目录项
 102        struct qstr d_name;                //目录项名可快速查找
 103
 104        struct list_head d_lru;            // 未使用目录以LRU 算法链接的链表
 105        /*
 106         * d_child and d_rcu can share memory
 107         */
 108        union {
 109                struct list_head d_child;       /* child of parent list */
 110                struct rcu_head d_rcu;
 111        } d_u;
 112        struct list_head d_subdirs;     //该目录项子目录项所形成的链表
 113        struct list_head d_alias;        //索引节点别名链表
 114        unsigned long d_time;           //重新生效时间
 115        const struct dentry_operations *d_op;    // 操作目录项的函数
 116        struct super_block *d_sb;       //目录项树的根
 117        void *d_fsdata;                        //具体文件系统的数据
 118
 119        unsigned char d_iname[DNAME_INLINE_LEN_MIN];    //短文件名
 120};
1>索引节点中的i_dentry指向了它目录项,目录项中的d_alias,d_inode又指会了索引节点对象,目录项中的d_sb又指回了超级块对象。
2>我们可以看到不同于VFS 中的索引节点对象和超级块对象,目录项对象中没有对应磁盘的数据结构,所以说明目录项对象并没有真正标存在磁盘上,那么它也就没有脏标志位。
3>目录项的状态(被使用,未被使用和负状态)
a.它们是靠d_count的值来进行区分的,当d_count为正值说明目录项处于被使用状态。当d_count=0时表示该目录项是一个未被使用的目录项, 但其d_inode指针仍然指向相关的的索引节点。该目录项仍然包含有效的信息,只是当前没有人引用他。d_count=NULL表示负(negative)状态,与目录项相关的inode对象不复存在(相应的磁盘索引节点可能已经被删除),dentry对象的d_inode 指针为NULL。但这种dentry对象仍然保存在dcache中,以便后续对同一文件名的查找能够快速完成。这种dentry对象在回收内存时将首先被释放。
4> d_subdirs:如果当前目录项是一个目录,那么该目录下所有的子目录形成一个链表。该字段是这个链表的表头;
      d_child:如果当前目录项是一个目录,那么该目录项通过这个字段加入到父目录的d_subdirs链表当中。这个字段中的next和prev指针分别 指向父目录中的另外两个子目录;
      d_alias:一个inode可能对应多个目录项,所有的目录项形成一个链表。inode结构中的i_dentry即为这个链表的头结点。当前目录项以这个字段处于i_dentry链表中。该字段中的prev和next指针分别指向与该目录项同inode的其他两个(如果有的话)目录项
3.dentry和inode的区别:
 inode(可理解为ext2 inode)对应于物理磁盘上的具体对象,dentry是一个内存实体,其中的d_inode成员指向对应的inode。也就是说,一个inode可以在运行的时候链接多个dentry,而d_count记录了这个链接的数量。
4.dentry与dentry_cache
dentry_cache简称dcache,中文名称是目录项高速缓存,是Linux为了提高目录项对象的处理效率而设计的。它主要由两个数据结构组成:
1>哈希链表dentry_hashtable:dcache中的所有dentry对象都通过d_hash指针域链到相应的dentry哈希链表中。
2>未使用的dentry对象链表dentry_unused:dcache中所有处于unused状态和negative状态的dentry对象都通过其d_lru指针域链入dentry_unused链表中。该链表也称为LRU链表。
目录项高速缓存dcache是索引节点缓存icache的主控器(master),也即 dcache中的dentry对象控制着icache中的inode对象的生命期转换。无论何时,只要一个目录项对象存在于dcache中(非 negative状态),则相应的inode就将总是存在,因为 inode的引用计数i_count总是大于0。当dcache中的一个dentry被释放时,针对相应inode对象的iput()方法就会被调用。
5对目录项进行操作的一组函数叫目录项操作表,由dentry_operation结构描述。它可以在 include/linux/dcache.h 中查到
 134struct dentry_operations {
 135        int (*d_revalidate)(struct dentry *, struct nameidata *);
 136        int (*d_hash) (struct dentry *, struct qstr *);
 137        int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
 138        int (*d_delete)(struct dentry *);
 139        void (*d_release)(struct dentry *);
 140        void (*d_iput)(struct dentry *, struct inode *);
 141        char *(*d_dname)(struct dentry *, char *, int);
 142};
a.int d_reavlidate(struct dentry *dentry ,int flags) 该函数判断目录对象是否有效。VFS准备从dcache中使用一个目录项时,会调用该函数.
b.int d_hash(struct dentry *dentry ,struct qstr *name):该目录生成散列值,当目录项要加入到散列表时,VFS要调用此函数。
c.int d_compare( struct dentry *dentry, struct qstr *name1, struct qstr *name2) 该函数来比较name1和name2这两个文件名。使用该函数要加dcache_lock锁。
d.int d_delete(struct dentry *dentry):当d_count=0时,VFS调用次函数。使用该函数要叫 dcache_lock锁。
e.void d_release(struct dentry *dentry):当该目录对象将要被释放时,VFS调用该函数。
f.void d_iput(struct dentry *dentry,struct inode *inode)当一个目录项丢失了其索引节点时,VFS就掉用该函数。
二.VFS中的文件对象
1.文件对象表示进程已经打开的文件 在内存中的表示,该对象不是物理上的文件。它是由相应的open()系统调用创建,由close()系统调用销毁。多个进程可以打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。
2一个文件对应的文件对象不是唯一的,但对应的索引节点和超级块对象是唯一的。
3.file结构中保存了文件位置,此外,还把指向该文件索引节点的指针也放在其中。file结构形成一个双链表,称为系统打开文件表 。它的定义在 include/linux/fs.h 中可以看到
 909struct file {
 910        /*
 911         * fu_list becomes invalid after file_free is called and queued via
 912         * fu_rcuhead for RCU freeing
 913         */
 914        union {
 915                struct list_head        fu_list;                            //每个文件系统中被打开的文件都会形成一个双链表
 916                struct rcu_head       fu_rcuhead;
 917        } f_u;
 918        struct path                           f_path;                    
 919#define f_dentry                      f_path.dentry            // 与该文件对应的dentry
 920#define f_vfsmnt                     f_path.mnt
 921        const struct file_operations    *f_op;            //指向文件操作表的指针
 922        spinlock_t                        f_lock;  /* f_ep_links, f_flags, no IRQ */
 923#ifdef CONFIG_SMP
 924        int                                    f_sb_list_cpu;         
 925#endif
 926        atomic_long_t                 f_count;                     //文件对象的使用计数
 927        unsigned int                    f_flags;                      //打开文件时所指定的标志
 928        fmode_t                          f_mode;                    //文件的访问模式
 929        loff_t                               f_pos;                         //文件当前的位移量
 930        struct fown_struct           f_owner
 931        const struct cred            *f_cred;                    
 932        struct file_ra_state          f_ra;                          //预读状态
 933
 934        u64                                 f_version;                  //版本号
 935#ifdef CONFIG_SECURITY
 936        void                                *f_security;                //安全模块
 937#endif
 938        /* needed for tty driver, and maybe others */
 939        void                                *private_data;           //tty设备hook
 940
 941#ifdef CONFIG_EPOLL
 942        /* Used by fs/eventpoll.c to link all the hooks to this file */
 943        struct list_head                f_ep_links;                
 944#endif /* #ifdef CONFIG_EPOLL */
 945        struct address_space    *f_mapping;                //页缓存映射
 946#ifdef CONFIG_DEBUG_WRITECOUNT
 947        unsigned long f_mnt_write_state;
 948#endif
 949};
1>文件对象实际上没有对应的磁盘数据,所以在结构体中没有代表其对象是否为脏,是否需要写回磁盘的标志。文件对象 通过f_path.dentry指针指向相关的目录项对象。目录项会指向相关的索引节点,索引节点会记录文件是否是脏的。
2>fu_list:每个文件系统中以被打开的文件都会形成一个双联表,这个双联表的头结点存放在超级块的s_files字段中。该字段的prev和next指针分别指向在链表中与当前文件结构体相邻的前后两个元素.

Tiger-John说明:

file结构中主要保存了文件位置,此外还把指向该文件索引节点的指针也放在其中。---》有人就问了,问什么不直接把文件位置存放在索引节点中呢?

因为:Linux中的文件是能够共享的,假如把文件位置存放在索引节点中,当有两个或更多个进程同时打开一个文件时,它们将去访问同一个索引节点,那么一个进程的lseek操作将影响到另一个进程的读操作,这显然是致命的错误。


4>对文件进行操作的一组函数叫文件操作表,由file_operations结构定义:可以在include/linux/fs.h 中查看
1488struct file_operations {
1489        struct module *owner;
1490        loff_t (*llseek) (struct file *, loff_t, int);
1491        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1492        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1493        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1494        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1495        int (*readdir) (struct file *, void *, filldir_t);
1496        unsigned int (*poll) (struct file *, struct poll_table_struct *);
1497        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1498        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1499        int (*mmap) (struct file *, struct vm_area_struct *);
1500        int (*open) (struct inode *, struct file *);
1501        int (*flush) (struct file *, fl_owner_t id);
1502        int (*release) (struct inode *, struct file *);
1503        int (*fsync) (struct file *, int datasync);
1504        int (*aio_fsync) (struct kiocb *, int datasync);
1505        int (*fasync) (int, struct file *, int);
1506        int (*lock) (struct file *, int, struct file_lock *);
1507        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1508        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1509        int (*check_flags)(int);
1510        int (*flock) (struct file *, int, struct file_lock *);
1511        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1512        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1513        int (*setlease)(struct file *, long, struct file_lock **);
1514};
owner:用于指定拥有这个文件操作结构体的模块,通常取THIS_MODULE;
llseek:用于设置文件的偏移量。第一个参数指明要操作的文件,第二个参数为偏移量,第三个参数为开始偏移的位置(可取SEEK_SET,SEEK_CUR和SEEK_END之一)。
read:从文件中读数据。第一个参数为源文件,第二个参数为目的字符串,第三个参数指明欲读数据的总字节数,第四个参数指明从源文件的某个偏移量处开始读数据。由系统调用read()调用;
write:往文件里写数据。第一个参数为目的文件,第二个参数源字符串,第三个参数指明欲写数据的总字节数,第四个参数指明从目的文件的某个偏移量出开始写数据。由系统调用write()调用;
mmap:将指定文件映射到指定的地址空间上。由系统调用mmap()调用;
open:打开指定文件,并且将这个文件和指定的索引结点关联起来。由系统调用open()调用;
release:释放以打开的文件,当打开文件的引用计数(f_count)为0时,该函数被调用;
fsync():文件在缓冲的数据写回磁盘
3.用户打开文件表
系统中的每一个进程都有自己的一组打开的文件 ,像根文件系统,当前目工作目录,安装点等。有四个数据结构将VFS层和系统的进程紧密联系在一起,它们分别是:files_struct,fs_struct,
file_system_type 和namespace结构体。

我们先看两个图:

 Linux 内核编程之文件系统(二)_第1张图片

此图来自陈莉君老师 博客,版权归属于陈莉君老师。        
1.>文 件描述符是用来描述打开的文件的。每个进程用一个files_struct结构来记录文件描述符的使用情况,这个结构称为用户打开文件表。它是进程的私有数据。
a.files_struct 结构体定义在文件 include/linux/fdtable.h该结构体由进程描述符中的files 域指向。所有与每个进程(per-process)相关的信息如打开的文件及文件描述符都包含在其中。
  44struct files_struct {
  45  /*
  46   * read mostly part
  47   */
  48        atomic_t count;                           //共享该表的进程数
  49        struct fdtable *fdt;                    
  50        struct fdtable fdtab;                  //定义了文件的一些属性
  51  /*
  52   * written part on a separate cache line in SMP
  53   */
  54        spinlock_t file_lock ____cacheline_aligned_in_smp;
  55        int next_fd;                                  //下一个文件描述符
  56        struct embedded_fd_set close_on_exec_init;   //*exec()关闭的文件描述符
  57        struct embedded_fd_set open_fds_init;        //文件描述符的初始集合
  58        struct file * fd_array[NR_OPEN_DEFAULT];    //默认的文件对象数组
  59};
a.看看 struct fdtable结构:
32struct fdtable {
  33        unsigned int max_fds;   //文件对象的上限
  34        struct file ** fd;      //全部文件对象数组
  35        fd_set *close_on_exec;   //*exec()关闭的文件描述符
  36        fd_set *open_fds;        //指向打开文件的描述符
  37        struct rcu_head rcu;   
  38        struct fdtable *next;    //指向该链表的下一个对象
  39};
  40
b.fd数组指针指向已打开的文件对象链表,默认情况下,指向fd_arry数组。因为NR_OPEN_DEFAULT等于32,所以该数组可以容纳32个文件对象。如果一个进程所打开的文件对象超过32个。内核将分配一个新数组,并且将fd指向它。


c.对于在fd数组中有入口地址的每个文件来说,数组的索引就是文件描述符。通常,数组的第一个元素(索引为0)表示进程的标准输入文件,数组的第二个元素(索引为1)是进程的标准输出文件,数组的第三个元素(索引为2)是进程的标准错误文件)
2.>fs_struct 结构
a.fs_struct 结构描述进程与文件系统的关系
b.我们来深入分析其代码,它的定义在 include/linux/fs_struct.h,
  6struct fs_struct {
   7        int users;           
   8        spinlock_t lock;                  //保护该结构体的锁
   9        int umask;                          //默认的文件访问权限
  10        int in_exec;
  11        struct path root, pwd;   
  12};
看一下struct path 结构的定义 :
  7struct path {
   8        struct vfsmount *mnt;
   9        struct dentry *dentry;
  10}
可以看到struct path 封装了vfsmount 和dentry;,所以struct path root,pwd包含了当前进程的当前工作目录和根目录以及根目录安装点对象和pwd安装点对象。

3>file_system_type结构体

在Linux中,用file_system_type来描述各种特定文件系统类型,比如ext3。也就是说Linux支持的所有文件系统类型都分别唯一的对应一个file_system_type结构

a.它的定义在 include/linux/fs.h中。

1736struct file_system_type {
1737        const char *name;                                                  //文件系统的类型名
1738        int fs_flags;                                                              //文件系统类型标志
1739        int (*get_sb) (struct file_system_type *, int,         //文件系统读入其超级块的函数指针
1740                       const char *, void *, struct vfsmount *);
1741        void (*kill_sb) (struct super_block *);                 //该函数用来终止访问超级块
1742        struct module *owner;       //通常设置为宏THIS_MODLUE,用以确定是否把文件系统作为模块安装
1743        struct file_system_type * next; 
1744        struct list_head fs_supers;
1745
1746        struct lock_class_key s_lock_key;
1747        struct lock_class_key s_umount_key;
1748        struct lock_class_key s_vfs_rename_key;
1749
1750        struct lock_class_key i_lock_key;
1751        struct lock_class_key i_mutex_key;
1752        struct lock_class_key i_mutex_dir_key;
1753        struct lock_class_key i_alloc_sem_key;
1754};

tiger-john说明:

1>name:文件系统的名字,不能为空;
2>get_sb:在安装文件系统时,调用此指针所指函数以在磁盘中获取超级块;
3>kill_sb:卸载文件文件系统时候,调用此指针所指函数以进行一些清理工作;
4>owner:如果一个文件系统以模块的形式加载到内核,则该字段用来说明哪个模块拥有这个结构。一般为THIS_MODULE;
5>next:所有的文件系统类型结构形成一个链表,该链表的头指针为全局变量file_systems(struct file_system_type *file_systems)。这个字段指向链表中下一个文件系统类型结构;
6>fs_supers:同一个文件系统类型下的所有超级块形成一个双联表,这个字段是这个双联表的头结点。超级块之间通过s_instances字段相互链接.

4>vfsmount结构体

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

b.vfsmount结构体被定义在 include/linux/mount.h中

 36/*
  37 * MNT_SHARED_MASK is the set of flags that should be cleared when a
  38 * mount becomes shared.  Currently, this is only the flag that says a


  39 * mount cannot be bind mounted, since this is how we create a mount
  40 * that shares events with another mount.  If you add a new MNT_*
  41 * flag, consider how it interacts with shared mounts.
  42 */
  43#define MNT_SHARED_MASK (MNT_UNBINDABLE)
  44#define MNT_PROPAGATION_MASK    (MNT_SHARED | MNT_UNBINDABLE)
  45
  46
  47#define MNT_INTERNAL    0x4000
  48
  49struct vfsmount {
  50        struct list_head mnt_hash;             //散列表
  51        struct vfsmount *mnt_parent;         //指向上一层安转点的指针
  52        struct dentry *mnt_mountpoint;      //安装点的目录项
  53        struct dentry *mnt_root;                   //安装树的根
  54        struct super_block *mnt_sb;          //指向超级块的指针
  55        struct list_head mnt_mounts;       //子链表
  56        struct list_head mnt_child;          / /通过mnt_child进行遍历

  57        int mnt_flags;                                 //安装标志
  58        /* 4 bytes hole on 64bits arches without fsnotify */
  59#ifdef CONFIG_FSNOTIFY
  60        __u32 mnt_fsnotify_mask;
  61        struct hlist_head mnt_fsnotify_marks;
  62#endif
  63        const char *mnt_devname;        /* Name of device e.g. /dev/dsk/hda1 */
  64        struct list_head mnt_list;
  65        struct list_head mnt_expire ;    /* link in fs-specific expiry list */
  66        struct list_head mnt_share;      /* circular list of shared mounts */
  67        struct list_head mnt_slave_list;/* list of slave mounts */
  68        struct list_head mnt_slave;      /* slave list entry */
  69        struct vfsmount *mnt_master;    /* slave is on master->mnt_slave_list */
  70        struct mnt_namespace *mnt_ns;   /* containing namespace */
  71        int mnt_id;                     /* mount identifier */
  72        int mnt_group_id;               /* peer group identifier */
  73        /*
  74         * We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
  75         * to let these frequently modified fields in a separate cache line
  76         * (so that reads of mnt_flags wont ping-pong on SMP machines)
  77         */
  78        atomic_t mnt_count;               //使用计数
  79        int mnt_expiry_mark;            /* true if marked for expiry */
  80        int mnt_pinned;
  81        int mnt_ghosts;
  82#ifdef CONFIG_SMP
  83        int __percpu *mnt_writers;
  84#else
  85        int mnt_writers;
  86#endif
  87};

a.vfsmount 结构还保存了在安装时指定的标志信息,该信息存储在mmt_flags中。

MNT_NOSUID:禁止该文件系统的可执行文件设置setuid和setgid标志

MNT_NODEV:禁止访问该文件系统上的设备文件

MNT_NOEXEC:禁止执行该文件系统上的可执行文件

Tiger-John说明:

在管理员安装一些不是很安全的移动设备时,这些标志很有用。

 

 

b.为了对系统中的所有安装点进行快速查找,内核把它们按哈希表来组织,mnt_hash就是形成哈希表的队列指针。
c.mnt_mountpoint是指向安装点dentry结构的指针。而dentry指针指向安装点所在目录树中根目录的dentry结构。
d.mnt_parent是指向上一层安装点的指针。如果当前的安装点没有上一层安装点(如根设备),则这个指针为NULL。同时,vfsmount结构中还有mnt_mounts和mnt_child两个队列头,只要上一层vfsmount结构存在,就把当前vfsmount结构中mnt_child链入上一层vfsmount结构的mnt_mounts队列中。这样就形成一颗设备安装的树结构,从一个vfsmount结构的mnt_mounts队列开始,可以找到所有直接或间接安装在这个安装点上的其他设备。如图8.2。
e.mnt_sb指向所安装设备的超级块结构super_block。
f.mnt_list是指向vfsmount结构所形成链表的头指针。

我们来看个图:

 Linux 内核编程之文件系统(二)_第2张图片

你可能感兴趣的:(编程,linux,struct,list,File)