無料Wikiサービス | デモページ
検索

アクセス数
最近のコメント
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
はじめ - ノース
はじめ - ノース
はじめ - 楽打連動ユーザー
はじめ - 楽打連動ユーザー
Adsense
広告情報が設定されていません。

完了通知(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はビジーウエイトいたします。
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;
}


最終更新 2012/04/10 18:52:36 - north
(2012/04/10 18:52:36 作成)