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

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{long ret;if (force_o_largefile())flags |= O_LARGEFILE;ret = do_sys_open(AT_FDCWD, filename, flags, mode);/* avoid REGPARM breakage on x86: */asmlinkage_protect(3, ret, filename, flags, mode);return ret;
}long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{/*获取文件名称,由getname()函数完成,其内部首先创建存取文件名称的空间,然后*从用户空间把文件名拷贝过来*/char *tmp = getname(filename);int fd = PTR_ERR(tmp);if (!IS_ERR(tmp)) {/*获取一个可用的fd,此函数调用alloc_fd()函数从fd_table中获取一个可用fd,并做些简单初始化,此函数内部实现比较简单,此次分析不细看*/fd = get_unused_fd_flags(flags);if (fd >= 0) {/*fd获取成功则开始打开文件,此函数是主要完成打开功能的函数,在此先放一放,下面详细分析*/struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);if (IS_ERR(f)) {/*打开失败,释放fd*/put_unused_fd(fd);fd = PTR_ERR(f);} else {/*文件如果已经被打开了,调用fsnotify_open()函数*/fsnotify_open(f->f_path.dentry);/*将文件指针安装在fd数组中*/fd_install(fd, f);}}/*释放放置从用户空间拷贝过来的文件名的存储空间*/putname(tmp);}return fd;
}

接下来即将进入到打开功能的真正实现功能的函数do_filp_open()函数:

