用户态open()源码分析&实践

本文对Linux-5.15内核中用户态open()函数进行源码分析与实践。先介绍open操作流程,接着用perf工具追踪函数调用关系,详细分析open系统调用各入口函数。最后从实践角度学习相关结构体,编写用户态测试函数和内核模块并调试,加深对open()函数的认识。

用户态open()源码分析&实践

一、引言:

在阅读了相关文章之后,对于open进行源码分析:

我们在用户态使用open打开一个文件时,需要传入两个参数:文件路径和打开权限

fd = open(file_path, O_RDONLY);//file_path文件路径,O_RDONLY打开权限
  • 内核首先检查这个文件路径存在的合法性
  • 同时还需检查使用者是否有合法权限打开该文件;
  • 如果一切顺利,那么内核将对访问该文件的进程创建一个file结构
  • open操作成功会返回一个fd文件描述符;

二、内核linux-5.15 open源码分析

在进入冗杂的源码分析前,可以先大致看一下下面这张图,对open的脉络有个有一定的印象:

在这里插入图片描述

2.0、通过perf打印open的函数调用关系:

在研究open()源码之前,先通过linux自带的perf工具追踪open所涉及的函数调用关系。

关于perf工具的安装和使用可以参考这篇文章:Perf的安装与简单使用_perf安装_问号小朋友的博客-优快云博客

这里默认虚拟机已经安装并可以正常使用perf工具;

2.0.1、测试程序:

先写一个用户态的调用open()函数的测试程序,内容如下:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h> // 包含头文件 <unistd.h>

int main() {
   
   
    char *file_path = "test.txt";
    int fd;
    while(1){
   
   //通过while循环打开关闭文件,便于perf监测到open()函数;
        fd = open(file_path, O_RDONLY);
    
        if (fd == -1) {
   
   
            perror("Error in opening file");
            return -1;
        } else {
   
   
            printf("File opened successfully with file descriptor: %d\n", fd);
            // 在此处可以执行其他操作,比如读取文件内容或者进行其他处理。
            close(fd); // 关闭文件
        }
    
        printf("PID==>%d\n",getpid());//获得该进程pid,便于使用perf跟踪该进程;
    }
    return 0;
}

程序要点:

  • while(1)语句循环调用open(),保证perf能监测到open()函数;
  • 通过getpid()获得进程pid号,方便perf传参;
2.0.2、使用perf监测程序:
  • 编译测试程序gcc -o open_test open_test.c;
  • 运行程序 ./open_test
  • 在另一个终端通过命令行使用perf监测该程序
    • 在运行的测试进程窗口会不停的open(),close(),输出该进程的进程号;
    • 在另一个终端输入sudo perf record -p 117069 -g -F 200 -- sleep 10
      • 这里的-p 是通过进程号跟踪某一进程,
      • -g是将跟踪结果输出为树状图;
      • -F是跟踪频率;
  • 跟踪结束后将结果流入一个文件中sudo perf report >op.txt;
  • 在op.txt中便可以找到跟踪结果的树状图;

在这里插入图片描述

2.1、open系统调用的入口函数:

open系统调用的入口函数应该为sys_open。不过,目前内核统一使用SYSCALL_DEFINEx宏来描述系统调用入口函数,因此可以在/fs/open.c文件中找到该入口函数,具体如下所示:

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)//通过该宏进行open的系统调用
{
   
   
	if (force_o_largefile())
		flags |= O_LARGEFILE;
	return do_sys_open(AT_FDCWD, filename, flags, mode);//进入do_sys_open
}

而SYSCALL_DEFINE3宏定义在/include/syscalls.h中,此处若干个SYSCALL_DEFINE(包含1~6)被宏定义为SYSCALL_DEFINEx()函数,该函数通过传参实现系统调用;

#define SYSCALL_DEFINE1(name, ...) SYSCA LL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE_MAXARGS	6

#define SYSCALL_DEFINEx(x, sname, ...)				\
	SYSCALL_METADATA(sname, x, __VA_ARGS__)			\
	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

SYSCALL_DEFINE3函数中可以看到其被映射到do_sys_open函数进行系统调用;

2.2、do_sys_open()

在/fs/open.c文件中定义着do_sys_open()函数,该函数的逻辑流程:

  • 通过build_open_how函数将用户态的flags和mode转换成对应的内核态标志,并构建成一个open_how结构体用于传递给文件打开操作;
  • 通过调用do_sys_openat2函数实际执行文件打开操作;

