작업 완료 시그널을 받는 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 대기 큐