Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1177139
  • 博文数量: 573
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 66
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-28 16:21
文章分类

全部博文(573)

文章存档

2018年(3)

2016年(48)

2015年(522)

分类: LINUX

2015-12-04 16:09:35

Linux内核源码阅读之打开文件篇

Linux中打开文件是通过open系统调用实现,其函数中调用了do_sys_open()函数完成打开功能,所以下面主要分析do_sys_open()函数,首先先看下open系统调用的入口函数,再具体看do_sys_open()函数:

  1. SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)  
  2. {  
  3.     long ret;  
  4.   
  5.     if (force_o_largefile())  
  6.         flags |= O_LARGEFILE;  
  7.   
  8.     ret = do_sys_open(AT_FDCWD, filename, flags, mode);  
  9.     /* avoid REGPARM breakage on x86: */  
  10.     asmlinkage_protect(3, ret, filename, flags, mode);  
  11.     return ret;  
  12. }  
  13.    
  14.    
  15. long do_sys_open(int dfd, const char __user *filename, int flags, int mode)  
  16. {  
  17.     /*获取文件名称,由getname()函数完成,其内部首先创建存取文件名称的空间,然后*从用户空间把文件名拷贝过来*/  
  18.     char *tmp = getname(filename);  
  19.     int fd = PTR_ERR(tmp);  
  20.    
  21.     if (!IS_ERR(tmp)) {  
  22.     /*获取一个可用的fd,此函数调用alloc_fd()函数从fd_table中获取一个可用fd,并做些简单初始化,此函数内部实现比较简单,此次分析不细看*/  
  23.         fd = get_unused_fd_flags(flags);  
  24.         if (fd >= 0) {  
  25.             /*fd获取成功则开始打开文件,此函数是主要完成打开功能的函数,在此先放一放,下面详细分析*/  
  26.             struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);  
  27.             if (IS_ERR(f)) {  
  28.                 /*打开失败,释放fd*/  
  29.                 put_unused_fd(fd);  
  30.                 fd = PTR_ERR(f);  
  31.             } else {  
  32.                 /*文件如果已经被打开了,调用fsnotify_open()函数*/  
  33.                 fsnotify_open(f->f_path.dentry);  
  34.                 /*将文件指针安装在fd数组中*/  
  35.                 fd_install(fd, f);  
  36.             }  
  37.         }  
  38.         /*释放放置从用户空间拷贝过来的文件名的存储空间*/  
  39.         putname(tmp);  
  40.     }  
  41.     return fd;  
  42. }  
