title: 阻塞IO与poll机制 tags: [Linux驱动, 阻塞IO, poll, select, 嵌入式, 等待队列, 异步通知] created: 2026-09-16 updated: 2026-09-17 pdf_ref:
💡 关联知识: [[Linux+C+C++技术体系梳理/2. Linux系统编程/04-IO多路复用]] | [[03-Linux驱动开发核心/04-并发同步与原子操作]] | [[03-Linux驱动开发核心/05-中断与定时器]]
这里的"IO"并非单片机中的GPIO引脚,而是Input/Output(输入/输出),指应用程序对驱动设备的读写操作。当应用程序对设备驱动进行操作时,可能无法立即获取到设备资源。
定义:当应用程序调用read()或write()时,如果设备不可用或数据未准备好,进程会进入休眠状态(阻塞),直到设备可用后被唤醒,再完成数据读写。
// 阻塞方式打开设备
int fd = open("/dev/xxx_dev", O_RDWR); // 默认阻塞模式
ret = read(fd, &data, sizeof(data)); // 无数据时进程休眠
特点:
定义:当设备不可用或数据未准备好时,read()/write()会立即返回一个错误码(-EAGAIN或-EWOULDBLOCK),应用程序可以选择重试或放弃。
// 非阻塞方式打开设备
int fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); // 添加O_NONBLOCK
ret = read(fd, &data, sizeof(data)); // 无数据时立即返回错误
特点:
graph TD
A[应用程序调用read] --> B{设备是否可用?}
B -->|是| C[读取数据并返回]
B -->|否| D{IO模式?}
D -->|阻塞IO| E[进程进入休眠态]
E --> F[等待设备就绪]
F --> G[被唤醒]
G --> C
D -->|非阻塞IO| H[立即返回错误码]
H --> I[应用程序重试或放弃]
| 方式 | 代码示例 | 说明 |
|---|---|---|
| 阻塞(默认) | open("/dev/xxx", O_RDWR) |
默认行为,无O_NONBLOCK |
| 非阻塞 | open("/dev/xxx", O_RDWR \| O_NONBLOCK) |
添加O_NONBLOCK标志 |
| 运行时切换 | fcntl(fd, F_SETFL, flags \| O_NONBLOCK) |
动态设置非阻塞 |
等待队列是Linux内核实现阻塞进程唤醒的机制。当设备不可用时,将进程添加到等待队列中使其休眠;当设备可用时(如中断发生),从队列中唤醒进程。
graph LR
A[进程A] -->|加入| C[等待队列头]
B[进程B] -->|加入| C
D[中断/事件] -->|唤醒| C
C -->|唤醒| E[进程A继续执行]
C -->|唤醒| F[进程B继续执行]
等待队列头是队列的入口,定义在include/linux/wait.h:
struct __wait_queue_head {
spinlock_t lock; // 自旋锁,保护队列
struct list_head task_list; // 链表,存放等待队列项
};
typedef struct __wait_queue_head wait_queue_head_t;
初始化方法:
// 方法1:动态初始化
wait_queue_head_t my_wq;
init_waitqueue_head(&my_wq);
// 方法2:静态初始化(推荐)
DECLARE_WAIT_QUEUE_HEAD(my_wq);
每个等待的进程对应一个队列项:
struct __wait_queue {
unsigned int flags; // 标志位
void *private; // 私有数据,通常指向task_struct
wait_queue_func_t func; // 唤醒回调函数
struct list_head task_list; // 链表节点
};
typedef struct __wait_queue wait_queue_t;
定义并初始化队列项:
// 定义并初始化一个等待队列项,关联当前进程
DECLARE_WAITQUEUE(wait, current);
// 将等待队列项添加到队列头
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
// 从队列头移除等待队列项
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
// 唤醒所有等待进程(包括TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE)
void wake_up(wait_queue_head_t *q);
// 只唤醒TASK_INTERRUPTIBLE状态的进程(可被信号打断)
void wake_up_interruptible(wait_queue_head_t *q);
使用场景:
wake_up():唤醒所有类型进程wake_up_interruptible():推荐使用,进程可被信号中断| 函数 | 描述 |
|---|---|
wait_event(wq, condition) |
无条件等待,进程状态为TASK_UNINTERRUPTIBLE |
wait_event_timeout(wq, condition, timeout) |
带超时等待,超时后自动唤醒 |
wait_event_interruptible(wq, condition) |
可中断等待,进程状态为TASK_INTERRUPTIBLE |
wait_event_interruptible_timeout(wq, condition, timeout) |
可中断+超时 |
使用示例:
// 等待releasekey有效(按键按下)
wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey));
sequenceDiagram
participant App as 应用程序
participant Driver as 驱动
participant IRQ as 中断
App->>Driver: read(fd, buf, cnt)
Driver->>Driver: DECLARE_WAITQUEUE(wait, current)
Driver->>Driver: add_wait_queue(&r_wait, &wait)
Driver->>Driver: __set_current_state(TASK_INTERRUPTIBLE)
Driver->>Driver: schedule() [进程休眠]
Note over IRQ: 设备就绪(如按键按下)
IRQ->>Driver: 中断处理函数
Driver->>Driver: wake_up_interruptible(&r_wait)
Driver->>App: 进程被唤醒,继续执行
Driver->>Driver: remove_wait_queue(&r_wait, &wait)
Driver->>App: 返回数据
当应用程序以非阻塞方式访问设备时,驱动需要提供轮询(poll)机制。应用程序通过select()、poll()或epoll()查询设备是否可操作,驱动中的poll()函数会被执行。
graph TD
A[应用程序] -->|select/poll/epoll| B[内核]
B -->|调用| C[驱动poll函数]
C -->|返回事件| B
B -->|通知| A
A -->|数据可读/可写| D[read/write]
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
参数说明:
nfds:最大文件描述符+1readfds:监视读事件的文件描述符集合writefds:监视写事件的文件描述符集合exceptfds:监视异常事件的文件描述符集合timeout:超时时间,NULL表示无限等待fd_set操作宏:
FD_ZERO(&readfds); // 清空集合
FD_SET(fd, &readfds); // 添加fd到集合
FD_CLR(fd, &readfds); // 从集合删除fd
FD_ISSET(fd, &readfds); // 测试fd是否在集合中
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
pollfd结构体:
struct pollfd {
int fd; // 文件描述符
short events; // 请求的事件(输入)
short revents; // 返回的事件(输出)
};
事件标志:
| 事件 | 描述 |
|---|---|
POLLIN |
有数据可以读取 |
POLLPRI |
有紧急数据需要读取 |
POLLOUT |
可以写数据 |
POLLERR |
发生错误 |
POLLHUP |
文件描述符挂起 |
POLLNVAL |
无效的请求 |
POLLRDNORM |
等同于POLLIN,普通数据可读 |
epoll是为处理大并发设计的,适合文件描述符数量多的场景:
// 创建epoll句柄
int epoll_create(int size);
// 控制epoll(添加/修改/删除监视)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 等待事件
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
epoll事件:
| 事件 | 描述 |
|---|---|
EPOLLIN |
有数据可以读取 |
EPOLLOUT |
可以写数据 |
EPOLLPRI |
有紧急数据需要读取 |
EPOLLERR |
发生错误 |
EPOLLHUP |
挂起 |
EPOLLET |
边沿触发(默认水平触发) |
EPOLLONESHOT |
一次性监视 |
unsigned int (*poll)(struct file *filp, struct poll_table_struct *wait);
实现要点:
poll_wait()将进程添加到等待队列根据设备状态返回相应事件
static unsigned int xxx_poll(struct file *filp, poll_table *wait) {
unsigned int mask = 0;
struct xxx_dev *dev = filp->private_data;
// 将进程添加到等待队列(不阻塞)
poll_wait(filp, &dev->r_wait, wait);
// 根据设备状态设置返回事件
if (atomic_read(&dev->data_ready)) {
mask |= POLLIN | POLLRDNORM; // 可读
}
if (!atomic_read(&dev->buffer_full)) {
mask |= POLLOUT | POLLWRNORM; // 可写
}
return mask;
}
void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p);
作用:将当前进程添加到poll_table对应的等待队列中,不会引起阻塞。
sequenceDiagram
participant App as 应用程序
participant Kernel as 内核
participant Driver as 驱动
App->>Kernel: select/poll/epoll
Kernel->>Driver: 调用file_operations.poll
Driver->>Driver: poll_wait()添加到等待队列
Driver->>Kernel: 返回事件掩码
Kernel->>App: 返回就绪的fd
alt 有数据可读
App->>Kernel: read(fd, buf, cnt)
Kernel->>Driver: 调用read函数
Driver->>App: 返回数据
else 超时
App->>App: 处理超时
end
异步通知类似于硬件中断,驱动主动向应用程序发送信号(如SIGIO),通知数据就绪。应用程序无需轮询,等待信号即可。
graph LR
A[驱动程序] -->|kill_fasync| B[发送SIGIO信号]
B --> C[应用程序]
C -->|信号处理函数| D[读取数据]
struct fasync_struct {
spinlock_t fa_lock; // 自旋锁
int magic; // 魔数
int fa_fd; // 文件描述符
struct fasync_struct *fa_next; // 链表下一个节点
struct file *fa_file; // 关联的文件
struct rcu_head fa_rcu; // RCU回调
};
在设备结构体中定义:
struct xxx_dev {
// ... 其他成员
struct fasync_struct *async_queue; // 异步通知队列
};
int (*fasync)(int fd, struct file *filp, int on);
实现示例:
static int xxx_fasync(int fd, struct file *filp, int on) {
struct xxx_dev *dev = filp->private_data;
// 调用fasync_helper初始化fasync_struct
if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
return -EIO;
return 0;
}
// 在file_operations中注册
static struct file_operations xxx_ops = {
.fasync = xxx_fasync,
.release = xxx_release, // 关闭时释放
};
static int xxx_release(struct inode *inode, struct file *filp) {
// 调用fasync函数,on=0表示删除异步通知
return xxx_fasync(-1, filp, 0);
}
void kill_fasync(struct fasync_struct **fp, int sig, int band);
参数:
fp:fasync_struct指针sig:要发送的信号(如SIGIO)band:可读设为POLL_IN,可写设为POLL_OUT使用示例:
// 在中断处理或定时器中发送信号
if (atomic_read(&dev->releasekey)) {
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
三步设置:
// 1. 注册信号处理函数
signal(SIGIO, sigio_signal_func);
// 2. 将进程号告诉内核
fcntl(fd, F_SETOWN, getpid());
// 3. 开启异步通知
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
信号处理函数示例:
static void sigio_signal_func(int signum) {
int err;
unsigned int keyvalue;
err = read(fd, &keyvalue, sizeof(keyvalue));
if (err < 0) {
// 读取错误处理
} else {
printf("SIGIO signal! key value=%d\r\n", keyvalue);
}
}
sequenceDiagram
participant App as 应用程序
participant Driver as 驱动
participant IRQ as 中断/定时器
App->>Driver: open("/dev/xxx")
App->>App: signal(SIGIO, handler)
App->>App: fcntl(F_SETOWN, getpid())
App->>App: fcntl(F_SETFL, FASYNC)
Driver->>Driver: fasync()被调用
Note over IRQ: 设备就绪
IRQ->>Driver: 中断处理函数
Driver->>App: kill_fasync(SIGIO)
Driver->>App: 信号处理函数执行
App->>Driver: read(fd, buf, cnt)
Driver->>App: 返回数据
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/device.h>
#include <linux/of_irq.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "blockio"
#define KEY0VALUE 0X01
#define INVAKEY 0XFF
#define KEY_NUM 1
struct irq_keydesc {
int gpio;
int irqnum;
unsigned char value;
char name[10];
irqreturn_t (*handler)(int, void *);
};
struct imx6uirq_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
atomic_t keyvalue;
atomic_t releasekey;
struct timer_list timer;
struct irq_keydesc irqkeydesc[KEY_NUM];
unsigned char curkeynum;
wait_queue_head_t r_wait; /* 读等待队列头 */
};
struct imx6uirq_dev imx6uirq;
/* 中断服务函数,开启定时器消抖 */
static irqreturn_t key0_handler(int irq, void *dev_id) {
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器服务函数,按键消抖 */
void timer_function(unsigned long arg) {
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio);
if (value == 0) { /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
} else { /* 按键松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1);
}
/* 唤醒进程 */
if (atomic_read(&dev->releasekey)) {
wake_up_interruptible(&dev->r_wait);
}
}
/* 按键IO初始化 */
static int keyio_init(void) {
// ... GPIO和中断初始化代码 ...
/* 初始化等待队列头 */
init_waitqueue_head(&imx6uirq.r_wait);
return 0;
}
/* 从设备读取数据(阻塞方式) */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt) {
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
/* 等待按键按下 */
ret = wait_event_interruptible(dev->r_wait,
atomic_read(&dev->releasekey));
if (ret) {
goto wait_error;
}
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) {
if (keyvalue & 0x80) { /* 松开按键 */
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
} else {
goto data_error;
}
return 0;
wait_error:
set_current_state(TASK_RUNNING);
remove_wait_queue(&dev->r_wait, &wait);
return ret;
data_error:
return -EINVAL;
}
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};
/* 读取函数(支持阻塞和非阻塞) */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt) {
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
if (filp->f_flags & O_NONBLOCK) { /* 非阻塞访问 */
if (atomic_read(&dev->releasekey) == 0)
return -EAGAIN;
} else { /* 阻塞访问 */
ret = wait_event_interruptible(dev->r_wait,
atomic_read(&dev->releasekey));
if (ret) {
goto wait_error;
}
}
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) {
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
} else {
goto data_error;
}
return 0;
wait_error:
return ret;
data_error:
return -EINVAL;
}
/* poll函数 */
static unsigned int imx6uirq_poll(struct file *filp,
struct poll_table_struct *wait) {
unsigned int mask = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
if (atomic_read(&dev->releasekey)) { /* 按键按下 */
mask = POLLIN | POLLRDNORM; /* 返回POLLIN */
}
return mask;
}
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.poll = imx6uirq_poll,
};
/* 设备结构体 */
struct imx6uirq_dev {
// ... 其他成员
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
/* fasync函数 */
static int imx6uirq_fasync(int fd, struct file *filp, int on) {
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
return fasync_helper(fd, filp, on, &dev->async_queue);
}
/* release函数 */
static int imx6uirq_release(struct inode *inode, struct file *filp) {
return imx6uirq_fasync(-1, filp, 0);
}
/* 定时器服务函数中发送信号 */
void timer_function(unsigned long arg) {
// ... 按键消抖处理 ...
if (atomic_read(&dev->releasekey)) {
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
}
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.poll = imx6uirq_poll,
.fasync = imx6uirq_fasync,
.release = imx6uirq_release,
};
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
static int fd = 0;
static void sigio_signal_func(int signum) {
int err = 0;
unsigned int keyvalue = 0;
err = read(fd, &keyvalue, sizeof(keyvalue));
if (err < 0) {
printf("Read error!\r\n");
} else {
printf("SIGIO signal! key value=%d\r\n", keyvalue);
}
}
int main(int argc, char *argv[]) {
int flags = 0;
char *filename;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
signal(SIGIO, sigio_signal_func);
fcntl(fd, F_SETOWN, getpid());
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
while (1) {
sleep(2);
}
close(fd);
return 0;
}
驱动编译(Makefile):
KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := blockio.o # 或 noblockio.o, asyncnoti.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译命令:
# 编译驱动模块
make -j32
# 交叉编译测试APP
arm-linux-gnueabihf-gcc blockioApp.c -o blockioApp
arm-linux-gnueabihf-gcc noblockioApp.c -o noblockioApp
arm-linux-gnueabihf-gcc asyncnotiApp.c -o asyncnotiApp
# 1. 拷贝文件到开发板
cp blockio.ko blockioApp /rootfs/lib/modules/4.1.15/
# 2. 加载驱动
cd /lib/modules/4.1.15
depmod
modprobe blockio.ko
# 3. 后台运行测试APP
./blockioApp /dev/blockio &
# 4. 按下KEY0按键测试
# 观察输出按键值
# 5. 查看CPU使用率
top
# 使用select方式测试
./noblockioApp /dev/noblockio &
# 或使用poll方式(修改源码中#if 0为#if 1)
./noblockioApp /dev/noblockio &
# 加载异步通知驱动
modprobe asyncnoti.ko
# 运行测试APP
./asyncnotiApp /dev/asyncnoti
# 按下KEY0按键,观察输出
# SIGIO signal! key value=1
| 模型 | 机制 | CPU占用 | 响应延迟 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|---|
| 阻塞IO | 等待队列 | 低 | 高 | 简单 | 低频设备 |
| 非阻塞IO | 轮询 | 高 | 低 | 简单 | 实时性要求高 |
| IO多路复用 | select/poll/epoll | 中 | 中 | 中等 | 多设备监控 |
| 异步通知 | 信号(SIGIO) | 低 | 低 | 复杂 | 事件驱动 |
选择建议:
| 特性 | IMX6ULL (Linux) | STM32 (FreeRTOS) | RK3568 (Linux) |
|---|---|---|---|
| 阻塞IO | 等待队列 | 任务通知/信号量 | 等待队列 |
| poll机制 | select/poll/epoll | 无原生支持 | select/poll/epoll |
| 异步通知 | SIGIO信号 | 无原生支持 | SIGIO信号 |
| 中断机制 | request_irq | xTaskNotifyFromISR | request_irq |
| 任务调度 | 内核调度器 | FreeRTOS调度器 | 内核调度器 |
嵌入式差异:
考察点:IO模型理解
参考答案:
-EAGAIN),应用程序需要重试考察点:poll机制实现
参考答案:
file_operations的poll函数poll函数中调用poll_wait()将进程添加到等待队列POLLIN/POLLOUT等)select()/poll()/epoll()监控考察点:等待队列机制
参考答案:
DECLARE_WAIT_QUEUE_HEAD(r_wait)或init_waitqueue_head()DECLARE_WAITQUEUE(wait, current)add_wait_queue(&r_wait, &wait)__set_current_state(TASK_INTERRUPTIBLE)schedule()wake_up_interruptible(&r_wait)remove_wait_queue(&r_wait, &wait)考察点:异步通知机制
参考答案:
kill_fasync()向应用程序发送信号(如SIGIO)signal()注册信号处理函数fcntl(F_SETOWN)将进程号告诉内核fcntl(F_SETFL, FASYNC)开启异步通知SIGIO等特定信号,不适合高频事件考察点:IO多路复用对比
参考答案:
| 特性 | select | poll | epoll |
|---|---|---|---|
| 最大fd数 | 1024(FD_SETSIZE) | 无限制 | 无限制 |
| 数据结构 | fd_set位图 | pollfd数组 | 红黑树+就绪链表 |
| 触发方式 | 水平触发 | 水平触发 | 水平/边沿触发 |
| 效率 | O(n)遍历 | O(n)遍历 | O(1)事件回调 |
| 内核实现 | 遍历所有fd | 遍历所有fd | 回调通知就绪fd |
| 适用场景 | 少量fd | 中等fd | 大量fd(高并发) |
选择建议:
代码来源: Linux驱动例程 14_blockio, 15_noblockio, 16_asyncnoti 最后更新: 2026-09-17