当前位置: 首页 > news >正文

驱动第二周

一.Linux 字符设备驱动框架及应用程序访问底层驱动过程

1.1Linux 字符设备驱动框架

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

1.应用层打开设备文件时,操作系统首先通过设备文件找到对应的inode结构体,再根据inode结构体中记录的设备号到字符设备映射表中查找。
2.若字符设备映射表中未找到对应设备号,直接报错返回文件找不到;若找到则可通过设备号和cdev结构体的关联找到对应的cdev结构体,进而获取到其中的file_operation操作接口,可操作底层设备。
3.打开成功后,系统会创建file结构体,将cdev结构体相关信息填充到file结构体中,后续操作可直接通过file结构体调用底层接口,无需重复查找,提高操作效率。
补充机制说明

inode这个结构体,第一次打开这个设备时,对应的i_cdev是没有值的,发现对应的指针为空,它就会通过设备号去查找,去找对应的底下的cdev,找到之后会把对应的cdev的地址放到i_cdev中,第二次open打开这个设备文件的时候,对应的i_cdev已经记录了,就可以通过i_cdev找到,
file这个结构体为啥也保存了file_operations的地址。因为我们对应打开文件之后,一般都是读写,那对于的read和write第一个参数都是文件描述符,你通过文件描述就是找到的是file结构体,从而找到file_operations,不然的话你需要重新找一边,大大提高了对应的效率。
这些结构体都在VFS层(虚拟文件系统),VFS是一个抽象层,其向上提供了统一的文件访问接口,而向下则兼容了各种不同的文件系统。

二.Linux Platform子系统框架

字符驱动框架,它的可移植性差,原因:驱动中包含了特定平台的硬件信息,如果是其他平台,硬件信息会有差异,所以驱动无法直接使用。

那么衍生了一种总线、设备、驱动的框架。目的就在于增加驱动的可扩展性和可移植性

核心思想:

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

设备和驱动分离了,驱动是如何获取具体设备信息的呢?
设备和驱动都会注册到总线上,当注册设备的时候,会去寻找同名的驱动,当注册驱动的时候,也会去找同名的设备。相互查找。一旦匹配成功,操作系统就会自动调用驱动提供的probe函数。我们只需要在probe函数中,使用操作系统提供的通用API获取硬件的信息即可。

当我们的设备和驱动匹配成功之后,就会出现设备中有驱动,驱动中有设备,也就是”你中有我,我中有你”。但是驱动像一个“渣男”,他可以“左拥右抱”包含多个设备,而我们的设备“深情专一”只包含一个驱动。

我们的总线种类可以分成两大类

1.平台总线 (CPU核与硬件控制器之间的通信),挂载都是控制器设备

platform bus

2.边缘设备之间通信的总线,挂载是符合总线时序的外围设备

i2c , spi ,usb , uart ...

不同的边缘设备之间通信的总线,总线时序是不一样的,对于这些总线,Linux 内核是单独实现的

总线在操作系统中本质就是两个链表:挂载设备的链表和挂载驱动的链表。

platform 总线维护设备和驱动两条链表,注册时自动用名字/compatible 做 match,每匹配成功一个设备就调用一次 probe;多设备场景下同一个 probe 被调用多次,驱动用全局指针数组(pled)把每次拿到的不同设备实例收集起来,实现"一份驱动代码管理多个同类设备"。

通过上文我们便可以知道驱动和设备分离了,我们就需要写出两个.c文件,一个就是我们的xxx_device.c(设备文件)另一个就是xxxx_driver.c(驱动文件)。

(1)根据自己的设备,来确定总线的类型
platform bus / i2C bus /usb bus/....

(2)根据总线的类型, 确定设备在总线上如何描述(结构体)
struct platform_device{
设备名;
设备的资源
通用的设备描述(struct device : platform_data->记录设备私有的信息)
id_entry:当设备和驱动的id_table中某一个成员匹配上的时候,这个成员就会记录id_table中匹配上的成员地址
};
struct resource{
资源的开始
资源的结束
资源的类型:IO资源(寄存器地址) 中断资源(中断号) DMA资源(通道)
};

(3)根据总线的类型, 确定驱动在总线上如何描述(结构体)
struct platform_driver{
probe函数 :设备和驱动匹配的时候,操作系统自动调用
remove函数:设备和驱动分离的时候,操作系统自动调用
通用的驱动描述(struct device_driver : 这里面可以记录驱动的名字)
id_table : 当前驱动支持平台设备(记录支持的平台设备名字)

},
};·
(4)根据总线的类型,确定在总线上如何注册设备
int platform_device_register(struct platform_device *pdev);
(5)根据总线的类型,确定在总线上如何注册驱动
int platform_driver_register(struct platform_driver *pdriver);
(6)根据总线的类型, 确定设备和驱动匹配原则
如果驱动提供了id_table,那就拿设备的名字和id_table中记录的名字匹配
如果驱动没有提供id_table,那就拿设备的名字和驱动的名字进行匹配
(7)一旦设备和驱动匹配后,操作系统就会调用驱动提供的probe函数。
在这个函数中,一般需要做两件事情:
<1>获取匹配的硬件资源
<2>注册字符设备(可选)

