理解linux虚拟文件系统VFS - 路径查找 path_lookup

路径查找是VFS的一个主要操作:给定一个文件名,获取该文件名的inode。路径查找是VFS中相当繁琐的一部分,主要是符号链接,文件系统装载点,以及. ..和//等奇怪路径 引入了复杂性。

nameidata数据结构

查找过程涉及到很多函数调用,在这些调用过程中,nameidata起到了很重要的作用:1. 向查找函数传递参数;2. 保存查找结果。

[html]  view plain copy
  1. struct nameidata {  
  2.         struct dentry   *dentry;  
  3.         struct vfsmount *mnt;  
  4.         struct qstr     last;  
  5.         unsigned int    flags;  
  6.         int             last_type;  
  7.         unsigned        depth;  
  8.         char *saved_names[MAX_NESTED_LINKS + 1];  
  9.   
  10.         /* Intent data */  
  11.         union {  
  12.                 struct open_intent open;  
  13.         } intent;  
  14. };  

查找完成后@dentry 包含了找到文件的dentry目录项; @mnt 包含了文件目录项所在的vfsmount

@last 包含了需要查找的名称,这是一个快速字符串,除了路径字符串本身外,还包含字符串的长度和一个散列值

@depth 当前路径深度。

@saved_names: 由于在符号链接处理时,nd的名字一直发生变化,这里用来保存符号链接处理中的路径名


内核路径查找的函数很多,我们这里以 path_lookup为列,内核版本2.6.24

path_lookup

[html]  view plain copy
  1. int fastcall path_lookup(const char *name, unsigned int flags,  
  2.                         struct nameidata *nd)  
  3. {  
  4.         return do_path_lookup(AT_FDCWD, name, flags, nd);  
  5. }  

给定三个参数@name是文件路径名(可以是全路径,也可以是相对路径名);@flags路径查找标记;@nd此时不包含任何有用信息,用来返回查找结果。


do_path_lookup

[html]  view plain copy
  1. 1119 static int fastcall do_path_lookup(int dfd, const char *name,  
  2. 1120                                 unsigned int flags, struct nameidata *nd)  
  3. 1121 {  
  4. 1122         int retval = 0;  
  5. 1123         int fput_needed;  
  6. 1124         struct file *file;  
  7. 1125         struct fs_struct *fs = current->fs;  
  8. 1126   
  9. 1127         nd->last_type = LAST_ROOT; /* if there are only slashes... */  
  10. 1128         nd->flags = flags;  
  11. 1129         nd->depth = 0;  
  12. 1130   
  13. 1131         if (*name=='/') {  
  14. 1132                 read_lock(&fs->lock);  
  15. 1133                 if (fs->altroot && !(nd->flags & LOOKUP_NOALT)) {  
  16. 1134                         nd->mnt = mntget(fs->altrootmnt);  
  17. 1135                         nd->dentry = dget(fs->altroot);  
  18. 1136                         read_unlock(&fs->lock);  
  19. 1137                         if (__emul_lookup_dentry(name,nd))  
  20. 1138                                 goto out; /* found in altroot */  
  21. 1139                         read_lock(&fs->lock);  
  22. 1140                 }  
  23. 1141                 nd->mnt = mntget(fs->rootmnt);  
  24. 1142                 nd->dentry = dget(fs->root);  
  25. 1143                 read_unlock(&fs->lock);  
  26. 1144         } else if (dfd == AT_FDCWD) {  
  27. 1145                 read_lock(&fs->lock);  
  28. 1146                 nd->mnt = mntget(fs->pwdmnt);  
  29. 1147                 nd->dentry = dget(fs->pwd);  
  30. 1148                 read_unlock(&fs->lock);  
  31. 1149         } else {  
  32. 1150                 struct dentry *dentry;  
  33. 1151   
  34. 1152                 file = fget_light(dfd, &fput_needed);  
  35. 1153                 retval = -EBADF;  
  36. 1154                 if (!file)  
  37. 1155                         goto out_fail;  
  38. 1157                 dentry = file->f_path.dentry;  
  39. 1158   
  40. 1159                 retval = -ENOTDIR;  
  41. 1160                 if (!S_ISDIR(dentry->d_inode->i_mode))  
  42. 1161                         goto fput_fail;  
  43. 1162   
  44. 1163                 retval = file_permission(file, MAY_EXEC);  
  45. 1164                 if (retval)  
  46. 1165                         goto fput_fail;  
  47. 1166   
  48. 1167                 nd->mnt = mntget(file->f_path.mnt);  
  49. 1168                 nd->dentry = dget(dentry);  
  50. 1169   
  51. 1170                 fput_light(file, fput_needed);  
  52. 1171         }  
  53. 1172   
  54. 1173         retval = path_walk(name, nd);  
  55. 1174 out:  
  56. 1175         if (unlikely(!retval && !audit_dummy_context() && nd->dentry &&  
  57. 1176                                 nd->dentry->d_inode))  
  58. 1177                 audit_inode(name, nd->dentry);  
  59. 1178 out_fail:  
  60. 1179         return retval;  
  61. 1180   
  62. 1181 fput_fail:  
  63. 1182         fput_light(file, fput_needed);  
  64. 1183         goto out_fail;  
  65. 1184 }  

