驱动第二次答辩

📅 2026/7/26 9:11:27 👁️ 阅读次数 📝 编程学习
驱动第二次答辩

字符设备驱动框架

对于应用层和接口

函数接口通过函数指针来规定返回值和参数规定死;上层代码只认这个,底层通过注册回调函数来填充实现,调用时通过指针间接执行

硬件来说只写操作设备的接口

由于对于上层来说只有设备文件;所以要设备文件和接口关联

函数接口通过结构体file_operations规定;里面存放了很多函数;

高类聚低耦合:把相关的紧密放一起(高内聚),不相关的别扯上关系(低耦合)

常见函数

函数功能分类核心作用
cdev_init()cdev初始化初始化cdev结构体,绑定file_operations
alloc_chrdev_region()设备号注册自动注册设备号
cdev_add()cdev绑定cdev和设备号绑定在cdev_map
class_create()设备类创建创建设备类/sys/class
device_create()设备节点创建通过struct class发送uevent事件通知mdev创建设备节点

应用程序访问底层驱动过程

当驱动被加载时,它会向内核注册自己的主设备号和一组操作函数(file_operations)。当用户程序调用open打开/dev下的对应设备文件时,内核通过文件中的主设备号找到并绑定该驱动,之后用户程序对该文件描述符的读写操作,就会自动被内核转发到驱动注册的对应函数,从而实现对硬件的控制。

Linux Platform子系统框架

总线、设备、驱动 => 增加驱动的可扩展性和可移植性
设备的信息从驱动中分离出来,我们需要在操作系统中,添加设备和驱动两部分。
设备中包含设备的信息(资源),驱动中包含的是操作设备函数接口。
为了让驱动最终能操作我们的硬件设备,我们在驱动中必须获取设备的信息(资源)。

设备和驱动是分离的,它们都会向内核总线(如platform总线)注册。当任何一方(无论是设备先来,还是驱动先来)注册时,内核都会触发一次总线上的匹配扫描,寻找名称或ID匹配的对方。一旦匹配成功,操作系统就会自动调用驱动中实现的probe函数

当设备和驱动匹配成功后,内核会将这个设备的详细信息(包括设备树解析出的资源、名称、ID等)封装成platform_device结构体,并通过probe函数的参数传递给驱动。

在Linux内核中,总线本质上是两个用于管理和匹配的链表,分别挂载着该总线上的设备和驱动;当设备或驱动注册时,内核通过总线遍历这两个链表进行匹配,匹配成功则调用驱动的probe函数。

基于总线写驱动流程:
1.根据设备确定总线类型
2.根据总线类型,确定设备在总线上如何描述struct platform_device;
内核解析设备树后,自动创建struct platform_device
3.根据总线类型,确定驱动在总线上如何描述struct platform_driver;
4.根据总线类型,确定如何在总线上注册设备platform_device_register;
设备树自动创建
5.根据总线类型,确定如何在总线上注册驱动platform_driver_register;
6.确定设备和驱动匹配原则(name、id_table、of_match_table)
7.设备和驱动匹配后,linux调用probe函数获取硬件资源、注册字符设备

常见函数

函数功能分类核心作用
platform_get_resource资源获取platform_device中获取指定类型的资源信息(如中断号、寄存器地址、DMA通道等)
platform_set_drvdata数据保存将私有数据指针保存到platform_device中,供后续回调函数使用
platform_get_drvdata数据获取platform_device中获取之前保存的私有数据指针
资源类型宏含义示例
IORESOURCE_MEM内存地址区域寄存器基址、RAM 区域
IORESOURCE_IOI/O 端口地址x86 的 I/O 端口
IORESOURCE_IRQ中断号硬件中断线
IORESOURCE_DMADMA 通道DMA 控制器通道号
IORESOURCE_BUS总线地址总线地址范围

设备树

Linux 内核platform总线上设备与驱动的匹配规则
<1>设备树中的compatible属性与驱动中指定的of_match_table中的compatible进行匹配
如果没有匹配成功:
<2>如果驱动中有id_table,则拿id_table中记录的名字与设备的名字匹配
如果驱动中没有id_table,则拿驱动的名字与设备的名字(platform_device结构体中name字段)匹配

Linux 中断子系统框架

1.中断打断其他程序的执行,所以中断处理的时候需要尽可能的快,不能在中断处理过程中做耗时很长的事情。

2.中断打断的当前的程序执行,所以在中断处理的时候,需要先保存现场(CPU的状态和CPU内部寄存器的值:压栈保存)在中断处理结束的时候,则恢复现场

中断控制器(GIC )负责对每个中断源进行编号、使能、屏蔽和优先级仲裁,并根据配置将中断请求分类为普通中断(IRQ)或快速中断(FIQ),最终分发给ARM 核心(ARM Core)。ARM 核接收到中断信号后,会立即保存当前程序的执行上下文(现场),然后跳转到异常处理向量表,执行对应的中断服务例程(ISR),处理完后再恢复现场并返回被打断的程序继续执行。

全局的irq_desc数组按中断号(0到NR_IRQS-1)索引,每个irq_desc结构体记录对应中断号、指向特定中断控制器操作函数集(irq_chip,如VIC0/VIC1)的指针,以及一个由irqaction结构体组成的链表。irqaction用于描述具体的中断处理动作,包含处理函数handler、传递给处理函数的参数、中断标志、设备名称,并通过next指针支持多个设备共享同一中断号,当发生中断时,内核会通过该中断号找到对应的irq_desc,进而调用其irq_chip中的控制函数(如enable/disable)并遍历执行action链表上的所有处理函数。

常见函数

函数功能分类核心作用
platform_get_resource资源获取platform_device中获取指定类型的资源信息(如中断号、触发方式、名称等)
devm_request_irq中断注册注册中断号、触发方式和中断回调函数,中断生命周期绑定设备,设备移除时自动释放

中断上半部和下半部

将中断处理函数中需要做的事情,分成两部分,在不同的函数中完成。

上半部在中断产生时立即执行,在中断上下文中运行且屏蔽中断,只负责紧急、快速的任务(如清中断标志、拷贝硬件数据);

下半部则在合适的时机(通常上半部结束后触发)执行,不屏蔽中断、可被打断,负责耗时较长或可能需要休眠的剩余工作(如数据处理、协议解析)。

下半部的实现机制

软中断

tasklet

tasklet 是基于软中断实现的

软中断不能直接使用,Linux 内核为了方便开发者,在软中断之上封装了 tasklet 接口,使用更简单。所以,除非对性能要求特别高,否则都应使用 tasklet

常见函数

tasklet_init()注册tasklet回调函数
tasklet_schedule()调用软中断

work queue

工作队列(work queue)是另一种将工作推后执行的形式,它把工作交由一个内核线程去执行,在进程上下文运行,但不能访问用户空间。其最大特点是允许重新调度甚至睡眠

选择工作队列还是软中断/tasklet,可遵循以下规则:

  1. 推后执行的任务需要睡眠→ 只能选工作队列

  2. 任务需要延时指定时间再触发→ 选工作队列(可利用timer延时);

  3. 任务需要在一个tick内处理→ 选软中断或tasklet(可抢占普通进程和内核线程);

  4. 任务对延迟时间无要求(无关紧要的任务) → 选工作队列

另外,如果需要用一个可以重新调度的实体来执行下半部,应使用工作队列。它是唯一能在进程上下文运行的下半部机制,也只有它可以睡眠。这在需要获取大量内存、获取信号量、执行阻塞式I/O操作时非常有用。

常见函数

函数功能分类核心作用
INIT_WORK工作队列初始化初始化work_struct结构体,并绑定工作队列回调函数
schedule_work工作队列调度将工作项放入系统默认工作队列的链表中,并唤醒工作线程执行
cancel_work_sync工作队列同步取消取消已提交但未执行的工作项,并同步等待正在执行的工作项完成
下半部机制上下文复杂度执行性能顺序执行保障
软中断中断
(需要自己确保软中断的执行顺序及锁机制)

(全部自己实现,便于调优)
没有
tasklet中断
(提供了简单的接口来使用软中断)
同类型不能同时执行
工作队列进程
(在进程上下文中运行,与写用户程序差不多)
没有
(和进程上下文一样被调度)

Linux Input子系统框架

Input 子系统用于管理各类输入设备(如按键、触摸屏、鼠标等),负责对外部事件的感知与上报。它将硬件层产生的原始输入事件,经过内核驱动和核心层处理后,通过统一的接口input_register_device()执行时,内核会自动创建(如/dev/input/eventX)提供给用户空间应用程序使用。

linux内核注册了多个handler驱动模块,并通过input_handler_list链表维护

设备匹配流程

Linux 内核中维护了一个名为 input_handler_list 的链表, 每个 input_handler 都会被注册到这个链表 上, 而链表是通过类型为 list_head 的成员 node 串联起来的。

和input_dev匹配成功后会创建input_handle,会分配新的次设备号、注册字符设备(提供evdev_fops)并创建设备节点(/dev/input/eventX)。

常见函数

函数功能分类核心作用
devm_input_allocate_device资源分配申请input_dev资源并在remove时自动释放;并绑定参数代表设备的生命周期
input_register_device设备注册注册input_dev设备,并创建一个input设备节点给应用层访问
input_report_key事件上报向核心层上报按键值,存入事件缓存
input_sync同步事件发送同步事件,表示应用层可读取一组输入事件

硬件

led

在源码/Documentation/devicetree/bindings:有各个设备树的解释

grep "gpio-cells" * -nR | grep "厂家名字"

查找到的信息如下

在自己的设备树中引用该节点

按键

在设备树下查找;看看芯片厂家怎么写的

grep "interrupt-controller" * -nR

combiner要看芯片手册支不支持

就近原则:谁直接控制我们中断就写谁;我们选图三

在芯片的参考文档linux-3.14/Documentation/devicetree/bindings中找

grep "#interrupt-cells" * -nR | grep exynos

先找板子再找厂家;从范围小的找起

参数来源
第一个参数(中断号)1硬件原理图:按键连接在 GPX1_1 引脚,对应中断编号 1
第二个参数(触发方式)8芯片手册/文档:低电平触发

所以写出来的设备树是

现象

问题

在同一个驱动管理多个设备时,open函数如何准确获取当前打开的是哪个设备的私有数据,从而正确操作对应硬件的问题。

对比维度方法一:container_of方法二:次设备号查链表/数组
依据通过inode->i_cdev反推包含它的外层结构体地址通过iminor(inode)获取次设备号,作为索引或键值查找
适用环境每个设备独立拥有自己的cdev,注册时cdev_add的 count 为 1多个设备共享同一个cdev,注册时cdev_add的 count 大于 1