작업 완료 시그널을 받는 wait_for_complition() 함수와 작업 완료 시그널을 보내는 complete() 함수의 처리 흐름도이다.
선언 및 초기화
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()을 사용한다.
- 현재 상태를 저장하고 action 인수로 받은 함수를 호출한다.
- } 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 대기 큐
