Linux内核学习之 -- 系统调用open()和write()的实现笔记

环境

  • linux 4.19

很多注释都写在了代码里,就不单独总结出来放到文章中了,看一下代码+注释回忆一下即可

1.open()系统调用。

用户空间使用open(),最终调用到内核的函数如下,至于系统调用的过程,请看我的另外一篇博客:Linux内核学习之 – ARMv8架构的系统调用

fs/open.c:
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) // g,三个参数为filename, flags, mode
{
   
	if (force_o_largefile())	// g, (BITS_PER_LONG != 32),其中BITS_PER_LONG应该是定义long类型的大小,32位架构long就是32,64位架构long是64
		flags |= O_LARGEFILE;	// g, 如果64位架构,补上O_LARGEFILE标志

	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

只调用了一个函数,就是do_sys_open(),进一步分析do_sys_open()函数:

fs/open.c:
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)	
{
   
	struct open_flags op;							// g,strcut open_flags这个结构体会包含一些控制flag,根据用户传入的参数进行初始化,之后open的操作就不再依赖于用户参数,而是依赖于这个结构体

	int fd = build_open_flags(flags, mode, &op);	// g, 根据传入的flagse会mode,来初始化op结构体,open的模式,flag等都在open_flags op结构体中
	struct filename *tmp;							// g, struct filename会在open过程中记录处理过后的文件名信息。之后open对h文件名的获取依赖于这个结构体

	if (fd)
		return fd;

	// g, 根据一个字符串指针,返回一个struct filename*,该结构体的->name域所指内存存储着文件名
	tmp = getname(filename);  	
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	fd = get_unused_fd_flags(flags);	// g, 分配一个没用过的,符合要求的fd号,同时更新current->files->fdt里面的三个重要bitmap,有必要的话会分配新的占用更多内存的fdt替换原来的fdt
	if (fd >= 0) {
   
		struct file *f = do_filp_open(dfd, tmp, &op); // g, 该函数解析传入的文件路径名(主要是获取路径的最后一个分量对应的dentry以及inode),然后创建一个新的struct file结构体,使用找到的dentry和inode初始化这个file
		if (IS_ERR(f)) {
   
			put_unused_fd(fd);
			fd = PTR_ERR(f);
		} else {
   
			fsnotify_open(f);			// g, 通知其他相关项如事件通知、audit机制、文件系统监控系统等等,该文件已经打开
			fd_install(fd, f);			// g, 把file和fd关联起来,建立了current(当前进程的tast_struct)->files->fdt->fd[fd]与f的联系(fdt->fd是个struct file**指针,其实就是current fd array)
		}
	}
	putname(tmp);
	return fd;
}

在这里做一个总结,do_sys_open()实现的功能可以概括为:

  1. 解析用户传入的各种参数,包括模式,权限,文件名
  2. 分配一个fd号
  3. 根据解析的参数,创建并初始化一个struct file结构体。
  4. 建立fd号与创建的struct file的联系,后续的write(),read()等可以直接通过fd找到对应的struct file

接下来对其中的几个重要函数进行分析,在此之前先整理一个调用层次:

do_sys_open()
	-->build_open_flags()		// 解析用户参数
	-->getname()				// 解析用户传入的文件名
		-->getname_flags()
	-->get_unused_fd_flags()	// 获取一个未使用过的fd
		-->__alloc_fd()
	-->do_filp_open()			// 打开文件,创建并初始化一个新的struct file结构体
		-->set_nameidata()
		-->path_openat()
			-->path_init()
			-->link_path_walk()
			-->do_last()
	-->fsnotify_open()			// 通知其他相关项如事件通知、audit机制、文件系统监控系统等等,该文件已经打开
	-->fd_install()				// 建立分配的fd号与创建的struct file的关系
		-->__fd_install()

所有函数调用并没有全部展示,只是列出了open()系统调用中几个重要的函数

1.1 getname()

该函数用来转换用户传入的路径名,解析用户传入的路径名来填充为一个内核中的结构体struct filename:

