SMPマシンでは、割り込みコンテキストのspin_lock_irqsave
ではなく、spin_lock_irq
を使用する必要があります。
フラグ(IFを含む)を保存するのはなぜですか?
中断する可能性のある別の割り込みルーチンはありますか?
カーネルは初めてですが、Robert Loveの本「Linux Kernel Development」から収集したものから、コードがロックを開始する前にプロセッサで割り込みが既に無効になっている場合、spin_unlock_irqを呼び出すと、誤った方法でロックが解除されます。フラグを保存し、フラグで解放すると、関数spin_lock_irqsaveは割り込みを以前の状態に戻すだけです。
spin_lock_irqsave
を使用した例
spinlock_t mLock = SPIN_LOCK_UNLOCK;
unsigned long flags;
spin_lock_irqsave(&mLock, flags); // save the state, if locked already it is saved in flags
// Critical section
spin_unlock_irqrestore(&mLock, flags); // return to the formally state specified in flags
spin_lock_irq
の例(irqsaveなし):
spinlock_t mLock = SPIN_LOCK_UNLOCK;
unsigned long flags;
spin_lock_irq(&mLock); // Does not know if already locked
// Critical section
spin_unlock_irq(&mLock); // Could result in an error unlock...
spin_lock_irqsave
は基本的に、スピンロックを取得する前に割り込み状態を保存するために使用されます。これは、ロックが割り込みコンテキストで取得されるとスピンロックが割り込みを無効にし、ロック解除時に再度有効にするためです。割り込み状態は保存されるため、再び割り込みを復元する必要があります。
例:
spin_lock_irq
は、割り込みxを無効にしてロックを取得しますspin_unlock_irq
は、割り込みxを有効にします。そのため、ロックを解除した後の上記の3番目のステップでは、ロックが取得される前に無効にされていた割り込みxが有効になります。
したがって、割り込みが無効になっていないことが確実な場合にのみ、spin_lock_irq
それ以外の場合は、常にspin_lock_irqsave
。
_spin_lock_irqsave
_以外の_spin_lock_irq
_の必要性は、_local_irq_disable
_のほかにlocal_irq_save(flags)
が必要な理由と非常に似ています。以下に、Robert LoveによるLinux Kernel Development Second Editionから取ったこの要件の良い説明を示します。
Local_irq_disable()ルーチンは、その呼び出しの前に割り込みがすでに無効にされている場合、危険です。 local_irq_enable()の対応する呼び出しは、割り込みが最初からオフであったにもかかわらず、無条件に割り込みを有効にします。代わりに、割り込みを以前の状態に復元するメカニズムが必要です。これは、呼び出しチェーンに応じて、割り込みを有効または無効にしてカーネル内の特定のコードパスに到達できるため、一般的な懸念事項です。たとえば、前のコードスニペットがより大きな関数の一部であることを想像してください。この関数が、割り込みを無効にする関数としない関数の2つの他の関数によって呼び出されると想像してください。関数に至るすべてのコードパスを知ることは、カーネルのサイズと複雑さが増すにつれて難しくなりつつあるため、割り込みシステムを無効にする前に状態を保存する方がはるかに安全です。次に、割り込みを再度有効にする準備ができたら、単にそれらを元の状態に復元します。
_unsigned long flags;
local_irq_save(flags); /* interrupts are now disabled */ /* ... */
local_irq_restore(flags); /* interrupts are restored to their previous
state */
_
これらのメソッドは少なくとも部分的にマクロとして実装されているため、flagsパラメーター(符号なしlongとして定義する必要があります)は値によって渡されるようです。このパラメーターには、割り込みシステムの状態を含むアーキテクチャ固有のデータが含まれます。少なくとも1つのサポートされているアーキテクチャが値にスタック情報を組み込んでいるため(ahem、SPARC)、フラグを別の関数に渡すことはできません(具体的には、同じスタックフレームに残っている必要があります)。このため、保存の呼び出しと割り込みの復元の呼び出しは、同じ関数で発生する必要があります。
以前のすべての関数は、割り込みコンテキストとプロセスコンテキストの両方から呼び出すことができます。
割り込みコンテキストで実行中のカーネルコード/スレッドがスリープできない理由 Robert Lovesへのリンク 記事 、これを読む:
一部の割り込みハンドラー(Linuxでは高速割り込みハンドラーとして知られています)は、ローカルプロセッサー上のすべての割り込みを無効にして実行されます。これは、割り込みハンドラが可能な限り迅速に割り込みなしで実行されるようにするために行われます。さらに、すべての割り込みハンドラーは、すべてのプロセッサーで現在の割り込み行を無効にして実行されます。これにより、同じ割り込みラインの2つの割り込みハンドラーが同時に実行されないことが保証されます。また、デバイスドライバーの作成者が再帰的な割り込みを処理する必要がなくなり、プログラミングが複雑になります。
以下は、Linuxカーネル4.15.18のコードの一部です。これは、spiin_lock_irq()が__raw_spin_lock_irq()を呼び出すことを示しています。ただし、以下のコードの一部を見ることができるようにフラグを保存しませんが、割り込みを無効にします。
static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
local_irq_disable();
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
以下のコードはspin_lock_irqsave()を示しています。これはフラグの現在のステージを保存してから、無効化をプリエンプトします。
static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
{
unsigned long flags;
local_irq_save(flags);
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
/*
* On lockdep we dont want the hand-coded irq-enable of
* do_raw_spin_lock_flags() code, because lockdep assumes
* that interrupts are not re-enabled during lock-acquire:
*/
#ifdef CONFIG_LOCKDEP
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
#else
do_raw_spin_lock_flags(lock, &flags);
#endif
return flags;
}
この質問は、偽のアサーションから始まります:_On an SMP machine we must use spin_lock_irqsave and not spin_lock_irq from interrupt context.
_
これらのいずれも、SMPまたはUPの割り込みコンテキストから使用しないでください。とはいえ、spin_lock_irqsave()
mayはより汎用的であるため、割り込みコンテキストから使用できます(割り込みコンテキストと通常コンテキストの両方で使用できます)が、spin_lock()
割り込みコンテキストから、およびspin_lock_irq()
またはspin_lock_irqsave()
は通常のコンテキストから。 spin_lock_irq()
の使用は、ほとんどの場合、このSMPまたはUPである割り込みコンテキストで行うのは間違っています。ほとんどの割り込みハンドラはIRQがローカルで有効になっているため動作する可能性がありますが、試してはいけません。