在 Linux 设备驱动中,阻塞机制用于管理用户进程在等待设备资源(如数据可读/写、硬件事件)时的休眠与唤醒。通过阻塞机制,驱动可以避免进程忙等待,提升系统效率。以下是阻塞机制的实现原理与核心方法:
1. 阻塞与非阻塞模式
- 阻塞模式(默认):当资源不可用时,进程进入休眠状态,直到资源就绪后被唤醒。
- 非阻塞模式(O_NONBLOCK):资源不可用时立即返回
-EAGAIN
,进程继续执行其他任务。
2. 核心数据结构:等待队列
(1) 等待队列头
用于管理等待资源的进程列表:
#include <linux/wait.h>
#include <linux/sched.h>
// 定义并初始化等待队列头
static DECLARE_WAIT_QUEUE_HEAD(my_wait_queue);
(2) 等待队列项
表示一个等待的进程:
typedef struct wait_queue_entry wait_queue_entry_t;
3. 阻塞机制实现步骤
(1) 进程休眠
当资源不可用时,将当前进程加入等待队列并休眠:
// 在 read 方法中等待数据可读
ssize_t mydev_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) {
struct mydev_private *priv = filp->private_data;
// 非阻塞模式直接返回 -EAGAIN
if (filp->f_flags & O_NONBLOCK && !priv->data_ready)
return -EAGAIN;
// 阻塞模式下等待数据就绪
wait_event_interruptible(my_wait_queue, priv->data_ready);
// 数据就绪后继续执行...
return copy_to_user(buf, priv->buffer, count);
}
(2) 进程唤醒
当资源可用时(如中断到达),唤醒等待队列中的进程:
// 中断处理函数或数据生产函数中唤醒进程
void data_available_handler(struct mydev_private *priv) {
priv->data_ready = 1;
wake_up_interruptible(&my_wait_queue); // 唤醒等待队列
}
4. 关键函数与宏
(1) 进程休眠函数
-
wait_event(queue, condition)
:不可中断休眠,直到condition
为真。 -
wait_event_interruptible(queue, condition)
:可被信号中断的休眠。 -
wait_event_timeout(queue, condition, timeout)
:休眠最多timeout
个时钟周期。
(2) 进程唤醒函数
-
wake_up(queue)
:唤醒所有等待在队列中的进程。 -
wake_up_interruptible(queue)
:唤醒所有可中断休眠的进程。
5. 高级用法:轮询支持(poll
方法)
为支持 select
/poll
/epoll
等多路复用接口,需实现 poll
方法:
unsigned int mydev_poll(struct file *filp, struct poll_table_struct *wait) {
struct mydev_private *priv = filp->private_data;
unsigned int mask = 0;
poll_wait(filp, &my_wait_queue, wait); // 将进程加入等待队列
if (priv->data_ready)
mask |= POLLIN | POLLRDNORM; // 数据可读事件
return mask;
}
// 在 file_operations 中注册
static struct file_operations mydev_fops = {
.poll = mydev_poll,
};
6. 实现注意事项
(1) 条件检查的原子性
在检查条件和进入休眠之间需使用锁(如自旋锁)防止竞态:
spin_lock(&priv->lock);
if (!priv->data_ready) {
spin_unlock(&priv->lock);
wait_event_interruptible(my_wait_queue, priv->data_ready);
} else {
spin_unlock(&priv->lock);
}
(2) 虚假唤醒处理
即使条件未满足,进程也可能被唤醒(如信号中断),需循环检查:
while (!priv->data_ready) {
wait_event_interruptible(my_wait_queue, priv->data_ready);
}
(3) 资源释放
在模块卸载时,需唤醒所有等待进程并清理队列:
void __exit mydev_exit(void) {
wake_up_all(&my_wait_queue);
// 其他清理操作...
}
7. 完整示例:带阻塞的字符设备驱动
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/sched.h>
static DECLARE_WAIT_QUEUE_HEAD(my_wait_queue);
static int data_ready = 0;
ssize_t mydev_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) {
// 非阻塞模式检查
if (filp->f_flags & O_NONBLOCK && !data_ready)
return -EAGAIN;
// 阻塞等待数据就绪
wait_event_interruptible(my_wait_queue, data_ready);
// 模拟数据读取
char data[] = "Hello from kernel!";
data_ready = 0; // 重置状态
return simple_read_from_buffer(buf, count, pos, data, sizeof(data));
}
// 数据到达时唤醒进程(例如在中断中调用)
void data_arrived(void) {
data_ready = 1;
wake_up_interruptible(&my_wait_queue);
}
static struct file_operations mydev_fops = {
.owner = THIS_MODULE,
.read = mydev_read,
};
module_init(mydev_init);
module_exit(mydev_exit);
总结
- 阻塞机制核心:等待队列 + 休眠/唤醒函数。
- 非阻塞支持:检查
O_NONBLOCK
标志并返回-EAGAIN
。 - 竞态避免:原子性检查条件,使用锁保护共享数据。
- 多路复用:通过
poll
方法支持select
/epoll
。
合理使用阻塞机制,可显著提升驱动性能和用户体验。
参考: