news 2026/10/2 2:29:01

Linux驱动阻塞与非阻塞访问的理解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux驱动阻塞与非阻塞访问的理解

一、阻塞I/O

应用程序的阻塞访问方式

int fd; int data = 0; fd = open("/dev/xxx_dev", O_RDWR); ret = read(fd, &data, sizeof(data));

阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,这样可以将CPU资源让出来,直到等待的条件被满足。而设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成。

示例:阻塞方式读取鼠标

#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main(int argc, char *argv[]) { char buf[100]; int fd, ret; fd = open("/dev/input/event3", O_RDONLY) if (-1 == fd) { perror("open error"); exit(-1); } memset(buf, 0, sizeof(buf)); ret = read(fd, buf, sizeof(buf)); if (0 > ret) { perror("read error"); close(fd); exit(-1); } printf("suceess to read<%d> bytes data\n", ret); close(fd); exit(0); }

二、非阻塞I/O

应用程序的非阻塞访问方式

int fd; int data - 0; fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); ret = read(fd, &data, sizeof(data));

示例:非阻塞方式读取鼠标

#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main(int argc, char *argv[]) { char buf[100]; int fd, ret; fd = open("/dev/input/event3", O_RDONLY|O_NONBLOCK) if (-1 == fd) { perror("open error"); exit(-1); } memset(buf, 0, sizeof(buf)); for(;;) { ret = read(fd, buf, sizeof(buf)); if (0 < ret) { printf("suceess to read<%d> bytes data\n", ret); close(fd); exit(0); } } }

三、驱动程序对阻塞进程的唤醒

等待队列wait queue实现了阻塞进程的唤醒工作。

等待队列要用到的数据结构:

1、使用等待队列,首先创建和初始化一个等待队列头,队列头结构体定义如下:

struct __wait_queue_head { spinlock_t lock; struct list_head task_list; }; typedef struct __wait_queue_head wait_queue_head_t;

2、每一个要访问设备的进程是一个队列项,队列项结构体:

struct __wait_queue { unsigned int flags; void *private; wait_queue_func_t func; struct list_head task_list; }; typedef struct __wait_queue wait_queue_t;

等待队列API函数:

1、初始化等待队列头:

void init_waitqueue_head(wait_queue_head_t *q);

使用宏 DECLARE_WAIT_QUEUE_HEAD直接一次性完成定义和初始化,如下:

DECLARE_WAIT_QUEUE_HEAD(q);

2、初始化等待队列项:

使用宏DECLARE_WAITQUEUE可定义和初始化一个等待队列项:

DECLARE_WAITQUEUE(wait, current);

current是当前进程。

3、将队列项添加到等待队列头:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

4、将队列项移除等待队列头:

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

5、唤醒等待队列头

唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程

void wake_up(wait_queue_head_t *q);

唤醒处于TASK_INTERRUPTIBLE状态的进程:

void wake_up_interruptible(wait_queue_head_t *q);

6、等待事件

等待以wq为等待队列头的等待队列被唤醒,将进程设置为TASK_INTERRUPTIBLE可被信号打断:

wait_event_interruptible(wq,condition)

增加了超时时间,以jiffers为单位,如果返回0表示超时时间到且conditon为假 :

wait_event_interruptible(wq,condition,timeout)

这些宏在内核头文件include/linux/wait.h中。

示例:设备驱动中使用等待队列

static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret; struct xxx_dev *dev = (struct xxx_dev *)filp->private_data; ret = wait_event_interruptible(dev->r_wait, condition); DECLARE_WAITQUEUE(wait, current); add_wait_queue(&dev->r_wait, &wait); avail = device_readable(...); if (avail < 0) { _set_current_state(TASK_INTERRUPTIBLE); schedule(); if (signal_pending(current)) { ret = -ERESTARTSYS; goto out; } __set_current_state(TASK_RUNNING); remove_wait_queue(&dev->r_wait, &wait); } device_read(...); out: remove_wait_queue(&dev->r_wait, &wait); __set_current_state(TASK_RUNNING); }

四、非阻塞I/O应用程序轮询

应用程序通过select、epoll、poll函数来查询设备是否可以操作。

1、select

int select(int nfds, fd_set *readfds, fd_set *writedfds, fd_set *exceptfds, struct timeval *timeout);

nfds:三类文件描述符集合里最大值加一,文件描述符从0开始编号,select 需要检查的范围是 [0, nfds),即 0 到 nfds - 1。

fd_set类型变量的每一位对应一个fd:

readfds时监视描述符集的读变化;

writefds监视文件是否可写;

exceptfds监视文件的异常。

上面的三个参数如果设置为NULL就表示不关心这些文件的变化。

fd_set变量的操作宏,定义在 sys/select.h:

FD_ZERO(fdsetp)

FD_SET(fd, fdsetp)

FD_CLR(fd, fdsetp)

FD_ISSET(fd, fdsetp)

timeout设置超时时间,如果时NULL表示无期限等待。

struct timeval { long tv_sec; long tv_usce; };

返回值0表示超时发生,但没有任何文件描述符可操作;返回值-1表示有错误发生;返回其他值表示可进行操作的文件描述符个数。

示例:应用程序使用select函数非阻塞访问读

