Linux下应用程序调用驱动程序过程:
(1)加载一个驱动模块(.ko),产生一个设备文件,有唯一对应的inode结构体
a、每个设备文件都有一个对应的’inode‘结构体,包含了设备的主次设备号,是设备的唯一标识。
b、驱动加载至内核,初始化时通常会调用’register_chrdev()‘或’register_blkdev()‘(字符设备和块设备),注册设备号和关联文件操作file_operation。
c、有了设备号后,用class_create()和 device_create()创建设备文件 ,device_create()该函数需要一个有效设备号
d、当设备文件创建后,文件系统会为其创建和初始化一个’inode‘结构体,包含文件类型、权限、所有者信息以及重要的设备号
(2)应用层调用open函数打开设备文件(该函数是在应用层中写好的),对于上层open调用到内核时,发生一次软中断,从用户空间进入到内核空间。
(3)open会调用到sys_open(内核系统调用函数),sys_open根据文件的地址(文件的路径),找到设备文件对应的struct inode结构体描述的信息,得知要操作的设备类型(字符设备还是块设备),还会分配一个struct file结构体。
- sys_open:
内核级的系统调用函数,用于打开文件和设备。系统调用是用户空间和内核空间之间的接口,通过系统调用可从用户态切换至内核态。
- sys_open函数的基本操作:
a、解析用户提供的文件名,确定文件位置和权限(通常为文件的路径名,如/dev/设备名)
b、检查文件访问权限
c、分配并初始化一个文件描述符(对该文件操作的引用,非负的整数,是该文件表条目在文件表中的索引)
d、将文件描述符返回给用户空间
- struct file:
- 内核中的重要结构体,表示一个已打开的文件(信息)。
- 包含相关的文件状态信息,如当前偏移量、文件操作指针file_operation(包含对文件读写的操作函数指针)、'inode'结构体指针
- 由sys_open创建,内核为打开的文件创建一个新的'struct file'实例
(4)根据struct inode结构体内记录的主次设备号,在驱动链表(管理所有设备驱动)里面,找到字符设备驱动。
这一步也是后续为了给创建的file结构体内的f_ops成员,文件操作指针赋值
- 驱动链表:
- Linux内核中用于管理设备驱动的关键数据结构,是一个链表或哈希表结构,其中包含了所有注册的设备驱动。
- 字符设备的驱动链表中,每个成员(设备)确实是通过 struct cdev 结构体来表示的。
- 字符设备驱动通过调用 cdev_add() 函数将自己的 cdev 结构体添加到内核的设备表中(这通常是在调用 register_chrdev_region() 或 alloc_chrdev_region() 注册设备号之后进行的。
(5)每个字符设备都有一个struct cdev结构体。描述了字符设备的所有信息,其中最重要的一项是字符设备的操作函数接口。
(6)找到struct cdev结构体后,linux内核就会将struct cdev结构体所在内存空间首地址记录在struct inode结构体i_cdev成员中,将struct cdev结构体中记录的函数操作接口地址记录在strct file结构体的f_ops成员中。
(7)执行xxx_open驱动函数。
流程架构参考自:最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客
inode结构体组成
变量赋值的来源
这些变量的值大多是由文件系统、内核的设备管理子系统,或驱动自身在注册设备时设置的。
例如:
- i_mode(文件模式):通常由驱动设置以标识设备文件的类型(字符设备或块设备)
- i_rdev(实际设备ID):包含主设备号和次设备号(高12位和低20位),由register_chrdev()或register_blkdev()设置
文件系统中对于'inode':
在创建和管理'inode'结构体中起到核心作用,当一个文件系统被挂载时,它会初始化其管理的所有 inode,并负责管理它们的生命周期。文件系统会定义自己的'inode'操作方法('inode_operation'结构体,也是inode结构体的成员变量),包括:
a、创建 (create):当新文件被创建时,文件系统会分配和初始化一个 inode 结构体。这包括设置文件类型、权限、所有者等。
b、查找 (lookup):在访问文件时,文件系统需要通过路径查找相应的 inode。
c、删除 (delete):当文件被删除时,文件系统负责清理 inode 结构体,并可能将其空间释放回 inode 缓存。
文件描述符:
用于访问文件和设备的一个抽象表示。文件描述符本质上是一个非负整数,当一个程序打开一个文件(包括设备文件)时,操作系统返回的文件描述符作为未来所有对该文件的操作的引用。
文件描述符的形式和功能
- 形式:文件描述符是一个简单的整数值。在一个进程的上下文中,每打开一个文件或设备,内核就会分配最低未被使用的文件描述符。
- 功能:文件描述符提供了一个通用的方法来引用所有类型的文件,无论它们是普通的磁盘文件、目录、链接还是设备(如键盘、硬盘、网络设备等)。
文件描述符的操作
当一个文件或设备被打开时,内核会创建一个文件表条目。文件描述符就是这个文件表条目在文件表中的索引。
文件表条目包括:文件位置、当前偏移量、访问权限、指向具体文件操作的指针等。
- 基于文件描述符的标准操作:
a、打开操作open(pathname, flags, ... ):打开一个已存在的文件或创建一个新文件,并返回一个文件描述符
b、读操作 (read(fd, buffer, size)):从由 fd 指定的文件或设备中读取数据。
c、写操作 (write(fd, buffer, size)):向由 fd 指定的文件或设备写入数据。
d、输入输出控制操作 (ioctl(fd, command, ...)):对特定的设备执行控制和配置操作。
e、文件控制操作(fcntl(fd, command, ...)):改变已打开的文件性质。
f、关闭操作 (close(int fd)):关闭文件描述符。
file结构体组成
struct file主要成员解释
- f_path:包含文件的路径和文件系统信息,struct path 结构体包括一个指向 dentry(目录项)和 vfsmount(文件系统挂载点)的指针。
- f_inode:指向与文件关联的 inode 结构体的指针,它存储了文件的大部分元数据。
- f_op:指向文件操作表的指针,这个表包含了各种文件操作相关的函数指针,如 read、write、open、release 等。
- f_lock:自旋锁,用于保护文件相关的敏感操作。
- f_count:原子类型,表示文件描述符的引用计数,用于文件描述符的复制和释放管理。
- f_flags:打开文件时使用的标志,如 O_RDONLY、O_NONBLOCK 等。
- f_mode:文件模式,指定文件的访问模式,如只读、只写等。
- f_pos:当前文件的偏移量,表示下一次读/写的起始位置。
- f_owner:文件所有者的信息,常用于信号发送。
- f_mapping:指向文件的地址空间结构,用于文件映射和内存管理。
驱动链表的设备匹配
当一个应用程序尝试通过系统调用(open())访问一个字符设备时,内核在驱动链表中找到相应的设备驱动的步骤:
1、设备号解析
应用程序请求打开一个设备文件(/dev/example)时,内核首先获取该设备文件的'inode'结构体,从中读取主次设备号
- 主设备号:用于识别该设备的驱动程序
- 次设备号:用于识别该驱动程序控制的特定设备实例(控制的是哪个设备)
2、驱动链表搜索
内核中维护了一个包含所有已注册字符设备的列表(register_chrdev()),每个注册设备通过一个'struct cdev'实例表示,并链接到全局链表中。内核遍历这个链表,比较每个'struct cdev'的设备号
- 链表遍历:从链表头部开始,内核遍历每个'cdev'条目
- 匹配主设备号:内核检测每个'cdev'的主设备号是否与请求的主设备号匹配
3、文件操作结构体关联
一旦找到匹配的'cdev'结构体,内核将使用该结构体中的'file_operations'指针。
- 设备操作:应用程序通过系统调用请求的操作(如读取或写入数据)将由file_operations结构体中相应的函数处理。
4、f_op指针设置
内核在打开文件时会创建file struct结构体,但此时部分成员未设置,包括f_op指针(file_operation的指针)。一旦找到正确的驱动,f_op指针会被设置为指向该驱动的file_operations结构体。
后续的操作(如再次读写文件)都会通过这些函数处理(file_operation中的函数)。
Linux设备号:
Linux中每个设备都有一个设备号,由主设备号和次设备号组成。主设备号对应一个具体的驱动,次设备号表示使用这个驱动的各个设备。Linux中提供了一个名为dev_t的数据类型表示设备号,定义在include/linux/types.h中。
1、dev_t类型
- _u32类型别名为_kernel_dev_t;
- _kernel_dev_t别名为dev_t
- 所以dev_t 是__u32 类型的(即unsigned int)
- _u32:32位的无符号整形unsigned int。
该32位数据构成了主设备号和次设备号,高12位为主设备号,低20位为次设备号,所以系统中主设备号范围为0~4095。
2、设备操作函数:
- MINORBITS:次设备号位数,20位。
- MINORMASK:次设备号掩码
- MAJOR(dev):从dev_t中获取主设备号,将dev_t右移20位
- MINOR(dev):从dev_t中获取次设备号,取dev_t低20位的值
- MKDEV(ma,mi):将给定的主设备号和次设备号的值组合成dev_t类型的设备号。(主设备号左移20位)