标签:linux kernel 阻塞 睡眠
在看阻塞睡眠实现机制前,我们来看一下内核中广泛用到的等待队列。
Linux内核的等待队列为双循环链表结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。它有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head(双链表)。通过这样一个双链表把等待进程链接起来。
下面来看两者数据结构:
struct __wait_queue_head {
spinlock_t lock; //自旋锁,实现对等待队列的互斥访问
struct list_head task_list; //双向循环链表,存放等待的进程。
};
typedef struct __wait_queue_head wait_queue_head_t;
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
我们知道缺省状态下IO是阻塞的(除非设定O_NONBLOC),如果一个进程调用 read 但是没有数据可用(尚未), 这个进程必须阻塞. 这个进程在有数据达到时被立刻唤醒, 并且那个数据被返回给调用者, 即便小于在给方法的 count 参数中请求的数量,反之,如果一个进程调用 write 并且在缓冲中没有空间, 这个进程必须阻塞,并且它必须在一个与用作 read 的不同的等待队列中. 当一些数据被写入硬件设备, 并且在输出缓冲中的空间变空闲, 这个进程被唤醒并且写调用成功, 尽管数据可能只被部分写入如果在缓冲只没有空间给被请求的 count 字节。
我们来看一个读操作的例子:
static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;
if (down_interruptible(&dev->sem)) //加锁
return -ERESTARTSYS;
while (dev->rp == dev->wp) //无东西可读
{
up(&dev->sem); //解锁
if (filp->f_flags & O_NONBLOCK) //非阻塞方式,立即返回
return -EAGAIN;
//阻塞访问,睡眠等待,等到读条件满足时继续执行。
if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
return -ERESTARTSYS;
if (down_interruptible(&dev->sem)) //重新加锁
return -ERESTARTSYS;
}
//读取数据。
……
up (&dev->sem);
wake_up_interruptible(&dev->outq);
return count;
}
从上面的例子我们可以看到,是通过调用wait_event_interruptible()实现阻塞等待的,来看一下wait_event_interruptible()实现
#define wait_event_interruptible(wq, condition)
({
int __ret = 0;
if ( (!condition) )
__wait_event_interruptible(wq, condition, __ret);
__ret;
})
#define __wait_event_interruptible(wq, condition, __ret)
do {
DEFINE_WAIT(__wait); // 定义等待队列__wait
for(;;) {
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); //将等待队列__wait加入以wq为首的等待队列链表中,并且将进程状态设置为TASK_INTERRUPTIBLE
if (condition) //如果condition满足则跳出
break;
if (!signal_pending(current)) { //没被信号唤醒
schedule(); // 放弃CPU,调度其它进程执行
continue;
}
ret = - ERESTARTSYS;
break;
}
finish_wait(&wq, &__wait); //将等待队列__wait从等待队列头wq指向的等待队列链表中移除,将进程状态设置为TASK_RUNNING
}while(0)
总结起来, 阻塞睡眠步骤一般为:
1)分配和初始化一个 wait_queue_t 结构, 随后将其添加到正确的等待队列. 当所有东西都就位了, 负责唤醒工作的人就可以找到正确的进程
2)设置进程的状态来标志它为睡眠(TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE)
3)调用schedule(),让出CPU。
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:linux kernel 阻塞 睡眠
原文地址:http://blog.csdn.net/kzq_qmi/article/details/47212407