void main(void) { int ret, fd; fd_set readfds; struct timeval timeout; fd = open("dev_xxx", O_RDWR | O_NONBLOCK); FD_ZERO(&readfds); FD_SET(fd, &readfds); timeout.tv_sec = 0; timeout.tv_usec = 500000; /* 500ms */ ret = select(fd + 1, &readfds, NULL, NULL, &timeout); switch(ret) { case 0: printf("timeout!\r\n"); break; case -1: printf("error!\r\n"); break; default: if (FD_ISSET(fd, &readfds)) { /* read data */ } break; } }

2、poll

单个线程中有,select函数能够监视的文件描述符数最大限制为1024,这时就可以使用poll函数,poll函数没有最大文件描述符限制。

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

nfds是要监视的文件描述符数量;

timeout单位是ms;

fds是要监视的文件描述符集合以及要监视的事件。

struct pollfd { int fd; short events; short revents; };

fd是要监视的文件描述符;

events是要监视的事件:

revents是返回参数,即返回的事件。

poll函数的返回值

返回-1表示有错误发生,并且会设置errno;返回0表示该调用在任意一个文件描述符成为就绪态之前就超时了;返回一个正整数表示有一个或多个文件描述符处于就绪态了,返回值表示fds数组中返回的revents变量不为0的struct pollfd对象的数量。

示例:应用程序使用poll函数非阻塞访问读

void main(void) { int ret; int fd; struct pollfd fds; fd = open(filename, O_RDWR | O_NONBLOCK); fds.fd = fd; fds.events = POLLIN; ret = poll(&fds, 1, 500); if (ret) { [...] /* read data */ [...] } else if (ret == 0) { /* timer out */ } else if (ret < 0) { /* error */ } }

五、驱动程序对select和poll非阻塞访问的行为

设备驱动程序提供非阻塞处理方式也就是轮询。当应用程序调用select或poll函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations操作集中的poll函数就会执行。

unsigned int (*poll)(struct file *filp, struct poll_table_struct *wait)

filp是要打开的设备文件(文件描述符);

wait是由应用程序传递进来的,一般将此函数传递给poll_wait函数。

void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)

参数wait_address是要添加到poll_table中的等待队列头;

参数p是file_operations中poll函数的wait参数。

示例:设备驱动poll()函数

static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret; struct xxx_dev *dev = (struct xxx_dev *)filp->private_data; if (filp->f_flags & O_NONBLOCK) { avail = device_readable(...); if (avail < 0) { ret = -EAGAIN; } else { ret = wait_event_interruptible(dev->r_wait, condition); if (ret) { retrun ret; } } } device_read(...); ret 0; } unsigned int xxx_poll(struct file *filp, struct poll_table_struct *wait) { unsigned int mask = 0; struct xxx_dev *dev = (struct xxx_dev *)filp->private_data; poll_wait(filp, &dev->r_wait, wait); avail = device_readable(...); if (avail) mask = POLLIN | POLLRDNORM; return mask; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 2:27:58

Windows 11 上安装配置 Claude Code 完整指南

最近我把主力开发机从 macOS 换到 Windows 11&#xff0c;第一件事就是把 Claude Code 装起来。这工具是 Anthropic 官方的命令行编程助手&#xff0c;直接在终端里跑 Claude&#xff0c;能读项目文件、改代码、执行终端命令&#xff0c;甚至能帮你完成 Git 提交。之前我在 Mac…

作者头像 李华
网站建设 2026/10/2 2:25:45

商务谈判后怎么快速找到会议内容?日历备忘场景用法

商务谈判结束后&#xff0c;多数职场人都会遇到同一个棘手问题&#xff1a;大量谈判录音、会议纪要零散堆积&#xff0c;时隔1-2天就难以精准定位对应场次的会议内容&#xff0c;复盘谈判细节、核对合作条款需要耗费大量时间翻找文件。市面上多数会议录音工具的检索功能存在明显…

作者头像 李华
网站建设 2026/10/2 2:25:37

JavaWeb获取HTTP请求体数据全解析

在如今做Java Web开发的时候, 大家经常会碰到那么一项任务, 就是去取那个HTTP请求里头的那个请求体数据这个东西。因为这个请求体的位置, 通常都会藏着客户端提交过来的各种数据, 比如说表单里面的内容, JSON格式的东西, 或者XML结构的数据之类的。对于咱们用Java写代码的同学来…

作者头像 李华
网站建设 2026/10/2 2:23:46

链表核心原理与实战指南:从C语言实现到面试算法题

1. 从一个“排队结账”的例子说起&#xff1a;链表为什么值得你认真学如果你去超市结账&#xff0c;收银台前的人是一个挨一个排着的。队伍中间的人只知道“我前面是谁、我后面是谁”&#xff0c;整条队伍没有一个总管理员拿着花名册报出每个人的位置。你想找排在第三个的人&am…

作者头像 李华
网站建设 2026/10/2 2:23:30

Debian 13无头服务器配置XFCE4+TigerVNC远程桌面并systemd自启

一台没有显示器的 Debian 13 服务器&#xff0c;日常维护全靠 SSH&#xff0c;突然某一天你需要在上面跑一个带界面的工具&#xff0c;或者想让不会命令行的人也能操作一下系统。这时候我第一个想到的永远不是装完整的 GNOME&#xff0c;也不是折腾 Wayland&#xff0c;而是 XF…

作者头像 李华