Linux驱动开发阻塞和非阻塞IO
Posted XXX_UUU_XXX
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动开发阻塞和非阻塞IO相关的知识,希望对你有一定的参考价值。
- IO:应用程序对设备驱动进行输入/输出操作。
- 阻塞IO:当资源不用时,将应用程序对应的线程挂起,当资源可用时,唤醒任务。
- 非阻塞IO:当资源不可用时,应用程序轮询等待或放弃。具有超时处理机制
设备驱动文件默认读取方式:阻塞式。
应用程序阻塞读取数据
int fd;
int data = 0;
fd = open("/dev/xxx", O_RDWR);
ret = read(fd, &data, sizeof(data));
应用程序阻塞读取数据,添加参数O_NONBLOCK
int fd;
int data = 0;
fd = open("/dev/xxx", O_RDWR | O_NONBLOCK);
ret = read(fd, &data, sizeof(data));
阻塞—等待队列
当设备文件不可操作时,进程进入休眠状态,将CPU资源让出来。在设备文件可以操作时,唤醒进程,一般是在中断函数中唤醒。
Linux内核使用等待队列实现阻塞进程的唤醒。
等待队列头
等待队列头是等待队列的头部。
使用结构体wait_queue_head_t定义一个等待队列头,然后用init_waitqueue_head函数初始化等待队列头。或使用宏DECLARE_WAIT_QUEUE_HEAD一次性定义初始化等待队列头。
等待队列项
每个访问设备的进程都是一个队列项,当设备不可用的时候,将这些进程对应的等待队列项添加到等待队列。
使用结构体wait_queue_t表示等待队列项,使用宏DECLARE_WAITQUEUE定义并初始化一个等待队列项。
DECLARE_WAITQUEUE(name, tsk)
- name:等待队列项名字。
- tsk:等待队列属于的进程,一般设置为current。Linux内核中current是一个全局变量,表示当前进程。
将等待队列项添加到等待队列头
当设备不可访问时,将进程对应的等待队列项添加到之前创建的等待队列头中,只有添加到等待队列头后,进程才能进入休眠态。
add_wait_queue函数原型如下:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
- q:等待队列项要加入的等待队列头。
- wait:等待队列项。
- 返回值:无。
移除等待队列头
当设备可以访问时,将进程对应的等待队列项从等待队列头中删除。
remove_wait_queue函数原型如下:
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
- q:要删除的等待队列项所处的等待队列头。
- wait:要删除的等待队列项。
- 返回值:无。
等待唤醒
当设备可用时,唤醒进入休眠态的进程。wake_up函数唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程。wake_up_interruptible函数只能唤醒处于TASK_INTERRUPTIBLE状态的进程。
void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_heat_t *q);
- q:要唤醒的等待队列头。
- 返回值:无。
等待事件
除了主动唤醒,还可以设置等待某个事件,当事件满足后自动唤醒等待队列中的进程。
wait_event(wq, condition)
wait_event函数,condition条件为真时,以wq为等待队列头的等待队列被唤醒,否则一直阻塞,会将进程设置为TASK_UNINTERRUPTIBLE状态。
wait_event_timeout(wq, condition, timeout)
wait_event_timeout函数,和wait_event函数类似,可以添加超时时间,以jiffies为单位,函数返回0,超时时间到,condition为假;返回1,条件满足,condition为真。
wait_event_interruptible(wq, condition)
wait_event_interruptible函数,和wait_event函数类似,将进程设置为TASK_INTERRUPTIBLE状态,可以被信号打断。
wait_event_interruptible_timeout(wq, condition, timeout)
wait_event_interruptible_timeout函数,和wait_event_timeout函数类似,将进程设置为TASK_INTERRUPTIBLE状态,可以被信号打断。
非阻塞—轮询
应用程序以非阻塞方式访问设备,设备驱动程序要提供非阻塞的处理方式。
- 应用程序通过select、epoll或poll函数查询设备是否可以操作,如果可以操作的话,从设备读取或向设备写入数据。
- 设备驱动程序执行poll函数。
应用程序
select
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
- nfds:要监视的三类文件描述符集合中,最大文件描述符加1。
- readfds、writefds和exceptfds:都是fd_set类型,fd_set类型变量的每一个位都代表一个文件描述符。readfds用于监视文件描述符集是否可以读取,如果集合中有一个文件可以读取,返回一个大于0的值表示可读,如果没有文件可以读取,根据timeout参数判断是否超时。如果readfds设置为NULL表示不关心文件的读变化。writefds用于监视文件是否可以进行写操作。exceptfds用于监视文件的异常。
- timeout:超时时间。当timeout设置为NULL表示无限期等待。
- 返回值:0,超时发生,但是没有任何文件描述符可以进行操作;-1,发生错误;其他值,进行操作的文件描述符个数。
当定义一个fd_set类型变量,可以进行使用宏进行操作。
void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)
- FD_ZERO用于将fd_set变量的所有位都清零。
- FD_SET用于将fd_set变量的某个位置1,也就是向 fd_set添加一个文件描述符,参数fd就是要加入的文件描述符。
- FD_CLR用于将 fd_set变量的某个位清零,也就是将一个文件描述符从fd_set中删除,参数fd就是要删除的文件描述符。
- FD_ISSET 用于测试一个文件是否属于某个集合,参数fd就是要判断的文件描述符。
select函数非阻塞访问示例:
void main(void)
int ret, fd; /* 要监视的文件描述符 */
fd_set readfds; /* 读操作 文件描述符 */
struct timeval timeout; /* 超时时间 */
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞访问 */
FD_ZERO(&readfds); /* 清楚readfds */
FD_SET(fd, &readfds); /* 将fd添加到readfds */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500 ms */
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函数读取数据代码 */
break;
poll
在单个线程中,select函数能够监视的文件描述符最大数量为1024,poll函数没有最大文件描述符限制。
struct pollfd
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
;
int poll(struct pollfd *fds;
nfds_t nfds,
int timeout)
- fd:要监视的文件描述符,fd无效则events监视事件无效,revents返回0。
- events:要监视的事件。events可监视事件的类型。
- POLLIN 有数据可以读取
- POLLPRI 有紧急的数据需要读取
- POLLOUT 可以写数据
- POLLERR 指定的文件描述符发生错误
- POLLHUP 无效的请求
- POLLNVAL 指定的文件描述符挂起
- POLLRDNORM 等同于POLLIN
- revents:返回的事件,由内核设置具体的返回事件。
- fds:要监视的文件描述符集合及要监视的事件,fds为一个数组,数组元素都是结构体pollfd类型。
- nfds:poll函数要监视的文件描述符数量。
- timeout:超时时间,单位ms。
- 返回值:返回发生事件或错误的文件描述符数量,0,超时;-1,发生错误并设置errno为错误类型。
poll函数非阻塞访问示例:
void main(void)
int ret, fd; /* 要监视的文件描述符 */
struct pollfd fds; /* pollfd结构体 */
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞访问 */
fds.fd = fd;
fds.events = POLLIN; /* 监视数据是否可读 */
ret = poll(&fds, 1, 500); /* 轮询文件是否可操作, 超时500ms */
if (ret)
/* 读取数据代码 */
else if (ret == 0)
/* 超时 */
else if (ret == -1)
/* 错误 */
epoll
select和poll函数都会随着监听fd数量的增加,出现效率低下的问题,poll函数每次必须遍历所有描述符来检查就绪的描述符,浪费时间。涉及的文件描述符较少时适合用select和poll函数。
epoll函数为处理大并发而准备的,常用于网络编程。
应用程序先使用epoll_create创建一个epoll句柄。
int epoll_create(int size)
- size:Linux版本2.6.8开始size参数没有意义,写一个大于0的值即可。
- 返回值:epoll句柄,-1表示创建失败。
epoll句柄创建成功后使用epoll_ctl向其中添加要监视的文件描述符和监视的事件。
struct epoll_event
uint32_t event; /* epoll事件 */
epoll_data_t data; /* 用户数据 */
;
int epoll_ctl(int epfd,
int op,
int fd,
struct epoll_event *event)
- events:要监视的事件。
- EPOLLIN 数据可读。
- EPOLLOUT 可写数据。
- EPOLLPRI 有紧急的数据需要读取。
- EPOLLERR 指定的文件描述符发生错误。
- EPOLLHUP 指定的文件描述符挂起。
- EPOLLET设置epoll为边沿触发,默认为水平触发。
- EPOLLONESHOT 一次性监视,当监视完成后还需要再次监视某个fd,需要将fd重新添加到epoll。
- epfd:epoll_create创建的句柄。
- op:对epfd进行的操作。
- EPOLL_CTL_ADD 向epfd添加参数fd表示的描述符。
- EPOLL_CTL_MOD 修改参数fd的event事件。
- EPOLL_CTL_DEL 从epfd中删除fd文件描述符 。
- fd:要监视的文件描述符。
- event:要监视的事件类型。
- 返回值:0,成功;-1,失败,并设置errno的值为相应的错误码。
应用程序使用epoll_wait等待事件发生。
int epoll_wait(int epfd;
struct epoll_event *events;
int maxevents;
int timeout)
- epfd:要等待的epoll。
- events:指向epoll_event结构体的数组,当有事件发生时Linux内核会填写events。
- maxevents:events数组大小,必须大于0。
- timeout:超时时间,单位ms。
- 返回值:0,超时;-1,错误;其他值,准备就绪的文件描述符数量。
设备驱动程序—poll操作函数
应用程序调用select或poll函数对驱动程序进行非阻塞访问时,驱动程序file_operations操作集中poll函数就会执行。
unsigned int (*poll)(struct file *filp, struct poll_table_struct *wait)
- filp:要打开的设备文件,即文件描述符。
- wait:poll_table_struct类型指针,应用程序传进来,wait参数传给poll_wait函数。
- 返回值:向应用程序返回设备状态。
- POLLIN 有数据可以读取
- POLLPRI 有紧急的数据需要读取
- POLLOUT 可以写数据
- POLLERR 指定的文件描述符发生错误
- POLLHUP 无效的请求
- POLLNVAL 指定的文件描述符挂起
- POLLRDNORM 等同于POLLIN
驱动程序的poll函数中调用poll_wait函数,poll_wait不会引起阻塞,只是将应用程序添加到poll_table中。
void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)
- filp:要打开的设备文件,即文件描述符。
- wait_address:要添加到poll_table中的等待队列头。
- p:file_operation中poll函数的wait参数。
以上是关于Linux驱动开发阻塞和非阻塞IO的主要内容,如果未能解决你的问题,请参考以下文章