用户态open函数如何调用内核态open函数

575 阅读3分钟

1.图示

首先使用华清远见的一张图式,比较清晰,本文会从用户态到内核态进行大致的分析 image.png

2.虚拟文件系统简介

在Linux系统中,对文件的操作抽象为对虚拟文件系统的操作,虚拟文件系统屏蔽了底层逻辑,使用多态的方式将不同的文件系统的操作接口赋值给虚拟文件系统,从而使得对文件的操作变为对虚拟文件系统的操作.每一个文件都用一个inode结点表示。在Linux系统中一个进程使用一个task_struct结构体表示,其中有一个抽象对象为file_struct指针,对于进程中的文件进行管理,在file_struct中有一个重要的数组fd_array[]下标为某一个具体的文件描述符索引,每一个索引存放文件的操作接口集合,打开文件的权限模式等,文件的读写位置路径等等,此数组存放的是struct file结构体,其中包括了一个file opreation结构体,下面会从inode的file_operation结构体于struct file结构体如何赋值进行讲解

可以看之前写的文章# Linux内核如何设计字符设备

3.应用层

3.1 创建设备文件

在传统的Linux驱动开发中会使用mknod这个命令来手动创建设备文件,现在使用的是class_create和device_create来自动创建设备文件这里创建一个名称为/dev/led的设备文件,其主设备号为20,次设备号为0,命令为:

mknod /dev/led 20 0

下图是mknod命令的调用过程,由于这是大致了解,中间过程忽略

image.png 最终会调用到init_special_inode函数,其具体实现为:

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
       inode->i_mode = mode;
       if (S_ISCHR(mode)) {//判断文件是否属于字符设备类型
              inode->i_fop = &def_chr_fops;//保存文件接口
             inode->i_rdev = rdev;//记录主次设备号
      } else if (S_ISBLK(mode)) {//判断文件是否为块设备类型
            inode->i_fop = &def_blk_fops;
               inode->i_rdev = rdev;
      } else if (S_ISFIFO(mode))//判断是否为FIFO文件
               inode->i_fop = &pipefifo_fops;
       else if (S_ISSOCK(mode))
               ;       /* leave it no_open_fops */
      else
}

首先判断文件的inode类型,如果是字符设备类型,则把def_chr_fops作为该文件的操作接口,并把设备号记录在inode->i_rdev中,def_chr_fops的代码表示为:

const struct file_operations def_chr_fops = {
      .open = chrdev_open,
       .llseek = noop_llseek,
};

3.2 open函数调用过程

可以看如下图解

image.png 其中get_unused_fd_flags为本次操作分配一个未使用过的文件操作符,do_file_open生成一个空白的struct file结构体,并且绑定到空闲元素上,从文件系中查找到文件对应的inode

static int do_dentry_open(                                                                 
struct file *f,
struct inode *inode,
int (*open)(struct inode *, struct file *),
const struct cred *cred)
{
	...
	/*把inode的i_fop赋值给struct file的f_op*/
	f->f_op = fops_get(inode->i_fop);
	...
	if (!open)
		open = f->f_op->open;
	if (open) {
		error = open(inode, f);
		if (error)
			goto cleanup_all;
	}
	...
}

do_dentry_open会将inode的i_fop赋值为file的f_op,从第2节来看,inode的i_fop为def_chr_fops,再调用file的f_op的open函数,即chrdev_open

static int chrdev_open(struct inode *inode, struct file *filp)
{
	const struct file_operations *fops;
	struct cdev *p;
	struct cdev *new = NULL;
	...
	struct kobject *kobj;
	int idx;
	/*从内核哈希表cdev_map中,根据设备号查找自己注册的sturct cdev,获取cdev中的file_operation接口*/
	kobj = kobj_lookup(cdev_map, inode>i_rdev,&idx);
	new = container_of(kobj, struct cdev, kobj);
	...
	inode->i_cdev = p = new;
	...
	fops = fops_get(p->ops);
	...
	/*把cdev中的file_operation接口赋值给struct file的f_op*/
	replace_fops(filp, fops);
	
	/*调用自己实现的file_operation接口中的open函数*/
	if (filp->f_op->open) {
		ret = filp->f_op->open(inode, filp);
		if (ret)
			goto out_cdev_put;
	}
	...
}

chrdev_open会从inode结点中保存的主次设备号与哈希表cdev_map中cdev保存的主次设备号进行对比,从而找到找到对应的cdev设备,再把把cdev中的file_operation接口赋值给struct file的f_op,再调用struct file的open函数,即cdev中保存的open函数

4.驱动层

在字符设备开发中,我们会使用到cdev_init,cdev_add来保存对应的cdev的主次设备号以及file_operation结构体,可以看之前写的文章

# Linux内核如何保存file_operation接口

以上便是用户态open函数如何调用内核态open函数的简单分析,具体可以看代码分析