接下来即将进入到打开功能的真正实现功能的函数do_filp_open()函数:



  1. struct file *do_filp_open(int dfd, const char *pathname,int open_flag, int mode, int acc_mode)  
  2. {  
  3.     /* 
  4.     *…若干变量声明 
  5.     */  
  6.     /*改变参数flag的值,具体做法是flag+1*/  
  7.     int flag = open_to_namei_flags(open_flag);  
  8.     int force_reval = 0;  
  9.   
  10.     /*根据__O_SYNC标志来设置O_DSYNC 标志,用以防止恶意破坏程序*/  
  11.     if (open_flag & __O_SYNC)  
  12.         open_flag |= O_DSYNC;  
  13.     /*设置访问权限*/  
  14.     if (!acc_mode)  
  15.         acc_mode = MAY_OPEN | ACC_MODE(open_flag);  
  16.   
  17.     /*根据 O_TRUNC标志设置写权限 */  
  18.     if (flag & O_TRUNC)  
  19.         acc_mode |= MAY_WRITE;  
  20.   
  21.     /* 设置O_APPEND 标志*/  
  22.     if (flag & O_APPEND)  
  23.         acc_mode |= MAY_APPEND;  
  24.    
  25.     /*如果不是创建文件*/  
  26.     if (!(flag & O_CREAT)) {  
  27.         /*返回特定的file结构体指针*/  
  28.         filp = get_empty_filp();  
  29.   
  30.         if (filp == NULL)  
  31.             return ERR_PTR(-ENFILE);  
  32.         /*填充nameidata 结构*/  
  33.         nd.intent.open.file = filp;  
  34.         filp->f_flags = open_flag;  
  35.         nd.intent.open.flags = flag;  
  36.         nd.intent.open.create_mode = 0;  
  37.         /*当内核要访问一个文件的时候,第一步要做的是找到这个文件,而查找文件的过程在vfs里面是由path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构*/  
  38.         error = do_path_lookup(dfd, pathname,lookup_flags(flag)|LOOKUP_OPEN, &nd);  
  39.         if (IS_ERR(nd.intent.open.file)) {  
  40.                 if (error == 0) {  
  41.                          error = PTR_ERR(nd.intent.open.file);  
  42.                          /*减少dentry和vsmount得计数*/  
  43.                          path_put(&nd.path);  
  44.                 }  
  45.         } else if (error)  
  46.             /*如果查找失败则释放一些资源*/  
  47.             release_open_intent(&nd);  
  48.         if (error)  
  49.             return ERR_PTR(error);  
  50.         goto ok;  
  51.     }  
  52.    
  53.     /*到此则是要创建文件*/  
  54. reval:  
  55.     /* path-init为查找作准备工作,path_walk真正上路查找,这两个函数联合起来根据一段路径名找到对应的dentry */  
  56.     error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);  
  57.     if (error)  
  58.         return ERR_PTR(error);  
  59.     if (force_reval)  
  60.         nd.flags |= LOOKUP_REVAL;  
  61.     /*这个函数相当重要,就如源代码注释的那样,是整个NFS的名字解析函数,其实也是NFS得以构筑的函数。这里作一重点分析。这里先作一个综述。该函数采用一个for循环,对name路径根据目录的层次,一层一层推进,直到终点或失败。在推进的过程中,一步步建立了目录树的dentry和对应的inode */  
  62.     error = path_walk(pathname, &nd);  
  63.     if (error) {  
  64.         if (nd.root.mnt)  
  65.             path_put(&nd.root);  
  66.         return ERR_PTR(error);  
  67.     }  
  68.     if (unlikely(!audit_dummy_context()))  
  69.         /*保存inode节点信息*/  
  70.         audit_inode(pathname, nd.path.dentry);  
  71.   
  72.     /*父节点信息*/  
  73.     error = -EISDIR;  
  74.     if (nd.last_type != LAST_NORM || nd.last.name[nd.last.len])  
  75.         goto exit_parent;  
  76.   
  77.     error = -ENFILE;  
  78.     /*获取文件指针*/  
  79.     filp = get_empty_filp();  
  80.     if (filp == NULL)  
  81.         goto exit_parent;  
  82.     /*填充nameidata 结构*/  
  83.     nd.intent.open.file = filp;  
  84.     filp->f_flags = open_flag;  
  85.     nd.intent.open.flags = flag;  
  86.     nd.intent.open.create_mode = mode;  
  87.     dir = nd.path.dentry;  
  88.     nd.flags &= ~LOOKUP_PARENT;  
  89.     nd.flags |= LOOKUP_CREATE | LOOKUP_OPEN;  
  90.     if (flag & O_EXCL)  
  91.         nd.flags |= LOOKUP_EXCL;  
  92.     mutex_lock(&dir->d_inode->i_mutex);  
  93.     /*从哈希表中查找nd对应的dentry*/  
  94.     path.dentry = lookup_hash(&nd);  
  95.     path.mnt = nd.path.mnt;  
  96.    
  97. do_last:  
  98.     error = PTR_ERR(path.dentry);  
  99.     if (IS_ERR(path.dentry)) {  
  100.         mutex_unlock(&dir->d_inode->i_mutex);  
  101.         goto exit;  
  102.     }  
  103.   
  104.     if (IS_ERR(nd.intent.open.file)) {  
  105.         error = PTR_ERR(nd.intent.open.file);  
  106.         goto exit_mutex_unlock;  
  107.     }  
  108.   
  109.     /*如果此dentry结构没有对应的inode节点,说明是无效的,应该创建文件节点 */  
  110.     if (!path.dentry->d_inode) {  
  111.         /*write权限是必需的*/  
  112.         error = mnt_want_write(nd.path.mnt);  
  113.         if (error)  
  114.             goto exit_mutex_unlock;  
  115.         /*按照namei格式的flag open*/  
  116.         error = __open_namei_create(&nd, &path, flag, mode);  
  117.         if (error) {  
  118.             mnt_drop_write(nd.path.mnt);  
  119.             goto exit;  
  120.         }  
  121.         /*根据nameidata 得到相应的file结构*/  
  122.         filp = nameidata_to_filp(&nd);  
  123.         /*放弃写权限*/  
  124.         mnt_drop_write(nd.path.mnt);  
  125.         if (nd.root.mnt)  
  126.             /*计数减一*/  
  127.             path_put(&nd.root);  
  128.         if (!IS_ERR(filp)) {  
  129.             error = ima_file_check(filp, acc_mode);  
  130.             if (error) {  
  131.                 fput(filp);  
  132.                 filp = ERR_PTR(error);  
  133.             }  
  134.         }  
  135.         return filp;  
  136.     }  
  137.    
  138.     /*要打开的文件已经存在*/  
  139.     mutex_unlock(&dir->d_inode->i_mutex);  
  140.     /*保存inode节点*/  
  141.     audit_inode(pathname, path.dentry);  
  142.    
  143.     /*省略若干flag标志检查代码        */  
  144.    
  145.     /*路径装化为相应的nameidata 结构*/  
  146.     path_to_nameidata(&path, &nd);  
  147.     error = -EISDIR;  
  148.     /*如果是文件夹*/  
  149.     if (S_ISDIR(path.dentry->d_inode->i_mode))  
  150.         goto exit;  
  151. ok:  
  152.     /*检测是否截断文件标志*/  
  153.     will_truncate = open_will_truncate(flag, nd.path.dentry->d_inode);  
  154.     if (will_truncate) {  
  155.     /*要截断的话就要获取写权限*/  
  156.         error = mnt_want_write(nd.path.mnt);  
  157.         if (error)  
  158.             goto exit;  
  159.     }  
  160.     //may_open执行权限检测、文件打开和truncate的操作  
  161.     error = may_open(&nd.path, acc_mode, flag);  
  162.     if (error) {  
  163.         if (will_truncate)  
  164.             mnt_drop_write(nd.path.mnt);  
  165.         goto exit;  
  166.     }  
  167.     filp = nameidata_to_filp(&nd);  
  168.     if (!IS_ERR(filp)) {  
  169.         error = ima_file_check(filp, acc_mode);  
  170.         if (error) {  
  171.             fput(filp);  
  172.             filp = ERR_PTR(error);  
  173.         }  
  174.     }  
  175.     if (!IS_ERR(filp)) {  
  176.         if (acc_mode & MAY_WRITE)  
  177.             vfs_dq_init(nd.path.dentry->d_inode);  
  178.   
  179.         if (will_truncate) {  
  180.             //处理截断  
  181.             error = handle_truncate(&nd.path);  
  182.             if (error) {  
  183.             fput(filp);  
  184.             filp = ERR_PTR(error);  
  185.             }  
  186.         }  
  187.     }  
  188.     //安全的放弃写权限  
  189.     if (will_truncate)  
  190.         mnt_drop_write(nd.path.mnt);  
  191.     if (nd.root.mnt)  
  192.         path_put(&nd.root);  
  193.     return filp;  
  194.    
  195. exit_mutex_unlock:  
  196.     mutex_unlock(&dir->d_inode->i_mutex);  
  197. exit_dput:  
  198.     path_put_conditional(&path, &nd);  
  199. exit:  
  200.     if (!IS_ERR(nd.intent.open.file))  
  201.         release_open_intent(&nd);  
  202. exit_parent:  
  203.     if (nd.root.mnt)  
  204.         path_put(&nd.root);  
  205.     path_put(&nd.path);  
  206.     return ERR_PTR(error);  
  207. //允许遍历连接文件,则手工找到连接文件对应的文件  
  208. do_link:  
  209.     error = -ELOOP;  
  210.     if (flag & O_NOFOLLOW)  
  211.         //不允许遍历连接文件,返回错误  
  212.         goto exit_dput;  
  213.           
  214.     /*以下是手工找到链接文件对应的文件dentry结构代码*/  
  215.     // 设置查找LOOKUP_PARENT标志  
  216.     nd.flags |= LOOKUP_PARENT;  
  217.     //判断操作是否安全  
  218.     error = security_inode_follow_link(path.dentry, &nd);  
  219.     if (error)  
  220.         goto exit_dput;  
  221.     // 处理符号链接  
  222.     error = __do_follow_link(&path, &nd);  
  223.     path_put(&path);  
  224.     if (error) {  
  225.         release_open_intent(&nd);  
  226.         if (nd.root.mnt)  
  227.             path_put(&nd.root);  
  228.         if (error == -ESTALE && !force_reval) {  
  229.             force_reval = 1;  
  230.             goto reval;  
  231.         }  
  232.         return ERR_PTR(error);  
  233.     }  
  234.     nd.flags &= ~LOOKUP_PARENT;  
  235.     // 检查最后一段文件或目录名的属性情况  
  236.     if (nd.last_type == LAST_BIND)  
  237.         goto ok;  
  238.     error = -EISDIR;  
  239.     if (nd.last_type != LAST_NORM)  
  240.         goto exit;  
  241.     if (nd.last.name[nd.last.len]) {  
  242.         __putname(nd.last.name);  
  243.         goto exit;  
  244.     }  
  245.     error = -ELOOP;  
  246.     // 出现回环标志: 循环超过32次  
  247.     if (count++==32) {  
  248.         __putname(nd.last.name);  
  249.         goto exit;  
  250.     }  
  251.     dir = nd.path.dentry;  
  252.     mutex_lock(&dir->d_inode->i_mutex);  
  253.     // 更新路径的挂接点和dentry  
  254.     path.dentry = lookup_hash(&nd);  
  255.     path.mnt = nd.path.mnt;  
  256.     __putname(nd.last.name);  
  257.     goto do_last;  
  258. }  
