Linux下一切设备均视为文件,本文来简单分析下系统调用里面是如何打开文件的。本文仅以下面简单代码为例,看Linux内核如何完成open的系统调用的(参考内核版本:4.14.13,x86_64)。为了简单起见,本文中暂时忽略并发性访问的同步问题以及错误处理(一般情况下,上述事例中的操作都会成功,除非有权限问题,所以我们先不考虑函数调用失败的情况)。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int fd = open("/home/gaobsh/a.txt", O_RDONLY);
if (fd >= 0) close(fd);
return 0;
}
从do_sys_open()函数开始分析,至于程序如何进入该函数,本文不做分析。该函数定义如下:
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
/* 函数参数如下:
* dfd = -100 (AT_FDCWD)
* filename = "/home/gaobsh/a.txt"
* flags = 0x8000 (O_RDONLY | O_LARGEFILE);
* mode = 0
*/
struct open_flags op;
int fd = build_open_flags(flags, mode, &op);
struct filename *tmp;
if (fd)
return fd;
tmp = getname(filename);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
fd = get_unused_fd_flags(flags);
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, &op);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
fd_install(fd, f);
}
}
putname(tmp);
return fd;
}
其中各个参数除了 int dfd 之外,其他的都很熟悉,就是open()系统调用的各个参数。至于参数dfd,它的值为 AT_FDCWD (-100).在这个函数里面,首先分析下 build_open_flags()函数。这个函数主要是用来构建flags,并返回到结构体 struct open_flags op中。该函数定义如下:
static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op)
{
int lookup_flags = 0;
int acc_mode = ACC_MODE(flags);
flags &= VALID_OPEN_FLAGS;
if (flags & (O_CREAT | __O_TMPFILE)) /* if 判断为 0 */
...
else
op->mode = 0;
/* Must never be set by userspace */
flags &= ~FMODE_NONOTIFY & ~O_CLOEXEC;
if (flags & __O_SYNC) /* if 判断为 0 */
...
if (flags & __O_TMPFILE) { /* if 判断为 0 */
...
} else if (flags & O_PATH) { /* if 判断为 0 */
...
}
op->open_flag = flags;
if (flags & O_TRUNC) /* if 判断为 0 */
...
if (flags & O_APPEND) /* if 判断为 0 */
...
op->acc_mode = acc_mode;
op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN; /* op->intent = LOOKUP_OPEN */
if (flags & O_CREAT) { /* if 判断为 0 */
...
}
if (flags & O_DIRECTORY) /* if 判断为 0 */
...
if (!(flags & O_NOFOLLOW)) /* if 判断为 1 */
lookup_flags |= LOOKUP_FOLLOW;
op->lookup_flags = lookup_flags;
/* 此时 op 各个成员变量值如下:
* op->mode = 0
* op->open_flag = 0x8000
* op->acc_mode = 0x4
* op->intent = 0x100 (LOOKUP_OPEN)
* op->lookup_flags = 0x1 (LOOKUP_FOLLOW)
*/
return 0;
}
可见在本示例中,不考虑很多特殊情况,该
build_open_flags()函数中很多代码是不用执行的。 在
get_unused_fd_flags()函数中,找到一个可用的文件描述符,并返回该值。其中涉及的函数和数据结构这里不做分析,
这篇博客解释的非常清楚,虽然是基于ARM的,但源码基本一样。
下面来重点介绍do_filp_open()函数。该函数定义如下:
struct file *do_filp_open(int dfd, struct filename *pathname,
const struct open_flags *op)
{
/* 函数入口参数:
* dfd = -100
* pathname.name = "/home/gaobsh/a.txt"
* op 见 build_open_flags
*/
struct nameidata nd;
int flags = op->lookup_flags;
struct file *filp;
set_nameidata(&nd, dfd, pathname);
filp = path_openat(&nd, op, flags | LOOKUP_RCU);
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(&nd, op, flags);
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
restore_nameidata();
return filp;
}
其中set_nameidata()函数主要用来设置结构体struct nameidata的值,这个结构体是个非常重要的结构提,在解析和查找路径名时会经常引用到。函数定义如下:
static void set_nameidata(struct nameidata *p, int dfd, struct filename *name)
{
struct nameidata *old = current->nameidata;
p->stack = p->internal;
p->dfd = dfd;
p->name = name;
p->total_link_count = old ? old->total_link_count : 0;
p->saved = old;
current->nameidata = p;
}
再下面就是
do_filp_open()函数的主要部分:调用
path_openat()函数。这个函数可能被调用三次。第一次调用尝试以
rcu模式打开,在本事例中我们分析在rcu模式中成功打开的情况。该函数定义如下:
static struct file *path_openat(struct nameidata *nd,
const struct open_flags *op, unsigned flags)
{
/* 函数入口参数: flags = LOOKUP_FOLLOW | LOOKUP_RCU */
const char *s;
struct file *file;
int opened = 0;
int error;
file = get_empty_filp();
if (IS_ERR(file)) /* if 判断为 0 */
...
file->f_flags = op->open_flag; /* f_flags == O_LARGEFILE */
if (unlikely(file->f_flags & __O_TMPFILE)) { /* if 判断为 0 */
...
}
if (unlikely(file->f_flags & O_PATH)) { /* if 判断为 0 */
...
}
s = path_init(nd, flags);
if (IS_ERR(s)) { /* if 判断为 0 */
...
}
while (!(error = link_path_walk(s, nd)) &&
(error = do_last(nd, file, op, &opened)) > 0) { /*do_last()函数返回 0,循环退出 */
...
}
}
terminate_walk(nd);
out2:
if (!(opened & FILE_OPENED)) { /* if 判断为 0 */
...
}
if (unlikely(error)) { /* if 判断为 0 */
...
}
return file;
}
先看其中的