linux设备驱动中的阻塞与非阻塞IO

阻塞和非阻塞I/O是设备访问的两种不同模式,驱动程序可以灵活的支持这两种用户空间对设备的访问方式。

1. 阻塞与非阻塞I/O

阻塞操作是指在执行设备操作时,若不能获取设备资源,则挂起进程知道满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列中移走,直到等待的条件被满足。而非阻塞的进程在不能进行设备操作时不被挂起,它要么放弃,要么不停的测试,直至可进行操作为止。

驱动程序通常需要提供这样的能力:当应用进程进行read()、write()等系统调用时,若设备资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应当在xxx_write()、xxx_read()等操作中将进程阻塞直到可以获得资源,此后,应用程序的read()、write()等调用才返回,整个过程仍然进行了正确的设备访问,用户并没有感知到。若用户以非阻塞的方式访问设备文件,当设备资源不可获得时,设备驱动的xx_write()、xxx_read()等操作立即返回,read()、write等系统调用也立即返回,应用程序收到-EAGAIN返回值。

阻塞访问时,阻塞的进程将会进入到休眠的状态,所以必须有一个地方能够唤醒阻塞的进程,这个地方往往是一个中断,因为硬件资源的获得同时往往伴随一个中断的产生。非阻塞的进程是不断地进行尝试,知道可以进行I/O。

1.1 等待队列

在linux驱动程序中,可以使用等待队列来实现阻塞进程的唤醒。等待队列是以队列为基础的数据结构,与进程调度机制密切结合,可以用来同步系统资源的访问。
Linux内核提供了如下关于等待队列的操作。
1. 定义“等待队列的头部”
wait_queue_head_t my_queue;
2. 初始化“等待队列头部”
init_waitqueue_head(&my_queue);
DECLARE_WAIT_QUEUE_HEAD(name);
3. 定义"等待队列元素"
DECLARE_WAITQUEUE(name,task);
4. 添加/移动等待队列
void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
5. 等待事件
wait_even(queue,condition);
wait_even_interruptible(queue,condition); //可以被中断打断,当condition满足时返回。
wait_even_timeout(queue,condition,timeout); //当超时时不论condition是否满足都将返回。
wait_even_interruptible_timeout(queue,condition,timeout); //可以被中断打断,当超时时不论condition是否满足都将返回。
6. 唤醒队列
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
7. 在等待队列上睡眠
sleep_on(wait_queue_t *q); //将队列状态设置为TASK_UNINTERRUPTIBLE,并定义一个等待元素,把其挂在队列头部q指向的双向链表。
interruptible_sleep_on(wait_queue_head_t *q);//将队列状态设置为TASK_INTERRUPTIBLE,并定义一个等待元素,把其挂在队列头部q指向的双向链表。

2. 轮询操作

2.1 应用程序的轮询编程

select是在多路转接IO中使用最多的,但是当多路复用的文件数量庞大、I/O流量频繁时,其效率会迅速降低。epoll的效率则不会随着fd数目的增多而降低。

2.1.1 select

在应用程序中使用最广泛的是BSD UNIX中引入的select()系统调用,其原型为:
int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *execptfds,struct timeval *timeout);
readfds,writefds,execptfds分别为读、写、异常文件监视的文件描述符集合,numfds的值是文件字最大值加一。任何一个处于监视的文件描述字的状态发生改变select都将返回,如果没有文件描述字发生改变则select调用处于阻塞态。
对文件描述字集合的集中操作:
FD_ZERO(fd_set* set) //清空文件描述字集合
FD_SET(int fd,fd_set *set) //将文件描述字加入到文件描述字集合中
FD_CLR(int fd,fd_set *set) //将文件描述字从文件描述字集合中清除
FD_ISSET(int fd,fd_set *set) //判断文件描述字是否在文件描述集合中

2.1.2 poll

1. 创建poll句柄
int epoll_create(int size); //告诉内核要监听多少个fd,epoll本身也算一个fd,使用完毕后要将其关闭。
2. epoll的控制操作
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
epfd: epoll_create的返回值。
op: 表述动作,EPOLL_CTL_ADD注册新的fd到epfd中。EPOLL_CTL_MOD修改已经注册fd的监听事件。EPOLL_DEL从epfd中删除fd。
fd:要监听的fd。
sevent:告诉内核监听事件的类型。struct epoll_event{
                                                                  __uint32_t events;
                                                                  epoll_data_t data;
                                                            };
events:EPOLLIN有文件可读,EPOLLOUT有文件可写,EPOLLPRI有紧急数据可读,EPOLLERR文件描述符发生错误,EPOLLHUP文件描述符被挂断,EPOLLET 设置触发方式,(1)边缘触发(Edge Triggered)只有文件描述符的状态改变时触发。(2)水平出发(Level Triggered)只要事件未处理则一直触发。
EPOLLONESHOT:一次性监听。
3. 等待事件的产生
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);

2.2 设备驱动的轮询编程

unsigned int (*poll) (struct file *filep,struct poll_table *wait);
filep: file的结构体指针。
wait: 轮询参数表。
该函数应该进行两种操作:
(1)  对于可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列的头部添加到poll_table中。
(2) 返回表是是否能够对设备进行无阻塞的读、写访问掩码。

void poll_wait(struct file *filep,wait_queue_heat_t *queue,poll_table *wait);
该函数不会引起阻塞,起作用是把wait添加到等待列表(poll_table)中。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值