在这里插入图片描述

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
   
   
    // 创建一个名为 "how" 的结构体,并通过 build_open_how 函数构建该结构体的实例
    struct open_how how = build_open_how(flags, mode);
    
    //调用 do_sys_openat2 函数,实际执行文件打开操作的地方,在指定的条件下打开文件
    return do_sys_openat2(dfd, filename, &how);
}

这里牵扯到了两个函数build_open_how以及do_sys_openat2,前者并不是本次学习open的重地,其简要分析如下,后者do_sys_openat2的详细分析参见下一节。

inline struct open_how build_open_how(int flags, umode_t mode)
{
   
   
    struct open_how how = {
   
   
        .flags = flags & VALID_OPEN_FLAGS,  // 使用按位与运算将 flags 与 VALID_OPEN_FLAGS 进行位操作,过滤掉不合法的标志位,然后将结果存储在 how 结构体的 flags 成员中。
        .mode = mode & S_IALLUGO,  // 初始化结构体中的 mode 成员,按位与运算将 mode 与 S_IALLUGO 进行位操作,以确保只保留合法的文件权限标志位,结果存储在 how 结构体的 mode 成员中
    };

    /* O_PATH beats everything else. */
    if (how.flags & O_PATH)
        how.flags &= O_PATH_FLAGS;  // 如果 flags 中包含 O_PATH 标志,那么将 flags 与 O_PATH_FLAGS 进行位操作,以确保只保留 O_PATH_FLAGS 中的标志位
    /* Modes should only be set for create-like flags. */
    if (!WILL_CREATE(how.flags))
        how.mode = 0;  // 如果 flags 不包含任何创建(create-like)标志,将 how 结构体的 mode 成员设置为 0,只有在打开文件时需要创建文件时,才会设置文件权限模式
    return how;  // 返回构建好的 how 结构体
}

总的来说,do_sys_open 函数负责处理用户空间传递过来的文件打开请求,构建相应的 open_how 结构体,然后调用 do_sys_openat2 函数完成文件的打开操作;

2.3、do_sys_openat2():

主要作用是根据传入的参数 dfd(目录文件描述符)、filename(要打开的文件名)、以及 how(文件打开选项和模式)来执行文件打开操作。

  • build_open_flags()构建文件打开标志;
  • get_unused_fd_flags():根据文件打开标志获得一个未使用过的fd文件描述符
  • do_filp_open():调用 do_filp_open 函数来打开文件;
  • fd_install():安装文件描述符,将文件描述符 fd 与打开的文件关联起来,即将fd与struct file进行绑定;

在这里插入图片描述

static long do_sys_openat2(int dfd, const char __user *filename,
			   struct open_how *how)
{
   
   
	struct open_flags op;//用于存储文件打开标志
	/*调用 build_open_flags 函数
	来创建打开文件标志,返回文件描述符 fd*/
	int fd = build_open_flags(how, &op);
	struct filename *tmp;//定义一个指向 filename 结构体的指针 tmp
	if (fd)
		return fd;//fd不为0,则表示已经打开;

	tmp = getname(filename);//通过 getname 函数获取文件名的 filename 结构体
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	/*获取一个该进程未使用的fd*/
	fd = get_unused_fd_flags(how->flags);// 根据文件打开标志分配一个未使用的fd文件描述符
	if (fd >= 0) {
   
   
		/*生成一个struct file*/
		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与struct file进行绑定*/
			fd_install(fd, f);// 打开文件成功后,通知文件系统监视器
		}
	}
	putname(tmp);// 释放文件名的 filename 结构体
	return fd;
}

此函数中重点就是do_filp_open(),通过do_filp_open函数实现真正的打开文件操作;

2.4、do_filp_open():

/fs/namei.c文件中定义着该函数。

在这里插入图片描述

struct file *do_filp_open(int dfd, struct filename *pathname,
		const struct open_flags *op)
{
   
   
	struct nameidata nd;//用于文件路径名的处理
	int flags = op->lookup_flags;//从传入的 open_flags 结构体 op 中提取 lookup_flags 字段的值,存储在本地变量 flags 中。
	struct file *filp;

	set_nameidata(&nd, dfd, pathname, NULL);//设置 nameidata 结构体 nd 的属性,以准备打开文件

	/*用 path_openat 函数以打开文件,
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值