wait_event族函数浅析

2017-06-03 


 

周末闲暇无事,聊聊内核中的wait_event*类函数的具体实现,等待事件必定涉及到某个条件,而这些函数的区别主要是等待后唤醒的方式……直奔主题,上源码

wait_event_interruptible

#define wait_event_interruptible(wq, condition)                \
({                                    \
    int __ret = 0;                            \
    if (!(condition))                        \
        __wait_event_interruptible(wq, condition, __ret);    \
    __ret;                                \
})

调用该宏首先会先检查条件,如果条件已经满足,则不用等了呀,返回吧……,否则调用__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)

 首先声明了一个关联当前进程的wait对象,然后进入一个for空循环,开始就调用prepare_to_wait,该函数代码如下,功能就是把wait对象加入到等待队列并设置当前进程的状态,注意此刻仅仅是设置了结构体的状态,并没有触发调度。在真正触发调度之前,需要再次检查条件是否满足,如果满足了,直接break,否则检查当前进程是否有信号存在,因为该宏设置的等待是可以被信号唤醒的,如果有信号则同样break。否则就调用schedule进行调度吧!其他种类的额wait函数都是同样的结构,区别就在于for循环内内部的不同,所以后续主要列举的是for循环的代码。

void
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
    unsigned long flags;

    wait->flags &= ~WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&q->lock, flags);
    /*保证只加入一次*/
    if (list_empty(&wait->task_list))
        __add_wait_queue(q, wait);
    set_current_state(state);
    spin_unlock_irqrestore(&q->lock, flags);
}

wait_event_interruptible_timeout

该函数和前面类似,但是增加了等待时间,还是简单看下__wait_event_interruptible_timeout的for循环

for (;;) {                            \
        prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);    \
        if (condition)                        \
            break;                        \
        if (!signal_pending(current)) {                \
            ret = schedule_timeout(ret);            \
            if (!ret)                    \
                break;                    \
            continue;                    \
        }                            \
        ret = -ERESTARTSYS;                    \
        break;                            \
    }            

在没有信号的状态下,是调用了schedule_timeout函数,该函数在调度之前会对当前进程设定一个定时器,并设定process_timeout回调函数用于时间到了就执行该函数,自然该函数就是完成唤醒进程的功能。关于定时器,本文不做介绍。

wait_event_timeout

该函数和上面区别就是没有了对信号的判断,并且设置进程状态为TASK_UNINTERRUPTIBLE,其__wait_event_timeout中for循环如下

for (;;) {                            \
        prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \
        if (condition)                        \
            break;                        \
        ret = schedule_timeout(ret);                \
        if (!ret)                        \
            break;                        \
    }        

wait_event

该函数是最简单的,设置状态TASK_UNINTERRUPTIBLE,进行条件判断,如果不符合就调度。没有其他额外的操作。

 内核中的sleep函数分析

static inline void sleep(unsigned sec)
{
    current->state = TASK_INTERRUPTIBLE;
    schedule_timeout(sec * HZ);
}

这里首先设置当前进程状态为TASK_INTERRUPTIBLE,然后调用了schedule_timeout,参数就是设置的睡眠时间,单位是秒,乘以时钟频率HZ(每秒钟发生时钟中断的次数)

signed long __sched schedule_timeout(signed long timeout)
{
    struct timer_list timer;
    unsigned long expire;

    switch (timeout)
    {
    case MAX_SCHEDULE_TIMEOUT:
        /*
         * These two special cases are useful to be comfortable
         * in the caller. Nothing more. We could take
         * MAX_SCHEDULE_TIMEOUT from one of the negative value
         * but I' d like to return a valid offset (>=0) to allow
         * the caller to do everything it want with the retval.
         */
        schedule();
        goto out;
    default:
        /*
         * Another bit of PARANOID. Note that the retval will be
         * 0 since no piece of kernel is supposed to do a check
         * for a negative retval of schedule_timeout() (since it
         * should never happens anyway). You just have the printk()
         * that will tell you if something is gone wrong and where.
         */
        if (timeout < 0) {
            printk(KERN_ERR "schedule_timeout: wrong timeout "
                "value %lx\n", timeout);
            dump_stack();
            current->state = TASK_RUNNING;
            goto out;
        }
    }
    /*定时器到期时间*/
    expire = timeout + jiffies;
    /*设置定时器*/
    setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
    __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
    /*调度*/
    schedule();
    /*回来就删除定时器*/
    del_singleshot_timer_sync(&timer);

    /* Remove the timer from the object tracker */
    destroy_timer_on_stack(&timer);

    timeout = expire - jiffies;
    /*timeout大于0意味这进程被提前唤醒*/
 out:
    return timeout < 0 ? 0 : timeout;
}

这里如果设置的睡眠时间足够长,就不设置定时器,直接调度。否则如果设置的时间小于0,就打印下栈信息,然后设置状态会TASK_RUNNING就返回了。正常情况下就设置一个定时器,根据传入的时间设置到期时间,然后再出发调度,这样在这段时间过后如果定时器被有被出发就会由时钟中断触发执行,看到定时器的回调函数是process_timeout,其中调用了wake_up_process唤醒了睡眠的进程。创建定时器之后就调用__mod_timer激活定时器。默认情况下是在当前CPU,但是如果当前CPU是空闲的即IDLE状态,这种情况如果配置了动态时钟会关闭周期时钟,这样会把定时器迁移到一个非空闲的CPU上。因为在动态时钟下,并不是每个jiffies都会发生中断,这样有可能造成延迟。在选定CPU后如果不是当前CPU那么需要对定时器做一些修改,然后调用internal_add_timer把定时器加入到管理向量数组中。从这里看没有考虑高分辨率定时器呀!!!

说明:在schedule函数中,如果当前进程非运行态,在允许抢占的情况下,极有可能会把task从就绪队列移除,而在唤醒进程的时候再重新加入到就绪队列。

以马内利!

参考:linux3.10.1源码

 

posted @ 2017-06-03 19:55  jack.chen  Views(6706)  Comments(0Edit  收藏  举报

以马内利