第8章 阻塞与非阻塞I/O

阻塞操作:在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待条件被满足。

非阻塞操作:在不能进行设备操作时,并不挂起,要么放弃要么不停地查询,直至可以进行操作为止。


唤醒进程的地方最大可能发生在中断里面,因为在硬件资源获得的同时往往伴随着一个中断。

8.1等待队列

        实现阻塞进程的唤醒

1.定义“等待队列头部”

wait_queue_head_t my_queue;//wait_queue_head_t是__wait_queue_head结构体的一个typedef(别名)。

2.初始化“等待队列头部”

init_waitqueue_head(&my_queue);

DECLARE_WAIT_QUEUE_HEAD(name);定义并初始化等待队列头部的“快捷方式”(1+2)

3.定义等待队列元素

DECLARE_WAITQUEUE(name,tsk)

4添加/移除等待队列

void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);//将队列元素wait添加到等待队列头部 q指向的双向链表

void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);//将队列元素wait从等待队列头部 q指向的双向链表移除

5.等待事件

wait_event(queue,condition)

wait_event_interruptible(queue,condition)

wait_event_timeout(queue,condition,timeout)

wait_event_interruptible_timeout(queue,condition,timeout)

等待第一个参数queue作为等待队列头部的队列被唤醒,且第二个参数condition必须满足,否则继续阻塞。第一个函数和第二个函数的区别在于后者可被信号打断,而前者不能。timeout为超时时间,以jiffy为单位。

6.唤醒队列

void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

唤醒以queue作为等待队列头部的队列中所有进程

wake_up()应该与wait_event()或wait_event_timeout()成对使用;wake_up_interruptible()应与wait_event_interruptible()或wait_event_interruptible_timeout()成对使用。其中wake_up()可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程,wake_upinterruptible()只能唤醒后者进程。

7.在等待队列上睡眠

sleep_on(wait_queue_head_t *q);//将进程状态设置成TASK_UNINTERRUPTIBLE,并定义一个等待队列元素挂到等待队列头部q指向的双向链表。和wake_up()成对使用

interruptible_sleep_on(wait_queue_head_t *q);//将进程状态设置成TASK_INTERRUPTIBLE并定义一个等待队列元素挂到等待队列头部q指向的双向链表。和wake_up_interruptible()成对使用

8.2轮询操作

select() (BSD UNIX) poll() (System V)

#include <sys/select.h>
int select (int maxfd + 1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval * timeout);
参数一:最大的文件描述符加1。
参数二:用于检查可读性,
参数三:用于检查可写性,
参数四:用于检查 带外数据

参数五:struct timeval{

long tv_sec; // seconds
long tv_usec; // microseconds
}

一个指向timeval结构的指针,用于决定select等待I/o的最长时间。如果为空将一直等待。

返回值:

>0:就绪描述字的正数目
-1:出错
0 :超时

#include <poll.h>

int poll(struct pollfd fds[], nfds_t nfds, int timeout);

参数说明:
fds:是一个struct pollfd结构类型的 数组,用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select()函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;
n fds:nfds_t类型的参数,用于标记 数组fds中的 结构体元素的总数量;
timeout:是poll 函数调用阻塞的时间,单位:毫秒;
返回值:
>0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;
==0:数组fds中没有任何socket描述符准备好读、写,或出错;此时poll超时,超时时间是timeout毫秒;换句话说,如果所检测的socket描述符上没有任何事件发生的话,那么poll()函数会阻塞timeout所指定的毫秒时间长度之后返回,如果timeout==0,那么poll() 函数立即返回而不阻塞,如果timeout==INFTIM,那么poll() 函数会一直阻塞下去,直到所检测的socket描述符上的感兴趣的事件发生是才返回,如果感兴趣的事件永远不发生,那么poll()就会永远阻塞下去;
-1: poll函数调用失败,同时会自动设置全局变量errno;

poll和select实现功能差不多,但poll效率高,以后要多用poll

设备驱动中的轮询编程

unsigned int (*poll)(struct file *filp, struct poll_table * wait);

第二个参数为轮询表指针。(1)对可能引起设备文件状态变化的等待队列调用poll_wait() 将对应的等待队列头部添加到poll_table中(2)返回表示是否能对设备进行无阻塞读写访问的掩码

void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);//作用是让唤醒参数queue对应的等待队列可以唤醒因select()而睡眠的进程。

poll操作返回值
通常返回下列定义“或”的结果
POLLIN 设备可无阻塞读
POLLOUT 设别可无阻塞写
POLLRDNORM 数据可读
POLLWRNORM 数据可写
设备可读通常返回: POLLIN | POLLRDNORM
设备可写通常返回: POLLOUT | POLLWRNORM


总结:

在设备驱动中

阻塞I/O一般基于等待队列来实现。

非阻塞I/O的应用程序可通过轮询函数来查询设备是否能立即被访问,用户空间调用select() poll()或者epoll()接口,设备驱动提供poll()函数。

其中设备驱动的poll()本身不会阻塞,但是与poll()、select()和epoll()相关的系统调用则可能会阻塞地等待至少一个文件描述符集合可访问或超时。

猜你喜欢

转载自blog.csdn.net/qq_28449863/article/details/80487512