当前位置: 首页 > backend >正文

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);
http://www.xdnf.cn/news/15545.html

相关文章:

  • 探究Netty 4.2.x版本
  • 增程式汽车底盘设计cad【9张】三维图+设计说明书
  • 单列集合顶层接口Collection
  • 医疗AI“全栈原生态“系统设计路径分析
  • 【游戏引擎之路】登神长阶(十八):3天制作Galgame引擎《Galplayer》——无敌之道心
  • 用AI做带货视频评论分析进阶提分【Datawhale AI 夏令营】
  • LLM大语言模型不适合统计算数,可以让大模型根据数据自己建表、插入数据、编写查询sql统计
  • 加速度传感器的用途与应用
  • es启动问题解决
  • 【C#】实体类定义的是long和值识别到的是Int64,实体类反射容易出现Object does not match target type
  • 高性能架构模式——高性能NoSQL
  • 【MySQL基础】MySQL事务详解:原理、特性与实战应用
  • 用PyTorch手写透视变换
  • 嵌入式学习-PyTorch(5)-day22
  • Towards Low Light Enhancement with RAW Images 论文阅读
  • ASP.NET Core Hosting Bundle
  • Debian 12中利用dpkg命令安装MariaDB 11.8.2
  • C++11迭代器改进:深入理解std::begin、std::end、std::next与std::prev
  • 在 kubernetes 上安装 jenkins
  • 数据结构自学Day7-- 二叉树
  • I3C通信驱动开发注意事项
  • PHP连接MySQL数据库的多种方法及专业级错误处理指南
  • 本地 LLM API Python 项目分步指南
  • Neo4j Python 驱动库完整教程(带输入输出示例)
  • HCIA第三次综合实验:VLAN
  • python实现自动化sql布尔盲注(二分查找)
  • 清理C盘--办法
  • Redis集群搭建(主从、哨兵、读写分离)
  • 26.将 Python 列表拆分为多个小块
  • Kafka 4.0 技术深度解析