阻塞操作:在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待条件被满足。
非阻塞操作:在不能进行设备操作时,并不挂起,要么放弃要么不停地查询,直至可以进行操作为止。
唤醒进程的地方最大可能发生在中断里面,因为在硬件资源获得的同时往往伴随着一个中断。
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)
参数五:struct timeval{
一个指向timeval结构的指针,用于决定select等待I/o的最长时间。如果为空将一直等待。
返回值:
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
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()相关的系统调用则可能会阻塞地等待至少一个文件描述符集合可访问或超时。