linux内核装载vfs过程

linux虚拟文件系统是设备驱动程序的之上的一个抽象层,致力于提供给应用程序一个统一的操作文件的接口。虚拟文件系统的各个数据结构之间的关系比较复杂,画了一张各个数据结构之间的关系图在 http://download.csdn.net/detail/lonewolfxw/4588935,这个清晰的给出了各结构的关系。

1. 目录查找

linux的虚拟文件系统最核心的结构就是dentry缓存,每次查找一个路径时,先在dentry缓存中查找是否有对应的项,例如cd /home/lonewolf则先解析目录结构,查找对应于home的dentry是否在缓存中,若不在则从底层设备中读取,然后查找lonewolf对应的dentry,依次进行,找到了dentry就找到文件对应的inode。因此,dentry缓存和文件目录树相同,按照树的方式组织,并且将已经得到的dentry项加入到dentry_hashtable缓存起来,避免每次都从低速的底层设备中读取,哈希表用来快速的查找。这当然只是一个查找的简略的过程,实际的过程相当复杂,需要处理很多情况,包括:

  • 检测符号链接,是否要跟踪符号链接
  • 检测挂载点,并且重定向到新的文件系统查找
  • 检查路径上每个目录的权限
  • 解析各种复杂的用户输入的路径,如/./home///..//.
linux中的路径查找工作由namei.c中的kern_path操作完成,完成这个函数的主要操作又集中在link_path_walk中:

[cpp]  view plain  copy
  1. /* 
  2.  * Name resolution. 
  3.  * This is the basic name resolution function, turning a pathname into 
  4.  * the final dentry. We expect 'base' to be positive and a directory. 
  5.  * 
  6.  * Returns 0 and nd will have valid dentry and mnt on success. 
  7.  * Returns error and drops reference to input namei data on failure. 
  8.  */  
  9. static int link_path_walk(const char *name, struct nameidata *nd)<span style="white-space:pre">                     </span>   /*struct nameidata只是一个查找结果的传送定义的一个结构体,查找成功则nd->path就是找到的结果,在执行这个函数之前,先对@nd进行初始化,表示路径查找的起点,若@name是以'/'开始,怎@nd->path就初始化fs_struct->root表示从跟目录开始查找,若@name不是以'/'开始,则初始化fs_struct->pwd表示从进程的当前目录开始查找*/  
  10. {  
  11.     struct path next;  
  12.     int err;  
  13.       
  14.     while (*name=='/'//若name是以/开始,则跳过,然后解析真正的路径的各个部分  
  15.         name++;  
  16.     if (!*name)  
  17.         return 0;  
  18.   
  19.     /* At this point we know we have a real path component. */  
  20.     for(;;) {  
  21.         unsigned long hash;  
  22.         struct qstr this;  
  23.         unsigned int c;  
  24.         int type;  
  25.   
  26.         err = may_lookup(nd); //检查权限,检查@nd对应inode的目录是否有执行权限,访问目录需要目录的执行权限  
  27.         if (err)  
  28.             break;  
  29. /*下面就是解析目录的各个部分, this表示解析的当前部分的结果*/  
  30.         this.name = name;  
  31.         c = *(const unsigned char *)name;  
  32.   
  33.         hash = init_name_hash();  
  34.         do {  
  35.             name++;  
  36.             hash = partial_name_hash(c, hash);//一个一个字符累加的哈希值,计算遍历到的部分的哈希值  
  37.             c = *(const unsigned char *)name;  
  38.         } while (c && (c != '/')); //字符串结束或者碰到下个部分的开始'/',则停止  
  39.         this.len = name - (const char *) this.name;  
  40.         this.hash = end_name_hash(hash);  
  41. /*type表示当前部分的类型,用来处理特殊目录'.'和'..'*/  
  42.         type = LAST_NORM;  
  43.         if (this.name[0] == '.'switch (this.len) {  
  44.             case 2:  
  45.                 if (this.name[1] == '.') {  
  46.                     type = LAST_DOTDOT;  
  47.                     nd->flags |= LOOKUP_JUMPED;  
  48.                 }  
  49.                 break;  
  50.             case 1:  
  51.                 type = LAST_DOT;  
  52.         }  
  53.         if (likely(type == LAST_NORM)) {   
  54.             struct dentry *parent = nd->path.dentry;  
  55.             nd->flags &= ~LOOKUP_JUMPED;  
  56.             if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {  
  57.                 err = parent->d_op->d_hash(parent, nd->inode,  
  58.                                &this);  
  59.                 if (err < 0)  
  60.                     break;  
  61.             }  
  62.         }  
  63.   
  64.         /*如果字符串已经结束,或者当前解析之后全是'/',则当前部分是路径的最后一部分,没有进过walk_component处理,因为有可能最后一个部分不是目录,从而跳到last_component处理,由调用link_path_walk出来最后一个部分*/  
  65.         if (!c)  
  66.             goto last_component;  
  67.         while (*++name == '/');  
  68.         if (!*name)  
  69.             goto last_component;  
  70.   
  71.         err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW);/*这个函数执行真正的dentry缓存的查找,在dentry_hashtable中查找@this的名字,找不到则使用inode的inode_operations->lookup中的操作从底层设备载入进来,这个哈希表用父目录的dentry和当前目录名字的hash值也就是this->hash作为键值。而且这个函数还处理目录的权限以及目录是装载点的情况,由于一个目录下可以装载多个文件系统,最新装载的文件系统隐藏以前的装载,若是装载点,则顺着装载点一直查找,直到最上层的装载点也就是当前可以看到的文件系统,当这个函数返回1,则表示这个目录是符号链接,下面进行特殊处理。函数调用成功则 @nd->path 表示this.name这个名字所表示的目录,也是就当前解析成功的目录,然后下一次循环解析下一个部分时候,这个目录就当做父目录在dentry缓存中查找,直至所有的部分全部完成*/  
  72.         if (err < 0)  
  73.             return err;  
  74.   
  75.         if (err) {  
  76.             err = nested_symlink(&next, nd);//如果err是1,则处理符号链接  
  77.             if (err)  
  78.                 return err;  
  79.         }  
  80.         if (can_lookup(nd->inode)) /*检查这个部分是否可以查找,也就是说检查这个部分是否是目录,由于除了最后一部分之外,中间的部分必须是目录,不是目录则出错。是最后一项会跳过此处的检查,直接跳到last_component*/  
  81.             continue;  
  82.         err = -ENOTDIR;   
  83.         break;  
  84.         /* here ends the main loop */  
  85.   
  86. last_component:  
  87.         nd->last = this;  
  88.         nd->last_type = type;  
  89.         return 0;  
  90.     }  
  91.     terminate_walk(nd);  
  92.     return err;  
  93. }  
[cpp]  view plain  copy
  1. static inline int walk_component(struct nameidata *nd, struct path *path,  
  2.         struct qstr *name, int type, int follow)  
  3. {  
  4.     struct inode *inode;  
  5.     int err;  
  6.     /* 
  7.      * "." and ".." are special - ".." especially so because it has 
  8.      * to be able to know about the current root directory and 
  9.      * parent relationships. 
  10.      */  
  11.     if (unlikely(type != LAST_NORM))  
  12.         return handle_dots(nd, type); /*处理目录是'.'和'..’的情况,'.'很好处理,直接跳过就可以了,'..'稍微麻烦,因为当前目录有可能是一个装载点,跳到上一级目录就要切换文件系统*/  
  13.     err = do_lookup(nd, name, path, &inode);/*这个从dentry缓存中查找,找不到就从底层设备中找,并且会处理装载点的情况*/   
  14.     if (unlikely(err)) {  
  15.         terminate_walk(nd);  
  16.         return err;  
  17.     }  
  18.     if (!inode) {//没有找到dentry,则表示文件不存在  
  19.         path_to_nameidata(path, nd);  
  20.         terminate_walk(nd);  
  21.         return -ENOENT;  
  22.     }  
  23.     if (should_follow_link(inode, follow)) {//检查是否要跟踪符号链接,若是返回1,有nested_symlink处理  
  24.         if (nd->flags & LOOKUP_RCU) {  
  25.             if (unlikely(unlazy_walk(nd, path->dentry))) {  
  26.                 terminate_walk(nd);  
  27.                 return -ECHILD;  
  28.             }  
  29.         }  
  30.         BUG_ON(inode != path->dentry->d_inode);  
  31.         return 1;  
  32.     }  
  33.     path_to_nameidata(path, nd);//将找到的path放到nd中返回  
  34.     nd->inode = inode;  
  35.     return 0;  
  36. }  

[cpp]  view plain  copy
  1. static inline int handle_dots(struct nameidata *nd, int type)  
  2. {  
  3.     if (type == LAST_DOTDOT) {  
  4.         if (nd->flags & LOOKUP_RCU) {  
  5.             if (follow_dotdot_rcu(nd))  
  6.                 return -ECHILD;  
  7.         } else  
  8.             follow_dotdot(nd);  
  9.     }  
  10.     return 0;  
  11. }  
  12. static int follow_dotdot_rcu(struct nameidata *nd)  
  13. {  
  14.     set_root_rcu(nd); //获得当前进程的根文件系统的path  
  15.   
  16.     while (1) {  
  17.         if (nd->path.dentry == nd->root.dentry &&  
  18.             nd->path.mnt == nd->root.mnt) { /*如果在根路径上执行"..",没有意义直接跳过就可以了*/  
  19.             break;  
  20.         }  
  21.         if (nd->path.dentry != nd->path.mnt->mnt_root) { /*不是装载点的根目录,就直接获得dentry的parent就可以了*/  
  22.             struct dentry *old = nd->path.dentry;  
  23.             struct dentry *parent = old->d_parent;  
  24.             unsigned seq;  
  25.   
  26.             seq = read_seqcount_begin(&parent->d_seq);  
  27.             if (read_seqcount_retry(&old->d_seq, nd->seq))  
  28.                 goto failed;  
  29.             nd->path.dentry = parent;  
  30.             nd->seq = seq;  
  31.             break;  
  32.         }  
  33.         if (!follow_up_rcu(&nd->path))  
  34.             break;  
  35.         nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);  
  36.     }  
  37.     follow_mount_rcu(nd);  
  38.     nd->inode = nd->path.dentry->d_inode;  
  39.     return 0;  
  40.   
  41. failed:  
  42.     nd->flags &= ~LOOKUP_RCU;  
  43.     if (!(nd->flags & LOOKUP_ROOT))  
  44.         nd->root.mnt = NULL;  
  45.     rcu_read_unlock();  
  46.     br_read_unlock(vfsmount_lock);  
  47.     return -ECHILD;  
  48. }  


查找操作比较麻烦,处理的情况很多,沿着目录一步一步一直找到最后一个目录,然后dentry缓存起到很好的加速作用,不用每次都从设备中读取,在解析目录各个部分考虑符号链接和装载点就可以了。

2. 文件系统装载

文件系统装载大概的过程就是先查找文件系统要装载的目录的dentry和vfsmount,然后新建一个vfsmount表示新的装载点,调用文件系统的mount操作,将其装载,并且将新的vfsmount加入vfsmount树中,相应的dentry项设置相关的flag。文件系统的装载由do_mount完成
[cpp]  view plain  copy
  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)) //验证目录的名字  
  29.         return -EINVAL;  
  30.   
  31.     if (data_page) //特定文件系统的私有项,大小为一页  
  32.         ((char *)data_page)[PAGE_SIZE - 1] = 0;  
  33.   
  34.     /* ... and get the mountpoint */  
  35.     retval = kern_path(dir_name, LOOKUP_FOLLOW, &path); //上文讲的查找装载点的路径  
  36.     if (retval)  
  37.         return retval;  
  38.   
  39.     retval = security_sb_mount(dev_name, &path,  
  40.                    type_page, flags, data_page); //直接调用security_ops->sb_mount,若成功直接返回  
  41.     if (retval)  
  42.         goto dput_out;  
  43. /*通过flag配置装载选项,下面是一个多路选择器,根据不同的装载选项调用不同的函数*/  
  44.     /* Default to relatime unless overriden */  
  45.     if (!(flags & MS_NOATIME))  
  46.         mnt_flags |= MNT_RELATIME;  
  47.   
  48.     /* Separate the per-mountpoint flags */  
  49.     if (flags & MS_NOSUID)  
  50.         mnt_flags |= MNT_NOSUID;  
  51.     if (flags & MS_NODEV)  
  52.         mnt_flags |= MNT_NODEV;  
  53.     if (flags & MS_NOEXEC)  
  54.         mnt_flags |= MNT_NOEXEC;  
  55.     if (flags & MS_NOATIME)  
  56.         mnt_flags |= MNT_NOATIME;  
  57.     if (flags & MS_NODIRATIME)  
  58.         mnt_flags |= MNT_NODIRATIME;  
  59.     if (flags & MS_STRICTATIME)  
  60.         mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);  
  61.     if (flags & MS_RDONLY)  
  62.         mnt_flags |= MNT_READONLY;  
  63.   
  64.     flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN |  
  65.            MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |  
  66.            MS_STRICTATIME);  
  67.   
  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,  
  79.                       dev_name, data_page);  
  80. dput_out:  
  81.     path_put(&path);  
  82.     return retval;  
  83. }  
装载新的文件系统通过do_new_mount来处理:
[cpp]  view plain  copy
  1. /* 
  2.  * create a new mount for userspace and request it to be added into the 
  3.  * namespace's tree 
  4.  */  
  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.     int err;  
  10.   
  11.     if (!type)  
  12.         return -EINVAL;  
  13.   
  14.     /* we need capabilities... */  
  15.     if (!capable(CAP_SYS_ADMIN)) //权限检查  
  16.         return -EPERM;  
  17.   
  18.     mnt = do_kern_mount(type, flags, name, data); /*新建一个vfsmount实例,并通过特定文件系统的操作装载到系统系统中,返回装载点的根目录*/  
  19.     if (IS_ERR(mnt))  
  20.         return PTR_ERR(mnt);  
  21.   
  22.     err = do_add_mount(mnt, path, mnt_flags); /*将vfsmount加入到vfsmount树中, 设置相关的数据结构的选项*/  
  23.     if (err)  
  24.         mntput(mnt);  
  25.     return err;  
  26. }  
do_add_mount通过各种检查,例如一个挂载点不能重复挂载其自身在相同的挂载点、挂载点必须是目录等,最后执行namespace.c中的attach_recursive_mnt处理各种数据结构:
[cpp]  view plain  copy
  1. static int attach_recursive_mnt(struct vfsmount *source_mnt,  
  2.             struct path *path, struct path *parent_path)  
  3. {  
  4.     LIST_HEAD(tree_list);  
  5.     struct vfsmount *dest_mnt = path->mnt;  
  6.     struct dentry *dest_dentry = path->dentry;  
  7.     struct vfsmount *child, *p;  
  8.     int err;  
  9.   
  10.     if (IS_MNT_SHARED(dest_mnt)) {  
  11.         err = invent_group_ids(source_mnt, true);  
  12.         if (err)  
  13.             goto out;  
  14.     }  
  15.     err = propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list); /*处理从属装载和共享装载,将相关的vfsmount通过tree_list返回*/  
  16.     if (err)  
  17.         goto out_cleanup_ids;  
  18.   
  19.     br_write_lock(vfsmount_lock);  
  20.   
  21.     if (IS_MNT_SHARED(dest_mnt)) {  
  22.         for (p = source_mnt; p; p = next_mnt(p, source_mnt))  
  23.             set_mnt_shared(p);  
  24.     }  
  25.     if (parent_path) { /*如果source_mnt之前装载在@parent_path,要迁移到@path上,则先从parent_path中移除,然后增加到@path路径上,移除包括从parent_path的vfsmount的子装载点中移除和从mount_hashtable中移除,因为mount_hashtable是通过父装载点的vfsmount和子装载的dentry来计算哈希值的*/  
  26.         detach_mnt(source_mnt, parent_path);  
  27.         attach_mnt(source_mnt, path);  
  28.         touch_mnt_namespace(parent_path->mnt->mnt_ns);  
  29.     } else { /*parent_path为空表示新挂载项,则设置source_mnt的mnt_parent、mnt_root、mnt_mountpoint,然后将其增加到父装载点的子装载点链表中,并将其加入哈希表*/  
  30.         mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);  
  31.         commit_tree(source_mnt);  
  32.     }  
  33. /*处理所有的从属装载和共享装载*/  
  34.     list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {  
  35.         list_del_init(&child->mnt_hash);  
  36.         commit_tree(child);  
  37.     }  
  38.     br_write_unlock(vfsmount_lock);  
  39.   
  40.     return 0;  
  41.   
  42.  out_cleanup_ids:  
  43.     if (IS_MNT_SHARED(dest_mnt))  
  44.         cleanup_group_ids(source_mnt, NULL);  
  45.  out:  
  46.     return err;  
  47. }  
至此,装载文件系统基本完成了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值