linux内核mount系统调用源码分析 http://blog.csdn.net/wugj03/article/details/41958029

@Author:  Gordon Wu

@Time : 2014/12 

0.摘要
mount是Linux很常见的命令,本文将从用户空间的命令行开始,一步一步切入到内核的源代码,解释一个文件系统是如果挂载的。本文基于linux 2.6.32

1.SYSCALL_DEFINE5, 系统调用
Linux kernel通过系统调用的方式为用户提供陷入到内核,mount的系统调用是SYSCALL_DEFINE5,位于fs/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.   
  10.     ret = copy_mount_string(type, &kernel_type);       // 把用户空间的挂载类型复制到内核  
  11.     if (ret < 0)  
  12.         goto out_type;  
  13.   
  14.     kernel_dir = getname(dir_name);                    //通过kmem_cache_alloc从names_cachep一个PATH_MAX大小的内核空间,把用户空间的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);                           //数据准备好,开始do_mount  
  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;  
  40. }  

2. do_mount

验证挂载的选项Flags,以及获取并填充相应挂载目录dir_name的路径Path结构。 再根据挂载选项Flags来判断,挂载的动作。

  1. /* 
  2.  * Flags is a 32-bit value that allows up to 31 non-fs dependent flags to 
  3.  * be given to the mount() call (ie: read-only, no-dev, no-suid etc). 
  4.  * 
  5.  * data is a (void *) that can point to any structure up to 
  6.  * PAGE_SIZE-1 bytes, which can contain arbitrary fs-dependent 
  7.  * information (or be NULL). 
  8.  * 
  9.  * Pre-0.97 versions of mount() didn't have a flags word. 
  10.  * When the flags word was introduced its top half was required 
  11.  * to have the magic value 0xC0ED, and this remained so until 2.4.0-test9. 
  12.  * Therefore, if this magic number is present, it carries no information 
  13.  * and must be discarded. 
  14.  */  
  15. long do_mount(char *dev_name, char *dir_name, char *type_page,  
  16.           unsigned long flags, void *data_page)  
  17. {  
  18.     struct path path;  
  19.     int retval = 0;  
  20.     int mnt_flags = 0;  
  21.   
  22.     /* Discard magic */  
  23.     if ((flags & MS_MGC_MSK) == MS_MGC_VAL)  
  24.         flags &= ~MS_MGC_MSK;  
  25.   
  26.     /* Basic sanity checks */  
  27.   
  28.     if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))           //挂载目录的有效性, memchr是判断dir_name是否在0~PAGE_SIZE(用户空间)中  
  29.         return -EINVAL;                                                    
  30.   
  31.     if (data_page)  
  32.         ((char *)data_page)[PAGE_SIZE - 1] = 0;  
  33.   
  34.     /* Default to relatime unless overriden */  
  35.     if (!(flags & MS_NOATIME))  
  36.         mnt_flags |= MNT_RELATIME;  
  37.   
  38.     /* Separate the per-mountpoint flags */  
  39.     if (flags & MS_NOSUID)  
  40.         mnt_flags |= MNT_NOSUID;                                           //忽略suid和sgid位的影响  
  41.     if (flags & MS_NODEV)  
  42.         mnt_flags |= MNT_NODEV;                                         //不允许访问设备专用文件  
  43.     if (flags & MS_NOEXEC)  
  44.         mnt_flags |= MNT_NOEXEC;                                    //不允许执行程序  
  45.     if (flags & MS_NOATIME)  
  46.         mnt_flags |= MNT_NOATIME;                                  //下面三个标志是关于是否更新文件或目录的atime  
  47.     if (flags & MS_NODIRATIME)  
  48.         mnt_flags |= MNT_NODIRATIME;  
  49.     if (flags & MS_STRICTATIME)  
  50.         mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);  
  51.     if (flags & MS_RDONLY)                                            //只读标志  
  52.         mnt_flags |= MNT_READONLY;  
  53.                                                                             //相应的一些标志已经备份到mnt_flags了,flags去除相应位  
  54.     flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE |  
  55.            MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |  
  56.            MS_STRICTATIME);  
  57.    
  58.     /* ... and get the mountpoint */  
  59.     retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);                  //!!根据dir_name,获取挂载目录的路径信息path  
  60.     if (retval)  
  61.         return retval;  
  62.   
  63.     retval = security_sb_mount(dev_name, &path,  
  64.                    type_page, flags, data_page);  
  65.     if (retval)  
  66.         goto dput_out;  
  67.         <span style="color:#FF0000;">/*根据不同选项,进行下面五种不同的挂载*/</span>  
  68.     if (flags & MS_REMOUNT)  
  69.         retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,  
  70.                     data_page);  
  71.     else if (flags & MS_BIND)  
  72.         retval = do_loopback(&path, dev_name, flags & MS_REC);  
  73.     else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))  
  74.         retval = do_change_type(&path, flags);  
  75.     else if (flags & MS_MOVE)  
  76.         retval = do_move_mount(&path, dev_name);  
  77.     else  
  78.         retval = do_new_mount(&path, type_page, flags, mnt_flags,                //do_new_mount是最常用的挂载,path: 挂载目录,dev_name:挂载设备  
  79.                       dev_name, data_page);  
  80. dput_out:  
  81.     path_put(&path);  
  82.     return retval;  
  83. }  

