Open()函数的内核追踪node
open函数相信你们都用过,这里就很少说它的使用方法等事项,现直接进入正题...linux
用户态程序调用open函数时,会产生一个中断号为5的中断请求,其值以该宏__NR__open进行标示.然后该进程上下文(process context)将会被切换到内核空间。待内核中的相关操做完成后,就会从内核返回,此时还须要一次进程上下文切换(process contextswitch)。数组
待进程执行流进入内核后,会经过一系列转换(这里咱们不关心),最终进入SYSCALL_DEFINE3(open,...)函数中。看起来该函数定义比较特殊,其实SYSCALL_DRFINE3是一个宏,它被定义成以下形式:ide
#defineSYSCALL_DEFINE3(name,...)SYSCALL_DEFINEx(3,_##name,__VA_ARGS__)函数 |
而SYSCALL_DEFINEx宏具备以下形式:ui
#ifdefCONFIG_FTRACE_SYSCALLS |
能够看到,不管是何种形式的宏定义,最终都会进入__SYSCALL_DEFINEx中,而__SYSCALL_DEFINEx的定义以下:.net
#ifdefCONFIG_HAVE_SYSCALL_WRAPPERS |
通过该宏的替换做用之后,最终咱们就会获得sys_open或SYS_open所对应的函数原型。以下:blog
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) |
在函数中首先调用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 |
它们将会经过选择性的+1操做转变成具备以下含义的值,并存入本地变量flag中:
00-no permissions needed |
好了,如今咱们来看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); |
若上述字符不是'/',则在检查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; |
记住,还没完呢,必定要调用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; |
从上面的程序片断中能够看到,每一个运行的进程都有属于本身的文件描述符表,由指针current->files指示。
上述操做都完成后,那么空间也将会随即返回到用户空间了,返回值固然就是fd。这就是咱们传递给read、write、fcntl、fioctl等函数的文件描述符。至此,文件的打开操做就所有完成了。