系统调用的内核实现,一文讲透open函数内核真实实现。

上一篇:https://blog.youkuaiyun.com/weixin_42523774/article/details/103341058

· 本文是Linux文件系列的第三篇,上文《系统调用如何进入内核层次,深入glibc寻找open函数真实实现。》,讲到了从应用程序到如何通过glibc中进入内核的过程,本文接着上文,讲系统调用在内核中如何实现,还是以open函数为例,后续会介绍read和write函数的内核实现。
· 本文采用linux3.2.1内核代码[下载处],并推荐使用Source Insight,这会让这个过程更容易。
· 我想先提醒一下,这些系统调用的操作者是某一个进程,open操作其实是在read和write操作之前的一个预备动作,如果是真实文件,这个操作就是将文件从硬盘(或flash)中读取到内存中,当然这个过程中要防止多个进程的同时操作,因此会有很多锁作为保护,这也是代码理解的难点。
· 首先将搜寻结构图展示出来,让大家先知其全貌,后续在一步一步细讲:(前面直接套用的函数就没写注释)

compat_sys_openat
|-->do_sys_open
	|-->do_filp_open
		|-->do_filp_open
			|-->path_openat
				|-->path_init  #  nd初始化。
				|-->link_path_walk  #  真实寻找。
				|	|-->for(;;) {
   
				|	|	|-->may_lookup  #  查询文件权限是否允许访。
				|	|	|-->hash = init_name_hash();  #  算出该文件名的哈希值,和文件名长度。
				|	|	|-->##  判断文件名是否使用了"."或者"..",来标明文件类型type  ##
				|	|	|-->d_hash # 查询是否有哈希表存在。
				|	|	|-->walk_component  #  依据刚刚识别的类型,做单次搜索。
				|	|	|	|-->handle_dots  #  "."".." 文件名处理。
				|	|	|	|-->do_lookup   # 其他文件的搜索。
				|	|	|	|	|-->__d_lookup_rcu   # 不带rcu搜寻。
				|	|	|	|	|-->__d_lookup      # 带rcu,可能引起阻塞搜寻。
				|	|	|	|	|-->d_alloc_and_lookup  # 上两步搜不到,就要通过硬盘文件系统搜寻。
				|	|	|	|-->should_follow_link #  查看是否可以继续链接文件,前面提到过,对链接次数有限制。
				|	|	|-->nested_symlink  # 限制递归调用不能超过8次,符号链接不能超过40次。
				|	|	|-->can_lookup  # 判断是否可以继续查找,可以则继续。
				|	|	|-->terminate_walk(nd);  #  查找完成操作,包括解RCU锁。
				|	|-->}
				|-->do_last #  查找完成,做打开文件操作

1.续接前文

· 书接上文,我们找到了内核代码位置include\asm-generic\unistd.h的如下语句:

#define __NR_openat 56
__SC_COMP(__NR_openat, sys_openat, compat_sys_openat)

· 根据这个分别找到了如下的宏定义:

#define __SC_COMP(_nr, _sys, _comp) __SYSCALL(_nr, _comp)
#define __SYSCALL(nr, call) [nr] = (call),

· 这里我们就明白了,我们这个宏定义,将 compat_sys_openat函数的地址赋给了一个变量,然后让swi指令去调用这个函数,我们在fs/compat.c找到了这个函数:

asmlinkage long
compat_sys_openat(unsigned int dfd, const char __user *filename, int flags, int mode)
{
   
	return do_sys_open(dfd, filename, flags, mode);
}

· asmlinkage 表示这个是通过汇编指令链接过来的,do_sys_open这就是open函数的真实实现,入参分别 AT_FDCWD, file, oflag, mode,下面我们看看它做了什么。

2.do_sys_open

· 本文将整个代码结构都展示出来,大部分内容通过代码中加注释的方式来解释,关键部分在代码后用文字说明。

long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
   
	struct open_flags op;  //  创建一个标志集合
	/*  从flags和mode中分离出lookup event bitmap,返回值lookup表示正在做的事件(寻找目录和结构),op中存有找到之后期望做的事件(执行或创建)*/
	int lookup = build_open_flags(flags, mode, &op);
	char *tmp = getname(filename); //  将文件名从用户空间复制到内核空间
	int fd = PTR_ERR(tmp);

	if (!IS_ERR(tmp)) {
   
		fd = get_unused_fd_flags(flags); //  根据flags获取一个对应类型的未使用的文件号
		if (fd >= 0) {
   
			struct file *f = do_filp_open(dfd, tmp, &op, lookup);  //  执行文件打开过程
			if (IS_ERR(f)) {
     // 判断是否出错
				put_unused_fd(fd);  // 释放刚用get_unused_fd_flags申请的文件号
				fd = PTR_ERR(f);    // 将指针转化为错误码返回
			} else {
   
				fsnotify_open(f);   // 通知其他相关项,该文件已经打开
				fd_install(fd, f);  // 安装文件指针到fd数组
			}
		}
		putname(tmp); //  释放内核态缓存
	}
	return fd;
}

· do_sys_open函数首先做了做了设置open_flags标志和转换用户空间的文件名到内核空间,核心功能是调用do_filp_open,我们进一步寻找:

3.do_filp_open

struct file *do_filp_open(int dfd, const char *pathname,
		const struct open_flags *op, int flags)
{
   
	struct nameidata nd;
	struct file *filp;
	/* 核心就是path_openat,就是沿着打开文件名的整个路径,一层层解析,最后得到文件对象 */
	filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
	if (unlikely(filp == ERR_PTR(-ECHILD)))
		filp = path_openat(dfd, pathname, &nd, op, flags);
	if (unlikely(filp == ERR_PTR(-ESTALE)))
		filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL);
	return filp;
}

· 这里的核心就是path_openat,就是沿着打开文件名的整个路径,一层层解析,最后得到文件对象。但是为什么最多会调用3次?
· 由操作系统发展趋势一文中讲过,我们讲过当前的操作系统的内存管理系统中,CPU只能间接通过将硬盘数据缓存到内存中才能使用。而用来缓存文件的区域叫dentry cache ,这是一个哈希表。
· 第一次搜索采用rcu-walk方式搜索dentry cache,这种方式不会阻塞等待(RCU才会阻塞),更高效,但是可能会失败(因为文件inode本身还有顺序锁和自旋锁的保护);
· 第二次搜索则是采用ref-walk方式搜索dentry cache,考虑rcu锁的情形,但是可能会阻塞。
· 如果这样仍然失败,说明内存中没有该文件,这样就需要第三次,直接通过硬盘文件系统,进入硬盘慢速搜索,要带LOOKUP_REVAL标志。

4.path_openat

static struct file *path_openat(int dfd, const char *pathname,
		struct nameidata *nd, const struct open_flags *op, int flags)
{
   
	struct file *base = NULL;
	struct file *filp;
	struct path path;
	int error;

	filp = get_empty_filp
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值