文件系统使用的数据结构

linux虚拟文件系统四大对象:

1)超级块(super block)

2)索引节点(inode)

3)目录项(dentry)

4)文件对象(file)

super_block

/*超级块代表了整个文件系统,超级块是文件系统的控制块,有整个文件系统信息,一个文件系统所有的inode都要连接到超级块上,
可以说,一个超级块就代表了一个文件系统
*/
struct super_block {
    struct list_head    s_list;        /* Keep this first 一个双向循环链表,把所有的super_block连接起来,一个super_block代表一个在linux上的文件系统,
    这个list上边的就是所有的在linux上记录的文件系统。*/
    dev_t            s_dev;        /* search index; _not_ kdev_t 包含该具体文件系统的块设备标识符。例如,对于 /dev/hda1,其设备标识符为 0x301*/
    unsigned char        s_blocksize_bits;//上面的size大小占用位数,例如512字节就是9 bits
    unsigned long        s_blocksize;//文件系统中数据块大小,以字节单位
    loff_t            s_maxbytes;    /* Max file size允许的最大的文件大小(字节数) */
    struct file_system_type    *s_type;//文件系统类型 ext2还是fat32 ? “文件系统”和“文件系统类型”不一样!一个文件系统类型下可以包括很多文件系统即很多的super_block
    const struct super_operations    *s_op;//指向某个特定的具体文件系统的用于超级块操作的函数集合
    const struct dquot_operations    *dq_op;//指向某个特定的具体文件系统用于限额操作的函数集合
    const struct quotactl_ops    *s_qcop;//用于配置磁盘限额的的方法,处理来自用户空间的请求
    const struct export_operations *s_export_op;
    unsigned long        s_flags;
    unsigned long        s_iflags;    /* internal SB_I_* flags */
    unsigned long        s_magic;//区别于其他文件系统的标识
    struct dentry        *s_root;//指向该具体文件系统安装目录的目录项
    struct rw_semaphore    s_umount;//对超级块读写时进行同步
    int            s_count;//对超级块的使用计数
    atomic_t        s_active;//引用计数
#ifdef CONFIG_SECURITY
    void            *s_security;
#endif
    const struct xattr_handler **s_xattr;

    struct hlist_bl_head    s_anon;        /* anonymous dentries for (nfs) exporting */
    struct list_head    s_mounts;    /* list of mounts; _not_ for fs use */
    struct block_device    *s_bdev;//指向文件系统被安装的块设备
    struct backing_dev_info *s_bdi;
    struct mtd_info        *s_mtd;
    struct hlist_node    s_instances;
    unsigned int        s_quota_types;    /* Bitmask of supported quota types */
    struct quota_info    s_dquot;    /* Diskquota specific options磁盘限额相关选项 */

    struct sb_writers    s_writers;

    char s_id[32];                /* Informational name */
    u8 s_uuid[16];                /* UUID */

    void             *s_fs_info;    /* Filesystem private info */
    unsigned int        s_max_links;
    fmode_t            s_mode;

    /* Granularity of c/m/atime in ns.
       Cannot be worse than a second */
    u32           s_time_gran;

    /*
     * The next field is for VFS *only*. No filesystems have any business
     * even looking at it. You had been warned.
     */
    struct mutex s_vfs_rename_mutex;    /* Kludge */

    /*
     * Filesystem subtype.  If non-empty the filesystem type field
     * in /proc/mounts will be "type.subtype"
     */
    char *s_subtype;

    /*
     * Saved mount options for lazy filesystems using
     * generic_show_options()
     */
    char __rcu *s_options;
    const struct dentry_operations *s_d_op; /* default d_op for dentries */

    /*
     * Saved pool identifier for cleancache (-1 means none)
     */
    int cleancache_poolid;

    struct shrinker s_shrink;    /* per-sb shrinker handle */

    /* Number of inodes with nlink == 0 but still referenced */
    atomic_long_t s_remove_count;

