一、开篇:回顾应用程序与驱动程序的基础交互
本篇主要讨论应用程序与驱动程序的更多交互方式。
大家可能会问:应用程序与驱动程序的交互,不就是通过 open、read 这些函数吗?
确实如此,我们先对这一基础交互进行回顾。在 Linux 系统中,应用程序与驱动程序之间存在明确的界限,那应用程序如何访问驱动程序呢?简单来说,应用程序调用 open 函数,再通过 read、write 函数,就能访问到驱动程序中的 open、read、write 函数。
二、核心问题:驱动程序的 read 函数何时返回?
现在我提出一个问题:如果应用程序想通过驱动程序的 read 函数读取某个按键的状态(判断按键是按下还是松开),这个 read 函数是 “即刻返回”,还是 “有按键动作(有数据)才返回”?
要回答这个问题,需要先明确一个核心原则:驱动程序只提供能力,不提供策略。
听起来可能有些抽象,具体来说:驱动程序既可以实现 “即刻返回”,也可以实现 “有数据才返回”—— 这是驱动提供的 “能力”;但具体使用哪种返回逻辑(何时用、怎么用),不由驱动程序决定,而是由应用程序决定 —— 这就是 “不提供策略”。
三、交互逻辑的关键:open 函数的 O_NONBLOCK 标记
应用程序如何选择驱动程序的返回策略?答案在 open 函数中。
调用 open 函数打开文件(驱动程序可视为特殊文件)时,可传入 flag(标记)和模式参数,其中关键的标记是O_NONBLOCK(非阻塞)。
驱动程序的 read 函数实现时,会根据这个标记判断逻辑:
- 若有数据:无论是否阻塞,都会立即返回数据(这一点没有争议);
- 若应用程序传入了 O_NONBLOCK 标记(要求非阻塞,即 “不要等待”),驱动会立即返回一个错误;
- 若未传入 O_NONBLOCK 标记,驱动会让应用程序等待,直到有数据后再返回。
结合场景总结:驱动程序可以设计得更完善、提供丰富功能,但功能的使用方式由应用程序决定 —— 通过 open 函数传入的 flag,就能指定驱动的交互策略。
四、应用程序与驱动程序的四种交互方式
常规的交互方式有四种,我们结合 “妈妈判断小孩是否醒了” 的生活场景类比理解,更易上手。
1. 第一种:查询方式(非阻塞即刻返回)
- 驱动逻辑:应用程序调用驱动的 read 函数时,有数据则返回数据,无数据则返回错误;
- 类比场景:妈妈需要不断打开房门查看小孩是否醒了 —— 中途可能要出来做事,之后再回去查看;
- 特点:实现简单,但妈妈会很累(对应程序中 “消耗较多 CPU 资源”),应用程序需一直调用 read 函数,效率很低。
2. 第二种:休眠唤醒方式(阻塞等待数据)
- 驱动逻辑:应用程序调用 read 函数后,若无数据,应用程序会进入 “休眠” 状态;当数据源(如按键)产生数据(按键按下)时,会通过中断等机制唤醒应用程序,再返回数据;
- 类比场景:妈妈打开房门发现小孩在睡,于是陪着小孩一起睡;等小孩醒了(哭了、饿了),哭声会把妈妈唤醒;
- 特点:解决了 CPU 资源消耗问题,但有明显缺点 —— 若数据源一直无数据(小孩一直睡),应用程序会一直休眠(妈妈一直等),期间无法做其他事。
3. 第三种:poll/select 方式(带超时的等待)
为解决 “休眠唤醒” 的缺点,可通过poll 函数或 select 函数改进(两者功能类似),核心是 “带超时的等待”。
- 驱动逻辑:调用 poll/select 函数时,需传入两个关键参数 —— 要监测的文件句柄(FD,可同时监测多个驱动)、超时时间;函数返回值会告知 “是超时返回(闹钟响了),还是有数据返回”;
- 类比场景:妈妈陪小孩睡时,定一个闹钟(比如半小时)—— 若小孩在半小时内醒了,哭声唤醒妈妈;若小孩睡超半小时,闹钟唤醒妈妈;
- 特点:既避免了 CPU 空耗,又不会因无限等待影响其他任务,本质是 “定闹钟” 的机制。
4. 第四种:异步通知方式(主动触发)
前三种都是 “同步机制”(应用程序需等待驱动返回),而第四种是 “异步机制”—— 应用程序无需等待,驱动会主动通知。
- 驱动逻辑:不依赖 read、poll 等 “主动调用函数”,而是类似单片机的 “中断”:应用程序先注册一个 “信号处理函数”,当驱动监测到事件(如按键按下),会主动给应用程序发送信号;
- 类比场景:小孩长大了,妈妈可以在外面一直干活;小孩醒了之后,会自己打开房门,主动告诉妈妈 “我醒了”;
- 特点:异步机制下,应用程序和驱动 “互不相干”—— 应用程序做自己的任务,驱动有事件时主动通知,效率最高;具体的信号注册与处理,后续会详细讲解。
五、写数据的交互逻辑(与读数据类似)
以上我们以 “读数据” 为例讲解,“写数据” 的逻辑完全类似:
应用程序调用驱动的 write 函数写数据时,可能遇到 “硬件未就绪”“硬件处理能力不足,无法接收更多数据” 的情况。此时驱动同样遵循 “只提供能力,不提供策略”:
- 能力:驱动可实现 “即刻返回错误”,或 “等硬件有空间后再写入返回”;
- 策略:应用程序通过 open 函数的 O_NONBLOCK 标记决定 —— 传入标记则非阻塞(写不成功直接返回错误),不传入则阻塞(等硬件就绪后再写)。
总结
应用程序与 Linux 驱动程序的四种核心交互方式,分别是:查询方式、休眠唤醒方式、poll/select 方式、异步通知方式。核心原则始终是 “驱动提供能力,应用程序决定策略”,而 open 函数的 O_NONBLOCK 标记是实现策略选择的关键。