fs/namei.c:
getname(const char __user * filename)
{
   
	return getname_flags(filename, 0, NULL);
}

其中只是单纯的调用了函数getname_flags():

fs/namei.c:

struct filename *
getname_flags(const char __user *filename, int flags, int *empty)
{
   
	struct filename *result;
	char *kname;
	int len;
	BUILD_BUG_ON(offsetof(struct filename, iname) % sizeof(long) != 0);

	// g, 与linux的审计机制有关,会记录一些自己设置的规则信息,如果开启了此功能就可以从此模块获取filename结构体。
	// g, 需要开启一个宏CONFIG_AUDITSYSCALL才有效果,看了下.config中CONFIG_AUDITSYSCALL is not set,所以暂时不去研究这个审计模块
	result = audit_reusename(filename);		
	if (result)
		return result;

	result = __getname();			// g, 是个宏,= kmem_cache_alloc(names_cachep, GFP_KERNEL),该函数会从names_cachep的slab分配器里分配一个struct filename出来
	if (unlikely(!result))	
		return ERR_PTR(-ENOMEM);

	/*
	 * First, try to embed the struct filename inside the names_cache
	 * allocation
	 */
	kname = (char *)result->iname;
	result->name = kname;				// g, result->name和result->iname[]都将指向同一个char

	len = strncpy_from_user(kname, filename, EMBEDDED_NAME_MAX);  // g, 应该跟copy_from_user大同小异,是一个用户空间和内存空间的字符串拷贝函数,最大复制的字符数不能超过EMBEDDED_NAME_MAX
	if (unlikely(len < 0)) {
   
		__putname(result);		// g, 如果有问题,直接释放这个slab
		return ERR_PTR(len);
	}

	
	/* -------------------- 分割线,上面是文件名不太大的情况,下面是当文件名超过了EMBEDDED_NAME_MAX = 4096 - offsetof(struct filename, iname) 之后的操作
	 * Uh-oh. We have a name that's approaching PATH_MAX. Allocate a
	 * separate struct filename so we can dedicate the entire
	 * names_cache allocation for the pathname, and re-do the copy from
	 * userland.
	 */
	// g, note: likely和unlikely也有点说法,可以优化if和else的执行顺序
	if (unlikely(len == EMBEDDED_NAME_MAX)) {
    					 // g, 如果文件名字符串很长,已经 >= EMBEDDED_NAME_MAX了
		// g, offsetof:返回iname[]在filenamed中的偏移值
		const size_t size = offsetof(struct filename, iname[1]); 
		kname = (char *)result;									// g, 使kname当temp指针记录一下result,暂时称为result_old

		/*
		 * size is chosen that way we to guarantee that
		 * result->iname[0] is within the same object and that
		 * kname can't be equal to result->iname, no matter what.
		 */
		result = kzalloc(size, GFP_KERNEL);		// g,分配一段&(struct filename.iname[1]) - &(struct filemae)这么大小的内存,此时result指向新的内存空间了,暂时称为result_new
		if (unlikely(!result)) {
   
			__putname(kname);
			return ERR_PTR(-ENOMEM);
		}
		result->name = kname; 					// g,相当于result_new->name = result_old
		len = strncpy_from_user(kname, filename, PATH_MAX);	 // g, 然后覆盖result_old,相当于把原来从slab中分配的内存全部当做文件名的缓存区了,result_old不再是一个struct filename结构体,而是变成了一段内存
		if (unlikely(len < 0)) {
   
			__putname(kname);
			kfree(result);
			return ERR_PTR(len);
		}
		if (unlikely(len == PATH_MAX)) {
   			// g, 文件名
			__putname(kname);
			kfree(result);
			return ERR_PTR(-ENAMETOOLONG);
		}
	}

	result->refcnt = 1;			// g,可能是个标记位置
	/* The empty path is special. */
	if (unlikely(!len)) {
   
		if (empty)
			*empty = 1;
		if 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值