    /* Being remounted read-only */
    int s_readonly_remount;

    /* AIO completions deferred from interrupt context */
    struct workqueue_struct *s_dio_done_wq;
    struct hlist_head s_pins;

    /*
     * Context in which to interpret filesystem uids, gids,
     * quotas, device nodes, extended attributes and security
     * labels.
     */
    struct user_namespace *s_user_ns;

    /*
     * Keep the lru lists last in the structure so they always sit on their
     * own individual cachelines.
     */
    struct list_lru        s_dentry_lru ____cacheline_aligned_in_smp;
    struct list_lru        s_inode_lru ____cacheline_aligned_in_smp;
    struct rcu_head        rcu;
    struct work_struct    destroy_work;

    struct mutex        s_sync_lock;    /* sync serialisation lock */

    /*
     * Indicates how deep in a filesystem stack this SB is
     */
    int s_stack_depth;

    /* s_inode_list_lock protects s_inodes */
    spinlock_t        s_inode_list_lock ____cacheline_aligned_in_smp;
    struct list_head    s_inodes;    /* all inodes */
};

Inode:

/*
 * Keep mostly read-only and often accessed (especially for
 * the RCU path lookup and 'stat' data) fields at the beginning
 * of the 'struct inode'
     inode有两种,一种是VFS的inode,一种是具体文件系统的inode。前者在内存中,后者在磁盘中。所以每次其实是将磁盘中的inode调进填充内存中的inode,
 这样才是算使用了磁盘文件inode。
    每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定(现代OS可以动态变化),一般每2KB就设置一个inode。
 一般文件系统中很少有文件小于2KB的,所以预定按照2KB分,一般inode是用不完的。所以inode在文件系统安装的时候会有一个默认数量,
 后期会根据实际的需要发生变化
    inode号是唯一的,表示不同的文件。其实在Linux内部的时候,访问文件都是通过inode号来进行的,所谓文件名仅仅是给用户容易使用的。
 当我们打开一个文件的时候,首先,系统找到这个文件名对应的inode号;然后,通过inode号,得到inode信息,最后,由inode找到文件数据所在的block,
 现在可以处理文件数据了。

    inode和文件的关系:当创建一个文件的时候,就给文件分配了一个inode。一个inode只对应一个实际文件,一个文件也会只有一个inode。
 inodes最大数量就是文件的最大数量。
 */
struct inode {
    umode_t            i_mode;//文件的类型和访问权限
    unsigned short        i_opflags;
    kuid_t            i_uid;//文件拥有者标号
    kgid_t            i_gid;//文件所在组标号
    unsigned int        i_flags;

#ifdef CONFIG_FS_POSIX_ACL
    struct posix_acl    *i_acl;
    struct posix_acl    *i_default_acl;
#endif

    const struct inode_operations    *i_op;//索引节点操作函数集
    struct super_block    *i_sb;//inode所属文件系统的超级块指针
    struct address_space    *i_mapping;//表示向谁请求页面 于描述页高速缓存中的页面的

#ifdef CONFIG_SECURITY
    void            *i_security;
#endif

    /* Stat data, not accessed from path walking */
    unsigned long        i_ino;//索引节点号,每个inode都是唯一的
    /*
     * Filesystems may only read i_nlink directly.  They shall use the
     * following functions for modification:
     *
     *    (set|clear|inc|drop)_nlink
     *    inode_(inc|dec)_link_count
     */
    union {
        const unsigned int i_nlink;
        unsigned int __i_nlink;
    };
    dev_t            i_rdev;//实际的设备标识
    loff_t            i_size;//inode所代表的的文件的大小,以字节为单位
    struct timespec        i_atime;//文件最后一次访问时间
    struct timespec        i_mtime;//文件最后一次修改时间
    struct timespec        i_ctime;//inode最后一次修改时间
    spinlock_t        i_lock;    /* i_blocks, i_bytes, maybe i_size */
    unsigned short          i_bytes;//文件中最后一个块的字节数
    unsigned int        i_blkbits;//块大小,字节单位
    blkcnt_t        i_blocks;//文件所占块数

#ifdef __NEED_I_SIZE_ORDERED
    seqcount_t        i_size_seqcount;
#endif

    /* Misc */
    unsigned long        i_state;
    struct mutex        i_mutex;

    unsigned long        dirtied_when;    /* jiffies of first dirtying */
    unsigned long        dirtied_time_when;

    struct hlist_node    i_hash;//指向hash链表指针,用于inode的hash表
    struct list_head    i_io_list;    /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
    struct bdi_writeback    *i_wb;        /* the associated cgroup wb */

    /* foreign inode detection, see wbc_detach_inode() */
    int            i_wb_frn_winner;
    u16            i_wb_frn_avg_time;
    u16            i_wb_frn_history;
#endif
    struct list_head    i_lru;        /* inode LRU list */
    struct list_head    i_sb_list;
    union {
        struct hlist_head    i_dentry;//指向目录项链表指针,注意一个inodes可以对应多个dentry,因为一个实际的文件可能被链接到其他的文件,
        //那么就会有另一个dentry,这个链表就是将所有的与本inode有关的dentry都连在一起
        struct rcu_head        i_rcu;
    };
    u64            i_version;//版本号
    atomic_t        i_count;//引用计数
    atomic_t        i_dio_count;
    atomic_t        i_writecount;//记录多少进程以刻写模式打开此文件
#ifdef CONFIG_IMA
    atomic_t        i_readcount; /* struct files open RO */
#endif
    const struct file_operations    *i_fop;    /* former ->i_op->default_file_ops 文件操作*/
    struct file_lock_context    *i_flctx;
    struct address_space    i_data;
    struct list_head    i_devices;//设备链表。共用同一个驱动程序的设备形成的链表
    union {
        struct pipe_inode_info    *i_pipe;//向管道文件(如果文件是管道文件时使用)
        struct block_device    *i_bdev;//指向块设备文件指针(如果文件是块设备文件时使用)
        struct cdev        *i_cdev;//指向字符设备文件指针(如果文件是字符设备时使用)
        char            *i_link;//链接
    };

    __u32            i_generation;

#ifdef CONFIG_FSNOTIFY
    __u32            i_fsnotify_mask; /* all events this inode cares about */
    struct hlist_head    i_fsnotify_marks;
#endif

    void            *i_private; /* fs or device private pointer */
};

dentry:

/*
    dentry则表示了不同层级之间的关系,也是链接所使用的结构体。dentry通过d_parent来和上级目录构成链接关系,
通过d_op来存储对应的实际文件系统的文件操作,如创建、删除、打开、读写等。d_sb指向实际文件系统的超级块,
该结构在上文已详细介绍。d_inode指向对应的inode,d_name表示该文件的文件名。
    一个有效的dentry结构必定有一个inode结构,这是因为一个目录项要么代表着一个文件,要么代表着一个目录,而目录实际上也是文件。
所以,只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。但是inode却可以对应多个
*/
struct dentry {
    /* RCU lookup touched fields */
    unsigned int d_flags;        /* protected by d_lock目录项缓存标识,可取 DCACHE_UNUSED    DCACHE_REFERENCED 等 */
    seqcount_t d_seq;        /* per dentry seqlock */
    struct hlist_bl_node d_hash;    /* lookup hash list 内核使用dentry_hashtable对dentry进行管理,dentry_hashtable是由list_head组成的链表,
    一个dentry创建之后,就通过d_hash链接进入对应的hash值的链表中。*/
    struct dentry *d_parent;    /*父目录的目录项 parent directory */
    struct qstr d_name;//目录项名称
    struct inode *d_inode;        /* 与该目录项关联的inode Where the name belongs to - NULL is * negative */
    unsigned char d_iname[DNAME_INLINE_LEN];    /* 存放短的文件名small names */

    /* Ref lookup also touches following */
    struct lockref d_lockref;    /* per-dentry lock and refcount */
    const struct dentry_operations *d_op;//目录项操作函数集
    struct super_block *d_sb;    /* The root of the dentry tree 这个目录项所属的文件系统的超级块*/
    unsigned long d_time;        /* used by d_revalidate 重新变为有效的时间!注意只要操作成功这个dentry就是有效的,否则无效*/
    void *d_fsdata;            /* fs-specific data 文件系统私有数据 */

    struct list_head d_lru;        /*最近未使用的目录项的链表   LRU list */
    struct list_head d_child;    /* child of parent list 目录项通过这个加入到父目录的d_subdirs中*/
    struct list_head d_subdirs;    /* our children本目录的所有孩子目录链表头 */
    /*
     * d_alias and d_rcu can share memory
     一个有效的dentry必然与一个inode关联,但是一个inode可以对应多个dentry,因为一个文件可以被链接到其他文件,
     所以,这个dentry就是通过这个字段链接到属于自己的inode结构中的i_dentry链表中的
     */
    union {
        struct hlist_node d_alias;    /* inode alias list这个dentry就是通过这个字段链接到属于自己的inode结构中的i_dentry链表中的 */
         struct rcu_head d_rcu;
    } d_u;
};

files

struct path {
    struct vfsmount *mnt;
    struct dentry *dentry;
};
struct file {
    union {
        struct llist_node    fu_llist;
        struct rcu_head     fu_rcuhead;
    } f_u;//用于通用文件对象链表的指针
    struct path        f_path;//指出该文件的已安装的文件系统vfsmount以及文件相关的目录项对象dentry
    struct inode        *f_inode;    /* cached value */
    const struct file_operations    *f_op;///*指向文件操作表的指针    --函数指针*/

    /*
     * Protects f_ep_links, f_flags.
     * Must not be taken from IRQ context.
     */
    spinlock_t        f_lock;
    atomic_long_t        f_count;//表示打开文件的引用计数
    unsigned int         f_flags;//表示打开文件的权限
    fmode_t            f_mode;//设置对文件的访问模式,例如:只读,只写
    struct mutex        f_pos_lock;
    loff_t            f_pos;//表示当前读写文件的位置 
    struct fown_struct    f_owner;//该结构的作用是通过信号进行I/O时间通知的数据  异步操作;记录一个进程ID,当某些事发送的时候发送给该ID进程的信号
    const struct cred    *f_cred;
    struct file_ra_state    f_ra;//文件预读状态,文件预读算法使用的主要数据结构,当打开一个文件时,f_ra中出了perv_page(默认为-1)和ra_apges(对该文件允许的最大预读量)这两个字段外,其他的所有西端都置为0

    u64            f_version;//记录文件的版本号,每次使用后都自动递增
#ifdef CONFIG_SECURITY
    void            *f_security;
#endif
    /* needed for tty driver, and maybe others */
    void            *private_data;

#ifdef CONFIG_EPOLL
    /* Used by fs/eventpoll.c to link all the hooks to this file */
    struct list_head    f_ep_links;
    struct list_head    f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
    struct address_space    *f_mapping;
} __attribute__((aligned(4)));    /* lest something weird decides that 2 is OK */

files_struct:

struct fdtable {
    unsigned int max_fds;
    struct file __rcu **fd;      /* current fd array */
    unsigned long *close_on_exec;
    unsigned long *open_fds;
    unsigned long *full_fds_bits;
    struct rcu_head rcu;
};

/*
 * Open file table structure
 */
