在 Linux 设备驱动中,非阻塞机制允许用户程序在设备未就绪时立即返回,而不是等待资源可用。这种机制通过 O_NONBLOCK 标志和 轮询(poll/select/epoll) 实现,适用于需要高响应性或异步 I/O 的场景。以下是详细的实现方法和代码示例:
1. 用户空间启用非阻塞模式
用户程序在打开设备时指定 O_NONBLOCK
标志:
int fd = open("/dev/mydevice", O_RDWR | O_NONBLOCK);
2. 驱动中的非阻塞处理
(1) 检查 O_NONBLOCK 标志
在驱动实现的 read
/write
等方法中,通过 filp->f_flags
判断是否启用非阻塞模式:
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(priv->wait_queue, priv->data_ready);
// ...数据拷贝操作...
}
(2) 立即返回未就绪状态
当设备无法立即完成操作时,返回 -EAGAIN
:
ssize_t mydev_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos) {
if (device_busy) {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN; // 非阻塞模式直接返回
else
wait_event(...); // 阻塞模式等待
}
// ...写入操作...
}
3. 实现 Poll 方法支持轮询
为了让用户程序通过 select
/poll
/epoll
检测设备状态,需实现 file_operations.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_table
poll_wait(filp, &priv->wait_queue, wait);
// 检查设备状态
if (priv->data_ready)
mask |= POLLIN | POLLRDNORM; // 数据可读
if (buffer_space_available)
mask |= POLLOUT | POLLWRNORM; // 数据可写
return mask;
}
// 注册到 file_operations
static struct file_operations mydev_fops = {
.owner = THIS_MODULE,
.read = mydev_read,
.write = mydev_write,
.poll = mydev_poll,
};
4. 用户空间轮询示例
用户程序使用 poll
监控设备状态:
#include <poll.h>
struct pollfd fds[1];
fds[0].fd = fd; // 设备文件描述符
fds[0].events = POLLIN; // 关注可读事件
int ret = poll(fds, 1, 1000); // 超时 1000ms
if (ret > 0) {
if (fds[0].revents & POLLIN) {
// 数据可读,执行 read 操作
read(fd, buf, sizeof(buf));
}
}
5. 关键机制详解
(1) 非阻塞模式下的行为
-
read
/write
:若资源未就绪,立即返回-EAGAIN
。 -
poll
/select
:返回当前设备状态(如POLLIN
表示可读),无需阻塞。
(2) 等待队列管理
- 数据就绪时唤醒队列:在中断处理或任务中触发唤醒:
wake_up_interruptible(&priv->wait_queue);
(3) 并发与竞态控制
- 自旋锁保护共享数据:
spin_lock(&priv->lock); priv->data_ready = 1; spin_unlock(&priv->lock); wake_up_interruptible(&priv->wait_queue);
6. 完整驱动示例
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/wait.h>
static DECLARE_WAIT_QUEUE_HEAD(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(wait_queue, data_ready);
data_ready = 0;
return simple_read_from_buffer(buf, count, pos, "data", 5);
}
unsigned int mydev_poll(struct file *filp, struct poll_table_struct *wait) {
poll_wait(filp, &wait_queue, wait);
unsigned int mask = 0;
if (data_ready)
mask |= POLLIN;
return mask;
}
static struct file_operations mydev_fops = {
.owner = THIS_MODULE,
.read = mydev_read,
.poll = mydev_poll,
};
// 模拟数据到达(例如在中断中调用)
void simulate_data_ready(void) {
data_ready = 1;
wake_up_interruptible(&wait_queue);
}
7. 测试与调试
(1) 用户空间测试程序
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int fd = open("/dev/mydevice", O_RDONLY | O_NONBLOCK);
char buf[10];
ssize_t ret = read(fd, buf, sizeof(buf));
if (ret == -1) {
perror("read failed"); // 预期输出:"read failed: Resource temporarily unavailable"
}
close(fd);
return 0;
}
(2) 查看等待队列状态
通过 /proc/<pid>/wchan
查看进程等待的队列:
cat /proc/$(pidof my_app)/wchan
总结
- 非阻塞模式:通过
O_NONBLOCK
标志启用,驱动返回-EAGAIN
避免阻塞。 - 轮询支持:实现
poll
方法,返回设备就绪状态。 - 性能优化:减少进程休眠,提升响应速度。
- 适用场景:实时系统、高性能服务器、GUI 应用等需快速响应的场景。
通过合理实现非阻塞机制,可显著提升设备驱动的灵活性和系统整体性能。
参考: