《独辟蹊径品Linux内核源代码导读》VFS一章内容笔记2

    技术2022-06-30  81

    虚拟文件系统的管理结构

            Linux 支持各种不同的文件系统,同时对上层抽象出一个统一的接口,例如应用程序无需关心文件系统的细节,只需要调用 open read,write 等系统调用,就能够对文件进行操作。为此提出了虚拟文件系统( Virtual FileSystem ),对于不同的文件系统,它的磁盘文件的结构布局肯定是不一样的,但是虚拟文件系统屏蔽了这些差异,向上层提供一个统一的接口。 虚拟文件系统管理的结构包括超级块, Inode ,目录项等。

     

    文件系统对象

    每一个文件系统驱动程序都有一个文件系统对象,定义如下:

    struct file_system_type {

        const char *name;

        int fs_flags;

        int (*get_sb) (struct file_system_type *, int,

                            const char *, void *, struct vfsmount *);

         void (*kill_sb) (struct super_block *);

        struct module *owner;

        struct file_system_type *next;

        struct list_head fs_supers;

    };

     

    name: 文件系统的名字 , 例如 "ext2" "iso9660" "msdos"

    fs_flags: 各种标志 ( 亦即 : FS_REQUIRES_DEV, FS_NO_DCACHE )

    get_sb: 每当该类型的文件系统被挂载时 , 调用该方法

    kill_sb: 每当该类型的文件系统被卸载时 , 调用该方法

    owner: VFS 内部使用 : 多数情况下该被赋值为 THIS_MODULE

    next: VFS 内部使用 : 多数情况下该被赋值为 NULL

    各文件系统驱动都需要调用 register_filesystem() 注册文件系统对象 。所有文件系统对象通过 next 指针来链接成链表,全局变量 file_systems 指向链表的头部。

     

    由于一个文件系统可能对应多个分区,因此 fs_supers 链表链接了各个分区的超级块

     

    Ext2 file_system_type 的结构定义如下:

    static struct file_system_type ext2_fs_type = {        

        .owner          = THIS_MODULE,

        .name           = "ext2",

        .get_sb         = ext2_get_sb,

        .kill_sb        = kill_block_super,

        .fs_flags       = FS_REQUIRES_DEV,

    };

     

    int __init init_ext2_fs(void)

    {

            return register_filesystem(&ext2_fs_type);

    }

     

    int init_module(void)

    {

            return init_ext2_fs();

    }

    VFS 的超级块

            VFS 的超级块是根据具体文件系统的超级块建立的内存结构。

     

    struct super_block {

        struct list_head    s_list;         /* Keep this first */

        dev_t            s_dev;         /* search index; _not_ kdev_t */

        unsigned long         s_blocksize;

        unsigned char         s_blocksize_bits;

        unsigned char         s_dirt;

        loff_t            s_maxbytes;     /* Max file size */

        struct file_system_type    * s_type;

        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_magic;

        struct dentry        * s_root;

        struct rw_semaphore    s_umount;

        struct mutex        s_lock;

        int             s_count;

        int             s_need_sync;

        atomic_t        s_active;

    # ifdef CONFIG_SECURITY

        void * s_security;

    # endif

        struct xattr_handler    * * s_xattr;

     

        struct list_head    s_inodes;     /* all inodes */

        struct hlist_head    s_anon;         /* anonymous dentries for (nfs) exporting */

        struct list_head    s_files;

        /* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */

        struct list_head    s_dentry_lru;     /* unused dentry lru */

        int             s_nr_dentry_unused;     /* # of dentry on lru */

        struct block_device    * s_bdev;

        struct backing_dev_info * s_bdi;

        struct mtd_info        * s_mtd;

        struct list_head    s_instances;

        struct quota_info    s_dquot;     /* Diskquota specific options */

        int             s_frozen;

        wait_queue_head_t    s_wait_unfrozen;

        char s_id[ 32] ;                 /* Informational name */

        void             * s_fs_info;     /* Filesystem private info */

        fmode_t            s_mode;

        /*

         * 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 */

        /* Granularity of c/m/atime in ns.

         Cannot be worse than a second */

        u32         s_time_gran;

        /*

         * 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 * s_options;

    } ;

    s_fs_info 字段指向一个文件系统信息的数据结构,对于Ext2文件系统,该字段指向ext2_sb_info类型的结构.

    Ext2的内存结构

            之前学习了Ext2磁盘上的布局,在使用过程中,内核需要频繁的访问某些结构,因此当磁盘驱动程序把相关数据从磁盘上读出来之后,内核会建立相应的内存中的结构。

    /include/linix/ext2_fs_sb.h 中定义如下:

        struct ext2_sb_info {

        unsigned long s_frag_size;  /* fragment 片的长度,以字节为单位 */

        unsigned long s_frags_per_block; /* 每块中fragment 片数 */

        unsigned long s_inodes_per_block ;/* 每块中inode */

        unsigned long s_frags_per_group; /* 每一块组中fragment */

        unsigned long s_blocks_per_group ;/* 每一块组中块数 */

        unsigned long s_inodes_per_group ;/* 每一块组中inode */

        unsigned long s_itb_per_group;  /* 每一块组中inod 表占用的块数 */

        unsigned long s_db_per_group;   /* 每一块组中描述符占用的块数 */

        unsigned long s_desc_per_block; /* 一块中组描述符数*/

        unsigned long s_groups_count;   /* 整个文件系统中的块组数 */

        struct buffer_head * s_sbh ; /* 指向内存中包含超级块的缓冲区的指针 */

        struct ext2_super_block * s_es ; / * 指向缓冲区中超级块的指针 */

        struct buffer_head ** s_group_desc ; /* 指向缓冲区组描述符数组的指针 * /

        struct buffer_head ** s_group_desc ; /* 指向缓冲区组描述符数组的指针 * /

        unsigned short s_loaded_inode_bitmaps; /* 装入缓冲区的inode 位图块数 */

        unsigned short s_loaded_block_bitmaps; /* 装入缓冲区的块位图块数 */

        unsigned long s_inode_bitmap_number[EXT2_MAX_GROUP_LOADED] ;

                                               /* inode 位图数组 */

        struct buffer_head * s_inode_bitmap[EXT2_MAX_GROUP_LOADED] ;

                                               /* inode 位图指针数组 */

        unsigned long s_block_bitmap_number[EXT2_MAX_GROUP_LOADED] ;

                                               /* 块位图数组 */

        struct buffer_head * s_block_bitmap[EXT2_MAX_GROUP_LOADED] ;

                                              /* 块位图指针数组 */

        int s_rename_lock;                 /* 重命名时的锁信号量 */

        struct wait_queue * s_rename_wait;  /* 重命名时的等待队列指针 */

        unsigned long  s_mount_opt ;        /* 安装选项 */

        unsigned short s_resuid; /* 可以使用保留块的用户uid */

        unsigned short s_resgid; /* 可以使用保留块的用户组gid */

        unsigned short s_mount_state; /* 超级用户使用的安装选项 */

        unsigned short s_pad;         /* 填充 */

        int s_addr_per_block_bits/* 块地址( 编号) 的位(bit) */

        int s_desc_per_block_bits/* 块描述符的位(bit) */

        int s_inode_size;           /* inode 长度 */

        int s_first_ino;            /* 第一个inode */

    };

      - 磁盘超级块中的大部分字段 - s_sbh指针,指向包含磁盘超级块的缓冲区的缓冲区首部 - s_es指针,指向磁盘超级块所在的缓冲区 - 组描述符的个数s_desc_per_block,可以放在一个块中 - s_group_desc指针,指向一个缓冲区(包含组描述符的缓冲区)首部数组(只用一项就够了) - 其他与安装状态、安装选项等有关的数据

     

    Ext2在内存中的Inode结构定义如下:

    include/linux/ext2_fs_i.h 中,如下所示:

    struct ext2_inode_info {

        __u32   i_data[15];   /* 数据块指针数组 */

        __u32   i_flags;      /* 文件标志(属性*/

        __u32   i_faddr;      /* Fragment (片)地址 */

        __u8    i_frag_no;    /* Fragment (片)号 */

        __u8    i_frag_size;  /* Fragment (片)大小 */

        __u16   i_osync;       /* 同步标志 */

        __u32   i_file_acl;   /* 文件访问控制链表 */

        __u32   i_dir_acl;    /* 目录访问控制链表 */

        __u32   i_dtime;      /* 文件删除时间 */

        __u32   i_version;    /* 文件版本 */

        __u32   i_block_group ;/* inode 所在块组号 */

        __u32   i_next_alloc_block ;/* 下一个要分配的块 */

        __u32   i_next_alloc_goal; /* 下一个要分配的对象 *

        __u32   i_prealloc_block;  * 预留块首地址 */

        __u32   i_prealloc_count;  /* 预留计数 */

        int i_new_inode:1;  /* 标志,是否为新分配的inode */

    };  

     

    下图表示的是与Ext2超级块和组描述符有关的缓冲区与缓冲区首部和ext2_sb_info数据结构之间的关系。

     

    当内核需要 mount 一个块设备的时候,会根据分区表中的信息分析这个块设备的文件系统类型,然后从 file_systems 链表中找到对应的文件系统驱动程序的文件系统对象, 调用它的 get_sb() 函数获取具体文件系统超级块的信息,然后根据这些信息初始化 VFS 超级块。结构中 s_fs_info 就指向具体文件系统的超级块内存对象。如果这个分区的 文件类型为 Ext2 ,那么这个结构就是 ext2_sb_info 由于各种文件系统的超级块不同,对超级块的操作也不一样,因此内核定义了一个 super_operations 结构。 get_sb 函数会根据文件系统类型设置不同的 super_operations 指针 。以 ext2 文件系统为例: ext2 文件系统的 get_sb 函数是 ext2_get_sb(),ext2_get_sb 请求磁盘驱动程序把相应的块读取出来以后,调用 ext2_fill_super 根据磁盘上 ext2_super_block 结构,初始化内存中的 ext2_sb_info VFS super_block 结构,同时把 s_op 设置为 ext2_sops.

    struct super_operations {

           struct inode *(*alloc_inode)(struct super_block *sb);

           void (*destroy_inode)(struct inode *);

           void (*dirty_inode) (struct inode *);

           int (*write_inode) (struct inode *, int);

           void (*drop_inode) (struct inode *);

           void (*delete_inode) (struct inode *);

           void (*put_super) (struct super_block *);

           void (*write_super) (struct super_block *);

           int (*sync_fs)(struct super_block *sb, int wait);

           int (*freeze_fs) (struct super_block *);

           int (*unfreeze_fs) (struct super_block *);

           int (*statfs) (struct dentry *, struct kstatfs *);

           int (*remount_fs) (struct super_block *, int *, char *);

           void (*clear_inode) (struct inode *);

           void (*umount_begin) (struct super_block *);

           int (*show_options)(struct seq_file *, struct vfsmount *);

           int (*show_stats)(struct seq_file *, struct vfsmount *);

    #ifdef CONFIG_QUOTA

           ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);

           ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);

    #endif

           int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);

    };

    static int ext2_fill_super(struct super_block *sb, void *data, int silent)

    {

           /* 参数 sb 指向 VFS 的超级块*/

           struct buffer_head * bh;

           /*

            *sbi 指向内存中的 Ext2 超级块, es 指向从磁盘读取到的 Ext2 超级块

            * 这个函数的主要工作就是根据 es 结构初始化 sbi sb 结构

            */

           struct ext2_sb_info * sbi;

           struct ext2_super_block * es;

           struct inode *root;

           unsigned long block;

           unsigned long sb_block = get_sb_block(&data);

           unsigned long logic_sb_block;

           unsigned long offset = 0;

           unsigned long def_mount_opts;

           long ret = -EINVAL;

          

           /*BLOCK_SIZE 默认为 1KB */

           int blocksize = BLOCK_SIZE;

           int db_count;

           int i, j;

           __le32 features;

           int err;

     

           /* 分配内存的 ext2_sb_info 结构 */

           sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);

           if (!sbi)

                  return -ENOMEM;

     

           sbi->s_blockgroup_lock =

                  kzalloc(sizeof(struct blockgroup_lock), GFP_KERNEL);

           if (!sbi->s_blockgroup_lock) {

                  kfree(sbi);

                  return -ENOMEM;

           }

          

           /*VFS 超级块的 s_fs_info 指向 Ext2 超级块 ext2_sb_info 结构 */

           sb->s_fs_info = sbi;

           /*Ext2 超级块的 s_sb_block 指向 VFS 超级块的 super_block 结构 */

           sbi->s_sb_block = sb_block;

     

           /*

            * See what the current blocksize for the device is, and

            * use that as the blocksize.  Otherwise (or if the blocksize

            * is smaller than the default) use the default.

            * This is important for devices that have a hardware

            * sectorsize that is larger than the default.

            */

           blocksize = sb_min_blocksize(sb, BLOCK_SIZE);

           if (!blocksize) {

                  ext2_msg(sb, KERN_ERR, "error: unable to set blocksize");

                  goto failed_sbi;

           }

     

           /*

            * If the superblock doesn't start on a hardware sector boundary,

            * calculate the offset. 

            */

           if (blocksize != BLOCK_SIZE) {

                  logic_sb_block = (sb_block*BLOCK_SIZE) / blocksize;

                  offset = (sb_block*BLOCK_SIZE) % blocksize;

           } else {

                  logic_sb_block = sb_block;

           }

          

           /*

            * 请求磁盘驱动程序读取超级块 : 在缓冲区页中分配一个缓冲区和缓冲区首部。然后从磁盘读入超级块存放在缓冲区中。

            *  如果一个块已在页高速缓存的缓冲区页而且是最新的,那么无需再分配。将缓冲区首部地址存放在Ext2超级块对象sbi的s_sbh字段

             */

           if (!(bh = sb_bread(sb, logic_sb_block))) {

                  ext2_msg(sb, KERN_ERR, "error: unable to read superblock");

                  goto failed_sbi;

           }

           /*

            * Note: s_es must be initialized as soon as possible because

            *       some ext2 macro-instructions depend on its value

            */

            /*bh->b_data 指向读取缓冲区的首地址, offset 是超级块的偏移 */

           es = (struct ext2_super_block *) (((char *)bh->b_data) + offset);

           /*Ext 内存超级块的 s_es 指向 Ext2 磁盘超级块 es( 现在这个结构在内存中 )*/

           sbi->s_es = es;

           sb->s_magic = le16_to_cpu(es->s_magic);

     

           if (sb->s_magic != EXT2_SUPER_MAGIC)

                  goto cantfind_ext2;

           ......

           /* 根据磁盘上的 s_log_block_size 计算逻辑块的大小 */

           blocksize = BLOCK_SIZE << le32_to_cpu(sbi->s_es->s_log_block_size);

     

           if (ext2_use_xip(sb) && blocksize != PAGE_SIZE) {

                  if (!silent)

                         ext2_msg(sb, KERN_ERR,

                                "error: unsupported blocksize for xip");

                  goto failed_mount;

           }

           ......

           /* 片大小,当前没有实现分片,片大小等于块大小 */

           sbi->s_frag_size = EXT2_MIN_FRAG_SIZE <<

                                   le32_to_cpu(es->s_log_frag_size);

           if (sbi->s_frag_size == 0)

                  goto cantfind_ext2;

           sbi->s_frags_per_block = sb->s_blocksize / sbi->s_frag_size;

            /* 每个组的块个数 */

           sbi->s_blocks_per_group = le32_to_cpu(es->s_blocks_per_group);

           /* 每个组的片个数 */

           sbi->s_frags_per_group = le32_to_cpu(es->s_frags_per_group);

           /* 每个组的 Inode 个数 */

           sbi->s_inodes_per_group = le32_to_cpu(es->s_inodes_per_group);

     

           if (EXT2_INODE_SIZE(sb) == 0)

                  goto cantfind_ext2;

           /* 一个块中 Inode 个数等于块大小处以 Inode 大小 */

           sbi->s_inodes_per_block = sb->s_blocksize / EXT2_INODE_SIZE(sb);

           if (sbi->s_inodes_per_block == 0 || sbi->s_inodes_per_group == 0)

                  goto cantfind_ext2;

           /* 一个组的 inode 数量除以一个块的 Inode 数量,就得到一个组中有几个块是用来存储 Inode */ 

           sbi->s_itb_per_group = sbi->s_inodes_per_group /

                                       sbi->s_inodes_per_block;

           /* 块大小处以组描述符大小,得到一个块中最多有几个组描述符 */

           sbi->s_desc_per_block = sb->s_blocksize /

                                       sizeof (struct ext2_group_desc);

           ......

           /* 由于没有实现分片,片大小不能与块大小就报错 */

           if (sb->s_blocksize != bh->b_size) {

                  if (!silent)

                         ext2_msg(sb, KERN_ERR, "error: unsupported blocksize");

                  goto failed_mount;

           }

     

           if (EXT2_BLOCKS_PER_GROUP(sb) == 0)

                  goto cantfind_ext2;

           /*

                  计算当前分组的个数,前面已经讨论过,组的个数由分区大小和块大小决定。

                  这里需要处理,分区中块的边界不能凑成一个组的情况。

           */

          sbi->s_groups_count = ((le32_to_cpu(es->s_blocks_count) -

                               le32_to_cpu(es->s_first_data_block) - 1)

                                      / EXT2_BLOCKS_PER_GROUP(sb)) + 1;

           db_count = (sbi->s_groups_count + EXT2_DESC_PER_BLOCK(sb) - 1) /

                     EXT2_DESC_PER_BLOCK(sb);

            /*

             * 为组描述符分配 buffer_head 结构 :

             */

           sbi->s_group_desc = kmalloc (db_count * sizeof (struct buffer_head *), GFP_KERNEL);

           if (sbi->s_group_desc == NULL) {

                  ext2_msg(sb, KERN_ERR, "error: not enough memory");

                  goto failed_mount;

           }

           bgl_lock_init(sbi->s_blockgroup_lock);

          /* 分配一个字节数组,每组一个字节,把它的地址存放在ext2_sb_info描述符的s_debts字段*/

           sbi->s_debts = kcalloc(sbi->s_groups_count, sizeof(*sbi->s_debts), GFP_KERNEL);

           if (!sbi->s_debts) {

                  ext2_msg(sb, KERN_ERR, "error: not enough memory");

                  goto failed_mount_group_desc;

           }

          

           /* 请求磁盘驱动程序,把组描述符读取出来,并设置对应的 s_group_desc 数组中的指针 */

           for (i = 0; i < db_count; i++) {

            /* 根据超级块中的 s_first_data_block, 块大小,以及组描述符的大小,计算第 i 个组描述符的逻辑块号 */

                  block = descriptor_loc(sb, logic_sb_block, i);

                  /* 请求磁盘驱动程序读取第 block */

                  sbi->s_group_desc[i] = sb_bread(sb, block);

                  /* 一个块包含多个组描述符 */

                  if (!sbi->s_group_desc[i]) {

                         for (j = 0; j < i; j++)

                                brelse (sbi->s_group_desc[j]);

                         ext2_msg(sb, KERN_ERR,

                                "error: unable to read group descriptors");

                         goto failed_mount_group_desc;

                  }

           }

           if (!ext2_check_descriptors (sb)) {

                  ext2_msg(sb, KERN_ERR, "group descriptors corrupted");

                  goto failed_mount2;

           }

           sbi->s_gdb_count = db_count;

           ......

           /*

            * set up enough so that it can read an inode

            */

            /*

              *设置 VFS 超级块的 super_operation 指针为 ext2_sops

              * 这样就建立了抽象的 VFS 超级块对象和具体的 ext2 超级块对象之间的关联

              */

           sb->s_op = &ext2_sops;

           sb->s_export_op = &ext2_export_ops;

           sb->s_xattr = ext2_xattr_handlers;

           /* 获取根目录的 inode*/

           root = ext2_iget(sb, EXT2_ROOT_INO);

           if (IS_ERR(root)) {

                  ret = PTR_ERR(root);

                  goto failed_mount3;

           }

           if (!S_ISDIR(root->i_mode) || !root->i_blocks || !root->i_size) {

                  iput(root);

                  ext2_msg(sb, KERN_ERR, "error: corrupt root inode, run e2fsck");

                  goto failed_mount3;

           }

          /* 初始化根的目录结构 */

           sb->s_root = d_alloc_root(root);

           if (!sb->s_root) {

                  iput(root);

                  ext2_msg(sb, KERN_ERR, "error: get root inode failed");

                  ret = -ENOMEM;

                  goto failed_mount3;

           }

           ......

           return ret;

    }


    最新回复(0)