完了通知(completion)
セマフォを使って、プロセスAがプロセスBの終了に同期して動作する処理で、まずプロセスAでロック状態のセマフォを自動変数で定義したといたします。そしてプロセスAは、このセマフォを引数にしてプロセスBを作成した後、プロセスAはセマフォを取得します。ロック状態で定義したセマフォ故、プロセスAは待機します。
プロセスBは、処理終了時にこのセマフォをup()開放します。プロセスAはこのタイミングでセマフォをdown()取得することが可能となります。で、2つのプロセスは同期が図られるわけですが、しかしプロセスAがそのまま呼び出し元へ復帰したとすると、自動変数のセマフォは無くなってしまいます。
up()処理は、wake_up_process()でセマフォでのウエイト待ちのプロセスを起床させることです。したがってこの時点でプロセスAが起床し、プロセスBのup()処理が完結しないまま、プロセスAの呼び出し元への復帰により、自動変数故のこのセマフォが無くなってしまう。ということではと・・・?
(Linux詳解の説明は・・・? ってところで、そこからの勝手な解釈です。)
上記の解決として、同一セマフォに対してup/downで、upが完結するまで、downが処理できないようにすればいいわけです。そのためには、専用のロックが必要となり、up/down処理毎に、そのロックのチェックが必要となってきます。通常のセマフォの利用において、このような処理は意味がありません。そのために実装されたのが完了通知(completion)です。(たぶん)
完了通知は、上記のようなセマフォの使い方を、upとdownにロックを介し、upが完全に終了してからdownを動作すると言った機能を、コンパクトに実装したと言ったところでしょうか・・・?
プロセスBでのupの処理に対応するのが、complete()です。完了通知はx->doneが1なら完了、0なら未完了ということで、プロセスAでこの変数をチェックすることで、完了通知として処理します。注目すべきところは、それをスピンロックでロックしているところです。プロセスAでも、x->doneをチェックする時、スピンロックを取得するようになっています。従って、complete()の最後のspin_unlock_irqrestore()がコールされるまで、プロセスAはビジーウエイトいたします。
プロセスBは、処理終了時にこのセマフォをup()開放します。プロセスAはこのタイミングでセマフォをdown()取得することが可能となります。で、2つのプロセスは同期が図られるわけですが、しかしプロセスAがそのまま呼び出し元へ復帰したとすると、自動変数のセマフォは無くなってしまいます。
up()処理は、wake_up_process()でセマフォでのウエイト待ちのプロセスを起床させることです。したがってこの時点でプロセスAが起床し、プロセスBのup()処理が完結しないまま、プロセスAの呼び出し元への復帰により、自動変数故のこのセマフォが無くなってしまう。ということではと・・・?
(Linux詳解の説明は・・・? ってところで、そこからの勝手な解釈です。)
上記の解決として、同一セマフォに対してup/downで、upが完結するまで、downが処理できないようにすればいいわけです。そのためには、専用のロックが必要となり、up/down処理毎に、そのロックのチェックが必要となってきます。通常のセマフォの利用において、このような処理は意味がありません。そのために実装されたのが完了通知(completion)です。(たぶん)
完了通知は、上記のようなセマフォの使い方を、upとdownにロックを介し、upが完全に終了してからdownを動作すると言った機能を、コンパクトに実装したと言ったところでしょうか・・・?
プロセスBでのupの処理に対応するのが、complete()です。完了通知はx->doneが1なら完了、0なら未完了ということで、プロセスAでこの変数をチェックすることで、完了通知として処理します。注目すべきところは、それをスピンロックでロックしているところです。プロセスAでも、x->doneをチェックする時、スピンロックを取得するようになっています。従って、complete()の最後のspin_unlock_irqrestore()がコールされるまで、プロセスAはビジーウエイトいたします。
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); } 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; } }プロセスAでのdownに相当するのが、wait_for_completio()です。主たる処理はdo_wait_for_common()で、spin_lock_irq()でスピンロックを取得して呼び出しています。スピンロックを取得できるということは、complete()でスピンロックを開放したという事です、したがってdo_wait_for_common()にあたって、complete()の処理の終了を保証するわけです。
void __sched wait_for_completion(struct completion *x) { wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE); } static long __sched wait_for_common(struct completion *x, long timeout, int state) { might_sleep(); spin_lock_irq(&x->wait.lock); timeout = do_wait_for_common(x, timeout, state); spin_unlock_irq(&x->wait.lock); return timeout; }x->doneが0なら、まだcomplete()がコールされていません。プロセスをウエイトリストにつないでschedule_timeout()をコールし、それをx->doneが1になるまでループでチェックし続けます。x->done=1になればスピンロックが取得でき、しかもcomplete()の処理が修理したということです。
static inline long __sched do_wait_for_common(struct completion *x, 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 = 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; }