Linux 2.4 内核说明文档(进程与中断管理篇)(4)
2.5. 等待队列
当进程要求内核完成一件当前不会发生但稍后可能发生的事情时,它就进入休眠并在事件条件符合时被唤醒。内核实现机制的其中之一就被称为“等待队列”。
Linux实现允许通过TASK_EXCLUSIVE标记来唤醒。对于等待队列,你可以采用通用的队列,然后简单地sleep_on /sleep_on_timeout /interruptible_sleep_on /interruptible_sleep_on_timeout;也可以定义自己的队列,并通过add/remove_wait_queue向它添加删除自己,在需要时通过wake_up/wake_up_interruptible来唤醒。
等待队列的种用法的例子就是页分配(mm/page_alloc.c:__alloc_pages()函数)与kswapd内核守护进程(mm/vmscan.c:kswap()函数)的交互,也就是定义在mm/vmscan.c里的kswapd_wait等待队列。Kswapd守护进程在这个队列休眠,当页分配函数需要释放一些内存页的时候它就被唤醒。
自治等待队列用法的例子就是用户进程通过read系统调用请求数据与中断环境的内核提供数据之间的交互。中断处理可能如下(drivers/char/rtc_interrupt()):
static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);
void rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
spin_lock(&rtc_lock);
rtc_irq_data = CMOS_READ(RTC_INTR_FLAGS);
spin_unlock(&rtc_lock);
wake_up_interruptible(&rtc_wait);
}
这样,中断处理函数通过读取一些设备指定的I/O端口获得了数据,然后唤醒rtc_wait等待队列中的任务。现在,read系统调用可以实现为:
ssize_t rtc_read(struct file file, char *buf, size_t count, loff_t *ppos)
{
DECLARE_WAITQUEUE(wait, current);
unsigned long data;
ssize_t retval;
add_wait_queue(&rtc_wait, &wait);
current.>;state = TASK_INTERRUPTIBLE;
do {
spin_lock_irq(&rtc_lock);
data = rtc_irq_data;
rtc_irq_data = 0;
spin_unlock_irq(&rtc_lock);
if (data != 0)
break;
if (file.>;f_flags & O_NONBLOCK) {
retval = .EAGAIN;
goto out;
}
if (signal_pending(current)) {
retval = .ERESTARTSYS;
goto out;
}
schedule();
} while(1);
retval = put_user(data, (unsigned long *)buf);
if (!retval)
retval = sizeof(unsigned long);
out:
current.>;state = TASK_RUNNING;
remove_wait_queue(&rtc_wait, &wait);
return retval;
}
rtc_read将执行以下步骤操作:
1) 定义一个指向当前进程环境的等待队列元素;
2) 将此元素添加倒rtc_wait等待队列;
3) 将当前环境标识为TASK_INTERRUPTIBLE,这意味着他下次休眠后不再被调度;
4) 检查是否有数据有效?如果有则跳出,拷贝数据到用户缓冲区,自身任务状态调整为TASK_RUNNING,并从等待队列中移出,然后返回。
5) 如果目前没有数据,则检查用户是否指定了非阻塞I/O。
6) 同样检查是否有信号等待处理。如果是,则通知上层必要时重启系统调用。
7) 后休眠,直到被中断处理函数唤醒。如果任务自身状态不是TASK_INTERRUPTIBLE,则调度函数会在稍后有数据可用时调度该任务,这会导致不必要的处理。
这非常有价值的一点就是,采用等待队列就比较容易的实现poll系统调用:
static unsigned int rtc_poll(struct file *file, poll_table *wait)
{
unsigned long l;
poll_wait(file, &rtc_wait, wait);
spin_lock_irq(&rtc_lock);
l = rtc_irq_data;
spin_unlock_irq(&rtc_lock);
if (l != 0)
return POLLIN | POLLRDNORM;
return 0;
}
所有的工作都在独立于设备的函数poll_wait中完成,它完成了必须的等待队列操作。我们所要做的就是将其指向一个由我们设备相关的中断处理函数唤醒的等待队列。
文章来源CU社区:Linux 2.4 内核说明文档(进程与中断管理篇)
相关文章