这个函数有一点长,但是逻辑很清楚,即为调用path_walk做准备,在进入do_path_lookup函数时,参数@nd不包含任何有用信息,而调用path_walk时,@nd则包含了查找起始点信息

因此从1127~1171行就是准备查找起始点的过程,这里分为三种情况:

1. 1131~1143 文件名包含绝对路径,因此我们优先使用文件系统的根目录作为查找起始点

2. 1144 ~ 1148 路径不是绝对路径,我们指定从当前目录开始开始查找

3. 1149 ~ 1171 函数第一个参数@dfd是一个目录文件描述符,我们就从这个目录开始查找。

1173 一切就绪,调用path_walk开始查找。


path_walk

[html]  view plain copy
  1. 1042 static int fastcall path_walk(const char * name, struct nameidata *nd)  
  2. 1043 {  
  3. 1044         current->total_link_count = 0;  
  4. 1045         return link_path_walk(name, nd);  
  5. 1046 }  

符号链接是需要特殊处理的,一般情况下我们都会跟踪链接,如果没有符号链接,那么文件系统的必定是一个完美的树结构,符号链接使得这棵树不那么完美,有时可能会导致树内存在循环,所以路径查询时,会对跟进符号链接的数目有一个最大限制,2.6.24硬编码为40

1044 在开始一次新查找之前,我们初始化为0


link_path_walk

[html]  view plain copy
  1. 1018 static int fastcall link_path_walk(const char *name, struct nameidata *nd)  
  2. 1019 {  
  3. 1020         struct nameidata save = *nd;  
  4. 1021         int result;  
  5. 1022   
  6. 1023         /* make sure the stuff we saved doesn't go away */  
  7. 1024         dget(save.dentry);  
  8. 1025         mntget(save.mnt);  
  9. 1026   
  10. 1027         result = __link_path_walk(name, nd);  
  11. 1028         if (result == -ESTALE) {  
  12. 1029                 *nd = save;  
  13. 1030                 dget(nd->dentry);  
  14. 1031                 mntget(nd->mnt);  
  15. 1032                 nd->flags |= LOOKUP_REVAL;  
  16. 1033                 result = __link_path_walk(name, nd);  
  17. 1034         }  
  18. 1035   
  19. 1036         dput(save.dentry);  
  20. 1037         mntput(save.mnt);  
  21. 1038   
  22. 1039         return result;  
  23. 1040 }  

这个函数看起来也有点烦,主要是有的函数会返回ESTALE错误,此时需要重新执行路径查找,并且不要使用dcache。

我们这里不care这种特殊情况,因此只需要考虑__link_path_walk


__link_path_walk

这个函数接近200行,超出了我的大脑空间处理能力,因此分段阅读

[html]  view plain copy
  1. 826 static fastcall int __link_path_walk(const char * name, struct nameidata *nd)  
  2. 827 {  
  3. 828         struct path next;  
  4. 829         struct inode *inode;  
  5. 830         int err;  
  6. 831         unsigned int lookup_flags = nd->flags;  
  7. 832   
  8. 833         while (*name=='/')  
  9. 834                 name++;  
  10. 835         if (!*name)  
  11. 836                 goto return_reval;  
  12. 837   
  13. 838         inode = nd->dentry->d_inode;  
  14. 839         if (nd->depth)  
  15. 840                 lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);  

833~836首先处理掉路径名前的/,

紧接着是一个大的循环,逐个路径分量进行处理。@name在循环内分解为各个路径分量。每个分量表示一个路径名。下图给出了代码流程图

权限检查

[html]  view plain copy
  1. 848                 nd->flags |= LOOKUP_CONTINUE;  
  2. 849                 err = exec_permission_lite(inode, nd);  
  3. 850                 if (err == -EAGAIN)  
  4. 851                         err = vfs_permission(nd, MAY_EXEC);  
  5. 852                 if (err)  
  6. 853                         break;  

计算路径分量hash

[html]  view plain copy
  1. 855                 this.name = name;  
  2. 856                 c = *(const unsigned char *)name;  
  3. 857   
  4. 858                 hash = init_name_hash();  
  5. 859                 do {  
  6. 860                         name++;  
  7. 861                         hash = partial_name_hash(c, hash);  
  8. 862                         c = *(const unsigned char *)name;  
  9. 863                 } while (c && (c != '/'));  
  10. 864                 this.len = name - (const char *) this.name;  
  11. 865                 this.hash = end_name_hash(hash);  


处理.和..

[html]  view plain copy
  1. 874                 /*  
  2. 875                  * "." and ".." are special - ".." especially so because it has  
  3. 876                  * to be able to know about the current root directory and  
  4. 877                  * parent relationships.  
  5. 878                  */  
  6. 879                 if (this.name[0] == '.') switch (this.len) {  
  7. 880                         default:  
  8. 881                                 break;  
  9. 882                         case 2:  
  10. 883                                 if (this.name[1] != '.')  
  11. 884                                         break;  
  12. 885                                 follow_dotdot(nd);  
  13. 886                                 inode = nd->dentry->d_inode;  
  14. 887                                 /* fallthrough */  
  15. 888                         case 1:  
  16. 889                                 continue;  
  17. 890                 }  

“.”表示当前路径,那么我们只需略过这个路径分量,开始处理下一个路径分量即可。

".."表示回到父目录,调用follow_dotdot即可,follow_dotdot并不像看起来那么简单,因为还要考虑安装点的存在


重新计算hash

[html]  view plain copy
  1. 891                 /*  
  2. 892                  * See if the low-level filesystem might want  
  3. 893                  * to use its own hash..  
  4. 894                  */  
  5. 895                 if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {  
  6. 896                         err = nd->dentry->d_op->d_hash(nd->dentry, &this);  
  7. 897                         if (err < 0)  
  8. 898                                 break;  
  9. 899                 }  

有些文件系统有他们自己的hash计算函数,比如fat文件系统并不区分大小写字母,因此hash函数需要做相应更改。

do_lookup

[html]  view plain copy
  1. 900                 /* This does the actual lookups.. */  
  2. 901                 err = do_lookup(nd, &this, &next);  
  3. 902                 if (err)  
  4. 903                         break;  

这个函数后面会重点介绍


处理符号链接

[html]  view plain copy
  1. 913                 if (inode->i_op->follow_link) {  
  2. 914                         err = do_follow_link(&next, nd);  
  3. 915                         if (err)  
  4. 916                                 goto return_err;  
  5. 917                         err = -ENOENT;  
  6. 918                         inode = nd->dentry->d_inode;  
  7. 919                         if (!inode)  
  8. 920                                 break;  
  9. 921                         err = -ENOTDIR;  
  10. 922                         if (!inode->i_op)  
  11. 923                                 break;  
  12. 924                 } else  
  13. 925                         path_to_nameidata(&next, nd);  

当inode->i_op->follow_link不为空时,说明这个inode对应的文件是一个符号链接文件,否则必定为空

do_follow_link用来处理符号链接的情况

924~925 不为符号链接,那么把next的结果赋给返回值@nd


do_lookup

[html]  view plain copy
  1. 779 /*  
  2. 780  *  It's more convoluted than I'd like it to be, but... it's still fairly  
  3. 781  *  small and for now I'd prefer to have fast path as straight as possible.  
  4. 782  *  It _is_ time-critical.  
  5. 783  */  
  6. 784 static int do_lookup(struct nameidata *nd, struct qstr *name,  
  7. 785                      struct path *path)  
  8. 786 {  
  9. 787         struct vfsmount *mnt = nd->mnt;  
  10. 788         struct dentry *dentry = __d_lookup(nd->dentry, name);  
  11. 789   
  12. 790         if (!dentry)  
  13. 791                 goto need_lookup;  
  14. 792         if (dentry->d_op && dentry->d_op->d_revalidate)  
  15. 793                 goto need_revalidate;  
  16. 794 done:  
  17. 795         path->mnt = mnt;  
  18. 796         path->dentry = dentry;  
  19. 797         __follow_mount(path);  
  20. 798         return 0;  
  21. 799   
  22. 800 need_lookup:  
  23. 801         dentry = real_lookup(nd->dentry, name, nd);  
  24. 802         if (IS_ERR(dentry))  
  25. 803                 goto fail;  
  26. 804         goto done;  
  27. 805   
  28. 806 need_revalidate:  
  29. 807         dentry = do_revalidate(dentry, nd);  
  30. 808         if (!dentry)  
  31. 809                 goto need_lookup;  
  32. 810         if (IS_ERR(dentry))  
  33. 811                 goto fail;  
  34. 812         goto done;  
  35. 813   
  36. 814 fail:  
  37. 815         return PTR_ERR(dentry);  
  38. 816 }  

@nd 是输入参数,这个结构指定了查找的父目录项以及它所在的vfsmount

