标签:linux驱动
在linux中,一个等待队列通过一个“等待队列头(wait queue head)”来管理,等待队列头是一个类型为wait_queue_head_t的结构体,定义在<linux/wait.h>中
两种定义并初始化方法:
DECLARE_WAIT_QUEUE_HEAD(name);
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
休眠宏:
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, conditon, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
注意:
queue是值传递,condition是任意一个布尔表达式,上面的宏在休眠前后都对该表达式求值,在条件为真之前会保持休眠。该表达式可能会被多次求值,所以对该表达式的求值不能带来任何副作用。interruptible版本会被信号中断,返回非0值而驱动程序也许要返回-ERESTARTSYS(这个值由虚拟文件系统层VFS内部使用,它或者重启系统调用,或者给用户空间返回-EINTR)。
后面两个宏时间以jiffy表示,当到达给定时间,这两个宏都会返回0值,无论condition如何求值。
唤醒操作:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
进程如何休眠:
1、分配并初始化一个wait_queue_t结构,该结构定义在<linux/wait.h>中
2、设置进程状态,将其标记为休眠。<linux/sched.h>中定义了多个任务状态
TASK_RUNNING表示进程可运行,TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE表示休眠。
直接改变进程状态可调度用:
void set_current_state(int new_state);
古老的方式可以直接修改current,但不推荐:
current->state=TASK_INTERRUPTIBLE;
手工休眠:
1、建立并初始化一个等待队列入口,两种方式:
DEFINE_WAIT(my_wait);
wait_queue_t my_wait;
init_wait(&my_wait);
2、将等待队列入口添加到等待队列
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
state为休眠状态
3、调用schedule,调用前需要检测条件是否满足,因为此处会产生一个窗口,导致唤醒丢失
4、清理
voif finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
独占等待:
void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);
它设置等待队列入口的独占标志。wait_event及其变种是不能执行独占等待的。
唤醒函数:
wake_up的所有变种:
wake_up(wait_queue_head_t *queue);
wake_up_interrruptible(wait_queue_head_t *queue);
唤醒所有非独占等待进程和1个独占等待进程,interruptible会跳过不可中断休眠的那些进程。
wake_up_nr(wait_queue_head_t *queue, int nr);
wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);
唤醒nr个非中断进程,如果nr为0则唤醒所有独占进程
wake_up_all(wait_queue_head_t *queue);
wake_up_interruptible_all(wait_queue_head_t *queue);
wake_up_interruptible_sync(wait_queue_head_t *queue);
被唤醒的进程可能会抢占当前的进程,并在wake_up返回前被调度到处理器上(原子上下文的不会),不希望被调度出处理器时可用这个函数。
poll和select
poll、select、和epoll的功能本质上是一样的:都允许进程决定是否可以对一个或多个打开的文件做非阻塞的读取或写入,这些调用也会阻塞进程,直到给定的文件描述符集合中的任何一个可读取或写入。同一个功能为什么由几个独立的函数提供呢,因为这是由不同的unix团体分别几乎同时实现的。
所有三个调用均通过驱动程序的poll方法提供:
unsigned int (*poll) (struct file *filp, poll_table *wait);
该设备方法分两步处理:
1、在一个或多个可指示poll状态变化等待队列上调用poll_wait。如果当前没有文件描述符可用来执行I/O,则内核将使进程在传递到该系统调用的所有文件描述符对应的等待队列上等待。
2、返回一个用来描述操作是否可以立即无阻塞执行的位掩码。
poll的实现具体不明,几个标志(<linux/poll.h>中定义)用来指明可能的操作:
POLLIN |
如果设备可以无阻塞地操作,就设置该位 |
POLLRDNORM |
如果“通常”的数据已经就绪,可以读取,就设置该位。一个可读设备返回(POLLIN|POLLRDNORM) |
POLLRDBAND |
这一位指示可以从设备读取out-of-band(频带之外)的数据。它当前只可以在linux内核的DECnet代码中使用,通常不用语设备驱动程序 |
POLLPRI |
可以无阻塞的读取高优先级(即out-of-band)的数据。设置该位会导致select报告文件发生一个异常,这是由于select把“out-of-band”的数据当作异常 |
POLLHUP |
当读取设备的进程到达文件尾时,驱动程序必须设置POLLHUP(挂起)位,依照select的功能描述,调用select的进程会被告知设备是可读的。 |
POLLERR |
设备发生了错误,如果调用poll,就会报告设备既可读也可写,因为读写都会无阻塞地返回一个错误码 |
POLLOUT |
设备可以无阻塞的写入 |
POLLWRNORM |
该位和POLLOUT的意义一样,有时其实就是同一个数字。一个可写设备返回(POLLOUT|PLOOWRNORM) |
POLLWRBAND |
与POLLRDBAND类似,这一位表示具有非零优先级的数据可以被写入。只有数据报(datagram)的poll实现中使用了这一位,因为数据报可以传输out-of-band数据 |
给段scullpipe的poll实现,自己体味:
static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;
down(&dev->sem);
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if(dev->rq != dev->wq)
mask |= POLLIN | POLLRDNORM;
if(spacefree(dev))
mask |= POLLOUT | POLLWRNORM;
up(&dev->sem);
return mask;
}
一些poll的规则
略
本文出自 “重剑无锋” 博客,请务必保留此出处http://qianyang.blog.51cto.com/7130735/1620574
标签:linux驱动
原文地址:http://qianyang.blog.51cto.com/7130735/1620574