3. do_new_mount

利用do_kern_mount为用户空间生成一个新的挂载,并do_add_mount把新安装加入到命名空间树上

  1. /* 
  2.  * create a new mount for userspace and request it to be added into the 
  3.  * namespace's tree 
  4.  */  // *path: 挂载目录, *name:挂载设备,*type:挂载文件系统类型  
  5. static int do_new_mount(struct path *path, char *type, int flags,             
  6.             int mnt_flags, char *name, void *data)  
  7. {  
  8.     struct vfsmount *mnt;                  //包含已挂载文件系统的信息。  
  9.   
  10.     if (!type)  
  11.         return -EINVAL;  
  12.   
  13.     /* we need capabilities... */           // root权限,才能挂载  
  14.     if (!capable(CAP_SYS_ADMIN))  
  15.         return -EPERM;  
  16.   
  17.         //内核锁  
  18.     lock_kernel();  
  19.     mnt = do_kern_mount(type, flags, name, data);        //完成mnt信息的填充。  
  20.     unlock_kernel();  
  21.     if (IS_ERR(mnt))  
  22.         return PTR_ERR(mnt);  
  23.   
  24.     return do_add_mount(mnt, path, mnt_flags, NULL);     //添加到命名空间树上  
  25. }  

4. do_kern_mount

4.1 do_kern_mount函数首先会调用get_fs_type来查看内核是否注册了参数type所指的文件系统,对于内核源码下fs目录下的所有文件系统都会通过调用register_filesystem来注册这个文件系统,其实就是添加到内核文件系统链表中,get_fs_type会将参数type字符串跟内核链表中所有已经注册的文件系统结构体file_system_type的name成员向比较,如果找到,则说明内核已经注册了相应文件系统,并且返回相应文件系统注册的file_system_type结构体。后面的挂载过程需要使用到这个结构体中的成员。[1]

  1. struct vfsmount *  
  2. do_kern_mount(const char *fstype, int flags, const char *name, void *data)  
  3. {  
  4.     struct file_system_type *type = get_fs_type(fstype);    //获取挂载文件系统的类型结构,见4.2,4.3简介  
  5.     struct vfsmount *mnt;  
  6.     if (!type)  
  7.         return ERR_PTR(-ENODEV);  
  8.     mnt = vfs_kern_mount(type, flags, name, data);             
  9.     if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&  
  10.         !mnt->mnt_sb->s_subtype)  
  11.         mnt = fs_set_subtype(mnt, fstype);  
  12.     put_filesystem(type);  
  13.     return mnt;  
  14. }  
4.2 struct file_system_type结构体,下面的get_sb函数会在下面的挂载过程中用到,用来完成具体文件系统挂载的操作。

  1. struct file_system_type {  
  2.     const char *name;  
  3.     int fs_flags;  
  4.     int (*get_sb) (struct file_system_type *, int,  
  5.                const char *, void *, struct vfsmount *);  
  6.     void (*kill_sb) (struct super_block *);  
  7.     struct module *owner;  
  8.     struct file_system_type * next;  
  9.     struct list_head fs_supers;  
  10.   
  11.     struct lock_class_key s_lock_key;  
  12.     struct lock_class_key s_umount_key;  
  13.   
  14.     struct lock_class_key i_lock_key;  
  15.     struct lock_class_key i_mutex_key;  
  16.     struct lock_class_key i_mutex_dir_key;  
  17.     struct lock_class_key i_alloc_sem_key;  
  18. };  
4.3 get_fs_type用到的最主要的函数: 就是从已挂载文件系统队列file_systems中查找,返回相应name类型的file_system_type.

  1. static struct file_system_type **find_filesystem(const char *name, unsigned len)  
  2. {  
  3.     struct file_system_type **p;  
  4.     for (p=&file_systems; *p; p=&(*p)->next)  
  5.         if (strlen((*p)->name) == len &&  
  6.             strncmp((*p)->name, name, len) == 0)  
  7.             break;  
  8.     return p;  
  9. }  


