Linux Kernel: mutex APIによるロック(排他)方法

前書き:mutexとは

Linux Kernelに限らず、様々なプログラミング言語やライブラリはロック機構を提供しています。ロック機構は、複数のプロセスが同時に共有データを書き換え、意図しないデータ状態となる事を防ぎます。代表的なロック機構には、

  • spinlock
  • semaphore
  • mutex

があります。

今回説明するmutex(Mutal Exclusion)は、一つのプロセス(スレッド)だけが共有リソースにアクセスする事を許可するロック機構です。Device Driverで用いるロック機構ではデファクトスタンダード扱いです。その理由は、他のロック機構がデメリット(以下)があるからです。

  • spinlockはロック解放待ち(ビジーウェイト)でCPUリソースを消費
    • ただし、割り込みコンテキストではspinlockを使用(mutexはデッドロックが発生する可能性あり)
  • semaphoreは共有データに複数プロセスが同時アクセス可能(ロック獲得条件が緩い)

本記事では、mutexを使用する上での注意点を述べた後に、Linux Kernelのmutex APIについて説明していきます。

                            

mutexを使用する上での注意点

Linux Kernelソースコードのinclude/linux/mutex.hには、mutexを使用する上での注意点(以下の箇条書き)が記載されています。User空間でも同じようなノウハウ(注意点)があると思いますが、赤字部分はHardwareを考慮した注意事点(Kernel空間ならではの注意点)です。

  • 一度に、一つのタスクだけがmutexを保持可能
  • mutex所有者のみがロックを解除可能
  • 複数のロック解除は許可されていない
  • 再帰的なロックは許可されていない
  • mutexオブジェクト(mutex用の構造体)は、API経由で初期化する事
  • mutexオブジェクトは、memset()やコピーによる初期化をしてはいけない
  • タスク(プロセス)は、mutex(ロック)を保持した状態で終了してはいけない
  • ロック獲得したままのメモリ領域は、解放(free)してはいけない
  • 獲得されたmutex(使用中のmutex)は、再初期化してはいけない
  • mutexは、taskletsやtimerのようなHW/SWのIRQ(割り込み)コンテキストで使用不可
    • 理由:割り込みハンドラがロック中のmutexを獲得しようとするとデッドロック発生するため

                                  

Linux Kernel mutex構造体

mutex構造体は、include/linux/mutex.hに定義が存在します。mutex APIを使う上でmutex内部の仕組みを知る必要はありませんが、簡単に説明します。

mutex構造体のメンバ変数 役割
atomic_t count atomic_t構造体(中身はint型のcounter)としてmutexの状態を管理します。1=アンロック状態、0=ロック状態。
spinlock_t wait_lock mutex内部で、mutex獲得待ちのタスクを起床(wake up)させる場合などに、spinlockを使用します。
struct list_head wait_list mutex獲得待ちのタスクをList形式で管理します。Linux Kernel内のListの仕組みに関しては、本サイトに説明記事があります。
struct task_struct *owner; mutex構造体を管理(使用)するタスクを意味します。
struct optimistic_spin_queue osq Mutex獲得時のMCSロック(スピン)機能が有効の場合、使用します。MCSロック機能では、1)他のCPUがmutexの所有者、2)タスクの再スケジューリングが不要、の条件を共に満たした場合、ロック獲得(※)のためにスピンして待ち続けます 。※他のCPUが処理実行中のため、直ぐにロックが解放される可能性があります。
void *magic mutexのセマンティックス違反やデッドロックを検知する場合に使用します。
struct lockdep_map dep_map lockdep機能(ロック獲得順番などの正当性チェッカ)で用いるロック依存関係マップです。

                    

Linux Kernel mutexの初期化(動的)

Linux Kernel内で、動的にmutexを初期化する場合、mutex_initマクロを使用します。初期化例は、以下の通りです。

mutex_initマクロは、以下の実装の通り、各APIの初期化関数をコールしているだけです。途中で登場するlock_class_key構造体は、lockdep機能におけるロッククラス(ロック規則に関して論理的に同じロックのグループ)に対して、一意(ユニーク)のアドレスを割り当てるために存在します。

          

Linux Kernel mutexの初期化(静的)

Linux Kernel内で、静的にmutexを初期化する場合、DEFINE_MUTEXマクロを使用します。ここでの静的とは、mutexの変数宣言時という意味です。DEFINE_MUTEXマクロをコールすると、新しいmutex構造体(__mutex)が作成され、初期化済みになります。初期化例は、以下の通りです。

DEFINE_MUTEXマクロの定義は、include/linux/mutex.hにあり、以下の通りです。mutexの初期化処理としては、sponlockやlistなどの各APIにおける静的初期化処理をコールしているだけです。

                              

mutex(ロック)獲得およびmutex(ロック)解放

mutex獲得にはmutex_lock()、mutex解放にはmutex_unlock()を用います。mutex_lock()はmutexを獲得できれば処理を継続しますが、mutexを獲得できなければ獲得できる状態になるまでスリープします。どちらのケースも、mutex_lock()の処理が終了した後では、mutexを獲得した状態になっています。

以下に実行例を示します。

                                                                                             

mutex(ロック)の状態をチェック

mutexの獲得はせず、mutex(ロック)の状態を調べたい場合、mutex_is_locked()を使用します。返り値が1の場合は他のタスクがmutexを獲得中(ロック状態)、0の場合はどのタスクもmutexを獲得していません(アンロック状態)。

以下に実行例を示します。

                  

mutex獲得を試み、獲得失敗時はスリープしない

mutexの獲得失敗時、mutex_lock()のようにスリープ状態に移行せず、そのまま処理を継続するには、mutex_trylock()を使用します。mutex_trylock()は、mutexの獲得成功時には1を返し、獲得失敗時は0を返します。

以下に実行例を示します。

                   

割り込み可能なスリープでmutex獲得を待機

mutex(ロック)獲得中に割り込みが発生し、mutex(ロック)獲得処理の呼び出し元がスリープ状態になる場合、mutex_lock_interruptible()を使用します。mutex_lock_interruptible()は、mutexの獲得成功時には1を返し、ロック獲得を試みている最中にシグナルによって中断された場合は-EINTRを返します。

ユーザコンテキストで処理が制御される場合、ユーザが急に処理を中断(“Ctrl+C”=”SIGINT”)する可能性があるため、mutex_lock()ではなくmutex_lock_interruptible()を使用します。mutex_lock()は、Ctrl+Cなどのシグナルが発生した場合も、スリープし続けます。

以下に実行例を示します。

                     

mutex獲得待機タスクのKILLを検出

mutex獲得待機タスクがKILLされた事を検出するには、mutex_lock_killable()を使用します。前述のmutex_lock_interruptible()は複数のシグナルを検出しますが、その一方でmutex_lock_killable()はタスク(プロセス)を終了させるSIGKILL(および他の重要なシグナル)を検出します(Ctrl+Cは、SIGKILLではなくSIGINT)。

mutex_lock_interruptible()は、mutex 獲得待ちの間に重要なシグナルを受信した場合、シグナル処理を優先するため、現在実行中のmutex 獲得待ちタスクをmutex獲得待ちリストから削除します。返り値は、mutexが獲得成功時に0、それ以外のケースは状況に応じたERROR番号(-EALREADY、-EINTR、-EDEADLK)です。

以下に実行例を示します。

                    

あわせて読みたい

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です