@name 输入参数,指定了路径分量名称。

@path 输出参数,保存查找结果。

788 在dentry cache中根据父dentry和路径分量名称查找,如果找到则在标号done中对path赋值,__follow_mount处理装载点的情况

790 如果没有在dentry cache中找到,那么就要调用底层文件系统的lookup进行查找了,real_lookup会调用底层lookup函数

792 如果存在dentry->d_op->d_revalidate,那么dentry cache中的目录项不一定是最新的,VFS并没有实现这个函数,但是为底层文件系统提供了这个接口,比如NFS文件系统,可能会导致本地dentry cache和远程文件内容不同步,我们不care这种情况。


__follow_mount

follow mount有两个版本,__follow_mount和follow_mount,差别不大。分析一个,另外一个其义自现

[html]  view plain copy
  1. 689 /* no need for dcache_lock, as serialization is taken care in  
  2. 690  * namespace.c  
  3. 691  */  
  4. 692 static int __follow_mount(struct path *path)  
  5. 693 {  
  6. 694         int res = 0;  
  7. 695         while (d_mountpoint(path->dentry)) {  
  8. 696                 struct vfsmount *mounted = lookup_mnt(path->mnt, path->dentry);  
  9. 697                 if (!mounted)  
  10. 698                         break;  
  11. 699                 dput(path->dentry);  
  12. 700                 if (res)  
  13. 701                         mntput(path->mnt);  
  14. 702                 path->mnt = mounted;  
  15. 703                 path->dentry = dget(mounted->mnt_root);  
  16. 704                 res = 1;  
  17. 705         }  
  18. 706         return res;  
  19. 707 }  

这个函数代码非常的简单,复杂在于其中隐含的概念。不过很难讲清楚,有点 只可意会,不可言传的意思。

我们知道,lookup的查找路径上可能存在着安装点,举个例子:

/mnt/sdcard/sd1是一个路径,有一个sd卡内部有文件file1 file2等,我们把一个sd卡mount到/mnt/sdcard/sd1/上。

此时我们的lookup查找/mnt/sdcard/sd1/file1,当我们查找到/mnt/sdcard/sd1时,我们得到的是根文件系统的vfsmount以及/mnt/sdcard/sd1的dentry,但是如果我们想要继续找到file1,那么我们一定要转为

sd1的vfsmount以及sd1的根dentry,才能继续查找file1.

ok, __follow_mount就是干的这个事情。循环是因为在sd1 mount到/mnt/sdcard/sd1之前,已经有其他设备也mount到/mnt/sdcard/sd1上了。

从这里,我们看到了 vfsmount的中要性了,因为dentry并不能唯一确定一个目录项,必须要由vfsmount和dentry二者共同确定,缺一不可。


do_follow_link

symlink和hardlink这小哥俩给文件系统引入了许多复杂性,hardlink对文件路径查找没有影响,symlink引入了一些麻烦

[html]  view plain copy
  1. 637 /*  
  2. 638  * This limits recursive symlink follows to 8, while  
  3. 639  * limiting consecutive symlinks to 40.  
  4. 640  *  
  5. 641  * Without that kind of total limit, nasty chains of consecutive  
  6. 642  * symlinks can cause almost arbitrarily long lookups.   
  7. 643  */  
  8. 644 static inline int do_follow_link(struct path *path, struct nameidata *nd)  
  9. 645 {  
  10. 646         int err = -ELOOP;  
  11. 647         if (current->link_count >= MAX_NESTED_LINKS)  
  12. 648                 goto loop;  
  13. 649         if (current->total_link_count >= 40)  
  14. 650                 goto loop;  
  15. 651         BUG_ON(nd->depth >= MAX_NESTED_LINKS);  
  16. 652         cond_resched();  
  17. 653         err = security_inode_follow_link(path->dentry, nd);  
  18. 654         if (err)  
  19. 655                 goto loop;  
  20. 656         current->link_count++;  
  21. 657         current->total_link_count++;  
  22. 658         nd->depth++;  
  23. 659         err = __do_follow_link(path, nd);  
  24. 660         current->link_count--;  
  25. 661         nd->depth--;  
  26. 662         return err;  
  27. 663 loop:  
  28. 664         dput_path(path, nd);  
  29. 665         path_release(nd);  
  30. 666         return err;  
  31. 667 }  

在路径查找中,symlink文件本身,并不是查找的目录,查找目标是它所表示的文件路径。

为了防止路径死循环,以及那些很nasty的情况(nasty这个词只可意会,不可言传啊),linux允许最大递归次数是8次,最大连接数是40个

发生递归是656行还有可能调用到do_follow_link,因此如果递归次数超过8次,647就会返回ELOOP

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值