struct file *do_filp_open(int dfd, const char *pathname,int open_flag, int mode, int acc_mode)
{/**…若干变量声明*//*改变参数flag的值,具体做法是flag+1*/int flag = open_to_namei_flags(open_flag);int force_reval = 0;/*根据__O_SYNC标志来设置O_DSYNC 标志,用以防止恶意破坏程序*/if (open_flag & __O_SYNC)open_flag |= O_DSYNC;/*设置访问权限*/if (!acc_mode)acc_mode = MAY_OPEN | ACC_MODE(open_flag);/*根据 O_TRUNC标志设置写权限 */if (flag & O_TRUNC)acc_mode |= MAY_WRITE;/* 设置O_APPEND 标志*/if (flag & O_APPEND)acc_mode |= MAY_APPEND;/*如果不是创建文件*/if (!(flag & O_CREAT)) {/*返回特定的file结构体指针*/filp = get_empty_filp();if (filp == NULL)return ERR_PTR(-ENFILE);/*填充nameidata 结构*/nd.intent.open.file = filp;filp->f_flags = open_flag;nd.intent.open.flags = flag;nd.intent.open.create_mode = 0;/*当内核要访问一个文件的时候,第一步要做的是找到这个文件,而查找文件的过程在vfs里面是由path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构*/error = do_path_lookup(dfd, pathname,lookup_flags(flag)|LOOKUP_OPEN, &nd);if (IS_ERR(nd.intent.open.file)) {if (error == 0) {error = PTR_ERR(nd.intent.open.file);/*减少dentry和vsmount得计数*/path_put(&nd.path);}} else if (error)/*如果查找失败则释放一些资源*/release_open_intent(&nd);if (error)return ERR_PTR(error);goto ok;}/*到此则是要创建文件*/
reval:/* path-init为查找作准备工作,path_walk真正上路查找,这两个函数联合起来根据一段路径名找到对应的dentry */error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);if (error)return ERR_PTR(error);if (force_reval)nd.flags |= LOOKUP_REVAL;/*这个函数相当重要,就如源代码注释的那样,是整个NFS的名字解析函数,其实也是NFS得以构筑的函数。这里作一重点分析。这里先作一个综述。该函数采用一个for循环,对name路径根据目录的层次,一层一层推进,直到终点或失败。在推进的过程中,一步步建立了目录树的dentry和对应的inode */error = path_walk(pathname, &nd);if (error) {if (nd.root.mnt)path_put(&nd.root);return ERR_PTR(error);}if (unlikely(!audit_dummy_context()))/*保存inode节点信息*/audit_inode(pathname, nd.path.dentry);/*父节点信息*/error = -EISDIR;if (nd.last_type != LAST_NORM || nd.last.name[nd.last.len])goto exit_parent;error = -ENFILE;/*获取文件指针*/filp = get_empty_filp();if (filp == NULL)goto exit_parent;/*填充nameidata 结构*/nd.intent.open.file = filp;filp->f_flags = open_flag;nd.intent.open.flags = flag;nd.intent.open.create_mode = mode;dir = nd.path.dentry;nd.flags &= ~LOOKUP_PARENT;nd.flags |= LOOKUP_CREATE | LOOKUP_OPEN;if (flag & O_EXCL)nd.flags |= LOOKUP_EXCL;mutex_lock(&dir->d_inode->i_mutex);/*从哈希表中查找nd对应的dentry*/path.dentry = lookup_hash(&nd);path.mnt = nd.path.mnt;do_last:error = PTR_ERR(path.dentry);if (IS_ERR(path.dentry)) {mutex_unlock(&dir->d_inode->i_mutex);goto exit;}if (IS_ERR(nd.intent.open.file)) {error = PTR_ERR(nd.intent.open.file);goto exit_mutex_unlock;}/*如果此dentry结构没有对应的inode节点,说明是无效的,应该创建文件节点 */if (!path.dentry->d_inode) {/*write权限是必需的*/error = mnt_want_write(nd.path.mnt);if (error)goto exit_mutex_unlock;/*按照namei格式的flag open*/error = __open_namei_create(&nd, &path, flag, mode);if (error) {mnt_drop_write(nd.path.mnt);goto exit;}/*根据nameidata 得到相应的file结构*/filp = nameidata_to_filp(&nd);/*放弃写权限*/mnt_drop_write(nd.path.mnt);if (nd.root.mnt)/*计数减一*/path_put(&nd.root);if (!IS_ERR(filp)) {error = ima_file_check(filp, acc_mode);if (error) {fput(filp);filp = ERR_PTR(error);}}return filp;}/*要打开的文件已经存在*/mutex_unlock(&dir->d_inode->i_mutex);/*保存inode节点*/audit_inode(pathname, path.dentry);/*省略若干flag标志检查代码        *//*路径装化为相应的nameidata 结构*/path_to_nameidata(&path, &nd);error = -EISDIR;/*如果是文件夹*/if (S_ISDIR(path.dentry->d_inode->i_mode))goto exit;
ok:/*检测是否截断文件标志*/will_truncate = open_will_truncate(flag, nd.path.dentry->d_inode);if (will_truncate) {/*要截断的话就要获取写权限*/error = mnt_want_write(nd.path.mnt);if (error)goto exit;}//may_open执行权限检测、文件打开和truncate的操作error = may_open(&nd.path, acc_mode, flag);if (error) {if (will_truncate)mnt_drop_write(nd.path.mnt);goto exit;}filp = nameidata_to_filp(&nd);if (!IS_ERR(filp)) {error = ima_file_check(filp, acc_mode);if (error) {fput(filp);filp = ERR_PTR(error);}}if (!IS_ERR(filp)) {if (acc_mode & MAY_WRITE)vfs_dq_init(nd.path.dentry->d_inode);if (will_truncate) {//处理截断error = handle_truncate(&nd.path);if (error) {fput(filp);filp = ERR_PTR(error);}}}//安全的放弃写权限if (will_truncate)mnt_drop_write(nd.path.mnt);if (nd.root.mnt)path_put(&nd.root);return filp;exit_mutex_unlock:mutex_unlock(&dir->d_inode->i_mutex);
exit_dput:path_put_conditional(&path, &nd);
exit:if (!IS_ERR(nd.intent.open.file))release_open_intent(&nd);
exit_parent:if (nd.root.mnt)path_put(&nd.root);path_put(&nd.path);return ERR_PTR(error);
//允许遍历连接文件,则手工找到连接文件对应的文件
do_link:error = -ELOOP;if (flag & O_NOFOLLOW)//不允许遍历连接文件,返回错误goto exit_dput;/*以下是手工找到链接文件对应的文件dentry结构代码*/// 设置查找LOOKUP_PARENT标志nd.flags |= LOOKUP_PARENT;//判断操作是否安全error = security_inode_follow_link(path.dentry, &nd);if (error)goto exit_dput;// 处理符号链接error = __do_follow_link(&path, &nd);path_put(&path);if (error) {release_open_intent(&nd);if (nd.root.mnt)path_put(&nd.root);if (error == -ESTALE && !force_reval) {force_reval = 1;goto reval;}return ERR_PTR(error);}nd.flags &= ~LOOKUP_PARENT;// 检查最后一段文件或目录名的属性情况if (nd.last_type == LAST_BIND)goto ok;error = -EISDIR;if (nd.last_type != LAST_NORM)goto exit;if (nd.last.name[nd.last.len]) {__putname(nd.last.name);goto exit;}error = -ELOOP;// 出现回环标志: 循环超过32次if (count++==32) {__putname(nd.last.name);goto exit;}dir = nd.path.dentry;mutex_lock(&dir->d_inode->i_mutex);// 更新路径的挂接点和dentrypath.dentry = lookup_hash(&nd);path.mnt = nd.path.mnt;__putname(nd.last.name);goto do_last;
}

分析完上述主要函数以后,我们来看一下整个打开流程是如何做到的:
在内核中要打开一个文件,首先应该找到这个文件,而查找文件的过程在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主要过程:

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

我们进一步看看path_walk

int path_walk(const char *name,struct nameidata *nd)
{return link_path_walk(name,nd);//path_walk其实相当于直接调用link_path_walk来完成工作
}

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查找完成之后,就会找到相应的文件,然后相应的打开工作就基本完成了。