5. vfs_kern_mount

具体文件系统file_system_type的get_sb成员函数是关键,它填充vfsmnt结构体的super_block结构体,是写文件系统的第一步。

  1. struct vfsmount *  
  2. vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)  
  3. {  
  4.     struct vfsmount *mnt;  
  5.     char *secdata = NULL;  
  6.     int error;  
  7.   
  8.     if (!type)  
  9.         return ERR_PTR(-ENODEV);  
  10.   
  11.     error = -ENOMEM;  
  12.     mnt = alloc_vfsmnt(name);      //alloc_vfsmnt以设备名为参数,为mnt函数分配一个空间,初始化mnt的基本信息,包括mnt->mnt_devname,以及一些list  
  13.     if (!mnt)  
  14.         goto out;  
  15.   
  16.     if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {  
  17.         secdata = alloc_secdata();  
  18.         if (!secdata)  
  19.             goto out_mnt;  
  20.   
  21.         error = security_sb_copy_data(data, secdata);  
  22.         if (error)  
  23.             goto out_free_secdata;  
  24.     }  
  25.   
  26.     error = type->get_sb(type, flags, name, data, mnt);      //调用具体文件系统file_system_type的get_sb函数,填充vfsmnt结构体的super_block结构体  
  27.     if (error < 0)  
  28.         goto out_free_secdata;  
  29.     BUG_ON(!mnt->mnt_sb);  
  30.   
  31.     error = security_sb_kern_mount(mnt->mnt_sb, flags, secdata);  
  32.     if (error)  
  33.         goto out_sb;  
  34.   
  35.     /* 
  36.      * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE 
  37.      * but s_maxbytes was an unsigned long long for many releases. Throw 
  38.      * this warning for a little while to try and catch filesystems that 
  39.      * violate this rule. This warning should be either removed or 
  40.      * converted to a BUG() in 2.6.34. 
  41.      */  
  42.     WARN((mnt->mnt_sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "  
  43.         "negative value (%lld)\n", type->name, mnt->mnt_sb->s_maxbytes);  
  44.   
  45.     mnt->mnt_mountpoint = mnt->mnt_root;                   //  
  46.     mnt->mnt_parent = mnt;  
  47.     up_write(&mnt->mnt_sb->s_umount);  
  48.     free_secdata(secdata);  
  49.     return mnt;  
  50. out_sb:  
  51.     dput(mnt->mnt_root);  
  52.     deactivate_locked_super(mnt->mnt_sb);  
  53. out_free_secdata:  
  54.     free_secdata(secdata);  
  55. out_mnt:  
  56.     free_vfsmnt(mnt);  
  57. out:  
  58.     return ERR_PTR(error);  
  59. }  

5.2 ext2_get_sb, 以ext2文件系统为例,通过ext2_fill_super来填充super_block的信息。

  1. static int ext2_get_sb(struct file_system_type *fs_type,  
  2.     int flags, const char *dev_name, void *data, struct vfsmount *mnt)  
  3. {  
  4.     return get_sb_bdev(fs_type, flags, dev_name, data, ext2_fill_super, mnt);  
  5. }  
5.3  get_sb_bdev

获取相应块设备的super_block,通过ext2_fill_super来完成super_block信息的填充。

  1. int get_sb_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.     struct vfsmount *mnt)  
  5. {  
  6.     struct block_device *bdev;          //块设备信息  
  7.     struct super_block *s;  
  8.     fmode_t mode = FMODE_READ;  
  9.     int error = 0;  
  10.   
  11.     if (!(flags & MS_RDONLY))  
  12.         mode |= FMODE_WRITE;  
  13.   
  14.     bdev = open_bdev_exclusive(dev_name, mode, fs_type);    //根据dev_name,去读取块设备信息。  
  15.     if (IS_ERR(bdev))  
  16.         return PTR_ERR(bdev);  
  17.   
  18.     /* 
  19.      * once the super is inserted into the list by sget, s_umount 
  20.      * will protect the lockfs code from trying to start a snapshot 
  21.      * while we are mounting 
  22.      */  
  23.     mutex_lock(&bdev->bd_fsfreeze_mutex);  
  24.     if (bdev->bd_fsfreeze_count > 0) {  
  25.         mutex_unlock(&bdev->bd_fsfreeze_mutex);  
  26.         error = -EBUSY;  
  27.         goto error_bdev;  
  28.     }  
  29.     s = sget(fs_type, test_bdev_super, set_bdev_super, bdev);     //根据bdev,去读取相应的super_block  
  30.     mutex_unlock(&bdev->bd_fsfreeze_mutex);  
  31.     if (IS_ERR(s))  
  32.         goto error_s;  
  33.   
  34.     if (s->s_root) {  
  35.         if ((flags ^ s->s_flags) & MS_RDONLY) {  
  36.             deactivate_locked_super(s);  
  37.             error = -EBUSY;  
  38.             goto error_bdev;  
  39.         }  
  40.   
  41.         close_bdev_exclusive(bdev, mode);  
  42.     } else {  
  43.         char b[BDEVNAME_SIZE];  
  44.   
  45.         s->s_flags = flags;  
  46.         s->s_mode = mode;  
  47.         strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id));  
  48.         sb_set_blocksize(s, block_size(bdev));  
  49.         error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);        //具体文件系统的fill_super,这里是ext2_fill_super,完成对super_block各个域的初始化  
  50.         if (error) {  
  51.             deactivate_locked_super(s);  
  52.             goto error;  
  53.         }  
  54.   
  55.         s->s_flags |= MS_ACTIVE;  
  56.         bdev->bd_super = s;  
  57.     }  
  58.   
  59.     simple_set_mnt(mnt, s);  
  60.     return 0;  
  61.   
  62. error_s:  
  63.     error = PTR_ERR(s);  
  64. error_bdev:  
  65.     close_bdev_exclusive(bdev, mode);  
  66. error:  
  67.     return error;  
  68. }  

