『Linuxデバイス駆動開発の詳細』--スピンロック(spinlock)

5600 ワード

7.4.1スピンロックの使用
スピンロック(spin lock)は、臨界リソースに対する反発ハンドアクセスの典型的な手段であり、その名前はその動作方式に由来する.スピンロックを取得するには、あるCPU上で実行されるコードは、まず原子操作を実行する必要があります.この操作は、原子操作であるため、他の実行ユニットがこのメモリ変数にアクセスすることはできません.
テスト結果がロックが空きであることを示す場合、プログラムはこのスピンロックを取得し、実行を継続する.テスト結果がロックがまだ占有されていることを示す場合、プログラムは、この「テストして設定する」操作を小さなサイクルで繰り返し、すなわち、いわゆる「スピン」を行い、一般的には「その場で回転する」ことになる.スピンロックの所有者が変数をリセットしてスピンロックを解放すると、待機中の「テストと設定」操作がロックが解放されたことを呼び出し者に報告します.スピンロックを理解する最も簡単な方法は、臨界領域を「現在実行中です.少々お待ちください」または「現在実行中ではありません.使用できます」とマークする変数として見ることです.A実行ユニットが最初にルーチンに入ると、スピンロックが保持される.B実行ユニットが同じルーチンに入ろうとすると、スピンロックが保持されていることがわかり、A実行ユニットが解放されるまで待たなければならない.
Linuxシステムにおけるスピンロックに関する動作は主に以下の4種類である.1スピンロックの定義
spinlock_t spin;2スピンロックの初期化
spin_lock_init(lock) 
このマクロは、スピンロックlock 3を動的に初期化してスピンロックを得るために用いられる
spin_lock(lock) 
このマクロはスピンロックlockを取得するために使用され、すぐにロックを取得できればすぐに戻り、そうでなければ、スピンロックの保持者が解放されるまでスピンをそこに置く.
spin_trylock(lock) 
このマクロはスピンロックlockを取得しようとしたが、すぐにロックを取得することができれば、ロックを取得して真に戻る.そうしないと、すぐに偽に戻り、実際には「その場で回転」しない.
4スピンロックを解除spin_unlock(lock)マクロはspin_trylockまたはspin_ロックペアで使用します.
スピンロックは一般的に次のように使用されます.
// 
spinlock_t lock; 
spin_lock_init(&lock); 
spin_lock (&lock) ; // , 
. . . // 
spin_unlock (&lock) ; // 

スピンロックは、主にSMPまたは単一CPUであるが、カーネルがプリエンプト可能である場合に用いられ、単一CPUおよびカーネルがプリエンプトをサポートしないシステムでは、スピンロックは空の動作に劣化する.シングルCPUとカーネルプリエンプト可能なシステムでは、スピンロックの保持中にカーネルのプリエンプトが禁止されます.カーネルプリエンプト可能な単一CPUシステムの挙動は実際にはSMPシステムと類似しているため,このような単一CPUシステムではスピンロックを使用する必要がある.
スピンロックを使用すると、他のCPUおよび本CPU内のプリエンプトプロセスによって臨界領域が邪魔されないことが保証されるが、ロックが得られるコードパスは、臨界領域が実行されると、割り込みおよび後半部(BH)の影響を受ける可能性がある.この影響を防止するためには,スピンロックの誘導が必要である.spin_lock()/spin_unlock()はスピンロック機構の基礎であり,それらとオフ中断local_irq_ disable()/開中断local_irq_enable()、関底半部local_bh_disable()/開底半部local_bh_enable()、オフ割り込み、ステータスワードlocal_を保存irq_save()/開中断して状態を回復local_irq_restore()結合はスピンロック機構全体を形成し、関係は以下の通りである.
spin_lock_irq() = spin_lock() + local_irq_disable() spin_unlock_irq() = spin_unlock() + local_irq_enable() spin_lock_irqsave() = spin_unlock() + local_irq_save() spin_unlock_irqrestore() = spin_unlock() + local_irq_restore() spin_lock_bh() = spin_lock() + local_bh_disable() spin_unlock_bh() = spin_unlock() + local_bh_enable()
ドライバエンジニアはスピンロックを慎重に使用し、使用中に以下のいくつかの問題に特に注意しなければならない.lスピンロックは実際にはビジーロックであり、ロックが使用できない場合、CPUは使用可能になるまで「テストと設定」を繰り返してロックを取得し、CPUはスピンロックを待つ間、何の役にも立たず、待つだけである.従って,ロックを占有する時間が極めて短い場合にのみ,スピンロックを用いることが合理的である.臨界領域が大きい場合や共有装置がある場合,ロックを長時間占有する必要があり,スピンロックを用いるとシステムの性能が低下する.lスピンロックはシステムのデッドロックを引き起こす可能性がある.この問題を引き起こす最も一般的な状況は、スピンロックを再帰的に使用することであり、すなわち、スピンロックをすでに持っているCPUがこのスピンロックを2回目に取得しようとすると、CPUはデッドロックする.さらに,プロセスがスピンロックを取得してからブロックされると,デッドロックの発生を招く可能性もある.copy_from_user()、copy_to_user()やkmalloc()などの関数はいずれもブロックを引き起こす可能性があるため,スピンロックの占有期間中にこれらの関数を呼び出すことはできない.コードリスト7.2は、デバイスが最大1つのプロセスでしか開かないように実装するために使用されるスピンロックの使用例を示す.
コードリスト7.2スピンロックを使用してデバイスを1つのプロセスでのみ開く
int xxx_count = 0;/* */

