wait_for_completion()

작업 완료 시그널을 받는 wait_for_complition() 함수와 작업 완료 시그널을 보내는 complete() 함수의 처리 흐름도이다.

wait_for_completion-1

 

선언 및 초기화

DECLARE_COMPLETION()

include/linux/completion.h

/**
 * DECLARE_COMPLETION - declare and initialize a completion structure
 * @work:  identifier for the completion structure
 *
 * This macro declares and initializes a completion structure. Generally used
 * for static declarations. You should use the _ONSTACK variant for automatic
 * variables.
 */
#define DECLARE_COMPLETION(work) \
        struct completion work = COMPLETION_INITIALIZER(work)

주어진 이름의 completion 구조체에 대해 초기화를 한다.

  • 예) DECLARE_COMPLETION(abc)
    • abc 라는 이름의 completion 구조체 초기화

 

COMPLETION_INITIALIZER()

include/linux/completion.h

#define COMPLETION_INITIALIZER(work) \
        { 0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }

주어진 이름의 completion 구조체의 FIFO 대기 큐를 초기화하고 done 이라는 멤버의 초기값을 0으로 클리어한다.

 

APIs

wait_for_completion()

kernel/sched/completion.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);

작업의 완료를 기다린다. 정상 완료 시 0이 반환되고 인터럽트된 경우 -ERESTARTSYS를 반환한다.

 

wait_for_common()

kernel/sched/completion.c

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

작업의 완료를 주어진 시간 만큼 기다린다. 정상 완료 시 0, 인터럽트된 경우 -ERESTARTSYS 그리고 타임아웃 시 양의 정수로 남은 timeout jiffies 값을 반환한다.

 

__wait_for_common()

kernel/sched/completion.c

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;
}

 

do_wait_for_common()

kernel/sched/completion.c

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);
                        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;
}

현재 태스크를 completion 구조체에 추가한 후 완료 시그널을 기다린다.

  • if (!x->done) {
    • 이미 대기 중이 아닌 경우
  • DECLARE_WAITQUEUE(wait, current);
    • 현재 태스크로 wait 노드를 생성한다.
  • __add_wait_queue_tail_exclusive(&x->wait, &wait);
    • x 인수로 받은 compltion 구조체의 wait 큐에 조금 전에 생성한 wait 노드를 마지막에 exclusive 플래그로 추가한다.
  • do { if (signal_pending_state(state, current)) { timeout = -ERESTARTSYS; break; }
    • 루프를 돌며 지연된 시그널이 있는 경우 루프를 빠져나간다.
  • __set_current_state(state); spin_unlock_irq(&x->wait.lock); timeout = action(timeout); spin_lock_irq(&x->wait.lock);
    • 현재 상태를 저장하고 action 인수로 받은 함수를 호출한다.
      • 기본 함수로 schedule_timeout()을 사용한다.
  • } while (!x->done && timeout);
    • 완료 시그널을 받았거나 타임 아웃된 경우가 아니면 루프를 계속 돈다.
  • __remove_wait_queue(&x->wait, &wait);
    • 대기 큐에서 추가하였던 wait 노드를 제거한다.
  • if (!x->done) return timeout;
    • 완료 시그널을 받지 않은 경우 timeout 값을 반환한다.
  • x->done–; return timeout ?: 1;
    • 완료 카운터 done을 1 감소시키고 timeout 값을 반환하거나 0인 경우 1을 반환한다.

 

complete()

kernel/sched/completion.c

/**
 * 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_locked(&x->wait, TASK_NORMAL, 1);
        spin_unlock_irqrestore(&x->wait.lock, flags);
}
EXPORT_SYMBOL(complete);

작업 완료를 기다리는 하나의 태스크를 깨우고 완료 신호를 보내 대기중인 함수(wait_for_compltion())에서 탈출하게 한다.

  • 대기큐에 하나 이상의 스레드들이 등록되어 있어 모두 깨어나게 할 필요가 있는 경우 complete_all() 함수를 사용한다.
  • TASK_NORMAL:
    • TASK_INTERRUPTIBLE | TASK_UNITERRUPTIBLE

 

__wake_up_locked()

kernel/sched/wait.c

/*
 * Same as __wake_up but called with the spinlock in wait_queue_head_t held.
 */
void __wake_up_locked(wait_queue_head_t *q, unsigned int mode, int nr)
{
        __wake_up_common(q, mode, nr, 0, NULL); 
}
EXPORT_SYMBOL_GPL(__wake_up_locked);

대기큐에 등록된 하나의 태스크가 슬립된 경우 깨어나게 한다.

 

__wake_up_common()

kernel/sched/wait.c

/*
 * 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) &&
                                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                        break;
        }
}

작업 완료(complete)를 기다리는 스레드들을 순회하며 깨우는 함수(func)를 호출하되 exclusive 설정된 태스크들만 nr_exclusive 수 만큼 깨운다.

  • func()
    • schedule_timeout()
    • io_schedule_timeout()

 

구조체

completion 구조체

include/linux/completion.h

/*
 * struct completion - structure used to maintain state for a "completion"
 *
 * This is the opaque structure used to maintain the state for a "completion".
 * Completions currently use a FIFO to queue threads that have to wait for
 * the "completion" event.
 *
 * See also:  complete(), wait_for_completion() (and friends _timeout,
 * _interruptible, _interruptible_timeout, and _killable), init_completion(),
 * reinit_completion(), and macros DECLARE_COMPLETION(),
 * DECLARE_COMPLETION_ONSTACK().
 */
struct completion {
        unsigned int done;
        wait_queue_head_t wait;
};
  • done
    • 초기 값은 0, 완료 시 1
  • wait
    • FIFO 대기 큐

 

댓글 남기기