1. 驱动开发中的阻塞机制概述
在Linux内核驱动开发中,阻塞(Blocking)是一个至关重要的概念。想象一下这样的场景:当用户空间的进程尝试从设备读取数据,但设备当前没有数据可读时,进程是应该持续占用CPU资源进行轮询,还是应该暂时让出CPU等待数据就绪?阻塞机制就是为解决这类问题而设计的优雅方案。
我从事内核开发十多年来,处理过无数与阻塞相关的bug。阻塞机制本质上是一种进程同步机制,它允许进程在等待某个条件满足时主动放弃CPU,直到条件满足后再被唤醒继续执行。这种机制在字符设备驱动、网络协议栈、文件系统等场景中应用广泛。
重要提示:阻塞与非阻塞(Non-blocking)是相对的两种I/O模型。在驱动开发中,我们需要同时支持这两种模式,通过file_operations结构体中的相应函数指针实现。
2. 阻塞相关核心数据结构解析
2.1 wait_queue_head_t 等待队列
等待队列是Linux内核中实现阻塞机制的基础数据结构,其定义位于<linux/wait.h>中:
c复制struct wait_queue_head {
spinlock_t lock;
struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;
每个需要支持阻塞操作的设备都应该声明自己的等待队列。在我的项目实践中,通常会在设备私有数据结构中嵌入等待队列:
c复制struct my_device {
wait_queue_head_t read_queue;
unsigned char buffer[BUFFER_SIZE];
int data_ready; // 标志位
// 其他设备特定字段...
};
初始化等待队列有两种常用方式:
- 静态初始化:DECLARE_WAIT_QUEUE_HEAD(name)
- 动态初始化:init_waitqueue_head(&dev->read_queue)
2.2 wait_queue_entry_t 等待队列项
当进程需要加入等待队列时,内核会为其创建一个等待队列项:
c复制struct wait_queue_entry {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head entry;
};
在早期内核版本中,这个结构体名为wait_queue_t,但在较新内核中已经更新。这个细节在移植旧驱动时需要特别注意。
3. 阻塞操作的核心函数实现
3.1 等待事件系列宏
内核提供了一组非常实用的宏来处理阻塞等待,这些宏内部处理了条件检查和进程状态设置的复杂性