6.do_new_mount->do_add_mount 

回到do_new_mount的do_add_mount函数,把mnt挂载到命名空间上。do_add_mount将新挂载的文件系统(由vfsmnt表示)添加到系统的命名空间结构体的已挂载文件系统链表中,命名空间是指系统中以挂载文件系统树,每个进程的PCB中都有namespace成员来表示该进程的命名空间,大多数的进程共享同一个命名空间,所以如果在一个进程中将磁盘挂载到系统中,在另一个进程也是可以看到的,这就是由命名空间来实现的[1]

  1. *  
  2.  * add a mount into a namespace's mount tree  
  3.  * - provide the option of adding the new mount to an expiration list  
  4.  */  
  5. int do_add_mount(struct vfsmount *newmnt, struct path *path,  
  6.          int mnt_flags, struct list_head *fslist)  
  7. {  
  8.     int err;  
  9.   
  10.     down_write(&namespace_sem);  
  11.     /* Something was mounted here while we slept */             /*挂载目录可能是已挂载的,follow_down可以把path->mnt和path->dentry指向上一层挂载*/  
  12.     while (d_mountpoint(path->dentry) &&  
  13.            follow_down(path))  
  14.         ;  
  15.     err = -EINVAL;  
  16.     if (!(mnt_flags & MNT_SHRINKABLE) && !check_mnt(path->mnt))  
  17.         goto unlock;  
  18.   
  19.     /* Refuse the same filesystem on the same mount point */  
  20.     err = -EBUSY;  
  21.     if (path->mnt->mnt_sb == newmnt->mnt_sb &&  
  22.         path->mnt->mnt_root == path->dentry)  
  23.         goto unlock;  
  24.   
  25.     err = -EINVAL;  
  26.     if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))  
  27.         goto unlock;  
  28.   
  29.     newmnt->mnt_flags = mnt_flags;  
  30.     if ((err = graft_tree(newmnt, path)))  
  31.         goto unlock;  
  32.   
  33.     if (fslist) /* add to the specified expiration list */                  /*添加*/  
  34.         list_add_tail(&newmnt->mnt_expire, fslist);  
  35.   
  36.     up_write(&namespace_sem);  
  37.     return 0;  
  38.   
  39. unlock:  
  40.     up_write(&namespace_sem);  
  41.     mntput(newmnt);  
  42.     return err;  
  43. }  

参考博文: [1]:  http://blog.csdn.net/skyflying2012/article/details/9748133  

版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您提供的链接是关于Windows Subsystem for Linux 2 (WSL 2)内核的文档。WSL 2是一种在Windows 10上运行Linux发行版的功能。要下载WSL 2内核更新包,您可以使用以下链接:\[1\] \[2\] \[3\]。请根据您的计算机架构(x64)选择适用于您的计算机的内核更新包。安装内核更新包后,您还需要下载Linux发行版,以便在WSL 2上运行Linux环境。 #### 引用[.reference_title] - *1* [Windows10安装WSL2](https://blog.csdn.net/shangsongwww/article/details/120217595)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [WSL升级WSl2 以及更新内核组件](https://blog.csdn.net/m0_49648190/article/details/118978891)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Windows上快速安装WSL2教程](https://blog.csdn.net/MrYushiwen/article/details/122199276)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值