进程挂起wait_event_interruptible

在进程执行过程中,有时候需要等待某个条件满足而进行进程阻塞。
常用的一种方法就是让调用者进程暂时挂起,直到目标进程返回结果后,再唤醒等待的进程。

wait_event_interruptible()定义如下:
#define wait_event_interruptible(wq, condition) \
({ \
    int _ret = 0; \
    if(!condition) \
        _wait_event_interruptible(wq, condition, _ret); \
    _ret;
})

也就是说,如果condition满足的话就直接返回0;否则,调用 _wait_event_interruptible进入可中断的挂起状态。

#define _wait_event_interruptible(wq, condition, ret) \
do{ \
    DEFINE_WAIT(_wait) \
        \
    for(;;){ \
        prepare_to_wait(&wq, &_wait, TASK_INTERRUPTIBLE); \
        if(condition) \
            break; \
        if(!signal_pending(current)){ \
            schedule(); \
            continue; \
        } \
        ret = -ERESTARTSYS; \
        break; \
    } \
    finish_wait(&wq, &_wait); \ 
}while(0)

1. prepare_to_wait()只是进行一些链表操作,以确保自己在等待队列中,不会漏掉事件;
2. 进程睡眠的位置为schedule()函数;
3. 进程在确定本身在队列后,再次检查条件,如果不检查,可能条件已经满足,直接去睡眠,可能就再也不会被唤醒;
4. 如果条件不满足就调用schedule()去睡眠,进程的状态在prepare_to_wait()里设置为TASK_INTERRUPTIBLE,所以后续调度的时候就看不到此进程,因为无法参与调度,此时称为休眠
5. 当条件满足后,会存在其他部分(其他进程、内核等)来唤醒等待队列上的进程,具体是唤醒等待队列上的所有进程还是只唤醒第一个,完全取决于唤醒者,
6. 一次唤醒所有进程并不一定是合理的,在多个进程是等待互斥资源的时候,所有等待队列中的进程可以分为互斥、非互斥进程
7. 互斥进程:等待的资源是互斥访问的;互斥进程由内核有选择的唤醒,等待队列项的flag字段为1;非互斥进程:等待的资源是可多进程同时访问的。非互斥进程在事件发生时,总是被内核唤醒,等待队列元素的flag字段为0。
8. 唤醒者通常调用__wake_up_common(),这样,依次取下等待队列中的__wait_queue_t结构, 调用该睡眠进程设置的func函数,即这里的autoremove_wake_function(), 将该进程的状态重新设置为RUNNING。
注意,此时该睡眠进程并不会立刻执行,只有等到下次调度的时候,该进程才有机会运行,即醒来了。醒来是从schedule()回来,继续运行__wait_event()

总结一下, 睡眠是自己设置好进程状态(TASK_UNINTERRUPTIBLE,等等),加入等待队列, 并调用schedule()去睡眠。 睡眠是自己的动作。唤醒是别人发现条件满足,调用__wake_up_common(),将睡眠进程从等待队列取下, 调用该睡眠进程设置的唤醒func,重新设置该睡眠进程为RUNNING。 从而可以在下次调度时运行。 唤醒是别人的动作。

代码由condition控制的for()循环中,所以当由shedule()返回时(当然是被wake_up之后,通过其他进程的schedule()而再次调度本进程),如果condition不满足,本进程将自动再次被设置为TASK_INTERRUPTIBLE状态,接下来执行schedule()的结果是再次被从runqueue队列中删除。这时候就需要再次通过wake_up重新添加到runqueue队列中。

如此反复,直到condition为真的时候被wake_up. 成功地唤醒一个被wait_event_interruptible()的进程,需要满足:
1) 调用wake_up()。
2)condition为真

特别注意:
如果要被wait_event_interruptible的当前进程有nonblocked pending signals, 那么会直接返回-ERESTARTSYS,当前进程不会被wait_event_interruptible 和从runqueue队列中删除。

宏定义_wait_event_interruptible里有个for死循环,跳出循环的条件是condition已经满足要求。首先将自己设置为TASK_INTERRUPTIBLE,也就是可中断挂起状态,然后进行schedule调度。可想而知,因为已经不是处于可运行状态,所以将不再分配到CPU时间,直到有调用wakeup将其唤醒。醒来后还是得先检查condition是否已经满足,否则将再次处于可中断挂起状态。

仔细分析wait_event_interruptible(),其逻辑实现为:
1、函数首先将进程的状态设置为TASK_INTERRUPTIBLE,然后调用schedule();
2、系统调度函数schedule()会将处于TASK_INTERRUPTIBLE状态的进程从队列runqueue中删除;
3、删除后,此进程将不再参与调度,除非通过其他函数(wake_up())将这个进程重新放入runqueue队列中。

wait_event_interruptible()和 wake_up的等效代码 

wait_event_interruptible(wq, condition) /*等效没有考虑返回值*/ 
{ 
     if (!(condition)) 
     { 
         wait_queue_t _ _wait; 
         init_waitqueue_entry(&_ _wait, current); 
         add_wait_queue(&wq, &_ _wait); 
         for (;;) 
         { 
            set_current_state(TASK_INTERRUPTIBLE); 
            if (condition) 
            break; 
            schedule();  /* implicit call: del_from_runqueue(current)*/ 
         } 
         current->state = TASK_RUNNING; 
         remove_wait_queue(&wq, &_ _wait); 
    } 
} 

void wake_up(wait_queue_head_t *q) 
{ 
      struct list_head *tmp; 
      wait_queue_t *curr; 
      list_for_each(tmp, &q->task_list) 
      { 
        curr = list_entry(tmp, wait_queue_t, task_list); 
        wake_up_process(curr->task); 
        /* implicit call: add_to_runqueue(curr->task);*/ 
        if (curr->flags) 
          break; 
      } 
}
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值