三.设备树

设备树本质是一个单独描述设备信息的文件,有自己的语法规则,原文件后缀为DTS,编译后生成DTP文件,设备信息不再以内核代码形式存在于Linux内核源码中,提升了内核的通用性,理论上只需更换设备树文件,内核就可以适配不同平台,实际仍会存在部分平台差异。

设备树采用树形结构,最外层是根节点,节点下可包含属性信息和子节点,每个节点代表一个设备,属性用于描述设备的具体信息,属性分为两类:标准属性(有固定属性名称,如compatible、reg,所有平台通用,需按规则编写)和自定义属性(为单个设备单独定义,可自定义名称和值)。

那我们应该如何操作,写一个自己的设备树节点呢?
(1)第一步要找到和当前平台相关的所有设备树文件(.dts、.dtsi文件,其中.dtsi可类比为.h头文件,.dts可类比为.c文件,编译后生成.dtb二进制设备树文件),单独拷贝出来后仅在拷贝的文件范围内查找信息,避免内核中大量无关设备树文件的干扰。

(2)再根据自己设备类型,再文件夹内输入grep “xxxx(如:gpio,interrupt)-controller” * -nR

例:

(3)我们根据查到的信息输入vi exynos4x12-pinctrl.dtsi找到我们自己需要的节点名

例:

(4)在我们内核源码的目录下输入cd Documentation/devicetree/bindings/,在这个目录下输入grep"#xxxx(如:gpio,interrupt)-cells"*-nR | grep "samsung(厂家名)"

(5)输入vi pinctrl/samsung-pinctrl.txt +29

这样我们就可以通过查询资料来编写我们自己的设备树节点。

(6)编写自己对设备树节点,在内核源码的目录下cd arch/arm/boot/dts,然后vi exynos4412-fs4412.dts

例:

四.Linux 中断子系统框架

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

4.1中断上半部和下半部

中断上半部和下半部是将中断处理函数中需要做的事情,分成两部分,在不同的函数中完成。
中断处理函数中完成的事情 ,是上半部(屏蔽外面的中断)。
而另外一个函数中完成的事情,是下半部(不屏蔽外面的中断)。
上半部由中断产生,就执行中断处理函数(上半部)

下半部在合适的时间点执行(一般是在上半部结束的时候,开始触发下半部)

紧急的事情,耗费时间不多,我们放在上半部耗时的时间长或需要休眠,我们放在下半部

下半部的实现机制

(1)软中断

(2)taklet

基于软中断实现

(3)workqueue

下半部机制上下文复杂度执行性能顺序执行保障
软中断中断
(需要自己确保软中断的执行顺序及锁机制)

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

4.2进程上下文和中断上下文

当内核代表某个进程执行时,就说内核处于进程上下文。

当 CPU 响应硬件中断(如网卡收到数据包、磁盘 IO 完成、定时器到期)时,内核会跳转到对应的中断处理程序(ISR, Interrupt Service Routine)执行。此时内核处于中断上下文

中断上下文不能休眠。

进程上下文睡眠:

进程A 调用 sleep() → 放入等待队列 → 调度器选择进程B → 进程A 被重新调度后恢复执行

↑ 这个过程的前提是:有一个"进程A"可以被挂起和恢复

中断上下文"睡眠":

ISR 调用 sleep() → ??? 谁来恢复 ISR 的执行?

↑ 没有进程关联 ISR,调度器不知道该唤醒谁!

五.Linux Input子系统框架

input子系统属于输入类子系统,作用是感知外部事件输入,向上层应用提供标准化的输入接口,统一各类输入设备的访问方式,降低上层应用开发和驱动开发的难度。

1.核心层(input core):是Linux系统内核中已经预先实现好的核心部分,负责input设备和input事件处理程序的匹配工作。
2.事件处理层(input handler):对各类输入外设进行分类,不同种类的外设对应不同的事件处理程序,常见的默认事件处理程序包括:通用事件接口(evdev),鼠标事件接口,游戏手柄摇杆接口(joydev),LED状态管理接口。
3.设备驱动层:是开发input子系统驱动时需要编写的部分,对应具体的外设硬件,需要结合对应外设的基础驱动子系统(如按键需结合GPIO子系统、触摸屏需结合I2C子系统、USB键盘/鼠标需结合USB子系统、部分键盘需结合SPI子系统)共同实现。

