首页 > 代码库 > Linux文件系统(七)---系统调用之open操作(三) 之 open_namei函数
Linux文件系统(七)---系统调用之open操作(三) 之 open_namei函数
Open系统调用
下面看看open_namei函数:
这个函数的基本的功能是:
首先我们知道filename,也就是知道路径了,那么我们可以根据上级目录项对象,查询下一级的目录项对象,如果在目录项缓存找到下一级的目录项对象,则直接返回,并填充nd的挂载点对象和目录项对象。否则,构建一个子目录项对象,并分配一个新的inode结构,将子目录项对象和inode结构相关联。这样,一直循环到最后一个路径分量。最后返回的是最后一个路径分量的目录项对象和挂载点对象。
分成两点:
第一:如果单纯是打开一个已有的文件,那么直接跟了lookup函数(细节:注意如果这个名字分量打开的是“符号链接,那么需要二次查找实际的位置,下面会说到”)找到打开即可
第二:如果是需要创建一个新的文件,那么需要得到父目录的目录项对象和挂载点,然后再执行创建过程
细节:对于我们的输入路径,可以是绝对路径(从‘/‘开始),也可以是相对路径,那么下面的处理也会根据是绝对路径还是相对路径进行不同的处理。另外一些查找的细节后面再说。
总之,下面的函数执行完成之后,就会得到
1006 /* 1007 * open_namei() 1008 * 1009 * namei for open - this is in fact almost the whole open-routine. 1010 * 1011 * Note that the low bits of "flag" aren't the same as in the open 1012 * system call - they are 00 - no permissions needed 1013 * 01 - read permission needed 1014 * 10 - write permission needed 1015 * 11 - read/write permissions needed 1016 * which is a lot more logical, and also allows the "no perm" needed 1017 * for symlinks (where the permissions are checked later). 1018 * SMP-safe 1019 */ 1020 int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd) 1021 { 1022 int acc_mode, error = 0; 1023 struct inode *inode; 1024 struct dentry *dentry; 1025 struct vfsmount *mnt; 1026 struct dentry *dir; 1027 int count = 0; 1028 1029 acc_mode = ACC_MODE(flag); 1030 1031 /* 1032 * The simplest case - just a plain lookup. 1033 */ 1034 if (!(flag & O_CREAT)) { /* 如果不是创建新的文件,那么仅仅根据提供的路径去寻找文件即可 */ 1035 error = path_lookup(pathname, lookup_flags(flag), nd); /* 寻找文件函数 */ 1036 if (error) 1037 return error; 1038 dentry = nd->dentry; /* 目录项:这个目录项就是之前path_lookup找到的路径最后一个分量也就是具体文件的对应的目录项对象 */ 1039 goto ok; 1040 } 1041 1042 /* 1043 * Create - we need to know the parent.(需要创建文件时候,需要知道parent目录,所以要先找parent) 1044 */ 1045 error = path_lookup(pathname, LOOKUP_PARENT, nd); /* nd中的dentry是父目录的目录项对象,last_type是最后一个分量也就是需要创建的文件的类型 */ 1046 if (error) 1047 return error; 1048 1049 /* 1050 * We have the parent and last component. First of all, check 1051 * that we are not asked to creat(2) an obvious directory - that 1052 * will not do. 1053 */ 1054 error = -EISDIR; /* 错误的类型(不是正常文件)不给创建文件 */ 1055 if (nd->last_type != LAST_NORM || nd->last.name[nd->last.len]) 1056 goto exit; 1057 1058 dir = nd->dentry; /* 获得父目录的目录项 */ 1059 down(&dir->d_inode->i_sem); 1060 dentry = lookup_hash(&nd->last, nd->dentry); /* 在父目录下找这个last-name名字的dentry是不是存在,如果不存在需要创建一个新的目录项 */ 1061 1062 do_last: 1063 error = PTR_ERR(dentry); 1064 if (IS_ERR(dentry)) { 1065 up(&dir->d_inode->i_sem); 1066 goto exit; 1067 } 1068 1069 /* Negative dentry, just create the file */ 1070 if (!dentry->d_inode) { /* 目录项是之前创建的,如果d_inode不存在,那么说明文件时不存在的,那么直接需要创建一个新的文件 */ 1071 error = vfs_create(dir->d_inode, dentry, /* 创建这个文件对应的inode */ 1072 mode & ~current->fs->umask); 1073 up(&dir->d_inode->i_sem); 1074 dput(nd->dentry); 1075 nd->dentry = dentry; 1076 if (error) 1077 goto exit; 1078 /* Don't check for write permission, don't truncate */ 1079 acc_mode = 0; 1080 flag &= ~O_TRUNC; 1081 goto ok; /* 文件创建完成 */ 1082 } 1083 1084 /* 1085 * It already exists.:这个说明上面找到的对应的文件,也就是说存在同名文件 1086 */ 1087 up(&dir->d_inode->i_sem); 1088 1089 error = -EEXIST; 1090 if (flag & O_EXCL) 1091 goto exit_dput; 1092 /* 找到最底层的文件系统,没有挂在其他文件系统 */ 1093 if (d_mountpoint(dentry)) { 1094 error = -ELOOP; 1095 if (flag & O_NOFOLLOW) 1096 goto exit_dput; 1097 while (__follow_down(&nd->mnt,&dentry) && d_mountpoint(dentry)); 1098 } 1099 error = -ENOENT; 1100 if (!dentry->d_inode) 1101 goto exit_dput; 1102 if (dentry->d_inode->i_op && dentry->d_inode->i_op->follow_link) 1103 goto do_link; /* 做符号链接 */ 1104 1105 dput(nd->dentry); 1106 nd->dentry = dentry; 1107 error = -EISDIR; 1108 if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) 1109 goto exit; 1110 ok: 1111 error = -ENOENT; 1112 inode = dentry->d_inode; /* 文件inode */ 1113 if (!inode) 1114 goto exit; 1115 1116 error = -ELOOP; 1117 if (S_ISLNK(inode->i_mode)) 1118 goto exit; 1119 1120 error = -EISDIR; 1121 if (S_ISDIR(inode->i_mode) && (flag & FMODE_WRITE)) 1122 goto exit; 1123 1124 error = permission(inode,acc_mode); 1125 if (error) 1126 goto exit; 1127 1128 /* 下面处理一些特殊的文件FIFO,socket文件,设备文件 1129 * FIFO's, sockets and device files are special: they don't 1130 * actually live on the filesystem itself, and as such you 1131 * can write to them even if the filesystem is read-only. 1132 */ 1133 if (S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) { 1134 flag &= ~O_TRUNC; 1135 } else if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode)) { 1136 error = -EACCES; 1137 if (nd->mnt->mnt_flags & MNT_NODEV) 1138 goto exit; 1139 1140 flag &= ~O_TRUNC; 1141 } else { 1142 error = -EROFS; 1143 if (IS_RDONLY(inode) && (flag & 2)) 1144 goto exit; 1145 } 1146 /* 1147 * An append-only file must be opened in append mode for writing. 1148 */ 1149 error = -EPERM; /* 追加模式 */ 1150 if (IS_APPEND(inode)) { 1151 if ((flag & FMODE_WRITE) && !(flag & O_APPEND)) 1152 goto exit; 1153 if (flag & O_TRUNC) 1154 goto exit; 1155 } 1156 1157 /* 1158 * Ensure there are no outstanding leases on the file. 1159 */ 1160 error = get_lease(inode, flag); 1161 if (error) 1162 goto exit; 1163 1164 if (flag & O_TRUNC) { 1165 error = get_write_access(inode); /* 写权限 */ 1166 if (error) 1167 goto exit; 1168 1169 /* 1170 * Refuse to truncate files with mandatory locks held on them. 1171 */ 1172 error = locks_verify_locked(inode); 1173 if (!error) { 1174 DQUOT_INIT(inode); 1175 1176 error = do_truncate(dentry, 0); /* 执行截断操作 */ 1177 } 1178 put_write_access(inode); /* 释放写权限 */ 1179 if (error) 1180 goto exit; 1181 } else 1182 if (flag & FMODE_WRITE) 1183 DQUOT_INIT(inode); 1184 1185 return 0; 1186 1187 exit_dput: 1188 dput(dentry); 1189 exit: 1190 path_release(nd); 1191 return error; 1192 1193 do_link: 1194 error = -ELOOP; 1195 if (flag & O_NOFOLLOW) 1196 goto exit_dput; 1197 /* 1198 * This is subtle. Instead of calling do_follow_link() we do the 1199 * thing by hands. The reason is that this way we have zero link_count 1200 * and path_walk() (called from ->follow_link) honoring LOOKUP_PARENT. 1201 * After that we have the parent and last component, i.e. 1202 * we are in the same situation as after the first path_walk(). 1203 * Well, almost - if the last component is normal we get its copy 1204 * stored in nd->last.name and we will have to putname() it when we 1205 * are done. Procfs-like symlinks just set LAST_BIND. 1206 */ 1207 UPDATE_ATIME(dentry->d_inode); 1208 mnt = mntget(nd->mnt); 1209 error = dentry->d_inode->i_op->follow_link(dentry, nd); 1210 dput(dentry); 1211 mntput(mnt); 1212 if (error) 1213 return error; 1214 if (nd->last_type == LAST_BIND) { 1215 dentry = nd->dentry; 1216 goto ok; 1217 } 1218 error = -EISDIR; 1219 if (nd->last_type != LAST_NORM) 1220 goto exit; 1221 if (nd->last.name[nd->last.len]) { 1222 putname(nd->last.name); 1223 goto exit; 1224 } 1225 error = -ELOOP; 1226 if (count++==32) { 1227 putname(nd->last.name); 1228 goto exit; 1229 } 1230 dir = nd->dentry; 1231 down(&dir->d_inode->i_sem); 1232 dentry = lookup_hash(&nd->last, nd->dentry); 1233 putname(nd->last.name); 1234 goto do_last; 1235 }
看一下path_lookup函数:
755 /* SMP-safe */ 756 int fastcall path_lookup(const char *path, unsigned flags, struct nameidata *nd) 757 { 758 int error = 0; 759 if (path_init(path, flags, nd)) /* 这个函数获得当前的目录项对象和安装点,同时在绝对路径情况下回探测能不能到达最后一个分量,如果可以,那么下面的path_walk就可以不需要执行了,如果失败了,那么需要执行下面的path_walk进行循环找到最后一个路径分量的目录项好安装点 */ 760 error = path_walk(path, nd); /* 找到最后一个名字路径分量的目录项对象和挂载点 */ 761 return error; 762 }
这个函数涉及到path_init和path_walk函数,下面分别看一下:
path_init函数主要是获取最初的开目录项(路径开始点)
765 /* SMP-safe */ 766 int fastcall path_init(const char *name, unsigned int flags, struct nameidata *nd) 767 { 768 nd->last_type = LAST_ROOT; /* if there are only slashes... */ 769 nd->flags = flags; 770 if (*name=='/') /* 如果路径第一个字符是'/',那么说明是绝对路径,从根目录开始搜索的路径 */ 771 return walk_init_root(name,nd); /* 执行这个函数,下面会分析 */ 772 read_lock(¤t->fs->lock); /* 如果不是绝对路径,那么就是相对路径了,那么直接获得当前的进程执行目录项即可 */ 773 nd->mnt = mntget(current->fs->pwdmnt); /* 如果需要打开的不是根目录,那么找到当前进程执行目录安装点 */ 774 nd->dentry = dget(current->fs->pwd); /* 找到进程当前执行目录 */ 775 read_unlock(¤t->fs->lock); 776 return 1; /* 这个地方仅仅是得到了当前的目录项对象,我们需要得到的是最后一个路径分量的对象,所以返回之后还需要walk_path,return 1 */ 777 }
如果是根目录的话,看一下这个函数walk_init_root:
736 /* SMP-safe */ 737 static inline int 738 walk_init_root(const char *name, struct nameidata *nd) 739 { 740 read_lock(¤t->fs->lock); 741 if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) { /* 如果用户有自己设置替换目录,那么使用这个 & 允许使用替换目录 */ 742 nd->mnt = mntget(current->fs->altrootmnt); 743 nd->dentry = dget(current->fs->altroot); 744 read_unlock(¤t->fs->lock); 745 if (__emul_lookup_dentry(name,nd)) /* 这个函数里面其实就是封装了walk_path进行路径的寻找,如果返回1,那么说明找到了最后一个了 */ 746 return 0; /* 既然找到了,说明nd被填充好了,那么后期返回的时候无需再寻找了~~~~~~~ */ 747 read_lock(¤t->fs->lock); 748 }/* 这下面仅仅是保存了当前的目录,还没有循环到最后一个目录分量,所以,返回后需要继续walk_path,所以return 1 */ 749 nd->mnt = mntget(current->fs->rootmnt); /* 如果没有设置替换目录,那么使用进程根目录即可 */ 750 nd->dentry = dget(current->fs->root); 751 read_unlock(¤t->fs->lock); 752 return 1; 753 }
668 int fastcall path_walk(const char * name, struct nameidata *nd) 669 { 670 current->total_link_count = 0; /* 初始化符号链接总数 */ 671 return link_path_walk(name, nd); /* 调用实际文件系统的路径分解函数 */ 672 }
注意这个link_path_walk函数主要是根据给定的路径,找到最后一个路径分量的目录项对象和安装点~
这个函数主要思路:打开一个文件,如果是仅仅打开,那么沿着路径分量不断往下找,最后返回最后一个名字分量的目录项对象就OK,如果是创建文件,那么返回的是倒数第二个分量也就是需要创建的文件的父目录的目录项对象。
细节:
第一:如果是打开的路径不是符号链接路径,那么按照路径(每个路径分量)层层去找到,直到找到最后一个路径分量代表的目录项对象,也就说最终返回的就是最后一个分量的目录项对象和挂载点。
第二:存在有些目录项对象对应的是符号链接,遇到这种情况,需要二次寻找这个真实的的路径分量,然后再继续往下找
450 /* 451 * Name resolution. 452 * 453 * This is the basic name resolution function, turning a pathname 454 * into the final dentry. 455 * 456 * We expect 'base' to be positive and a directory. 457 */ 458 int fastcall link_path_walk(const char * name, struct nameidata *nd) 459 { 460 struct dentry *dentry; 461 struct inode *inode; 462 int err; 463 unsigned int lookup_flags = nd->flags; 464 /* 从根目录之后的目录项开始 */ 465 while (*name=='/') 466 name++; 467 if (!*name) 468 goto return_reval; 469 /* 获得之前保存的开始位置的目录项中对象的inode节点 */ 470 inode = nd->dentry->d_inode; 471 if (current->link_count) 472 lookup_flags = LOOKUP_FOLLOW; 473 474 /* At this point we know we have a real path component. */ 475 for(;;) { /* 注意下面是一个词一个词的处理,例如home/tt/desktoop/xxx.txt,那么先处理home,在处理tt,在处理。。。 */ 476 unsigned long hash; 477 struct qstr this; 478 unsigned int c; 479 /* 检测权限问题 */ 480 err = permission(inode, MAY_EXEC); 481 dentry = ERR_PTR(err); 482 if (err) 483 break; 484 485 this.name = name; 486 c = *(const unsigned char *)name; 487 488 hash = init_name_hash(); /* 默认=0 */ 489 do { 490 name++; /* 指向下一个字符,做准备 */ 491 hash = partial_name_hash(c, hash); /* 计算部分hash值,例如上面home的hash值 */ 492 c = *(const unsigned char *)name; 493 } while (c && (c != '/')); 494 this.len = name - (const char *) this.name; /* 中间一个名字分量长度 */ 495 this.hash = end_name_hash(hash); /* 这个分量的hash值 */ 496 <span> </span> 497 /* remove trailing slashes? */ 498 if (!c) /* 如果已经是最后一个名字分量 */ 499 goto last_component; 500 while (*++name == '/'); 501 if (!*name) 502 goto last_with_slashes; 503 504 /* 505 * "." and ".." are special - ".." especially so because it has 506 * to be able to know about the current root directory and 507 * parent relationships. 508 *//* 注意下面是处理当前目录'.'和父目录'..' */ 509 if (this.name[0] == '.') switch (this.len) { 510 default: 511 break; 512 case 2: /* 注意这个地方存在两种情况,第一种是父目录,第二种是隐藏文件,前面也是'.'开始 */ 513 if (this.name[1] != '.') /* 这种情况就是隐藏文件 */ 514 break; 515 follow_dotdot(nd); /* 否则,这样情况就是父目录情况,需要回溯到父目录 '..' */ 516 inode = nd->dentry->d_inode; 517 /* fallthrough */ 518 case 1: /* 当前目录开始 */ 519 continue; 520 } 521 /* 522 * See if the low-level filesystem might want 523 * to use its own hash.. 524 */ /* 使用底层文件系统自己的计算hash值函数 */ 525 if (nd->dentry->d_op && nd->dentry->d_op->d_hash) { 526 err = nd->dentry->d_op->d_hash(nd->dentry, &this); 527 if (err < 0) 528 break; 529 } 530 /* This does the actual lookups.. */ 531 dentry = cached_lookup(nd->dentry, &this, LOOKUP_CONTINUE); /* 注意这个函数是在dentry的缓冲区去寻找是不是存在这个dentry缓存 */ 532 if (!dentry) { /* 这个在前面的dentry缓冲具体说过 */ 533 dentry = real_lookup(nd->dentry, &this, LOOKUP_CONTINUE); /* 如果在目录项缓存中不存在,那么,需要分配一个新的dentry缓冲区加入到里面,这个在前面也说过,同样的链接:http://blog.csdn.net/shanshanpt/article/details/39829281 */ 534 err = PTR_ERR(dentry); 535 if (IS_ERR(dentry)) 536 break; 537 } 538 /* Check mountpoints.. */ /* 检测挂载点然后继续向下寻找 */ 539 while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry)) 540 ; 541 <span> </span> 542 err = -ENOENT; 543 inode = dentry->d_inode; /* 获得inode */ 544 if (!inode) 545 goto out_dput; 546 err = -ENOTDIR; 547 if (!inode->i_op) 548 goto out_dput; 549 /* 如果当前解析的分量指向的是一个符号链接,那么转到处理符号链接 */ 550 if (inode->i_op->follow_link) { 551 struct vfsmount *mnt = mntget(nd->mnt); 552 err = do_follow_link(dentry, nd); /* 处理符号链接函数 */ 553 dput(dentry); 554 mntput(mnt); 555 if (err) 556 goto return_err; 557 err = -ENOENT; 558 inode = nd->dentry->d_inode; /* 获得真实inode */ 559 if (!inode) 560 break; 561 err = -ENOTDIR; 562 if (!inode->i_op) 563 break; 564 } else { 565 dput(nd->dentry); 566 nd->dentry = dentry; /* 新的dentry赋值给nd的dentry,然后回到上面继续下一个分量处理 */ 567 } 568 err = -ENOTDIR; 569 if (!inode->i_op->lookup) 570 break; 571 continue; 572 /* here ends the main loop */ 573 574 last_with_slashes: 575 lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY; 576 last_component: /* 对于路径最后一个名字分量的处理 */ 577 if (lookup_flags & LOOKUP_PARENT) /* 如果是创建文件,那么是存在这个标识 */ 578 goto lookup_parent; 579 if (this.name[0] == '.') switch (this.len) { /* 关于'.'和'..'情况和上面说的一样 */ 580 default: 581 break; 582 case 2: 583 if (this.name[1] != '.') 584 break; 585 follow_dotdot(nd); 586 inode = nd->dentry->d_inode; 587 /* fallthrough */ 588 case 1: 589 goto return_reval; 590 }/* 底层自己的hash函数 */ 591 if (nd->dentry->d_op && nd->dentry->d_op->d_hash) { 592 err = nd->dentry->d_op->d_hash(nd->dentry, &this); 593 if (err < 0) 594 break; 595 } 596 dentry = cached_lookup(nd->dentry, &this, nd->flags); /* 同样是在dentry缓存中中寻找dentry */ 597 if (!dentry) { 598 dentry = real_lookup(nd->dentry, &this, nd->flags); /* 没有dentry换成就会创建一个新的dentry缓存 */ 599 err = PTR_ERR(dentry); 600 if (IS_ERR(dentry)) 601 break; 602 } 603 while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry)) /* 检测挂载点,继续寻找 */ 604 ; 605 inode = dentry->d_inode; 606 if ((lookup_flags & LOOKUP_FOLLOW) /* 处理符号链接 */ 607 && inode && inode->i_op && inode->i_op->follow_link) { 608 struct vfsmount *mnt = mntget(nd->mnt); 609 err = do_follow_link(dentry, nd); /* 二次寻找~~~ */ 610 dput(dentry); 611 mntput(mnt); 612 if (err) 613 goto return_err; 614 inode = nd->dentry->d_inode; 615 } else { 616 dput(nd->dentry); 617 nd->dentry = dentry; 618 } 619 err = -ENOENT; 620 if (!inode) /* 没有存在的inode,那么另外处理 */ 621 goto no_inode; 622 if (lookup_flags & LOOKUP_DIRECTORY) { /* 如果打开的文件时文件夹,那么error */ 623 err = -ENOTDIR; 624 if (!inode->i_op || !inode->i_op->lookup) 625 break; 626 } 627 goto return_base; 628 no_inode: 629 err = -ENOENT; 630 if (lookup_flags & (LOOKUP_POSITIVE|LOOKUP_DIRECTORY)) 631 break; 632 goto return_base; 633 lookup_parent: /* 如果是创建文件,那么需要先找到父目录,在处理!需要注意的是,父目录就是之前访问的那个目录,所以在这个地方,没有必要将nd中的dentry换成现在孩子的dentry,想换也没有啊,后来需要创建的,所以这个时候nd中保存的就已经是父目录的dentry了~ */ 634 nd->last = this; /* 最后一个分量名 */ 635 nd->last_type = LAST_NORM; /* 最后一个分量类型 */ 636 if (this.name[0] != '.') /* 不是'.'说明是正常的文件 */ 637 goto return_base; 638 if (this.len == 1) /* 否则是'.'情况 */ 639 nd->last_type = LAST_DOT; 640 else if (this.len == 2 && this.name[1] == '.') /* '..'情况 */ 641 nd->last_type = LAST_DOTDOT; 642 else 643 goto return_base; 644 return_reval: 645 /* 646 * We bypassed the ordinary revalidation routines. 647 * Check the cached dentry for staleness. 648 */ 649 dentry = nd->dentry; 650 if (dentry && dentry->d_op && dentry->d_op->d_revalidate) { 651 err = -ESTALE; 652 if (!dentry->d_op->d_revalidate(dentry, 0)) { 653 d_invalidate(dentry); 654 break; 655 } 656 } 657 return_base: 658 return 0; 659 out_dput: 660 dput(dentry); 661 break; 662 } 663 path_release(nd); 664 return_err: 665 return err; 666 }
注意这里面有一个函数叫follow_dotdot,这个函数是回溯到父目录的函数
412 413 static inline void follow_dotdot(struct nameidata *nd) 414 { 415 while(1) { 416 struct vfsmount *parent; 417 struct dentry *dentry; 418 read_lock(¤t->fs->lock); 419 if (nd->dentry == current->fs->root && /* 如果回溯到的是进程的根目录。不允许 */ 420 nd->mnt == current->fs->rootmnt) { 421 read_unlock(¤t->fs->lock); 422 break; 423 } 424 read_unlock(¤t->fs->lock); 425 spin_lock(&dcache_lock); 426 if (nd->dentry != nd->mnt->mnt_root) { /*如果目录项对象不是根目录,则返回上一级目录项对象*/ 427 dentry = dget(nd->dentry->d_parent); /* 得到parent目录项 */ 428 spin_unlock(&dcache_lock); 429 dput(nd->dentry); 430 nd->dentry = dentry; 431 break; 432 } 433 parent=nd->mnt->mnt_parent; /* 否则得到挂载点parent */ 434 if (parent == nd->mnt) { 435 spin_unlock(&dcache_lock); 436 break; 437 } 438 mntget(parent); 439 dentry=dget(nd->mnt->mnt_mountpoint); 440 spin_unlock(&dcache_lock); 441 dput(nd->dentry); 442 nd->dentry = dentry; 443 mntput(nd->mnt); 444 nd->mnt = parent; 445 }/* 下面是一直回溯到没有挂载其它文件系统的挂载点,mnt指向这个最底层的挂载点 */ 446 while (d_mountpoint(nd->dentry) && __follow_down(&nd->mnt, &nd->dentry)) 447 ; 448 }
/************至此,open_namei函数完成***************************************************************************************/
Linux文件系统(七)---系统调用之open操作(三) 之 open_namei函数