Linux 系统调用之open(一)

本文深入分析Linux内核中open()系统调用的实现,从do_sys_open()开始,涉及build_open_flags(), do_filp_open(), set_nameidata()等关键函数,详细解释了路径解析和权限检查的过程,最终在vfs_open()中完成文件打开操作。" 136227578,8057374,Golang 指针与结构体详解,"['golang', '开发语言', '后端']
摘要由CSDN通过智能技术生成
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;
}

先看其中的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值