EN JA
LOCKING(9)
LOCKING(9) FreeBSD Kernel Developer's Manual LOCKING(9)

名称

lockingカーネル同期基本関数

解説

FreeBSD カーネルは、開発者が安全にアクセスでき、多くのデータタイプを操作できるようにする、いくつかの異なった同期基本関数を要求するように、複数の CPU に渡って実行するように書かれています。

ミューテックス (mutex)

ミューテックス (また、"ブロッキングミューテックス"と呼ばれる) は、最も一般的に使用されるカーネルの同期 (synchronization) 基本関数です。スレッドは、(割り込みスレッドを含んで) 他のスレッドと共有されるデータにアクセスする前に、ミューテックス (ロック) を取得して、その後、それを解放 (アンロック) します。ミューテックスを取得できないなら、それを要求するスレッドは、ウェートします。ミューテックスは、デフォルトで適応され、競合するミューテックスの所有者が別の CPU で現在実行しているなら、ミューテックスを獲得することを試みるスレッドは、プロセッサを放棄するのではなくスピンすることを意味します。ミューテックスは、優先順序伝播 (propagation) を完全にサポートしています。

詳細については、 mutex(9) を参照してください。

スピンミューテックス (spin mutex)

スピンミューテックスは、基本的なミューテックスの変形です。 2 つの間の主な違いは、スピンミューテックスが決してフロックしないことです。代わりに、それらは、解放されるロックがウェートする間にスピンします。デッドロックを回避するために、スピンミューテックを保持するスレッドは、その CPU を決して放棄してはなりません。通常のミューテックスと異なり、スピンミューテックスは、獲得されるとき、割り込みを無効にします。割り込みを無効にすることが高価であるかもしれいなので、それらは、獲得して、解放することが一般的により遅いです。スピンミューテックスは、例えば、割り込みフィルタコードを共有するデータを保護するために (詳細については、 bus_setup_intr(9) を参照)、または内部のスケジューラのために、絶対に必要なときのみ、使用されるべきです。

ミューテックスプール (mutex pool)

ミューテックスのような、ほとんどの同期基本関数で、プログラマは、基本関数を保持するためにメモリを提供しなければなりません。例えば、ミューテックスは、それが保護する構造体の内側に埋め込まれるかもしれません。ミューテックスプールは、この要求を回避するためにミューテックスの前もって割り付けられたセットを提供します。プールからのミューテックスは、単にリーフ (leaf) ロックとしてのみ使用されることに注意してください。

詳細については、 mtx_pool(9) を参照してください。

読み込み側/書き込み側ロック (Reader/Writer Lock)

読み込み側/書き込み側ロックによって、複数のスレッドによる保護されたデータへの共用アクセス、または単一スレッドによる排他的なアクセスを可能とします。それらは、保護されたデータを読む込むだけであるべきなので、共用アクセスがあるスレッドは、 読み込み側 として知られています。保護されたデータを変更するかもしれないので、排他的なアクセスがあるスレッドは、 書き込み側 として知られています。

共有された/排他的なセマンティクスで、読み込み側/書き込み側ロックを、ミューテックス (上記と mutex(9) を参照) として扱うことができます。読み込み側/書き込み側ロックは、ミューテックスのような優先順序伝播 (propagation) をサポートしますが、優先順序は、排他的なホルダにのみ伝播されます。この制限は、共有された所有者が匿名であるという事実から来ています。

詳細については、 rwlock(9) を参照してください。

ほとんどの読み込みロック (Read-Mostly Lock)

ほとんどの読み込みロック (Read-Mostly Lock) は、 reader/write (読み込み側/書き込み側) ロックに似ていますが、非常にまれな書き込みロックに対して最適化されています。 Read-mostly (ほとんどの読み込み) ロックは、呼び出し側によって供給された tracker (追跡者) データ構造を使用して共有された所有者を追跡することによって、完全な優先権の伝播を実装しています。

詳細については、 rmlock(9) を参照してください。

スリープ可能なほとんどの読み込みロック (Sleepable Read-Mostly Lock)

スリープ可能なほとんどの読み込みロックは、ほとんどの読み込みロック (read-mostly lock) の変形です。排他的ロックを保持しているスレッドは、スリープするかもしれませんが、共有されるロックを保持しているスレッドは、そうではありません。優先順序は、排他的な所有者にではなく、共有される所有者に伝播されます。

共有/排他的ロック (shared/exclusive lock)

共有/排他的ロックは、読み込み側/書き込み側と似ています。それらの主な違いは、共有/排他的ロックが無限のスリープの間に保持されることです。競合する共有/排他的ロックを獲得することは、無限のスリープを実行することができます。これらのロックは、優先順序伝播をサポートしていません。

