系统调用Open()函数的内核追踪(下篇)

From: http://blog.chinaunix.net/uid-24585858-id-2125501.html

接着上篇我们来回答文章最后提出的问题:在do_filp_open()函数中有具体做了哪些工作呢?文件是如何被创建的呢?以及文件若存在的话,又是怎样被找到,而后被打开的呢?下面我们来回答这些问题。


可以推断,在do_filp_open函数做了open函数的全部工作,包括创建,打开等等。该函数的原型是这样的:


struct file *do_filp_open(int dfd, const char *pathname, int open_flag, int mode, int acc_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()函数的实现。细心的朋友会发现,在函数的开始会进行一系列flagmode标志位的检查,这里我们不关心。之后就会调用path_init()函数进行后续操作前的初始化工作。path_init()函数主要是为了填充nd(nd是一个指向struct nameidata结构的指针)结构。


在函数path_init内部,会判断*pathname是不是字符'/',若是则通过如下代码设置nd->rootnd->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 *)。这里的dfdopen函数返回的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(类型为:structfile*)指针了。若申请成功,则将filp放入nd.intent.open.file域中,同时初始化filpnd.intent.open指针结构所指的实例中的f_flagsflagscreate_mode等域。


接下来就剩open的最实质性的工作了。它是通过do_last函数来完成的。


do_last函数的原型如下(位于文件fs/namei.c)

static struct file *do_last(struct nameidata *nd, strcut path *path, int open_flag, int acc_mode, int mode, 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被成功创建后,其相关的创建时间,访问时间,修改时间,giduideuid以及访问权限等一些属性信息就会被正确的初始化。该过程结束后,我们还会调用fsnotifyhook函数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确定了,接下来就是filpfd的绑定了。


它是如何完成的呢。说明这个之前,我们先看以下fsnotify_open()函数,它的作用是将filp的监控点打开,并将其添加到监控系统中。


完成filpfd绑定功能的函数是fd_install()。它的功能就是将filp添加到fdt->fd指定的数组空间,索引是fd的位置中去。其中structfdtable *fdt是通过如下方式得到的:


struct files_struct *file = current->files;
struct fdtable *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。这就是我们传递给readwritefcntlfioctl等函数的文件描述符。至此,文件的打开操作就全部完成了。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux操作系统中,系统调用是应用程序(通常是用户空间程序)向内核请求服务的一种方式。它们提供了一种安全的接口,使得用户进程能够执行那些通常只能由特权级(如内核)才能完成的操作,比如文件I/O、网络通信等。Linux中的系统调用是由CPU的陷阱指令引发的,通常通过操作系统提供的`syscall`或`syscall_number`(如`int 0x80`在早期的x86架构)来实现。 每个系统调用内核中都有一个对应的入口点,这些入口点通常是位于`arch/$(arch)/entry.S`这样的内核源码文件中。当你使用`syscall`指令时,实际上是将一个编号和一些参数放入CPU的特定寄存器(比如`eax`和`ebx`),然后进入内核系统调用处理程序。 下面是一些关键概念: 1. **系统调用号(System Call Number)**:这是每个系统调用内核中的唯一标识符,通常是一个整数,比如`read`、`write`、`open`等。 2. **系统调用表(System Call Table)**:内核维护了一个表格,将每个调用号映射到相应的函数地址。 3. **用户态和内核态**:系统调用发生在从用户态(非特权级别)到内核态(特权级别)的上下文中切换。 4. **参数传递**:用户模式下,参数通常通过寄存器传递,调用结束后通过返回值机制(如`eax`)传递结果回用户空间。 如果你对Linux系统调用的具体实现或者如何编写与之交互的用户空间代码感兴趣,我可以进一步讲解相关的编程接口如glibc的`syscall`宏,或者解释内核如何处理这些调用的过程。是否有特定的问题想要深入了解?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值