完了通知(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;
}