static int xxx_open(struct inode *inode,struct file *filp)
{
    ...
    spinlock(&xxx_lock);
    if (xxx_count)/* */
    {
        spin_unlock(&xxx_lock);
        return  - EBUSY;
    }
    xxx_count++;/* */
    spin_unlock(&xxx_lock);
    ...
    return 0; /*     */
}

static int xxx_release(struct inode *inode, struct file *filp)
{
    ...
    spinlock(&xxx_lock);
    xxx_count--; /* */
    spin_unlock(&xxx_lock);

    return 0;
}

7.4.2読み書きスピンロックスピンロックはロックの臨界領域がどのような操作を行うかに関心を持たず、読み書きにかかわらず、平等である.複数の実行ユニットが同時に臨界リソースを読み出してもロックされます.実際には、共有リソースへの同時アクセスでは、複数の実行ユニットが同時に読み出すことに問題はなく、スピンロックの派生ロック読み書きスピンロック(rwlock)は、読み書きの同時を許可することができる.
読み書きスピンロックは、スピンロックよりも粒度が小さいロックメカニズムであり、「スピン」の概念を保持しているが、書き込み操作では最大1つの書き込みプロセスしかなく、読み取り操作では複数の読み取り実行ユニットを同時に持つことができる.
もちろん、読み書きも同時に行うことはできません.
読み書きスピンロックに関する動作を以下に示す.1読み書きスピンロックrwlock_の定義と初期化t my_rwlock = RW_LOCK_UNLOCKED;/* 静的初期化*/rwlock_t my_rwlock;  rwlock_init(&my_rwlock);/* 動的初期化*/
2リードロックvoid read_lock(rwlock_t *lock);  void read_lock_irqsave(rwlock_t *lock, unsigned long flags);  void read_lock_irq(rwlock_t *lock);  void read_lock_bh(rwlock_t *lock);
3リードロック解除void read_unlock(rwlock_t *lock);  void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);  void read_unlock_irq(rwlock_t *lock);  void read_unlock_bh(rwlock_t *lock);共有リソースを読み込む前に、読み込みロック関数を呼び出し、完了したら読み込みロック解除関数を呼び出す必要があります.read_lock_irqsave() 、read_lock_IRq()とread_lock_bh()はそれぞれread_lock()とlocal_irq_save() 、 local_irq_disable()とlocal_bh_disable()の組合せ、読み込みロック解除関数read_unlock_irqrestore()、read_unlock_ irq()、read_unlock_bh()の場合はこれと似ています.
4書き込みロックvoid write_lock(rwlock_t *lock);  void write_lock_irqsave(rwlock_t *lock, unsigned long flags);  void write_lock_irq(rwlock_t *lock);  void write_lock_bh(rwlock_t *lock);  int write_trylock(rwlock_t *lock); 
5書き込みロック解除void write_unlock(rwlock_t *lock);  void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);  void write_unlock_irq(rwlock_t *lock);  void write_unlock_bh(rwlock_t *lock); 
write_lock_irqsave() 、 write_lock_irq() 、 write_lock_bh()はそれぞれwrite_lock()とlocal_irq_save() 、 local_irq_disable()とlocal_bh_disable()の組み合わせ、書き込みロック解除関数write_unlock_irqrestore()、write_unlock_irq()、write_unlock_bh()の場合はこれと似ています.共有リソースを読み込む前に、書き込みロック関数を呼び出し、完了したら書き込みロック解除関数を呼び出す必要があります.とspin_trylock()同様、write_trylock()も読み書きスピンロックを取得しようとするだけで、成功に失敗してもすぐに戻ります.読み書きスピンロックは一般的に次のように使用されます.
rwlock_t lock;    //  rwlock 
rwlock_init(&lock); //  rwlock 
// 
read_lock(&lock); 
...    // 
read_unlock(&lock); 
// 
write_lock_irqsave(&lock, flags); 
... // 
write_unlock_irqrestore(&lock, flags);