驱动之路#24:Linux设备四种读写模型

描述

 

正文

Linux 设备四种读写模型——其实核心就 4 种方式:查询、休眠 - 唤醒、poll、异步通知。它们不是中断本身,而是 “应用 - 驱动” 的上层交互逻辑(中断是底层硬件触发机制),但高效交互几乎都依赖中断实现。

1 查询方式

核心原理:应用层主动、周期性查询设备状态,不管设备是否就绪,CPU 都在循环检查 —— 像你每隔 1 分钟去门口看快递到没到。

代码示例(应用层):

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#include #include #include int main() {    int fd = open("/dev/key", O_RDWR);    if (fd < 0) { perror("open"); return -1; }    int key_state;    while (1) {        read(fd, &key_state, sizeof(key_state)); // 主动查询        if (key_state == 1) {             printf("按键按下!n");            break;        }        usleep(100000); // 100ms查一次,仍占CPU    }    close(fd);    return 0;}

优缺点与场景

 优点:实现最简单,无需驱动复杂逻辑,调试方便

 缺点:CPU 占用率极高(哪怕设备几小时没响应),浪费资源

 适用场景:仅调试或极简单设备(如 LED 状态查询),生产环境慎用!

 

2 休眠 - 唤醒

核心原理:设备未就绪时,应用进程主动休眠(释放 CPU 给其他任务);当设备就绪(如按键按下触发中断),驱动唤醒进程继续执行 —— 像你听到门铃(中断)再去取快递,否则在家休息。

代码示例(驱动 + 应用):

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
驱动层(关键逻辑)#include #include static wait_queue_head_t key_waitq; // 等待队列static int key_pressed = 0// 设备就绪标记// 读操作:未就绪则休眠static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {    wait_event_interruptible(key_waitq, key_pressed); // 休眠    copy_to_user(buf, &key_pressed, sizeof(key_pressed));    key_pressed = 0;    return sizeof(key_pressed);}// 中断服务函数:设备就绪时唤醒irqreturn_t key_isr(int irq, void *dev_id) {    key_pressed = 1;    wake_up_interruptible(&key_waitq); // 唤醒进程    return IRQ_HANDLED;}
应用层(极简)int main() {    int fd = open("/dev/key", O_RDWR);    int key_state;    read(fd, &key_state, sizeof(key_state)); // 阻塞等待    printf("按键按下!n");    close(fd);    return 0;}

优缺点与场景

 优点:CPU 占用率极低(休眠时 0 占用),实现简单

 缺点:不支持超时,只能阻塞等待单个设备

 适用场景:大多数字符设备(按键、串口、传感器),嵌入式开发首选!

 

3 poll 方式

核心原理:结合 “查询” 和 “休眠 - 唤醒” 的优点:应用层通过poll()/select()设置超时时间,内核监控多个设备 —— 超时前设备就绪则立即返回,超时后也会唤醒,避免无限阻塞。

像你告诉快递员 “10 分钟内到就等,超时不等”,还能同时等快递和外卖。

代码示例(驱动 + 应用):

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
驱动层(poll 函数实现)#include static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait) {    unsigned int mask = 0;    poll_wait(filp, &key_waitq, wait); // 加入等待队列    if (key_pressed) {        mask |= POLLIN | POLLRDNORM; // 标记可读就绪    }    return mask;}// 驱动操作集绑定static const struct file_operations key_fops = {    .owner = THIS_MODULE,    .read = key_read,    .poll = key_poll, // 关键:绑定poll函数};应用层(监控按键 + 超时)#include int main() {    int fd = open("/dev/key", O_RDWR | O_NONBLOCK);    struct pollfd fds[1] = {{fd, POLLIN, 0}}; // 关注可读事件    while (1) {        int ret = poll(fds, 11000); // 等待1秒        if (ret > 0 && (fds[0].revents & POLLIN)) {            int key_state;            read(fd, &key_state, sizeof(key_state));            printf("按键按下!n");            break;        } else if (ret == 0) {            printf("等待超时...n");        }    }    close(fd);    return 0;}

优缺点与场景

 优点:CPU 占用率低,支持超时、支持多设备同时监控,兼容得阻塞 IO

 缺点:实现复杂,高并发下开销大

 适用场景:多设备监听、需超时(串口 + 网卡)

 

4 异步通知

核心原理:完全反转交互方向:应用层无需查询 / 等待,设备就绪时(触发中断),驱动主动发送信号(如 SIGIO) 通知应用层,应用层执行信号处理函数 —— 像快递员直接把快递送上门,不用你等。

代码示例(驱动 + 应用)

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
驱动层(异步通知实现)#include static struct fasync_struct *key_async;// 异步通知初始化static int key_fasync(int fd, struct file *filp, int mode) {    return fasync_helper(fd, filp, mode, &key_async);}// 中断服务函数:发送信号irqreturn_t key_isr(int irq, void *dev_id) {    if (key_async) {        kill_fasync(&key_async, SIGIO, POLL_IN); // 发送SIGIO信号    }    return IRQ_HANDLED;}
应用层(信号处理)#include void sigio_handler(int signum) // 信号处理函数    int fd = open("/dev/key", O_RDWR);    int key_state;    read(fd, &key_state, sizeof(key_state));    printf("异步通知:按键按下!n");    close(fd);}int main() {    int fd = open("/dev/key", O_RDWR);    fcntl(fd, F_SETOWN, getpid()); // 设置信号接收进程    int flags = fcntl(fd, F_GETFL);    fcntl(fd, F_SETFL, flags | FASYNC); // 启用异步通知    signal(SIGIO, sigio_handler); // 注册信号处理函数    // 主线程可自由执行其他逻辑    while (1) {        printf("主线程运行中...n");        sleep(1);    }    close(fd);    return 0;}

优缺点与场景

 优点:CPU 占用率最低,完全异步,应用层无阻塞

 缺点:实现复杂(需处理信号安全),信号可能丢失

 适用场景:高实时性设备(网卡、磁盘 IO)、应用层需同时处理多任务的场景


本人专注 Linux 驱动 & Linux/Android BSP 开发调试,可接外包项目/技术支持/问题定位。有需求或交个朋友可加微信:【Chen_WeChat2025】。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 相关推荐
  • 热点推荐
  • Linux

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分