web-dev-qa-db-ja.com

Linuxのスピンロックとは何ですか?

Linuxスピンロックについて詳しく知りたい。誰かが私にそれらを説明できますか?

34
Sen

スピンロックは、共有リソースが2つ以上のプロセスによって同時に変更されるのを防ぐ方法です。リソースを変更しようとする最初のプロセスは、ロックを「取得」し、リソースに対して必要なことを行って、途中で続行します。その後ロックを取得しようとする他のプロセスはすべて停止します。これらは、最初のプロセスによってロックが解放されるのを "スピンインプレース"で待機していると言われ、スピンロックと呼ばれます。

Linuxカーネルは、特定の周辺機器にデータを送信するときなど、多くのことに対してスピンロックを使用します。ほとんどのハードウェア周辺機器は、複数の状態更新を同時に処理するように設計されていません。 2つの異なる変更を行う必要がある場合、一方を他方に厳密に従う必要があり、それらを重ねることはできません。スピンロックは必要な保護を提供し、変更が一度に1つずつ行われるようにします。

スピンロックは、そのスレッドのCPUコアが他の作業を実行できないようにブロックするため、問題になります。 Linuxカーネルは、その下で実行されるユーザー空間プログラムにマルチタスクサービスを提供しますが、その汎用マルチタスク機能はカーネルコードにまで拡張されません。

この状況は変化しており、Linuxのほとんどの存在に当てはまります。 Linux 2.0までは、カーネルはほぼ純粋にシングルタスクプログラムでした。CPUがカーネルコードを実行しているときは、すべての共有リソースを保護するビッグカーネルロック(BKL)と呼ばれる単一のスピンロックがあったため、CPUコアは1つしか使用されませんでした)。 Linux 2.2から、BKLはゆっくりと多くの独立したロックに分割され、それぞれがより集中したリソースのクラスを保護しています。現在、カーネル2.6でもBKLは存在しますが、BKLは非常に古いコードでのみ使用され、より細かいロックに簡単に移動することはできません。マルチコアボックスですべてのCPUが有用なカーネルコードを実行できるようになりました。

Linuxカーネルには一般的なマルチタスク機能がないため、BKLを分割するユーティリティには制限があります。カーネルスピンロックでCPUコアがスピンをブロックされた場合、ロックが解放されるまで他の処理を実行するために再タスクすることはできません。ロックが解除されるまで、座って回転します。

ワークロードがすべてのコアが常に1つのスピンロックを待機するようなものである場合、スピンロックは、モンスターの16コアボックスをシングルコアボックスに効果的に変えることができます。これは、Linuxカーネルのスケーラビリティの主な制限です。CPUコアを2から4に2倍にすると、Linuxボックスの速度がほぼ2倍になりますが、16から32に倍にすると、ほとんどのワークロードではおそらく倍になりません。

37
Warren Young

スピンロックとは、プロセスがロックを削除するために継続的にポーリングする場合です。プロセスがサイクルを(通常)不必要に消費しているため、これは悪いと見なされます。これはLinux固有ではなく、一般的なプログラミングパターンです。そして、これは一般的に悪い習慣と考えられていますが、実際には正しい解決策です。スケジューラを使用するコストが、スピンロックが続くと予想される数サイクルのコストよりも(CPUサイクルの観点から)高くなる場合があります。

スピンロックの例:

#!/bin/sh
#wait for some program to clear a lock before doing stuff
while [ -f /var/run/example.lock ]; do
  sleep 1
done
#do stuff

スピンロックを回避する方法はしばしばあります。この特定の例では、 inotifywait というLinuxツールがあります(通常、デフォルトではインストールされません)。 Cで記述されている場合は、Linuxが提供する inotify APIを使用するだけです。

同じ例で、inotifywaitを使用すると、スピンロックなしで同じことを実行する方法が示されます。

#/bin/sh
inotifywait -e delete_self /var/run/example.lock
#do stuff
11
Shawn J. Goff

スレッドがロックを取得しようとすると、失敗した場合に3つのことが起こります。試行してブロックできるか、試行して続行できるか、試行してスリープ状態になり、OSに何らかのイベントが発生したときに起動するように指示します。

これで、トライアンドコンティニューは、トライアンドブロックよりもはるかに少ない時間で済みます。とりあえず、「トライアンドコンティニュー」にはi単位の時間がかかり、「トライアンドブロック」には100時間がかかるとしましょう。

今のところ、平均してスレッドがロックを保持するのに4単位の時間がかかると仮定します。 100ユニット待つのはもったいない。したがって、代わりに「試行して続行」のループを記述します。 4回目の試行では、通常、ロックを取得します。これはスピンロックです。これは、スレッドがロックを取得するまでその場所でスピンし続けるためです。

追加の安全対策は、ループの実行回数を制限することです。したがって、例ではforループを6回実行します。失敗した場合は、「試行してブロック」します。

スレッドが常に200ユニットのロックを保持することがわかっている場合は、試行して続行するたびにコンピュータの時間を浪費しています。

したがって、最終的には、スピンロックは非常に効率的または無駄になります。ロックを保持する「通常の」時間が「試行してブロックする」のにかかる時間よりも長い場合は、無駄になります。ロックを保持する一般的な時間が「試行してブロックする」時間よりもはるかに短い場合は、効率的です。