核心链表与结构体
1.内核中维护两条链表:input_handler_list(链接所有已注册的事件处理程序,每个节点为input_handler结构体,包含事件处理名称、回调函数等成员)、input_dev_list(链接所有已注册的输入外设,每个节点为input_dev结构体,描述外设的能力属性)。
2.当新注册一个input_dev时,核心层会遍历input_handler_list,查找能匹配的事件处理程序;当新注册一个input_handler时,核心层会遍历input_dev_list,查找能匹配的外设设备。
3.二者匹配成功后,会调用input_handler的connect回调函数,生成input_handle结构体,该结构体同时关联匹配的input_dev和input_handler,将两者通过链表连接起来,完成配对。

自己编写的按键输入子系统

(1)原理图

(2)设备树节点

(3)编写驱动代码并在板子上测试

我们的input设备注册之后与handler进行匹配,怎么知道我们的设备与那个handler匹配了呢?
在代码中我们设置了位图。

这样就设置他的功能,我们handler也记录了自己的功能,这样我们dev与handler就会匹配。

我们自己写的驱动代码里没有字符设备注册函数为啥在上层应用依旧可以调用oren,read...呢?
那是因为在我们connect函数中设置了。

你的驱动: input_register_device()

Input Core: input_attach_handler()

│ 匹配到 evdev

evdev_handler->connect() = evdev_connect()

├── 1. 分配次设备号 (input_get_new_minor)

├── 2. 分配 evdev 结构体 (kzalloc)

├── 3. 建立 handle 绑定 (input_register_handle)

├── 4. cdev_init + cdev_add ← 注册字符设备!

└── 5. device_add ← 创建 /dev/input/eventX 节点!

http://www.jsqmd.com/news/1345434/

相关文章:

  • 2026上海局部改造避坑要点:8个关键问题必看,益鸟美居“闭口合同零增项”标准化做法值得借鉴 - 优家闲谈
  • 自制八倍潜望镜:从光学原理到DIY实践,打造你的专属观察系统
  • Godot引擎F6调试失效全解析:从快捷键冲突到脚本错误的系统排查指南
  • 硬件接口EMC设计实战:从原理到防护电路与PCB布局
  • TC3xx IOM深度解析:从引脚复用到硬件逻辑处理器的实战应用
  • 嵌入式基础_初识STM32F103ZET6
  • 拒绝流水线辩护!白云区知名律所对比,解锁刑事维权正确方式 - 资讯综合
  • 延迟指标解析:缓存命中率、流水线停顿与分支预测
  • STM32 HAL库 I2C协议实战:驱动AI智能小车与K210模块通信
  • 小白勇闯《苍穹外卖》Day3
  • CSDN发布版_AI边缘计算如何以云边端三级架构重塑千行百业智能底座(附六大行业实战解析)
  • 机器学习实战:房屋价格预测模型构建与优化
  • 会议室预约系统首选!高校、大企业都在用的智能预约工具
  • 从科幻隐喻到工程实践:构建稳定、权能固化的复杂系统架构
  • 手把手拆解Unity新输入系统:从零到懂的完整指南
  • 海外求职有哪些AI简历工具推荐-5款海外AI简历工具深度实测与选型指南
  • 鸿蒙DevEco开发环境运行模拟器,windows家庭版 报 Hyper-V 缺失,完整安装教程
  • 环评公示登报费用、模板流程及审批材料全解析:线上办理更省心 - 实用干货补给站
  • UE5 C++ 编辑器扩展:实现可视化顶点编辑控制柄
  • 性价比高的工业品阿里代运营哪个公司好? - 米諾
  • 03-FDEvs解决方案架构师vs算法工程师到底有什么不同
  • 基于HTML5 Canvas的前端趣味游戏开发实践:从社区梗到交互应用
  • 布鲁可大力神吊钩模型改造:从结构强化到可动关节实战指南
  • FPGA数据流缓冲设计:从乒乓操作到握手流控的本质解析
  • 硬件接口EMC设计实战:从TVS选型到PCB布局的可靠防护指南
  • 三维基因组学实战:TAD鉴定方法、保守性分析与多组学整合
  • C++友元类深度解析:从封装破例到高效协作的设计艺术
  • CMake 实战终篇:Aether 项目 CMake 重构实战,从「能跑」到「专业」
  • 电赛智能小车开发全攻略:从硬件飞线到PID循迹避障算法
  • 三升四暑假学习资料包 语数全科衔接 复习 + 预习一站式配齐