linux基础之completion

来源:互联网 发布:eclipse端口号 编辑:程序博客网 时间:2024/06/02 02:02

completion也是一种同步机制,与信号量类似。


信号量可以用于实现同步,但往往可能会出现一些不好的结果。例如:当进程A分配了一个临时信号量变量,把它初始化为关闭的MUTEX,并把其地址传递给进程B,然后在A之上调用down(),进程A打算一旦被唤醒就撤销给信号量。随后,运行在不同CPU上的进程B在同一个信号量上调用up()。然而,up()和down()的目前实现还允许这两个函数在同一个信号量上并发。因此,进程A可以被唤醒并撤销临时信号量,而进程B还在运行up()函数。结果p()可能试图访问一个不存在的数据结构。这样就会出现错误。为了防止发生这种错误就专门设计了completion机制专门用于同步。


关于completion 比信号量完善。专业解释如下  

http://bbs.chinaunix.net/thread-4156398-1-1.html

引入completion时(应该是2.4.7版本)的semaphore确实存在问题,问题模型为:
http://lkml.iu.edu//hypermail/linux/kernel/0107.3/0674.html,google上应该还有其它的邮件。
那时的semaphore是没有锁保护的。到少我手上2.6.24的源码里就没有。

现在semaphore已经没有问题了。甚至有人还提议用semaphore从新实现completion:
http://lwn.net/Articles/277621/ 
从git来看,最终社区并没有接纳。


现在高版本的semaphore 已经不存在问题了。只是从概念层面,completion 和semaphore 应该被用来不同的
场合,completion 是用来等待一个条件成力,而semaphore应该是等待一个资源。条件是没有数量的,而资源
是有数量的。从实现来说,completion更轻(我没有验正过)
请在http://www.makelinux.net/ldd3/chp-5-sect-4里搜lightweight。
 


自己理解不是太好,认为释放似乎不是问题,主要是功能有些差别。关于释放踩空问题,自己理解如下:因为completion机制是定义一个队列头,然后每个进程往这个队列头里添加队列项,所以即使线程A将队列项地址传递给线程B后又销毁了这个地址时,线程B调用complete也不会踩到空地址。不会发生crash




定义

static struct completion marlin_ack = {0};


初始化

init_completion (&marlin_ack);



wait_for_completion等待在completion上;如果加了interruptible,就表示线程等待可被外部发来的信号打断;如果加了killable,就表示线程只可被kill信号打断;如果加了timeout,表示等待超出一定时间会自动结束等待,timeout的单位是系统所用的时间片jiffies(多为1ms)。try_wait_for_completion则是非阻塞地获取completion;completion_done检查是否有线程阻塞在completion上;

ret = wait_for_completion_timeout( &marlin_ack, msecs_to_jiffies(100) );



complete唤醒阻塞在completion上的首个线程;complete_all唤醒阻塞在completion上的所有线程。

complete(&marlin_ack);


completion_done检查是否有线程阻塞在completion上;




看源码就知道了:



/kernel/include/linux/completion.h 

/**
 * init_completion - Initialize a dynamically allocated completion
 * @x:  completion structure that is to be initialized
 *
 * This inline function will initialize a dynamically created completion
 * structure.
 */
static inline void init_completion(struct completion *x)
{
    x->done = 0;
    init_waitqueue_head(&x->wait);
}


./kernel/kernel/wait.c 

void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)

{
    spin_lock_init(&q->lock);
    lockdep_set_class_and_name(&q->lock, key, name);
    INIT_LIST_HEAD(&q->task_list);
}






kernel/kernel/sched/core.c

/**
 * wait_for_completion: - waits for completion of a task
 * @x:  holds the state of this particular completion
 *
 * This waits to be signaled for completion of a specific task. It is NOT
 * interruptible and there is no timeout.
 *
 * See also similar routines (i.e. wait_for_completion_timeout()) with timeout
 * and interrupt capability. Also see complete().
 */
void __sched wait_for_completion(struct completion *x)
{
    wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion);


static long __sched
wait_for_common(struct completion *x, long timeout, int state)
{
    return __wait_for_common(x, schedule_timeout, timeout, state);
}


static inline long __sched
__wait_for_common(struct completion *x,
          long (*action)(long), long timeout, int state)
{
    might_sleep();


    spin_lock_irq(&x->wait.lock);
    timeout = do_wait_for_common(x, action, timeout, state);
    spin_unlock_irq(&x->wait.lock);
    return timeout;
}




static inline long __sched
do_wait_for_common(struct completion *x,
           long (*action)(long), long timeout, int state)
{
    if (!x->done) {
        DECLARE_WAITQUEUE(wait, current);


        __add_wait_queue_tail_exclusive(&x->wait, &wait);
        do {
            if (signal_pending_state(state, current)) {
                timeout = -ERESTARTSYS;
                break;
            }
            __set_current_state(state);
            spin_unlock_irq(&x->wait.lock);
            timeout = action(timeout); // 这里action 函数即是schedule_timeout;进程就睡眠在这个函数了,直到timeout时间到。
            spin_lock_irq(&x->wait.lock);
        } while (!x->done && timeout);
        __remove_wait_queue(&x->wait, &wait);
        if (!x->done)
            return timeout;
    }
    x->done--;
    return timeout ?: 1;
}





/**
 * complete: - signals a single thread waiting on this completion
 * @x:  holds the state of this particular completion
 *
 * This will wake up a single thread waiting on this completion. Threads will be
 * awakened in the same order in which they were queued.
 *
 * See also complete_all(), wait_for_completion() and related routines.
 *
 * It may be assumed that this function implies a write memory barrier before
 * changing the task state if and only if any tasks are woken up.
 */
void complete(struct completion *x)
{
    unsigned long flags;


    spin_lock_irqsave(&x->wait.lock, flags);
    x->done++;
    __wake_up_common(&x->wait, TASK_NORMAL, 1, 0, NULL);
    spin_unlock_irqrestore(&x->wait.lock, flags);
}




/*
 * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
 * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
 * number) then we wake all the non-exclusive tasks and one exclusive task.
 *
 * There are circumstances in which we can try to wake a task which has already
 * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
 * zero in this (rare) case, and we handle it by continuing to scan the queue.
 */
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
            int nr_exclusive, int wake_flags, void *key)
{
    wait_queue_t *curr, *next;


    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
        unsigned flags = curr->flags;


        if (curr->func(curr, mode, wake_flags, key) &&//这个func是 DECLARE_WAITQUEUE时,默认的default_wake_function
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
            break;
    }
}








0 0
原创粉丝点击