###導入###
| Linux では、CPU 時間だけを待つプロセスを準備完了プロセスと呼び、実行キューに配置され、準備完了プロセスのステータス フラグは TASK_RUNNING になります。実行中のプロセスのタイム スライスが使い果たされると、Linux カーネル スケジューラはプロセスから CPU の制御を奪い、実行キューから適切なプロセスを選択して実行します。
|
もちろん、プロセスは CPU の制御をアクティブに解放することもできます。関数schedule()は、他のプロセスがCPUを占有するようにスケジュールするために、プロセスによってアクティブに呼び出すことができるスケジューリング関数です。自発的に CPU を放棄したプロセスが CPU を占有するように再スケジュールされると、最後に停止した場所から実行を開始します。つまり、schedule() を呼び出すコードの次の行から実行を開始します。
場合によっては、デバイスの初期化、I/O 操作の完了、タイマーの期限切れなど、特定のイベントが発生するまでプロセスを待機する必要があります。この場合、プロセスは実行キューから削除され、待機キューに追加される必要があり、この時点でプロセスはスリープ状態になります。
Linux でのスリープ状態分類の処理
1 つは割り込み可能なスリープ状態で、そのステータス フラグは TASK_INTERRUPTIBLE;
もう 1 つは中断不可能なスリープ状態であり、そのステータス フラグは TASK_UNINTERRUPTIBLE です。割り込み可能なスリープ状態のプロセスは、特定の条件が true になるまでスリープします。たとえば、ハードウェア割り込みの生成、プロセスが待機しているシステム リソースの解放、シグナルの送信などが、プロセスをウェイクアップする条件になる可能性があります。中断不可能なスリープ状態は中断可能なスリープ状態と似ていますが、例外が 1 つあります。つまり、このスリープ状態に信号を送信するプロセスはその状態を変更できません。つまり、ウェイクアップする信号に応答しません。通常、中断不可能なスリープ状態はあまり使用されませんが、特定のイベントが発生するまでプロセスを待機する必要があり、中断できないなど、特定の状況では依然として非常に役立ちます。
最新の Linux オペレーティング システムでは、通常、プロセスは、schedule() を呼び出すことによってスリープ状態に入ります。次のコードは、
を実行します。
は、実行中のプロセスをスリープ状態にする方法を示しています。
sleep_task = 現在;
set_current_state(TASK_INTERRUPTIBLE);
スケジュール();###
func1();
/* コードの残りの部分 ... */
最初のステートメントでは、プログラムはプロセス構造体ポインター sleep_task のコピーを保存します。current は現在実行中の
を指すマクロです。
プロセス構造。 set_current_state() は、プロセスの状態を実行状態 TASK_RUNNING からスリープ状態に変更します
タスク_中断可能。ステータス TASK_RUNNING のプロセスによってスケジュールされた場合、schedule() は CPU を占有する別のプロセスをスケジュールします; ステータス TASK_INTERRUPTIBLE または TASK_UNINTERRUPTIBLE のプロセスによってスケジュールされた場合、追加の手順があります。実行中: 現在実行中のプロセスは、別のプロセスがスケジュールされる前に実行キューから削除されます。これにより、実行中のプロセスは実行キューに存在しなくなるためスリープ状態になります。
次の関数を使用して、スリープ状態になったプロセスを復帰させることができます。
wake_up_process(sleeping_task);
wake_up_process() を呼び出した後、スリープ状態のプロセスのステータスは TASK_RUNNING に設定され、スケジューラ
実行キューに追加されます。もちろん、このプロセスは、スケジューラによって次回スケジュールされたときにのみ実行できます。
無効なウェイクアップ
ほとんどの場合、プロセスは特定の条件をチェックし、条件が満たされていないことが判明するとスリープ状態になります。でも時々###
しかし、判定条件が成立した後にプロセスがスリープを開始してしまうと、プロセスはいつまでもスリープしてしまう、いわゆる無効ウェイクアップ問題となります。オペレーティング システムでは、複数のプロセスが共有データに対して何らかの処理を実行しようとし、最終結果がプロセスの実行順序に依存する場合、競合状態が発生します。これはオペレーティング システムの一般的な問題です。 up それはまさに競争条件によるものです。
2 つのプロセス A と B があるとします。プロセス A はリンク リストを処理しています。リンク リストが空かどうかを確認する必要があります。空でない場合は、リンクを処理します
テーブル内のデータはいくつかの操作を受けており、プロセス B もリンク リストにノードを追加しています。リンク リストが空の場合、操作するデータがないため、プロセス A はスリープ状態になります。プロセス B がリンク リストにノードを追加すると、プロセス A が起動します。コードは次のとおりです。
プロセス:###
1 スピンロック(&リストロック);
2 if(list_empty(&list_head)) {
3 スピンロック解除(&リストロック);
4 set_current_state(TASK_INTERRUPTIBLE);
5 スケジュール();
6 スピンロック(&リストロック);
7}###
8
9 /* コードの残りの部分 ... */
10 スピンロック解除(&リストロック);
B工程:
100 スピンロック(&リストロック);
101 list_add_tail(&list_head, new_node);
102 スピンロック解除(&リストロック);
103 wake_up_process(processa_task);
ここで問題が発生します。プロセス A が 3 行目以降、4 行目より前に実行されると、プロセス B は別のプロセッサによってスケジュールされます
サービスを開始します。このタイム スライス内で、プロセス B はすべての命令を完了したため、プロセス A をウェイクアップしようとします。この時点では、プロセス A はまだスリープに入っていないため、ウェイクアップ操作は無効です。この後、プロセス A は実行を続けますが、この時点ではリンク リストがまだ空であると誤って認識されるため、ステータスを TASK_INTERRUPTIBLE に設定し、schedule() を呼び出してスリープ状態になります。プロセス B はウェークアップ コールに失敗したため、無期限にスリープ状態になりますが、リンク リストに処理する必要のあるデータがある場合でもプロセス A はスリープ状態のままであるため、これが無効なウェークアップの問題です。
無効なウェイクアップを回避する
無効なウェイクアップの問題を回避するにはどうすればよいですか?無効なウェイクアップは主に条件を確認した後に発生し、プロセス状態がスリープ状態に設定されていることがわかりました。
この状態になる前に、プロセス B の wake_up_process() は、プロセス A の状態を TASK_RUNNING に設定する機会を提供していましたが、残念ながらこの時点ではプロセス A の状態はまだ TASK_RUNNING だったので、wake_up_process() によってプロセス A の状態がスリープから変更されました。状態から実行状態へ。この努力は期待した効果をもたらしませんでした。この問題を解決するには、保証メカニズムを使用して、リンク リストが空であることを判断し、プロセス状態をスリープ状態に設定することが不可欠なステップになる必要があります。つまり、競合状態の原因を排除して、wake_up_process が( ) は、スリープ状態にあるプロセスを目覚めさせる役割を果たすことができます。
理由を見つけたら、プロセス A のコード構造を再設計して、上記の例の無効なウェイクアップの問題を回避します。
プロセス:###
1 set_current_state(TASK_INTERRUPTIBLE);
2 スピンロック(&リストロック);
3 if(list_empty(&list_head)) {
4 スピンロック解除(&リストロック);
5 スケジュール();
6 スピンロック(&リストロック);
7}###
8 set_current_state(TASK_RUNNING);
9
10 /* コードの残りの部分 ... */
11 スピンロック解除(&リストロック);
ご覧のとおり、このコードは条件をテストする前に現在の実行プロセスの状態を TASK_INTERRUPTIBLE に設定し、リンク リストが空でない場合は自身を TASK_RUNNING 状態に設定します。このように、A工程内にB工程がある場合、工程チェック
リンクされたリストが空になった後で wake_up_process() を呼び出すと、プロセス A のステータスが元の TASK_INTERRUPTIBLE
から自動的に変更されます。
その後、プロセスが再度schedule()を呼び出しても、現在のステータスはTASK_RUNNINGなので実行キューからは削除されませんので、誤ってスリープに移行することはなく、もちろん問題は解決します。無効なウェイクアップが回避されます。
Linux カーネルの例
Linux オペレーティング システムでは、カーネルの安定性が非常に重要です。Linux オペレーティング システム カーネルでの無効なウェイクアップの問題を回避するには、
Linux カーネルは、プロセスをスリープさせる必要がある場合、次のような操作を使用する必要があります:
/* 「q」はスリープさせたい待機キューです */
DECLARE_WAITQUEUE(待機,現在);
add_wait_queue(q, &wait);
set_current_state(TASK_INTERRUPTIBLE);
/* または TASK_INTERRUPTIBLE */
while(!condition) /* ‘condition’ は待機条件です*/
スケジュール();###
set_current_state(TASK_RUNNING);
Remove_wait_queue(q, &wait);
上記の操作により、プロセスは次の一連の手順を通じて、スリープの待機キューに安全に追加できます。最初に
を調整します。
DECLARE_WAITQUEUE () を使用して待機キュー項目を作成し、次に add_wait_queue() を呼び出して自分自身を待機キューに追加し、プロセスのステータスを TASK_INTERRUPTIBLE または TASK_INTERRUPTIBLE に設定します。次に、ループは条件が true かどうかをチェックします。true であれば、スリープする必要はありません。条件が true でない場合は、schedule() が呼び出されます。プロセスによってチェックされた条件が満たされると、プロセスは自身を TASK_RUNNING に設定し、remove_wait_queue() を呼び出して待機キューから自身を削除します。
上記からわかるように、Linux カーネル コードのメンテナは、プロセスが条件をチェックする前に、プロセスの状態をスリープ状態に設定します。
次に、ループによって条件がチェックされます。プロセスがスリープを開始する前に条件が満たされた場合、ループは終了し、 set_current_state() を使用して状態を準備完了に設定します。これにより、プロセスが誤ってスリープに入る傾向がなくなり、当然のことながら、エラーは発生しません。無効なウェイクアップの問題です。
Linux カーネルの例を使用して、Linux カーネルが無効なスリープをどのように回避するかを見てみましょう。このコードは Linux 2.6 カーネル (linux-2.6.11/kernel/sched.c: 4254) からのものです。
4253 /* kthread_stop を待つ */
4254 set_current_state(TASK_INTERRUPTIBLE);
4255 while (!kthread_Should_stop()) {
4256 スケジュール();
4257 set_current_state(TASK_INTERRUPTIBLE);
4258 }
4259 __set_current_state(TASK_RUNNING);
4260 0 を返す;
上記のコードは移行サービス スレッド migration_thread に属しており、このスレッドは kthread_Should_stop()、
を継続的にチェックします。
kthread_Should_stop() が 1 を返すまでループを終了できません。これは、kthread_Should_stop() が 0 を返す限りプロセスはスリープすることを意味します。コードから、プロセスのステータスが TASK_INTERRUPTIBLE に設定された後、チェック kthread_Should_stop() が実際に実行されることがわかります。したがって、条件チェックの後、schedule() の前に別のプロセスがプロセスをウェイクアップしようとした場合、プロセスのウェイクアップ操作は無効になりません。
要約
上記の議論を通じて、Linux でプロセスの無効なウェイクアップを回避するための鍵は次のとおりであることがわかります。
ステータスは TASK_INTERRUPTIBLE または TASK_UNINTERRUPTIBLE に設定されており、チェックされた条件が満たされる場合は、
ステータスを TASK_RUNNING にリセットします。これにより、プロセスの待機条件が成立するか否かに関わらず、プロセスがレディキューから外されるため、誤ってスリープ状態に移行することがなくなり、無効なウェイクアップの問題が回避されます。