字符设备文件的打开流程
相关结构体
流程涉及相关结构体如下:
struct inode {
……
dev_t i_rdev;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
unsigned i_dir_seq;
};
……
}
mknod /dev/hillo c 255 0 //创建设备节点 生成 inode 结构体来描述文件的静态属性
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
// 每个文件都对应一个 struct file 结构体,打开一个文件,系统都会将 struct file 添加到 struct fdtable 的数组中
struct fdtable {
unsigned int max_fds;
struct file __rcu **fd; /* current fd array */
fd_set *close_on_exec;
fd_set *open_fds;
struct rcu_head rcu;
struct fdtable *next;
};
// 返回的文件描述符就是此文件的 struct file 在数组中的下标,从3开始(stdin-0 stdout-1 stderr-2)
struct file {
struct path f_path; // 文件路径
const struct file_operations *f_op; // 文件操作对象
unsigned int f_flags; //文件标志
fmode_t f_mode; // 文件权限
loff_t f_pos; // 文件当前偏移量
void *private_data; // 私有数据
... ...
}
open函数调用过程
fd = open("/dev/hello",O_RDWR); //应用程序调用 open函数 用户空间
|
---> sys_open(const char *,int ,int,); //系统调用 内核空间
|
---> static int chardev_open(struct inode *inode,struct file *filp); //VFS层
|
---> kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
---> new = container_of(kobj, struct cdev, kobj); //通过在cdev_map中查找到的cdev的成员变量 struct kobject
//结构体 地址,转化成字符设备 cdev 结构体的地址。
---> inode->i_cdev = p = new;
---> fops = fops_get(p->ops); // 通过fops_get得到p指向的file_operations对象ops
---> replace_fops(filp, fops); // 通过replace_fops用它替换掉file中的f_op,后续就是通过filp来操作文件
---> ret = filp->f_op->open(inode, filp); //调用驱动中的open函数
linux内核源码
static int chrdev_open(struct inode *inode, struct file *filp)
{
const struct file_operations *fops;
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj);
spin_lock(&cdev_lock);
/* Check i_cdev again in case somebody beat us to it while
we dropped the lock. */
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
ret = -ENXIO;
fops = fops_get(p->ops);
if (!fops)
goto out_cdev_put;
replace_fops(filp, fops);
if (filp->f_op->open) {
ret = filp->f_op->open(inode, filp);
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
流程总结:
通过 inode 结构体中的 i_rdev 搜索对应的字符设备 i_cdev — 对应 struct cdev 结构体
查找 struct cdev 结构体的过程,是通过一个全局变量 cdev_map 来完成的。cdev_map 的成员变量 probes 是一个数组,在驱动的 init 函数中,将驱动的文件操作函数集(和其他一些数据结构)以设备号为索引塞进这个数组里面,而在(第一次)调用chrdev_open()函数的时候,再以文件号为索引,从这个数组里面找到我们需要的文件操作集(以及其他相关的数据结构)。
struct file 结构体 — 表示一个已经打开的文件,当文件关闭时,该结构体被释放
struct cdev 结构体 — 表示一个字符设备,对应一个字符设备驱动
struct inode 结构体 — 表示一个 /dev/目录下的一个字符设备驱动节点
应用程序执行open函数,将一个inode结构体(/dev/xxx)通过系统调用传入给内核,调用内核中的 chardev_open函数,在该函数中通过inode结构体中的成员变量(inode->i_rdev 包含设备号),在 cdev_map 中查找设备号所对应的字符设备驱动程序cdev 结构体的成员变量kobject,利用该成员变量找到对应的 cdev 结构体,此时,完成由设备驱动节点(inode)找到对应设备驱动(cdev)的步骤。之后inode结构体和cdev结构体完成双向链表的建立,并将cdev结构体中的file_opertion结构体赋给file结构体中的程序变量(filp->f_op)。至此,完成了从 inode结构体 --> cedv结构体 --> file结构体 的建立,之后,对设备进行的read, write等操作,就会执行cdev的相应操作。
参考路径:https://blog.csdn.net/o0o0o0D/article/details/52993674