您的位置:首页 > 其它

阻塞睡眠实现机制

2015-08-02 21:56 218 查看
  在看阻塞睡眠实现机制前,我们来看一下内核中广泛用到的等待队列。

  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 但是没有数据可用, 这个进程必须阻塞. 这个进程在有数据达到时被立刻唤醒, 并且那个数据被返回给调用者, 反之,如果一个进程调用 write 并且在缓冲中没有空间, 这个进程必须阻塞,并且它必须在一个与用作 read 的不同的等待队列中. 当一些数据被写入硬件设备, 并且在输出缓冲中的空间变空闲, 这个进程被唤醒并且写调用成功。

  我们来看一个读操作的例子:

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: