首先使用华清远见的一张图式,比较清晰,本文会从用户态到内核态进行大致的分析
在Linux系统中,对文件的操作抽象为对虚拟文件系统的操作,虚拟文件系统屏蔽了底层逻辑,使用多态的方式将不同的文件系统的操作接口赋值给虚拟文件系统,从而使得对文件的操作变为对虚拟文件系统的操作.每一个文件都用一个inode结点表示。在Linux系统中一个进程使用一个task_struct结构体表示,其中有一个抽象对象为file_struct
指针,对于进程中的文件进行管理,在file_struct
中有一个重要的数组fd_array[]
下标为某一个具体的文件描述符索引,每一个索引存放文件的操作接口集合,打开文件的权限模式等,文件的读写位置路径等等,此数组存放的是struct file结构体,其包含了一个file opreation结构体,下面会从inode的file_operation结构体于struct file结构体如何赋值进行讲解
可以看之前写的文章# Linux内核如何设计字符设备
在传统的Linux驱动开发中会使用mknod这个命令来手动创建设备文件,现在使用的是class_create和device_create来自动创建设备文件,这里创建一个名称为/dev/led的设备文件,其主设备号为20,次设备号为0,命令为:
mknod /dev/led 20 0
下图是mknod命令的调用过程,由于这是大致了解,中间过程忽略
最终会调用到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,
};
可以看如下图解
其中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函数
在字符设备开发中,我们会使用到cdev_init,cdev_add来保存对应的cdev的主次设备号以及file_operation结构体,可以看之前写的文章,所以就可以和之前小节衔接起来了
# Linux内核如何保存file_operation接口
以上便是用户态open函数如何调用内核态open函数的简单分析,具体可以看代码分析