Linux驱动基础:阻塞、休眠、poll、异步通知
休眠/唤醒、poll、异步通知、阻塞/非阻塞
文章目录
- 休眠/唤醒、poll、异步通知、阻塞/非阻塞
- I 休眠与唤醒
- 1.基本概念
- 2.休眠分类
- 3.休眠唤醒流程
- 4.休眠函数
- 5.唤醒函数
- 6.在驱动中进行休眠和唤醒
- II poll
- 1.基本概念
- 2.poll工作流程
- 2.相关函数
- 3.在应用和驱动中使用poll
- III 异步通知
- 1.基本概念
- 2.工作流程
- 3.在应用和驱动中使用异步通知
- IV 阻塞与非阻塞
- 1.基本概念
- 2.给fd设置阻塞模式
I 休眠与唤醒
1.基本概念
- 休眠:指进程或线程暂时停止执行的过程;进程主动放弃 CPU 使用权,等待特定事件后再恢复运行。
- 唤醒:与休眠相对,是将休眠的进程或线程从停止执行状态恢复到正常运行状态的过程;唤醒通常由外部事件如硬件中断、其他进程的通知触发。
- 意义:休眠/唤醒机制允许在等待资源或事件时释放CPU,提高系统资源利用率;通过同步机制确保数据一致性,避免竞态条件。
2.休眠分类
- 可中断休眠(TASK_INTERRUPTIBLE):在休眠过程中可被信号打断,唤醒后会处理信号并重新判断等待条件。
- 不可中断休眠(TASK_UNINTERRUPTIBLE):在休眠过程中不会被信号打断,仅能通过特定事件唤醒,常用于必须等待关键操作完成的场景。
3.休眠唤醒流程
以app读取按键数据为例:
- APP调用read等函数试图读取按键数据;
- APP进入内核态,调用驱动中对应函数,发现有数据则复制到用户空间并马上返回;
- 如果APP在内核态,也就是在驱动程序中发现没有数据,则驱动调用 wait_event_interruptible 等函数将当前进程(APP对应的内核线程)置为休眠状态(TASK_INTERRUPTIBLE),等待按键被按下
- 当按下按键时,驱动程序的中断服务程序被调用,它会记录数据、唤醒 APP;
- APP继续运行它的内核态代码,也就是驱动程序中的函数,复制数据到用户空间并马上返回继续执行app剩余内容
4.休眠函数
-
wait_event_interruptible(wq, condition)
休眠,直到 condition 为真;休眠期间是可被打断的,可以被信号打断=
-
wait_event(wq, condition)
休眠,直到 condition 为真;退出的唯一条件是 condition 为真,信号也不能唤醒
-
wait_event_interruptible_timeout(wq,condition, timeout)
休眠,直到 condition 为真或超时;休眠期间是可被打断的,可以被信号打断
-
wait_event_timeout(wq, condition,timeout)
休眠,直到condition为真;退出的唯一条件是condition为真,信号也不能唤醒
-
参数wq: waitqueue,等待队列;参数condition:等待条件,可以是一个变量,也可以是任何表达式
5.唤醒函数
wake_up_interruptible(x)
唤醒 x 队列中状态为“ TASK_INTERRUPTIBLE”的线程,只唤醒其中的一个线程
wake_up_interruptible_nr(x, nr)
唤醒 x 队列中状态为“ TASK_INTERRUPTIBLE”的线程,只唤醒其中的 nr 个线程
wake_up_interruptible_all(x)
唤醒 x 队列中状态为“ TASK_INTERRUPTIBLE”的线程,唤醒其中的所有线程
wake_up(x)
唤 醒 x 队 列 中 状 态 为 “ TASK_INTERRUPTIBLE ” 或“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中的一个线程
wake_up_nr(x, nr)
唤 醒 x 队 列 中 状 态 为 “ TASK_INTERRUPTIBLE ” 或“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中 nr 个线程
wake_up_all(x)
唤 醒 x 队 列 中 状 态 为 “ TASK_INTERRUPTIBLE ” 或“TASK_UNINTERRUPTIBLE”的线程,唤醒其中的所有线程
6.在驱动中进行休眠和唤醒
以app调用read读取按键为例:
-
初始化等待队列
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
-
在驱动read函数中将休眠等待
wait_event_interruptible(gpio_key_wait, key_event)
-
在中断服务函数中唤醒等待
wake_up_interruptible(&gpio_key_wait);
-
示例:
static int key_event; static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);static irqreturn_t gpio_key_irq_handler(int irq, void* dev_id){struct gpio_key *key_get = dev_id;int value = gpio_get_value(key_get->gpio);if (value < 0) {printk(KERN_ERR "[%s] Failed to get GPIO value for key %d, error: %d\n",__func__, key_get->gpio, value);return IRQ_HANDLED;}/*upper 8 bits is whic gpio_key, lower 8 bits is the value of gpio_key status*/key_event = (gpio_key_info->gpio << 8) | value;wake_up_interruptible(&gpio_key_wait);return IRQ_HANDLED; }ssize_t gpio_key_driver_read(struct file *fd, char __user *buf, size_t size, loff_t *lof){if (wait_event_interruptible(gpio_key_wait, key_event))return -ERESTARTSYS;int err = copy_to_user(buf, &key_event, sizeof(key_event));if (err) {printk(KERN_ERR "[%s] Failed to copy key event to user space, error: %d\n", __func__, err);return -EFAULT;}key_event = 0;return sizeof(key_event); }static struct file_operations gpio_key_ops = {.owner = THIS_MODULE,.read = gpio_key_driver_read, };
II poll
1.基本概念
- 与休眠类似,poll机制在读取数据时首先调用poll查询数据是否就绪,如果没有就先休眠一段时间,在这段时间内若数据就绪则唤醒进程,调用read读取数据并继续下文,若超过设定时间数据仍未就绪则直接返回
2.poll工作流程
- 以app读取按键为例:
- app调用poll()系统调用,传入待监控的按键文件描述符
- 内核检查每个文件描述符的当前状态
- 若无就绪描述符,将进程挂起等待按键就绪或直到超时
- 返回就绪描述符数量及对应事件,若就绪则app调用read读取数据
2.相关函数
- 用户空间:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 内核空间:驱动程序中实现poll对应的函数
3.在应用和驱动中使用poll
-
驱动程序
static unsigned int gpio_key_driver_poll(struct file *file, struct poll_table_struct *table){printk(KERN_INFO "[%s]:called\n!",__func__);poll_wait(file,&gpio_key_wait, table);return key_event ? POLLIN : 0; }static struct file_operations gpio_key_ops = {.owner = THIS_MODULE,.poll = gpio_key_driver_poll, };
-
应用程序
struct pollfd fds = {.fd = fdf,.events = POLLIN, };while(1){int ret = poll(&fds, 1, timeout);if(ret == 1 && (fds.revents & POLLIN)){read(fdf, &event, sizeof(event));button = (event >> 8) & 0xff; value = (event) & 0xff;printf("[%s]:button event : butto %u value %u\n",argv[0], (unsigned int)button, (unsigned int)(value));}elseprintf("[%s]time out\n",argv[0]);}
III 异步通知
1.基本概念
- 异步通知:一种通知机制,允许驱动程序在数据就绪时主动通知应用程序,而不需要应用程序轮询或阻塞等待
- 信号驱动I/O:应用程序预先注册信号处理函数,当特定事件发生时内核发送信号通知应用程序处理
- 意义:减少CPU轮询开销,提高系统响应效率,适用于低延迟场景
2.工作流程
- 应用程序注册信号处理函数并设置文件描述符的异步通知属性
- 驱动程序实现fasync接口,在数据就绪时发送信号
- 内核向应用程序发送SIGIO信号
- 应用程序的信号处理函数被调用,执行读取等操作
3.在应用和驱动中使用异步通知
-
驱动程序
static irqreturn_t gpio_key_irq_handler(int irq, void* dev_id){struct gpio_key *key_get = dev_id;int value = gpio_get_value(key_get->gpio);if (value < 0) {printk(KERN_ERR "[%s] Failed to get GPIO value for key %d, error: %d\n",__func__, key_get->gpio, value);return IRQ_HANDLED;}//printk(KERN_INFO "[%s]key %d interrupt, the value is %d\n", __func__,// key_get->gpio, value);/*upper 8 bits is whic gpio_key, lower 8 bits is the value of gpio_key status*/key_event = (gpio_key_info->gpio << 8) | value;kill_fasync(&gpio_key_fasync, SIGIO, POLLIN);return IRQ_HANDLED; }ssize_t gpio_key_driver_read(struct file *fd, char __user *buf, size_t size, loff_t *lof){int err = copy_to_user(buf, &key_event, sizeof(key_event));if (err) {printk(KERN_ERR "[%s] Failed to copy key event to user space, error: %d\n", __func__, err);return -EFAULT;}key_event = 0;return sizeof(key_event); }static int gpio_key_driver_fasync(int fd, struct file *file, int on){int ret = fasync_helper(fd, file, on, &gpio_key_fasync);if(ret >= 0)return 0;elsereturn -EIO; }static struct file_operations gpio_key_ops = {.owner = THIS_MODULE,.read = gpio_key_driver_read,.fasync = gpio_key_driver_fasync, };
-
应用程序
static int fd, event; static uint8_t button, value;static void signal_handler(int sig){read(fd, &event, sizeof(event));button = (event >> 8) & 0xff; value = (event) & 0xff;printf("[%s]:button event : butto %u value %u\n",__func__, (unsigned int)button, (unsigned int)(value)); }int main(int argc, char *argv[]){if(argc != 2){printf("[%s]:Usage:%s <file>\n", argv[0], argv[1]);return -1;}signal(SIGIO, signal_handler);fd = open(argv[1], O_RDWR);if(fd < 0){perror("open");return -1;}fcntl(fd, F_SETOWN, getpid());int flags = fcntl(fd, F_GETFL);fcntl(fd, F_SETFL, flags | FASYNC);while(1){sleep(10);printf("[%s] funciton calld\n", argv[0]);}close(fd);return 0; }
IV 阻塞与非阻塞
1.基本概念
- 阻塞I/O:当进程执行 I/O 操作(如read、write)时,若资源未就绪(如无数据可读、缓冲区满),进程会进入休眠状态(TASK_INTERRUPTIBLE 或TASK_UNINTERRUPTIBLE),释放 CPU,直到 I/O 操作完成或超时才被唤醒。
- 非阻塞I/O:当进程执行 I/O 操作时,若资源未就绪,操作会立即返回错误,并设置errno为EAGAIN或EWOULDBLOCK,进程不会休眠,可继续执行其他任务。
2.给fd设置阻塞模式
int flags = fcntl(fd, F_GETFL);// 设置为非阻塞模式fcntl(fd, F_SETFL, flags | O_NONBLOCK);// 恢复为阻塞模式fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
作完成或超时才被唤醒。
- **非阻塞I/O**:当进程执行 I/O 操作时,若资源未就绪,操作会立即返回错误,并设置errno为EAGAIN或EWOULDBLOCK,进程不会休眠,可继续执行其他任务。#### 2.给fd设置阻塞模式```cint flags = fcntl(fd, F_GETFL);// 设置为非阻塞模式fcntl(fd, F_SETFL, flags | O_NONBLOCK);// 恢复为阻塞模式fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);