Ext3 mount过程分析

发布时间:2019-09-19 08:00:54编辑:auto阅读(1609)

    Ext3 mount原理

     
    本质上,Ext3 mount的过程实际上是inode被替代的过程。例如,/dev/sdb块设备被mount到/mnt/alan目录。那么mount这个过程所需要解决的问题就是将/mnt/alan的dentry目录项所指向的inode屏蔽掉,然后重新定位到/dev/sdb所表示的inode索引节点。在没有分析阅读linux vfs mount代码的时候,我的想法是修改dentry所指向的inode索引节点,以此实现mount文件系统的访问。经过分析,在实际的vfs mount实现过程中,还是和我原始的想法略有差别,但是,基本目标还是相同的。
     
    Linux VFS的mount过程基本原理如下图所示:
     

     当用户输入”mount /dev/sdb /mnt/alan”命令后,Linux会解析/mnt/alan字符串,并且从Dentry Hash表中获取相关的dentry目录项,然后将该目录项标识成DCACHE_MOUNTED。一旦该dentry被标识成DCACHE_MOUNTED,也就意味着在访问路径上对其进行了屏蔽。

     
    在mount /dev/sdb设备上的ext3文件系统时,内核会创建一个该文件系统的superblock对象,并且从/dev/sdb设备上读取所有的superblock信息,初始化该内存对象。Linux内核维护了一个全局superblock对象链表。s_root是superblock对象所维护的dentry目录项,该目录项是该文件系统的根目录。即新mount的文件系统内容都需要通过该根目录进行访问。在mount的过程中,VFS会创建一个非常重要的vfsmount对象,该对象维护了文件系统mount的所有信息。Vfsmount对象通过HASH表进行维护,通过path地址计算HASH值,在这里vfsmount的HASH值通过“/mnt/alan”路径字符串进行计算得到。Vfsmount中的mnt_root指向superblock对象的s_root根目录项。因此,通过/mnt/alan地址可以检索VFSMOUNT Hash Table得到被mount的vfsmount对象,进而得到mnt_root根目录项。
     
    例如,/dev/sdb被mount之后,用户想要访问该设备上的一个文件ab.c,假设该文件的地址为:/mnt/alan/ab.c。在打开该文件的时候,首先需要进行path解析。在解析到/mnt/alan的时候,得到/mnt/alan的dentry目录项,并且发现该目录项已经被标识为DCACHE_MOUNTED。之后,会采用/mnt/alan计算HASH值去检索VFSMOUNT Hash Table,得到对应的vfsmount对象,然后采用vfsmount指向的mnt_root目录项替代/mnt/alan原来的dentry,从而实现了dentry和inode的重定向。在新的dentry的基础上,解析程序继续执行,最终得到表示ab.c文件的inode对象。
     
    关键数据结构说明
    Linux VFS mount所涉及的关键数据结构分析如下。
    Vfsmount数据结构
     
    Vfsmount数据结构是vfs mount最为重要的数据结构,其维护了一个mount点的所有信息。该数据结构描述如下:
     
    1. struct vfsmount {  
    2.     struct list_head mnt_hash;  /* 连接到VFSMOUNT Hash Table */  
    3.     struct vfsmount *mnt_parent;    /* 指向mount树中的父节点 */  
    4.     struct dentry *mnt_mountpoint;  /* 指向mount点的目录项 */  
    5.     struct dentry *mnt_root;    /* 被mount的文件系统根目录项 */  
    6.     struct super_block *mnt_sb; /* 指向被mount的文件系统superblock */  
    7. #ifdef CONFIG_SMP  
    8.     struct mnt_pcp __percpu *mnt_pcp;  
    9.     atomic_t mnt_longterm;      /* how many of the refs are longterm */  
    10. #else  
    11.     int mnt_count;  
    12.     int mnt_writers;  
    13. #endif  
    14.     struct list_head mnt_mounts;    /* 下级(child)vfsmount对象链表 */  
    15.     struct list_head mnt_child; /* 链入上级vfsmount对象的链表点 */  
    16.     int mnt_flags;  
    17.     /* 4 bytes hole on 64bits arches without fsnotify */  
    18. #ifdef CONFIG_FSNOTIFY  
    19.     __u32 mnt_fsnotify_mask;  
    20.     struct hlist_head mnt_fsnotify_marks;  
    21. #endif  
    22.     const char *mnt_devname;    /* 文件系统所在的设备名字,例如/dev/sdb */  
    23.     struct list_head mnt_list;  
    24.     struct list_head mnt_expire;    /* link in fs-specific expiry list */  
    25.     struct list_head mnt_share; /* circular list of shared mounts */  
    26.     struct list_head mnt_slave_list;/* list of slave mounts */  
    27.     struct list_head mnt_slave; /* slave list entry */  
    28.     struct vfsmount *mnt_master;    /* slave is on master->mnt_slave_list */  
    29.     struct mnt_namespace *mnt_ns;   /* containing namespace */  
    30.     int mnt_id;         /* mount identifier */  
    31.     int mnt_group_id;       /* peer group identifier */  
    32.     int mnt_expiry_mark;        /* true if marked for expiry */  
    33.     int mnt_pinned;  
    34.     int mnt_ghosts;  
    35. };  
    在Linux内核中不仅存在VFSMOUNT的Hash Table,而且还维护了一棵Mount对象树,通过该mount树,我们可以了解到各个文件系统之间的关系。该mount树描述如下:
     

    上图所示为三层mount文件系统树。第一层为系统根目录“/”;第二层有两个mount点,一个为/mnt/a,另一个是/mnt/b;第三层在/mnt/a的基础上又创建了两个mount点,分别为/mnt/a/c和/mnt/a/d。通过mount树,可以对整个系统的mount结构一目了然。
     
    Superblock数据结构
     
    每个文件系统都会拥有一个superblock对象对其基本信息进行描述。对于像ext3之类的文件系统而言,在磁盘上会持久化存储一份superblock元数据信息,内存的superblock对象由磁盘上的信息初始化。对于像block device 之类的“伪文件系统”而言,在mount的时候也会创建superblock对象,只不过很多信息都是临时生成的,没有持久化信息。Vfs superblock数据结构定义如下:
     
    1. struct super_block {  
    2.     struct list_head    s_list;     /* 链入全局链表的对象*/  
    3.     dev_t           s_dev;      /* search index; _not_ kdev_t */  
    4.     unsigned char       s_dirt;  
    5.     unsigned char       s_blocksize_bits;  
    6.     unsigned long       s_blocksize;  
    7.     loff_t          s_maxbytes; /* Max file size */  
    8.     struct file_system_type *s_type;  
    9.     const struct super_operations   *s_op;      /* superblock操作函数集 */  
    10.     const struct dquot_operations   *dq_op;  
    11.     const struct quotactl_ops   *s_qcop;  
    12.     const struct export_operations *s_export_op;  
    13.     unsigned long       s_flags;  
    14.     unsigned long       s_magic;  
    15.     struct dentry       *s_root;    /* 文件系统根目录项 */  
    16.     struct rw_semaphore s_umount;  
    17.     struct mutex        s_lock;  
    18.     int         s_count;  
    19.     atomic_t        s_active;  
    20. #ifdef CONFIG_SECURITY  
    21.     void                    *s_security;  
    22. #endif  
    23.     const struct xattr_handler **s_xattr;  
    24.  
    25.     struct list_head    s_inodes;   /* all inodes */  
    26.     struct hlist_bl_head    s_anon;     /* anonymous dentries for (nfs) exporting */  
    27. #ifdef CONFIG_SMP  
    28.     struct list_head __percpu *s_files;  
    29. #else  
    30.     struct list_head    s_files;  
    31. #endif  
    32.     /* s_dentry_lru, s_nr_dentry_unused protected by dcache.c lru locks */  
    33.     struct list_head    s_dentry_lru;   /* unused dentry lru */  
    34.     int         s_nr_dentry_unused; /* # of dentry on lru */  
    35.  
    36.     /* s_inode_lru_lock protects s_inode_lru and s_nr_inodes_unused */  
    37.     spinlock_t      s_inode_lru_lock ____cacheline_aligned_in_smp;  
    38.     struct list_head    s_inode_lru;        /* unused inode lru */  
    39.     int         s_nr_inodes_unused; /* # of inodes on lru */  
    40.  
    41.     struct block_device *s_bdev;  
    42.     struct backing_dev_info *s_bdi;  
    43.     struct mtd_info     *s_mtd;  
    44.     struct list_head    s_instances;  
    45.     struct quota_info   s_dquot;    /* Diskquota specific options */  
    46.  
    47.     int         s_frozen;  
    48.     wait_queue_head_t   s_wait_unfrozen;  
    49.  
    50.     char s_id[32];              /* Informational name */  
    51.     u8 s_uuid[16];              /* UUID */  
    52.  
    53.     void            *s_fs_info; /* Filesystem private info */  
    54.     fmode_t         s_mode;  
    55.  
    56.     /* Granularity of c/m/atime in ns.  
    57.        Cannot be worse than a second */  
    58.     u32        s_time_gran;  
    59.  
    60.     /*  
    61.      * The next field is for VFS *only*. No filesystems have any business  
    62.      * even looking at it. You had been warned.  
    63.      */  
    64.     struct mutex s_vfs_rename_mutex;    /* Kludge */  
    65.  
    66.     /*  
    67.      * Filesystem subtype.  If non-empty the filesystem type field  
    68.      * in /proc/mounts will be "type.subtype"  
    69.      */  
    70.     char *s_subtype;  
    71.  
    72.     /*  
    73.      * Saved mount options for lazy filesystems using  
    74.      * generic_show_options()  
    75.      */  
    76.     char __rcu *s_options;  
    77.     const struct dentry_operations *s_d_op; /* default d_op for dentries */  
    78.  
    79.     /*  
    80.      * Saved pool identifier for cleancache (-1 means none)  
    81.      */  
    82.     int cleancache_poolid;  
    83.  
    84.     struct shrinker s_shrink;   /* per-sb shrinker handle */  
    85. };  

    代码流程分析

     
    Linux中实现mount操作需要一定的代码量,下面对Linux VFS Mount代码进行分析说明,整个分析过程按照mount操作函数调用流程进行。代码分析基于Linux-3.2版本。
     
    当用户在用户层执行mount命令时,会执行系统调用从用户态陷入linux内核,执行如下函数(namespace.c):
    1. SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,  
    2.         char __user *, type, unsigned long, flags, void __user *, data)  
    3. {  
    4.     int ret;  
    5.     char *kernel_type;  
    6.     char *kernel_dir;  
    7.     char *kernel_dev;  
    8.     unsigned long data_page;  
    9.     /* 获取mount类型 */  
    10.     ret = copy_mount_string(type, &kernel_type);  
    11.     if (ret < 0)  
    12.         goto out_type;  
    13.     /* 获取mount点目录字符串 */  
    14.     kernel_dir = getname(dir_name);  
    15.     if (IS_ERR(kernel_dir)) {  
    16.         ret = PTR_ERR(kernel_dir);  
    17.         goto out_dir;  
    18.     }  
    19.     /* 获取设备名称字符串 */  
    20.     ret = copy_mount_string(dev_name, &kernel_dev);  
    21.     if (ret < 0)  
    22.         goto out_dev;  
    23.     /* 获取其它选项 */  
    24.     ret = copy_mount_options(data, &data_page);  
    25.     if (ret < 0)  
    26.         goto out_data;  
    27.     /* 主要函数,执行挂载文件系统的具体操作 */  
    28.     ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags,  
    29.         (void *) data_page);  
    30.  
    31.     free_page(data_page);  
    32. out_data:  
    33.     kfree(kernel_dev);  
    34. out_dev:  
    35.     putname(kernel_dir);  
    36. out_dir:  
    37.     kfree(kernel_type);  
    38. out_type:  
    39.     return ret;  

     

    do_mount()函数是mount操作过程中的核心函数,在该函数中,通过mount的目录字符串找到对应的dentry目录项,然后通过do_new_mount()函数完成具体的mount操作。do_mount()函数分析如下:

    1. long do_mount(char *dev_name, char *dir_name, char *type_page,  
    2.           unsigned long flags, void *data_page)  
    3. {  
    4.     struct path path;  
    5.     int retval = 0;  
    6.     int mnt_flags = 0;  
    7.  
    8. 。。。  
    9.  
    10.     /* 通过mount目录字符串获取path,path结构中包含有mount目录的dentry目录对象 */  
    11.     retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);  
    12.     if (retval)  
    13.         return retval;  
    14.  
    15.     。。。  
    16.  
    17.     /* Separate the per-mountpoint flags */  
    18.     if (flags & MS_NOSUID)  
    19.         mnt_flags |= MNT_NOSUID;  
    20.     if (flags & MS_NODEV)  
    21.         mnt_flags |= MNT_NODEV;  
    22.     if (flags & MS_NOEXEC)  
    23.         mnt_flags |= MNT_NOEXEC;  
    24.     if (flags & MS_NOATIME)  
    25.         mnt_flags |= MNT_NOATIME;  
    26.     if (flags & MS_NODIRATIME)  
    27.         mnt_flags |= MNT_NODIRATIME;  
    28.     if (flags & MS_STRICTATIME)  
    29.         mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);  
    30.     if (flags & MS_RDONLY)  
    31.         mnt_flags |= MNT_READONLY;  
    32.  
    33.     flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN |  
    34.            MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |  
    35.            MS_STRICTATIME);  
    36.  
    37.     /* remount操作 */  
    38.     if (flags & MS_REMOUNT)  
    39.         retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,  
    40.                     data_page);  
    41.     else if (flags & MS_BIND)  
    42.         retval = do_loopback(&path, dev_name, flags & MS_REC);  
    43.     else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))  
    44.         retval = do_change_type(&path, flags);  
    45.     else if (flags & MS_MOVE)  
    46.         retval = do_move_mount(&path, dev_name);  
    47.     else  
    48.         /* 正常的mount操作,完成具体的mount操作 */  
    49.         retval = do_new_mount(&path, type_page, flags, mnt_flags,  
    50.                       dev_name, data_page);  
    51. dput_out:  
    52.     path_put(&path);  
    53.     return retval;  

    do_new_mount()函数主要分成两大部分:第一部分建立vfsmount对象和superblock对象,必要时从设备上获取文件系统元数据;第二部分将vfsmount对象加入到mount树和Hash Table中,并且将原来的dentry对象无效掉。do_new_mount函数说明如下:

    1. static int do_new_mount(struct path *path, char *type, int flags,  
    2.             int mnt_flags, char *name, void *data)  
    3. {  
    4.     struct vfsmount *mnt;  
    5.     int err;  
    6.  
    7.     。。。  
    8.  
    9.     /* 在内核建立vfsmount对象和superblock对象 */  
    10.     mnt = do_kern_mount(type, flags, name, data);  
    11.     if (IS_ERR(mnt))  
    12.         return PTR_ERR(mnt);  
    13.     /* 将vfsmount对象加入系统,屏蔽原有dentry对象 */  
    14.     err = do_add_mount(mnt, path, mnt_flags);  
    15.     if (err)  
    16.         mntput(mnt);  
    17.     return err;  

    do_new_mount()中的第一步调用do_kern_mount()函数,该函数的主干调用路径如下:

    do_kern_mount--> vfs_kern_mount--> mount_fs
    在mount_fs()函数中会调用特定文件系统的mount方法,如果mount是ext3文件系统,那么在mount_fs函数中最终会调用ext3的mount方法。Ext3的mount方法定义在super.c文件中:
    1. static struct file_system_type ext3_fs_type = {  
    2.     .owner      = THIS_MODULE,  
    3.     .name       = "ext3",  
    4.     .mount      = ext3_mount,       /* ext3文件系统mount方法 */  
    5.     .kill_sb    = kill_block_super,  
    6.     .fs_flags   = FS_REQUIRES_DEV,  
    7. }; 

    Ext3 mount函数主干调用路径为:ext3_mount--> mount_bdev。Mount_bdev()函数主要完成superblock对象的内存初始化,并且加入到全局superblock链表中。该函数说明如下:

    1. struct dentry *mount_bdev(struct file_system_type *fs_type,  
    2.     int flags, const char *dev_name, void *data,  
    3.     int (*fill_super)(struct super_block *, void *, int))  
    4. {  
    5.     struct block_device *bdev;  
    6.     struct super_block *s;  
    7.     fmode_t mode = FMODE_READ | FMODE_EXCL;  
    8.     int error = 0;  
    9.  
    10.     if (!(flags & MS_RDONLY))  
    11.         mode |= FMODE_WRITE;  
    12.     /* 通过设备名字获取被mount设备的bdev对象 */  
    13.     bdev = blkdev_get_by_path(dev_name, mode, fs_type);  
    14.     if (IS_ERR(bdev))  
    15.         return ERR_CAST(bdev);  
    16.  
    17.     /*  
    18.      * once the super is inserted into the list by sget, s_umount  
    19.      * will protect the lockfs code from trying to start a snapshot  
    20.      * while we are mounting  
    21.      */  
    22.     mutex_lock(&bdev->bd_fsfreeze_mutex);  
    23.     if (bdev->bd_fsfreeze_count > 0) {  
    24.         mutex_unlock(&bdev->bd_fsfreeze_mutex);  
    25.         error = -EBUSY;  
    26.         goto error_bdev;  
    27.     }  
    28.     /* 查找或者创建superblock对象 */  
    29.     s = sget(fs_type, test_bdev_super, set_bdev_super, bdev);  
    30.     mutex_unlock(&bdev->bd_fsfreeze_mutex);  
    31.     if (IS_ERR(s))  
    32.         goto error_s;  
    33.  
    34.     if (s->s_root) {  
    35.         /* 被mount文件系统的根目录项已经存在 */  
    36.         if ((flags ^ s->s_flags) & MS_RDONLY) {  
    37.             deactivate_locked_super(s);  
    38.             error = -EBUSY;  
    39.             goto error_bdev;  
    40.         }  
    41.  
    42.         /*  
    43.          * s_umount nests inside bd_mutex during  
    44.          * __invalidate_device().  blkdev_put() acquires  
    45.          * bd_mutex and can't be called under s_umount.  Drop  
    46.          * s_umount temporarily.  This is safe as we're  
    47.          * holding an active reference.  
    48.          */  
    49.         up_write(&s->s_umount);  
    50.         blkdev_put(bdev, mode);  
    51.         down_write(&s->s_umount);  
    52.     } else {  
    53.         /* 文件系统根目录项不存在,通过filler_super函数读取磁盘上的superblock元数据信息,并且初始化superblock内存结构 */  
    54.         char b[BDEVNAME_SIZE];  
    55.  
    56.         s->s_flags = flags | MS_NOSEC;  
    57.         s->s_mode = mode;  
    58.         strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id));  
    59.         sb_set_blocksize(s, block_size(bdev));  
    60.         /* 对于ext3文件系统,调用ext3_fill_super函数 */  
    61.         error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);  
    62.         if (error) {  
    63.             deactivate_locked_super(s);  
    64.             goto error;  
    65.         }  
    66.  
    67.         s->s_flags |= MS_ACTIVE;  
    68.         bdev->bd_super = s;  
    69.     }  
    70.     /* 正常返回被mount文件系统根目录项 */  
    71.     return dget(s->s_root);  
    72.  
    73. error_s:  
    74.     error = PTR_ERR(s);  
    75. error_bdev:  
    76.     blkdev_put(bdev, mode);  
    77. error:  
    78.     return ERR_PTR(error);  

     

    do_new_mount()函数的第二步是将创建的vfsmount对象加入到mount树和VFSMOUNT Hash Table中,并且将老的dentry目录项无效掉。该过程主干函数调用过程如下所示:

    do_new_mount--> do_add_mount--> graft_tree--> attach_recursive_mnt
    attach_recursive_mnt()函数完成第二步过程的主要操作。至此,文件系统的mount操作已经完成。Mount完成之后,如果用户想要访问新mount文件系统中的文件,那么需要在path解析过程中重定位dentry,该过程主要在follow_managed()函数中完成。在该函数中会判断一个dentry是否已经被标识成DCACHE_MOUNTED,如果该标志位已经被设置,那么通过VFSMOUNT Hash Table可以重定位dentry。
     
    如有不对之处,敬请指出更正(tl_wzj@yahoo.com.cn)。

关键字