设为首页 收藏本站
查看: 1414|回复: 0

[经验分享] Open()函数的内核追踪

[复制链接]

尚未签到

发表于 2019-1-27 11:42:04 | 显示全部楼层 |阅读模式
  Open()函数的内核追踪
  open函数相信大家都用过,这里就不多说它的使用方法等事项,现直接进入正题...
  用户态程序调用open函数时,会产生一个中断号为5的中断请求,其值以该宏__NR__open进行标示.而后该进程上下文(process context)将会被切换到内核空间。待内核中的相关操作完成后,就会从内核返回,此时还需要一次进程上下文切换(process contextswitch)。
  待进程执行流进入内核后,会通过一系列转换(这里我们不关心),最终进入SYSCALL_DEFINE3(open,...)函数中。看起来该函数定义比较特殊,其实SYSCALL_DRFINE3是一个宏,它被定义成如下形式:
  #defineSYSCALL_DEFINE3(name,...)SYSCALL_DEFINEx(3,_##name,__VA_ARGS__)
  而SYSCALL_DEFINEx宏具有如下形式:
  #ifdefCONFIG_FTRACE_SYSCALLS
  #defineSYSCALL_DEFINEx(x,sname,...)        \
  staticconstchar*types_##sname[]={      \
  __SC_STR_TDECL##x(__VA_ARGS__)      \
  };              \
  staticconstchar*args_##sname[]={      \
  __SC_STR_ADECL##x(__VA_ARGS__)      \
  };              \
  SYSCALL_METADATA(sname,x);        \
  __SYSCALL_DEFINEx(x,sname,__VA_ARGS__)
  #else
  #defineSYSCALL_DEFINEx(x,sname,...)        \
  __SYSCALL_DEFINEx(x,sname,__VA_ARGS__)
  #endif
  可以看到,不论是何种形式的宏定义,最终都会进入__SYSCALL_DEFINEx中,而__SYSCALL_DEFINEx的定义如下:
  #ifdefCONFIG_HAVE_SYSCALL_WRAPPERS
  #defineSYSCALL_DEFINE(name)staticinlinelongSYSC_##name
  #define__SYSCALL_DEFINEx(x,name,...)          \
  asmlinkagelongsys##name(__SC_DECL##x(__VA_ARGS__));    \
  staticinlinelongSYSC##name(__SC_DECL##x(__VA_ARGS__));  \
  asmlinkagelongSyS##name(__SC_LONG##x(__VA_ARGS__))    \
  {                \
  __SC_TEST##x(__VA_ARGS__);        \
  return(long)SYSC##name(__SC_CAST##x(__VA_ARGS__));  \
  }                \
  SYSCALL_ALIAS(sys##name,SyS##name);        \
  staticinlinelongSYSC##name(__SC_DECL##x(__VA_ARGS__))
  #else/* CONFIG_HAVE_SYSCALL_WRAPPERS */
  #defineSYSCALL_DEFINE(name)asmlinkagelongsys_##name
  #define__SYSCALL_DEFINEx(x,name,...)          \
  asmlinkagelongsys##name(__SC_DECL##x(__VA_ARGS__))
  #endif/* CONFIG_HAVE_SYSCALL_WRAPPERS */
  经过该宏的替换作用以后,最终我们就会得到sys_open或SYS_open所对应的函数原型。如下:
  asmlinkage long sys_open(const char __user filename,intflags,intmode);
  这也就是我们最常见到的open函数所对应的在内核中的实现部份。其实,对于linux下所有的系统调用函数,采用上述方法均可找到与其对应的内核函数sys_xxx().
  接下来我们来看sys_open()函数。其实现如下:
  SYSCALL_DEFINE3(open,const char __user*,filename,int,flags,int,mode)
  {
  longret;
  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);
  returnret;
  }
  在函数中首先调用force_o_largefile()宏进行LARGEFILE?确认。若是LARGEFILE则将其在flags中置位。随后调用主处理函数do_sys_open进行后续处理。其实,open的工作也就是在该函数中进行的。该函数原型如下:
  longdo_sys_open(intdfd,constchar__user*filename,intflags,intmode);
  在该函数中,如果通过getname()得到filename变量中文件名的指针没有错误的话,接下来就会掉用get_unused_fd_flags()函数获得一个没被使用的文件描述符fd。注意,对于文件描述符fd来讲,它只对本进程有效,也即它只在该进程中可见而在其它进程中代表着完全不同的文件。在32位系统中,一个进程最多打开32个文件,而在64位系统中可以打开64个文件。该函数就是用来获得一个未被使用的文件描述符fd.至于它的获取过程是很复杂的,这里不进行讲述。有兴趣的话,可以到www.kernel.org下载最新的内核源码进行研究。
  在获得了有效的fd之后,我们通过do_filp_open()函数打开或者创建相应的文件,并且返回与之对应的文件结构struct file *f。如果函数返回的结构地址有效的话,那么就会调用fsnotify_open()函数(参数为f)将该文件加入到文件监控的系统中。该系统是用来监控文件被打开,创建,读写,关闭,修改等操作的,具体工作原理见后面文章。本文中不做讲述。随后调用fd_install()函数将struct file *f加入到fd索引位置处的数组中。如果后续过程中,有对该文件描述符的操作的话,就会通过查找该数组得到对应的文件结构,而后在进行相关操作。完成这些工作之后,open函数就返回了。返回值也就是刚才得到的fd.
  那么,在do_filp_open()函数中有具体做了哪些工作呢?文件是如何被创建的呢?以及文件若存在的话,又是怎样被找到,而后被打开的呢?在中篇中我们将会回答这些问题.
  接着上篇我们来回答文章最后提出的问题:在do_filp_open()函数中有具体做了哪些工作呢?文件是如何被创建的呢?以及文件若存在的话,又是怎样被找到,而后被打开的呢?下面我们来回答这些问题。
  可以推断,在do_filp_open函数做了open函数的全部工作,包括创建,打开等等。该函数的原型是这样的:
  structfile*do_filp_open(intdfd,constchar*pathname,intopen_flag,intmode,intacc_mode);
  需要说明的是该函数参数列表中的open_flag的低两位的含义和该函数内部的flag变量中的是不同的。具体的区别如下:
  当open_flag参数(其实它就是sys_open函数中的flag参数)中的低两位具有如下含义时:
  00-read-only
  01-write-only
  10-read-only
  11-special
  它们将会通过选择性的+1操作转变成具有如下含义的值,并存入本地变量flag中:
  00-no permissions needed
  01-read-permission
  10-write-permission
  11-read-write
  好了,现在我们来看do_filp_open()函数的实现。细心的朋友会发现,在函数的开始会进行一系列flag和mode标志位的检查,这里我们不关心。之后就会调用path_init()函数进行后续操作前的初始化工作。path_init()函数主要是为了填充nd(nd是一个指向struct nameidata结构的指针)结构。
  在函数path_init内部,会判断*pathname是不是字符'/',若是则通过如下代码设置nd->root和nd->path,该root即是指向current->fs->root的指针.
  set_root(nd);
  nd->path=nd->root;
  path_get(&nd->root);
  若上述字符不是'/',则在检查dfd参数,如果该值为AT_FDCWD,那么我们就调用get_fs_pwd()函数将nd->path设置成current->fs->pwd指针所指向的当前工作目录。这里需要说明一下AT_FDCWD宏的含义。该宏的值是-100,它主要是用来指示openat应使用当前工作目录。
  如果以上判断都不为真的话,那么,此时会调用fget_light()函数来获得dfd所对应的file结构(struct file *)。这里的dfd和open函数返回的fd,也就是上篇中分配的fd是不是具用相同的含义,这里还不得而知。个人感觉好像是由VFS分配或查找以存在的文件时得到的。并且若不是create文件的话,它们应该具用相同的含义,否则不相同。这里指示猜测,还没深究。希望能有高人先来告诉鄙人一下,或是一起发贴来讨论一下。我会尽快来澄清这个问题的。
  上面通过fget_light函数得到的file需要通过S_ISDIR(file->f_path.dentry->d_inode->i_mode)来检查这个已经存在的inode是不是一个目录,若是则一切OK。否则fail。若是目录的话,接下来调用file_permission(file,MAY_EXEC)来判断该文件的执行权限。若是具有执行权限,那么此时也应具有read权限,若全OK的话,审查就算是通过了。之后,通过如下语句设置nd->path,并将其引用计数加一。
  nd->path=file->f_path;
  path_get(&file->f_path);
  记住,还没完呢,一定要调用fput_light()将fget_light()返回的file指针空间释放掉。同时,实参中的fput_needed要和fget_light()函数返回的值相同。否则,后果...自己去体验一下就知道了...
  好了,到这里我们的前期初始化工作就完成了。下一步通过调用link_path_walk(pathname,&nd)函数进行文件名解析。其实它是一个很基本的文件名字解析函数,用于将pathname转换成最终的dentry,并存储于nd->path.dentry中。注意这里返回的dentry其实是其父结点相应信息。不理解的话,继续往下看。待该函数返回时,如果没有错误,那么之后就可以通过get_enpty_filp()为basename(pathname)申请filp(类型为:struct file *)指针了。若申请成功,则将filp放入nd.intent.open.file域中,同时初始化filp和nd.intent.open指针结构所指的实例中的f_flags、flags、create_mode等域。
  接下来就剩open的最实质性的工作了。它是通过do_last函数来完成的。
  do_last函数的原型如下(位于文件fs/namei.c中):
  staticstructfile*do_last(structnameidata*nd,strcut path*path,intopen_flag,intacc_mode,intmode,const char*pathname)
  函数中首先会根据open_flag中的标志位判断该文件是不是需要被创建,若否,那么会进行最后阶段的inode节点的查找,如果此时查找成功,这直接调用path_to_nameidate()函数通过如下语句设置nd->path.dentry的值:
  nd->path.dentry=path->dentry
  若没有查找成功并且flag中有没有将O_CREAT标志为置位。那么,此时通过将error设置成-ENOTDIR并向用户态返回NODIR的出错提示。
  但是,如果用户在使用open函数时,使用了create选项的话,那么我们只好进行下面的操作了:创建新的inode节点。这项工作是通过__open_namei_create()函数完成的。在该函数内部,会调用VFS层的vfs_create()函数将控制下发到不同的文件系统处理函数中。它是通过这样的调用过成完成的:
  error=dir->i_op->create(dir,dentry,mode,nd)
  如果当前使用的是ext4文件系统的话,那么create指针指向的是ext4_create,否则就有可能是ext3_create等等。具体的还要视具体情况而定。到了这里我们也可以初步的了解VFS的作用了吧。就是将更低层的不同文件系统类型作统一管理,为上层的使用提供统一的函数接口,这为其作用之一。欲想知道create函数指针是如何被初始化的,请看或查阅内核模块加载的过程。这里不再详述。inode被成功创建后,其相关的创建时间,访问时间,修改时间,gid,uid,euid以及访问权限等一些属性信息就会被正确的初始化。该过程结束后,我们还会调用fsnotify的hook函数fsnotify_create将新建文件节点的事件提交到监控系统。待完成后,操作流就会调用nameidata_to_filp(nd)进行nd->path.dentry的设置,并调用__denry_open()函数做打开文件的工作。其实在该函数仍然是VFS层函数,做实质工作的函数是通过f->f_op->open方式进行相应文件系统处理函数的调用的。open的初始化,请看create函数的过程实现。待这些操作完成后,控制将会返回到do_sys_open中。
  另外,接前文如果文件已经存在,那好,余下的就是一点点收尾工作,在函数finish_open()中调用nameidata_to_filp()函数将文件打开。之后同样,控制返回到do_sys_open()中。
  好了,现在我们已经回到了最初的地方,dentry找到了,inode生成了,filp确定了,接下来就是filp和fd的绑定了。
  它是如何完成的呢。说明这个之前,我们先看以下fsnotify_open()函数,它的作用是将filp的监控点打开,并将其添加到监控系统中。
  完成filp和fd绑定功能的函数是fd_install()。它的功能就是将filp添加到fdt->fd指定的数组空间,索引是fd的位置中去。其中struct fdtable *fdt是通过如下方式得到的:
  structfiles_struct*file=current->files;
  structfdtable*fdt;
  spin_lock(&files->file_lock);
  fdt=files_fdtable(files);
  rcu_assign_pointer(fdt->fd[fd],filp);
  spin_unlock(&files->file_lock)
  从上面的程序片段中可以看到,每个运行的进程都有属于自己的文件描述符表,由指针current->files指示。
  上述操作都完成后,那么空间也将会随即返回到用户空间了,返回值当然就是fd。这就是我们传递给read、write、fcntl、fioctl等函数的文件描述符。至此,文件的打开操作就全部完成了。


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-668203-1-1.html 上篇帖子: 2014微软open day 运维网 讲师、博主、版主~ 下篇帖子: pvcreate出错: Can't open /dev/sdb7 exclusively. Mounted filesystem?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表