struct files_struct {
  /*
   * read mostly part
   */
    atomic_t count;/* 共享该表的进程数 */
    bool resize_in_progress;
    wait_queue_head_t resize_wait;
/*包括一个struct fdtable变量实例和一个struct fdtable类型指针;
    而struct fdtable中的成员变量close_on_exec,open_fds,fd又分别指向struct files_struct中
    的成员close_on_exec_init,open_fds_init和fd_array*/
    struct fdtable __rcu *fdt;
    struct fdtable fdtab;
  /*
   * written part on a separate cache line in SMP
   */
    spinlock_t file_lock ____cacheline_aligned_in_smp; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/
    int next_fd;/*已分配的文件描述符加1*/
    unsigned long close_on_exec_init[1];/*指向执行exec( )时需要关闭的文件描述符*/
    unsigned long open_fds_init[1];/*指向打开文件描述符的指针*/
    unsigned long full_fds_bits_init[1];/*文件描述符的初值集合*/
    struct file __rcu * fd_array[NR_OPEN_DEFAULT];;/* 文件对象指针的初始化数组  默认使用 不够时动态分配*/
    /*通常,fd字段指向files_struct结构的fd_array字段,该字段包括32个文件对象指针。
    如果进程打开的文件数目多于32,内核就分配一个新的、更大的文件指针数组,
    并将其地址存放在fd字段中,内核同时也更新max_fds字段的值。*/
};

 文件系统使用的数据结构

  linux系统中,一个进程打开的文件数是有初步限制的,即其文件描述符数初始时有最大化定量,即一个进程一般只能打开NR_OPEN_DEFAULT个文件,该值在32位机上为32个,在64位机上为64个。上面的files_struct这种初始化正是体现了进程初步所能打开文件的内核结构描述。这里需要说明的是,这不仅仅限于类似上面的静态初始化,当init进程fork一个子进程时,也是如此(此时是files_struct是动态分配的)。

do_fork------>copy_process
        |------>dup_task_struct------>alloc_task_struct
        |------>copy_files------>dup_fd

  即先调用alloc_task_struct分配一个task_struct结构实例,然后使用alloc_files来分配并初始化files_struct变量实例。
上面的 files_struct初始化和上面例子中的静态初始化并无本质差别:
语句fdt = &newf->fdtab取出newf的struct fdtable实例变量fdtab的指针,然后通过rcu_assign_pointer函数将其赋值给newf->fdt,那么newf->fdt还是指向其自身中的struct fdtable实例变量,fdt的成员close_on_exec、open_fds和fd也是如此。

  当进行struct files_struct扩充时,会分配一个新的struct fdtable,为了叙述方便,下面该变量用指针用nfdt来表示。另外还分配了满足扩充要求的fd数组(即struct file数组),以及与fd相对的bitmap描述close_on_exec,open_fds的存储空间。
然后将新分配的close_on_exec,open_fds,fd空间指针赋值给nfdt->close_on_exec,nfdt->open_fds和nfdt->fd。注意,这里的close_on_exec,open_fds和上面初始化时close_on_exec_init,open_fds_init的差别:
close_on_exec,open_fds的最大值按bit来说为__FDSET_LONGS,实际值为1024位,即文件描述符的最大数为1024个。但它们也是按需分配,并和file数组的大小一致,分配的实际值会同时赋值给nfdt->max_fds。
分配并初始化新的struct fdtable变量后,原先指向fdtab的struct files_struct指针成员fdt,会调整为指向新分配的struct fdtable变量。这时,struct files_struct实例变量中就包含两个struct fdtable存储区:一个是其自身的,一个新分配的,用fdt指向。
执行完上面的操作后,其关系如下图:
文件系统使用的数据结构

  上图中同颜色的存储区,代表的是两者内容是一致的。即执行完上述的操作后,还要将旧的结构存储区的内容拷贝到新的存储区,这包括files_struct自身所包含的close_on_exec,open_fds,fd到新分配的close_on_exec,open_fds,fd的拷贝。
执行完上述拷贝之后,就要释放旧的struct fdtable,

上一篇:深入理解linux文件系统与日志分析


下一篇:linux文件属性