詳細については、 sx(9) を参照してください。

ロック管理者ロック (Lockmanager lock)

ロック管理者ロック (Lockmanager lock) は、 ( vnode(9) ロックとして) ほとんどの VFS(9) とバッファキャッシュ ( BUF_LOCK(9)) で使用されるスリープ可能な共有/排他的ロックです。それらは、スリープのタイムアウト、アップグレードのブロック、書き込み側飢餓回避、ドレイン (draining) とインタロックミューテックスのようなものがない他のロックタイプの機能がありますが、これは、それらを使用することと実装することの両方を複雑にします。この理由で、それらは、回避されるべきです。

詳細については、 lock(9) を参照してください。

計数セマフォ (counting semaphore)

計数セマフォは、リソースのプールへのアクセスを同期するためのメカニズムを提供しています。ミューテックスと異なって、セマフォには所有者の概念がないので、それらは、1 つのスレッドが、リソースを取得する必要があって、別のスレッドがそれを解放する必要がある状況で役に立つかもしれません。それらは、大いに推奨されません。

詳細については、 sema(9) を参照してください。

条件変数 (condition variable)

条件変数は条件が真実になるのを待つためにロックと共に使用されます。条件変数は、真となる条件をウェートするためにロックと連動して使用されます。スレッドは、 cv_wait() 関数を呼び出す前に関連するロックを保持しなければなりません。スレッドが条件でウェートするとき、ロックは、スレッドがプロセッサを放棄する前に、不可分に解放され、関数呼び出しが返る前に、再獲得されます。条件変数は、ブロッキングミューテックス、読み込み側/書き込み側ロック、ほとんど読み込みロックと共有/排他的ロックと共に使用できます。

詳細については、 condvar(9) を参照してください。

スリープ/ウェークアップ (Sleep/Wakeup)

また、関数 tsleep(), msleep(), msleep_spin(), pause(), wakeup() と wakeup_one() は、イベントベースのスレッドのブロッキングを扱います。条件変数と異なり、任意のアドレスは、チャネルを待つように使用され、専用の構造体は、割り付ける必要はありません。しかしながら、ウェートチャネルのアドレスは、イベントに特有であることを保証するために注意しなければなりません。スレッドが外部のイベントをウェートしなければならないなら、 tsleep(), msleep(), msleep_spin() または pause() によってスリープ状態に置かれます。また、スレッドは、ロックの基本的なスリープルーチン mtx_sleep(9), rw_sleep(9) または sx_sleep(9) の 1 つを使用してウェートします。

パラメータ chan は、スレッドがスリープ状態に置かれるイベントをユニークに識別する任意のアドレスです。単一の chan でスリープするすべてのスレッドは、スレッドがブロックしているイベントが起こったことを示すために、 (割り込みルーチンの中からしばしば呼び出される) wakeup() によって後で起こされます。

msleep(), msleep_spin() を含むスリープ関数のいくつかとロック基本スリープルーチンは、追加ロックパラメータを指定します。ロックは、スリープルーチンが返る前に、スリープして再獲得される前に、解放されます。 priorityPDROP フラグを含んでいるなら、ロックは、返る前に、再獲得されません。ロックは、条件を不可分にチェックすることができ、条件の変更、または関連する wakeup をなして現在のスレッドをサスペンドすることができることを保証するために使用されます。さらに、スリープルーチンのすべては、スレッドがサスペンドしている間に、 Giant ミューテックス (繰り返されても) を完全に落として、関数が返る前に (あらゆる再帰を復旧して) Giant ミューテックスを再獲得します。

pause() 関数は、スレッドが実行を再開する前に、指定された量の時間が通過するのを待つ特別のスリープ関数です。このスリープは、明示的な wakeup() またはシグナルのいずれかによって早期に終了することはできません。

詳細については、 sleep(9) を参照してください。

Giant

Giant は、まだそれら自体のロックを持っていないデータ構造を保護するために使用される特別のミューテックスです。それは、古い spl(9) インタフェースと同種のセマンティクスを提供するので、Giant は、次の特徴があります:
  1. それは、再帰的です。
  2. ドライバは、それら自体の MPSAFE をマークしないことによって、 Giant がそれらの周りでロックされるように要求することができます。これを行うインフラストラクチャは、適切にロックされるか、または見えなくなる、 MPSAFE でないドライバのようにゆっくり立ち去っていることに注意してください。
  3. Giant は、他のスリープ可能でないロックの前にロックされなければなりません。
  4. Giant は、無限のスリープの間に落とされ、ウェークアップ (wakeup) の後に再獲得されます。
  5. Giant を落として、再びそれを採用するカーネルの場所があります。スリープロックは、スリープの前にこれを行います。同様に、ネットワークまたは VM コードの部分は、これを行うかもしれません。利用者が、そうしたくても、利用者のコードがスリープするなら、実行から他のコードを保持する Giant で、頼りにすることができないことを意味します。

