基础知识:
1. struct inode
每创建一个文件,都会生成一个设备节点inode;可以通过inode找到设备号,然后找到cdv;
驱动只有一份,存在多个同类字符设备的时候,得由驱动来区分不同的设备,怎么区别呢?
在注册设备驱动程序的时候会使用device_create()函数,会给不同的同类设备分配不同的次设备号。主设备号用于区分设备的类型,次设备号用于标记相同类型的设备的不同个体。Linux内核中使用dev_t类型来定义设备号,dev_t这种类型其实质为32位的unsigned int,其中高12位为主设备号,低20位为次设备号。主设备号相同的设备都使用同一个 file_operations 来操作。
1.知道主设备号与次设备号,可通过dev_t dev = MKDEV(主设备号,次设备号)获得设备号; 2.从设备号分解出主设备号:主设备号=MAJOR(dev_t dev)
3.从设备号分解出次设备号:次设备号=MINOR(dev_t dev)
在应用层,只需要open对应的/dev/... 即可,因为open会传递一个inode参数,所以驱动可以据此分辨不同次设备。
2.struct file
而file结构是设备驱动程序所使用的第二个重要的数据结构。它是一个内核结构,不会出现在用户程序中。它不仅仅代表一个打开的文件。它由内核在open时创建,并将自身传递给该文件上进行操作的所有函数,只到最后的close函数,在文件的所有实例都被关闭后,内核会释放这个数据结构。
小结:
struct file结构体中包含有struct file_operations结构体,struct file_operations是struct file的一个域;我们在使用系统调用open()打开一个设备节点struct inode时,我们会得到一个文件struct file,同时返回一个文件描述符,该文件描述符是一个整数,我们称之为句柄,通过访问句柄我们能够访问设备文件struct file,描述符是一个有着特殊含义的整数,特定位都有一定的意义或属性。
open调用具体流程:
应用程序执行open函数,将一个inode结构体(/dev/xxx)通过系统调用传入给内核,调用内核中的 static int chrdev_open(struct inode *inode, struct file *filp)函数(注意会传入两个参数),在该函数中通过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的相应操作。
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函数
具体源码参考:https://blog.csdn.net/qq_41882586/article/details/127012495