Linux内核源码阅读之打开文件篇相关推荐

  1. 【转载】ubuntu下linux内核源码阅读工具和调试方法总结

    http://blog.chinaunix.net/space.php?uid=20940095&do=blog&cuid=2377369 一 linux内核源码阅读工具 window ...

  2. Linux内核源码阅读以及工具(转)

    Linux内核源码阅读以及工具(转) 转载地址:Linux内核源码阅读以及工具(转)

  3. Linux内核源码阅读以及工具详解

    接上篇Linux内核源码下载方法 这篇总结了如何利用source insight对Linux内核代码进行阅读和学习(资料来源于网络) 随着linux的逐步普及,现在有不少人对于Linux的安装及设置已 ...

  4. linux实现自己的write函数,Linux 内核源码阅读 - write 系统调用的实现

    最近在看write系统调用的实现,虽然还有一下细节不是很清楚,但是大致的实现机理还是有一定的理解了.总结如下: 这里假设最普通的情况,不考虑Direct IO 的情况.从全家的高度看,要往一个文件中写 ...

  5. xilinx linux内核,Xilinx-Zynq Linux内核源码编译过程

    本文内容依据http://www.wiki.xilinx.com网址编写,编译所用操作系统为ubuntu 14 1.交叉编译环境的安装配置 2.uboot的编译 1)下载uboot源代码 下载uboo ...

  6. Ubuntu 安裝 GNU Global(gtags) 阅读Linux内核源码

    CSDN GitHub Ubuntu 安裝 GNU Global(gtags) 阅读Linux内核源码 AderXCoding/system/tools/global 本作品采用知识共享署名-非商业性 ...

  7. ubuntu下wget下载Linux内核源码、make生成.config文件

    根据资料,如果要调试Linux内核源码,需要自己编译内核源码:原因是,一般下载的内核并不是为调试而编译的,要在编译时开启 "Compile the kernel with debug inf ...

  8. 最小的linux内核代码,带你阅读linux内核源码:下载源码、编译内核并运行一个最小系统...

    要学习linux内核,先要能够编译内核,还需要了解内核如何启动根文件系统.这样你才能在修改linux内核代码之后,完成验证的过程. 本文教你完成下列过程: 1.下载linux并编译linux内核源码 ...

  9. 深入分析Linux内核源码oss.org.cn/kernel-book/

    本html页面地址:http://oss.org.cn/kernel-book/ 深入分析Linux内核源码 前言         第一章 走进linux 1.1 GNU与Linux的成长 1.2 L ...

最新文章

  1. python 的保留字
  2. 微型计算机三包法第30条内容,我不同意国家对电视机三包有明确规定,还是检测手段有问题。保修内容明确维护好消费者的权益。- 法律快车法律咨询...
  3. 《CCNP TSHOOT 300-135认证考试指南》——6.4节SVI故障检测与排除
  4. 漂亮表格的CSS定义
  5. Flutter使用ScreenUtil获取屏幕宽高初始化报错
  6. 永川机器人五小区_永川清掏化粪池报价---蚂众蚁
  7. ASP.NET Web Pages 和 WebMatrix (Razor Syntax、Forms、Data、Grid、Chart、Files、Images、Video)的学习资源...
  8. 会计基础(1)利得和损失
  9. python3视频教程哪个好_python3入门视频教程
  10. 《马云点评创业》读书笔记
  11. python表单数据系统_python 网页提交表单数据库
  12. PostgreSQL eighth class
  13. R语言中创建股票走势图
  14. 赤子城科技三年两变:音视频社交成主力军,营收结构稳定性存疑
  15. 余淼杰老师 经济学原理复习笔记(微观)
  16. 【大数据开发】scala——tuple、list(含高阶方法)、wordcount案例、set、并行处理数据和sorted、sortBy、sortWith的区别
  17. linux 图片编辑 java_ImageJ For Linux x64(免费开源图片编辑软件)V1.49v官方版下载 - 下载吧...
  18. 752_LaTeX基础软件使用测试
  19. RHEL 8.0安装中文输入法
  20. 代码质量检测-SonarQube

热门文章

  1. Java 算法 阿尔法乘积
  2. 平安性格测试题及答案_性格趣味小测试题 有趣的心理测试题大全及答案
  3. python开发个人博客_[Web开发] Flask+Python 开发个人博客(一)
  4. java se开发_JAVA_SE基础——3.Java程序的开发流程
  5. 八皇后问题初始思路python_【单人解谜】经典的八皇后问题解析
  6. Xavier上的第一个I2C驱动
  7. jQuery获取元素索引值index()方法
  8. jmeter中CSV Data Set Config的使用
  9. 四种转换方式:自动,强制,Parse,Convert
  10. #pragma onece