相互作用

基本関数は、相互に作用し、それらがどのように結合できるかどうに関して多くのルールがあります。これらの多くのルールは、 witness(4) によってチェックされます。

有限なスリープ対無限のスリープ (bounded vs. unbounded sleep)

有限なスリープ (また“blocking” (ブロッキング) と呼ばれる) は、スレッドの実行を再開するために必要とされるリソースのみがスレッドが獲得するのを待っているロックの所有者のための CPU 時間です。無限のスリープ (しばしば単に“sleeping”と呼ばれる) は、スレッドが外部的なイベントのため、または真となる条件のために待ちます。特に、常に利用可能な CPU 時間があるので、有限なスリープでのスレッドの依存関係のチェーンは、常に前進するべきです。これは、有限なスリープでのスレッドが無限のスリープでのスレッドによって保持されたロックを待たないことを必要とします。優先順序の反転を回避するために、有限なスリープでのスレッドは、それが待っているロックの所有者にその優先順序を与えます。

次の基本関数は、有限なスリープを実行します: ミューテックス、読み込み側/書き込み側ロックとほとんどの読み込みロック。

次の基本関数は、無限のスリープを実行します: スリープ可能なほとんど読み込みロック、共有/排他的ロック、ロック管理者ロック、計数セマフォ、条件変数とスリープ/ウェークアップ。

一般的なプリンシプル

  • それは、スピンミューテックスを保持する間にプロセッサを放棄する結果となるかもしれないあらゆる操作を行うエラーです。
  • それは、'有限のスリープ' グループからのあらゆる基本関数を保持する間に無限のスリープの結果となるかもしれないあらゆる操作を行うエラーです。例えば、ミューテックスを保持する間の共有/排他的ロックを獲得しようと試みるための、または読み込み側/書き込み側ロックを保持する間に M_WAITOK でメモリを割り付けようと試みるためのエラーです。

    スレッドが無限のスリープに入り、この規則を破らない前に、 sleep() または cv_wait() 関数の 1 つに渡されたロックが落とされることに注意してください。

  • それは、割り込みフィルタの内部で実行するとき、プロセッサの放棄の結果となるかもしれない、あらゆる操作を行うエラーです。
  • それは、割り込みスレッドの内部で実行するとき、無限のスリープの結果となるかもしれない、あらゆる操作を行うエラーです。

相互作用テーブル

次のテーブルは、議論されたロック基本関数の 1 つを保持している間に、利用者が、できることとできないことを表示しています: “sleep”は、 sema_wait(), sema_timedwait(), cv_wait() 関数のいずれか、と sleep() 関数のいずれかを含むことに注意してください。
You want: spin mtx mutex/rw rmlock sleep rm sx/lk sleep
You have: -------- -------- ------ -------- ------ ------
spin mtx ok no no no no no-1
mutex/rw ok ok ok no no no-1
rmlock ok ok ok no no no-1
sleep rm ok ok ok ok-2 ok-2 ok-2/3
sx ok ok ok ok ok ok-3
lockmgr ok ok ok ok ok ok

*1 スリープしようとしているとき、この基本関数を不可分に解放し、ウェークアップ ( mtx_sleep(), rw_sleep(), msleep_spin(), その他) で、それを再獲得する呼び出しがあります。

*2 これらの場合は、スリープ可能なほとんど読み込みロックで書き込みロックを保持している間のみ許可されます。

*3 このロックを保持している間にスリープすることができますが、さらに、スリープしようとしているとき、この基本関数を不可分に解放して、ウェークアップでそれを再獲得するために、 sleep() 関数を使用することができます。

ロックのブロッキングを行なわない試みの操作は、常に許可されることに注意してください。

コンテキストモードテーブル (context mode table)

次のテーブルは、異なったコンテキストで使用できることを示しています。現時点で、これは、かなり覚えていやすいテーブルです。
Context: spin mtx mutex/rw rmlock sleep rm sx/lk sleep
interrupt filter: ok no no no no no
interrupt thread: ok ok ok no no no
callout: ok ok ok no no no
system call: ok ok ok ok ok ok

歴史

これらの関数は、 FreeBSD 7.0 を経て BSD/OS 4.1 で登場しました。

バグ

選択するために多すぎるロック基本関数があります。
June 30, 2013 FreeBSD