completions - wait for completion handling
이 문서는 3.18.0(linux-next)를 기반으로 작성되었다.
This document was originally written based on 3.18.0 (linux-next)
Introduction:
어떤 프로세스가 특정 지점이나 특정 상태가 되기를 기다려야 하는 하나 이상의 스레드를 가지고 있다면 completions는 이 문제에 대한 race-free 솔루션이 될 수 있다. 의미 상으로 completions는 pthread_barrier와 다소 비슷하며 유스 케이스도 비슷하다.
If you have one or more threads of execution that must wait for some process to have reached a point or a specific state, completions can provide a race-free solution to this problem. Semantically they are somewhat like a pthread_barrier and have similar use-cases.
completions는 락을 잘못사용하는 것보다 바람직한 코드 동기화 메커니즘이다. yield() 또는 약간의 기발한 msleep (1) 루프를 사용하여 처리하는 것보다 wait_for_completion*() 함수 중 하나를 사용하는 것이 좋다.
Completions are a code synchronization mechanism which is preferable to any misuse of locks. Any time you think of using yield() or some quirky msleep(1) loop to allow something else to proceed, you probably want to look into using one of the wait_for_completion*() calls instead.
completions를 사용하는 이점은 코드의 의도가 명확하다는 것 뿐만 아니라 실제로 필요한 결과가 나올 때까지 두 스레드가 계속 될 수 있으므로 보다 효율적인 코드이다.
The advantage of using completions is clear intent of the code, but also more efficient code as both threads can continue until the result is actually needed.
completions는 Linux의 generic event infrastructure 위에 구축된 event이다. 이벤트는 실행을 대기중인 태스크에게 계속 실행해도 안전한지를 알려주는 completion 구조체 안의 단순한 플래그(완료라고 부를 수 있는)로 작아진다.
Completions are built on top of the generic event infrastructure in Linux, with the event reduced to a simple flag (appropriately called "done") in struct completion that tells the waiting threads of execution if they can continue safely.
completions는 스케줄링과 관련있으므로 코드를 kernel/sched/completion.c에서 찾을 수 있다.
As completions are scheduling related, the code is found in kernel/sched/completion.c.
사용법(Usage):
completion을 사용하는 과정은 세 부분으로 이루어져 있다.
completion 구조체의 초기화
completion을 사용하는데는 completion 구조체를 초기화하는 부분, wait_for_completion() 류의 API중 하나를 호출하는 것을 통해 대기하는 파트, complete() 혹은 complete_all()을 호출하는 것을 통해 신호를 전달하는 파트, 총 세가지 파트가 있다. 또한 completions 상태를 확인하는 데 도움이되는 몇 가지 helper 함수가 있다.
There are three parts to using completions, the initialization of the struct completion, the waiting part through a call to one of the variants of wait_for_completion() and the signaling side through a call to complete() or complete_all(). Further there are some helper functions for checking the state of completions.
completions를 사용하려면 <linux/completion.h>를 포함하고 completion 구조체 변수를 생성해야한다. completions 처리에 사용되는 구조는 다음과 같습니다.
To use completions one needs to include <linux/completion.h> and create a variable of type struct completion. The structure used for handling of completions is:
struct completion {
unsigned int done;
wait_queue_head_t wait;
};
대기할 태스크를 위한 대기 큐를 제공하고 상태를 나타내는 플래그를 제공한다.
providing the wait queue to place tasks on for waiting and the flag for indicating the state of affairs.
completions는 waiter의 의도를 명확히 전달할 수 있도록 이름을 지어야 한다. 좋은 예는 다음과 같다.
Completions should be named to convey the intent of the waiter. A good example is:
wait_for_completion(&early_console_added);
complete(&early_console_added);
좋은 이름을 사용하면 (항상) 가독성에 도움이 된다.
Good naming (as always) helps code readability.
역주) waiter는 이벤트를 기다리며 대기하는 태스크를 지칭함
completion 초기화하기(Initializing completions)
동적으로 할당 된 completions의 초기화는 종종 다른 구조체에 포함되어 다음과 같이 수행된다.
Initialization of dynamically allocated completions, often embedded in other structures, is done with:
void init_completion(&done);
초기화는 waitqueue를 초기화하고 기본 상태를 "사용할 수 없음"으로 설정하면 끝난다. 즉, "완료됨"은 0이다.
Initialization is accomplished by initializing the wait queue and setting the default state to "not available", that is, "done" is set to 0.
재 초기화 함수인 reinit_completion()은 done 멤버변수를 "not available"으로 재설정하고 waitqueue를 건드리지 않고 다시 0으로 재설정한다. 동일한 completion 객체에서 init_completion ()을 두 번 호출하면 대기열을 빈 대기열로 다시 초기화하고 대기열에 있는 작업이 "손실"될 수 있으므로 버그가 발생할 가능성이 크다. 이 경우 reinit_completion()을 사용하자.
The re-initialization function, reinit_completion(), simply resets the done element to "not available", thus again to 0, without touching the wait queue. Calling init_completion() twice on the same completion object is most likely a bug as it re-initializes the queue to an empty queue and enqueued tasks could get "lost" - use reinit_completion() in that case.
정적 선언 및 초기화를 위한 매크로를 사용할 수 있다. 매크로는 아래와 같다.
For static declaration and initialization, macros are available. These are:
static DECLARE_COMPLETION(setup_done)
file 스코프에서 정적 선언을 위해 사용된다. 함수 내에서의 정적 선언을 위해서는 항상 아래를 사용해야 한다.
used for static declarations in file scope. Within functions the static initialization should always use:
DECLARE_COMPLETION_ONSTACK(setup_done)
스택내의 자동/지역 변수에 적합하며 lockdep에 잘 동작한다. 또한 작업 스레드에 전달 된 completion이 범위 내에 남아 있는지 확인하고 초기화 함수가 반환 될 때 참조가 스택에 남아 있지 않아야 한다.
suitable for automatic/local variables on the stack and will make lockdep happy. Note also that one needs to make *sure* the completion passed to work threads remains in-scope, and no references remain to on-stack data when the initiating function returns.
_timeout 또는 _interruptible / _killable 확장함수를 호출하는 코드에 대한 온스택 completions를 사용하면 타임아웃/시그널 케이스에서 온스택 completion 객체가 범위를 벗어나는 것을 방지하기 위해 추가 동기화가 필요하므로 바람직하지 않다. wait_for_completion()의 _interruptible / _killable 또는 _timeout 확장함수를 사용하려는 경우 동적으로 할당 된 completion를 사용하는 것을 고려해야 한다.
Using on-stack completions for code that calls any of the _timeout or _interruptible/_killable variants is not advisable as they will require additional synchronization to prevent the on-stack completion object in the timeout/signal cases from going out of scope. Consider using dynamically allocated completions when intending to use the _interruptible/_killable or _timeout variants of wait_for_completion().
completion 기다리기(Waiting for completions):
실행중인 스레드가 concurrent work가 끝나기를 기다리기 위해 초기화한 completion 구조체를 사용해서 wait_for_completion()을 호출한다. 일반적인 사용 시나리오는 다음과 같다.
For a thread of execution to wait for some concurrent work to finish, it calls wait_for_completion() on the initialized completion structure. A typical usage scenario is:
struct completion setup_done;
init_completion(&setup_done);
initialize_work(...,&setup_done,...)
/* run non-dependent code */ /* do setup */
wait_for_completion(&setup_done);
complete(setup_done)
이것은 wait_for_completion()과 complete() 호출에 대한 시간 순서를 의미하지 않는다. wait_for_completion() 호출 전에 complete()이 호출되면 대기하려는 쪽은 모든 의존관계가 만족되었다면 즉시 계속 실행한다. 만약 그렇지 않다면 complete()에 의해 완료 시그널이 도착할 때까지 블러킹된다.
This is not implying any temporal order on wait_for_completion() and the call to complete() - if the call to complete() happened before the call to wait_for_completion() then the waiting side simply will continue immediately as all dependencies are satisfied if not it will block until completion is signaled by complete().
wait_for_completion()은 spin_lock_irq() / spin_unlock_irq()를 호출하므로 인터럽트가 활성화 된 경우에만 안전하게 호출 할 수 있다. hard-irq 또는 irqs-off atomic 컨텍스트에서 호출하면 인터럽트를 감지하기 어렵다.
Note that wait_for_completion() is calling spin_lock_irq()/spin_unlock_irq(), so it can only be called safely when you know that interrupts are enabled. Calling it from hard-irq or irqs-off atomic contexts will result in hard-to-detect spurious enabling of interrupts.
wait_for_completion():
void wait_for_completion(struct completion *done):;
기본 동작은 timeout 없이 대기하고 태스크를 uninterruptible 상태로 표시하는 것이다. wait_for_completion()과 다른 확장함수들은 프로세스 컨텍스트(잠들 수 있음)에서만 안전하지만 atomic 컨텍스트, irq가 비활성화되어 있는 인터럽트 컨텍스트 혹은 선점이 비활성화되어 있을때에는 안전하지 않다. atmoic/인터럽트 컨텍스트에서 completion를 처리하려면 아래의 try_wait_for_completion()을 참고하자.
The default behavior is to wait without a timeout and to mark the task as uninterruptible. wait_for_completion() and its variants are only safe in process context (as they can sleep) but not in atomic context, interrupt context, with disabled irqs. or preemption is disabled - see also try_wait_for_completion() below for handling completion in atomic/interrupt context.
wait_for_completion()의 모든 확장함수들이 오랫동안 (분명히) 블러킹될 수 있기 때문에, 뮤텍스를 잡은 상태에서 호출하기를 원하지는 않을 것이다.
As all variants of wait_for_completion() can (obviously) block for a long time, you probably don't want to call this with held mutexes.
사용가능한 확장함수들(Variants available):
아래의 확장함수는 모두 상태를 리턴하며 이 상태는 대부분의 (/모든) 경우에 체크해야 한다 - 상태를 의도적으로 확인하지 않을 때에는 이 내용을 설명하는 것이 좋다 (예 : arch/arm/kernel/smp.c : __cpu_up()).
The below variants all return status and this status should be checked in most(/all) cases - in cases where the status is deliberately not checked you probably want to make a note explaining this (e.g. see arch/arm/kernel/smp.c:__cpu_up()).
흔히 발생하는 문제는 리턴 타입의 할당을 잘못하는 것이므로 올바른 타입의 변수에 반환 값을 할당하도록 주의를 기울여야 한다. 리턴 값를 체크하는 것에서도 매우 부정확한 게 발견되고는 한다. 예를 들어 if (! wait_for_completion_interruptible_timeout (...))와 같은 구문은 성공적인 completion 및 중단 된 경우에 대해 동일한 코드 경로를 실행한다. 아마도 원하는 것이 아닐 것이다.
A common problem that occurs is to have unclean assignment of return types, so care should be taken with assigning return-values to variables of proper type. Checking for the specific meaning of return values also has been found to be quite inaccurate e.g. constructs like if (!wait_for_completion_interruptible_timeout(...)) would execute the same code path for successful completion and for the interrupted case - which is probably not what you want.
int wait_for_completion_interruptible(struct completion *done)
이 함수는 태스크를 TASK_INTERRUPTIBLE로 설정한다. 기다리는 동안 시그널이 전달되면 -ERESTARTSYS가 리턴된다. 그렇지 않으면 0이 리턴된다.
This function marks the task TASK_INTERRUPTIBLE. If a signal was received while waiting it will return -ERESTARTSYS; 0 otherwise.
unsigned long wait_for_completion_timeout(struct completion *done, unsigned long timeout)
태스크는 TASK_UNINTERRUPTIBLE로 설정되고 대부분 jiffies 단위의 타임아웃까지 대기한다. 타임아웃이 발생하면 0을 반환하고 남은 시간은 jiffies 단위로(최소한 1) 반환한다. 타임아웃은 msecs_to_jiffies() 또는 usecs_to_jiffies()를 사용하여 계산하는 것이 바람직하다. 반환 된 타임아웃 값을 의도적으로 무시하는 경우에는 이유를 설명해야 한다 (예 : drivers/mfd/wm8350-core.c wm8350_read_auxadc() 참조).
The task is marked as TASK_UNINTERRUPTIBLE and will wait at most 'timeout' (in jiffies). If timeout occurs it returns 0 else the remaining time in jiffies (but at least 1). Timeouts are preferably calculated with msecs_to_jiffies() or usecs_to_jiffies(). If the returned timeout value is deliberately ignored a comment should probably explain why (e.g. see drivers/mfd/wm8350-core.c wm8350_read_auxadc())
long wait_for_completion_interruptible_timeout( struct completion *done, unsigned long timeout)
이 함수는 jiffies 단위의 타임 아웃을 인자로 전달하고 태스크를 TASK_INTERRUPTIBLE로 설정한다. 시그널이 전달되면 -ERESTARTSYS가 리턴된다. 그렇지 않고 completion이 타임아웃되면 0을 리턴하고 completion이 발생하면 남은 시간을 jiffies 단위로 리턴한다.
This function passes a timeout in jiffies and marks the task as TASK_INTERRUPTIBLE. If a signal was received it will return -ERESTARTSYS; otherwise it returns 0 if the completion timed out or the remaining time in jiffies if completion occurred.
추가 확장함수에는 _killable이 있는데, 태스크 상태로 TASK_KILLABLE을 사용하고 시그널이 전달되면 -ERESTARTSYS를 리턴한다. completion이 발생할 경우에는 0을 리턴한다. _timeout 확장함수도 있다. :
Further variants include _killable which uses TASK_KILLABLE as the designated tasks state and will return -ERESTARTSYS if it is interrupted or else 0 if completion was achieved. There is a _timeout variant as well:
long wait_for_completion_killable(struct completion *done)
long wait_for_completion_killable_timeout(struct completion *done, unsigned long timeout)
확장함수인 wait_for_completion_io()는 IO를 대기하는 회계 대기 시간을 제외하고 wait_for_completion()과 동일하게 작동한다. 이는 스케줄링통계에서 태스크가 어떻게 처리되는지에 영향을 준다.
The _io variants wait_for_completion_io() behave the same as the non-_io variants, except for accounting waiting time as waiting on IO, which has an impact on how the task is accounted in scheduling stats.
void wait_for_completion_io(struct completion *done)
unsigned long wait_for_completion_io_timeout(struct completion *done unsigned long timeout)
완료신호 보내기(Signaling completions):
(waiter의 대기를 종료할) 조건을 달성했다는 신호를 보내려는 스레드는 complete()를 호출하여 계속 진행할 수있는 waiter 중 하나에게 신호를 보낸다.
A thread that wants to signal that the conditions for continuation have been achieved calls complete() to signal exactly one of the waiters that it can continue.
void complete(struct completion *done)
혹은 complete_all()을 통해 current 혹은 미래의 waiter에게 신호를 보낸다.
or calls complete_all() to signal all current and future waiters.
void complete_all(struct completion *done)
스레드가 대기 상태가 되기 전에 completion 시그널이 발생했더라도 시그널은 예상대로 작동한다. 이는 waiter가 completion 구조체의 completion 멤버변수를 "소비"(감소)함으로써 이루어진다. 대기중인 스레드의 웨이크 업 순서는 대기열에 있던 순서와 동일하다 (FIFO 순서).
The signaling will work as expected even if completions are signaled before a thread starts waiting. This is achieved by the waiter "consuming" (decrementing) the done element of struct completion. Waiting threads wakeup order is the same in which they were enqueued (FIFO order).
complete()가 여러 번 호출되면 해당 수의 waiter가 계속 진행할 수 있다. complete()를 호출하면 done 멤버변수의 값만 증가시킨다. complete_all()을 여러 번 호출하는 것은 버그이다. complete() 및 complete_all()은 hard-irq/atomic 컨텍스트에서 안전하게 호출 할 수 있다.
If complete() is called multiple times then this will allow for that number of waiters to continue - each call to complete() will simply increment the done element. Calling complete_all() multiple times is a bug though. Both complete() and complete_all() can be called in hard-irq/atomic context safely.
complete(), complete_all()을 호출하는 스레드는 waitqueue의 spinlock에 의해 serialize 되므로 한 순간에 하나만 존재할 수 있다. complete() 또는 complete_all()에 대한 동시 호출은 아마도 디자인 버그일 것이다.
There only can be one thread calling complete() or complete_all() on a particular struct completion at any time - serialized through the wait queue spinlock. Any such concurrent calls to complete() or complete_all() probably are a design bug.
hard-irq 컨텍스트에서 완료 시그널을 보내는 것은 괜찮다. 이 코드는 적절하게 spin_lock_irqsave / spin_unlock_irqrestore와 함께 적절히 잠길 것이고 결코 잠들 수 없다.
Signaling completion from hard-irq context is fine as it will appropriately lock with spin_lock_irqsave/spin_unlock_irqrestore and it will never sleep.
try_wait_for_completion()/completion_done():
try_wait_for_completion() 함수는 스레드를 대기열에 넣지 않고 스레드를 대기열에 넣어야 하는 경우 false를 반환하고 그렇지 않으면 posted completion을 소모(감소)한 다음 true를 반환한다.
The try_wait_for_completion() function will not put the thread on the wait queue but rather returns false if it would need to enqueue (block) the thread, else it consumes one posted completion and returns true.
bool try_wait_for_completion(struct completion *done)
마지막으로 completion의 상태를 변경하지 않고 검사하려면 completion_done()을 호출한다. waiter에 의해 소모(감소)되지 않은 posted completion이 없다면(waiter가 존재한다고 가정함) false를 리턴하고 그렇지 않다면 true를 리턴한다.
Finally, to check the state of a completion without changing it in any way, call completion_done(), which returns false if there are no posted completions that were not yet consumed by waiters (implying that there are waiters) and true otherwise;
bool completion_done(struct completion *done)
try_wait_for_completion() 및 completion_done()은 모두 hard-irq 또는 atomic 컨텍스트에서 호출하는 것이 안전하다.
Both try_wait_for_completion() and completion_done() are safe to be called in hard-irq or atomic context.
'커널 번역(기사,문서)' 카테고리의 다른 글
[번역] lwn – Debugging the kernel using Ftrace — part 1 (0) | 2018.09.08 |
---|---|
[번역] lwn – Debugging the kernel using ftrace — Part 2 (0) | 2018.09.08 |
[번역] scheduler/sched-arch.txt (0) | 2018.09.08 |
[번역] dev-tools/kmemleak.txt (0) | 2018.09.08 |
[번역] scheduler/sched-rt-group.txt (0) | 2018.09.08 |