追伸:スレッドについて読む本は、「A Thread Primer」です。

7
HandyGandy

lock は、2つ以上のタスク(プロセス、スレッド)を同期する方法です。具体的には、両方のタスクが一度に1つのタスクのみが使用できるリソースへの断続的なアクセスが必要な場合、それはタスクが同時にリソースを使用しないように調整する方法です。リソースにアクセスするには、タスクは次の手順を実行する必要があります。

take the lock
use the resource
release the lock

別のタスクがすでにロックを取得している場合、ロックを取得することはできません。 (ロックは物理的なトークンオブジェクトと考えてください。オブジェクトは引き出しにあるか、誰かが手に持っています。オブジェクトを保持している人だけがリソースにアクセスできます。)したがって、「ロックをかける」とは、「他の誰もロックを保持していないので、それを取得してください。」.

高レベルの観点から見ると、ロックを実装するには、スピンロックと条件の2つの主要な方法があります。 spinlocks の場合、ロックを取得することは、他の誰かがロックを取得するまで、単に「スピン」する(つまり、ループで何もしない)ことを意味します。条件付きで、タスクがロックを取得しようとしたが、別のタスクがそれを保持しているためにブロックされた場合、新人は待機キューに入ります。リリース操作は、ロックが使用可能になったことを待機中のタスクに通知します。

(これらの説明は、原子性については何も言っていないため、ロックを実装するには不十分です。ただし、原子性はここでは重要ではありません。)

スピンロックは明らかに無駄です。待機中のタスクは、ロックが取得されているかどうかをチェックし続けます。では、なぜ、いつ使用されるのでしょうか。スピンロックは、ロックが保持されていない場合に、非常に安価に入手できることがよくあります。これは、ロックが保持される機会が少ないときに魅力的です。さらに、スピンロックは、ロックの取得に時間がかかると予想されない場合にのみ実行可能です。そのため、スピンロックは非常に短い時間保持される状況で使用される傾向があり、ほとんどの試行は最初の試行で成功することが期待され、待機を必要とする試行は長く待機しません。

Linuxデバイスドライバー の第5章に、Linuxカーネルのスピンロックとその他の同時実行メカニズムに関する適切な説明があります。

スピンロックは、スケジューラを無効にすることで動作するロックであり、ロックが取得された特定のコアで割り込み(irqsaveバリアント)を行う可能性があります。ミューテックスとは異なり、スケジューリングが無効になるため、スピンロックが保持されている間はスレッドのみを実行できます。ミューテックスを使用すると、保留中に他の優先度の高いスレッドをスケジュールできますが、保護されたセクションを同時に実行することはできません。スピンロックはマルチタスクを無効にするため、スピンロックを取得してから、ミューテックスを取得しようとする他のコードを呼び出すことはできません。スピンロックセクション内のコードはスリープしないでください(コードは通常、ロックされたミューテックスまたは空のセマフォに遭遇するとスリープします)。

ミューテックスとのもう1つの違いは、通常、スレッドがミューテックスをキューに入れるため、その下のミューテックスにキューがあることです。一方、スピンロックは、必要な場合でも他のスレッドが実行されないようにするだけです。したがって、ファイル外でスリープしないかどうかわからない関数を呼び出すときは、スピンロックを保持してはなりません。

スピンロックを割り込みと共有する場合は、irqsaveバリアントを使用する必要があります。これはスケジューラを無効にするだけでなく、割り込みも無効にします。それは理にかなっていますか?スピンロックは、他に何も実行されないようにすることで機能します。割り込みを実行したくない場合は、割り込みを無効にして、安全にクリティカルセクションに進みます。

マルチコアマシンでは、スピンロックは実際には、ロックを保持している別のコアがそれを解放するのを待ってスピンします。このスピンは、マルチコアマシンでのみ発生します。シングルコアマシンでは発生しないためです(スピンロックを保持して続行するか、ロックが解放されるまで実行しない)。

スピンロックは理にかなったところで無駄ではありません。非常に小さなクリティカルセクションでは、重要な作業を完了するのにかかる数マイクロ秒の間単にスケジューラを一時停止するのと比較して、ミューテックスタスクキューを割り当てるのは無駄です。 io操作(スリープする場合があります)全体でスリープまたはロックを保持する必要がある場合は、ミューテックスを使用します。確かにスピンロックを決してロックせず、それを割り込み内で解放しようとします。これは機能しますが、while(flagnotset);のarduinoがらくたのようになります。そのような場合はセマフォを使用してください。

メモリトランザクションのブロックに対して単純な相互排除が必要な場合は、スピンロックを取得します。ミューテックスロックの直前に複数のスレッドを停止し、ミューテックスが解放されたとき、および同じスレッドでロックして解放したときに続行するように最も優先度の高いスレッドを選択する場合は、ミューテックスを取得します。セマフォを1つのスレッドまたは割り込みにポストし、別のスレッドに取得する場合は、それを取得します。これは、相互排除を確実にするための3つのわずかに異なる方法であり、わずかに異なる目的で使用されます。

3
Martin