分析完上述主要函数以后,我们来看一下整个打开流程是如何做到的:
在内核中要打开一个文件,首先应该找到这个文件,而查找文件的过程在vfs里面是由do_path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构。
基本函数流程及调用方式如下所示:
打开过程首先是open系统调用访问SYSCALL_DEFINE3函数,然后调用do_sys_open 函数完成主要功能,再调用函数do_filp_open完成主要的打开功能,下面详细看下do_filp_open中调用的do_path_lookup主要过程:



  1. staic int  do_path_lookup(int dfd,const char *name,unsigned int flags,struct nameidata *nd)  
  2. {  
  3.     int retval=path_init(dfd,name,flags,nd);  
  4.     //设置nd->root=根路径(绝对地址)或者当前工作目录(相对地址) 。  
  5.     //这一步做完了后,内核会建立一些数据结构(dentry,inode)来初始化查找的起点  
  6.     if(!retval)  
  7.         retval = path_walk(name,nd);  
  8.     //path_walk,会遍历路径的每一份量,也就是用“/”分隔开的每一部分,  
  9.     //最中找到name指向的文件,walk的意思就是walk path的每一个组分(component)  
  10. }  
我们进一步看看path_walk
  1. int path_walk(const char *name,struct nameidata *nd)  
  2. {  
  3.     return link_path_walk(name,nd);  
  4.     //path_walk其实相当于直接调用link_path_walk来完成工作  
  5. }  

link_path_walk的主要工作是有其内部函数__link_path_walk 来完成的
result = __link_path_walk(name,nd)



至此我们转向最重要的代码__link_walk_path,该函数把传进来的字符串name,也就是用户指定的路径,按路径分隔符分解成一系列小的component。比如用户说,我要找/path/to/dest这个文件,那么我们的文件系统就会按path,to,dest一个一个来找,知道最后一个分量是文件或者查找完成。他找的时候,会先用path_init初始化过的根路径去找第一个分量,也就是path。然后用path的dentry->d_inode去找to,这样循环到最后一个。注意,内核会缓存找到的路径分量,所以往往只有第一次访问一个路径的时候,才会去访问磁盘,后面的访问会直接从缓存里找,下面会看到,很多与页告诉缓存打交道的代码。但不管怎样,第一遍查找总是会访问磁盘的。
static int __link_path_walk(const char *name,struct nameidata *nd)
{
}
至此,按照每一个component查找完成之后,就会找到相应的文件,然后相应的打开工作就基本完成了。

阅读(500) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~