linuxカーネル学習の同期
4856 ワード
Linuxカーネル学習の同期
[臨界領域と競合条件]
臨界領域とは,共有データにアクセスし操作するコードセグメントである.複数の実行スレッドが同じリソースに同時にアクセスするのは通常安全ではありません.臨界領域での同時アクセスを避けるために、coderはこれらのコード原子の実行を保証する必要があります.
2つの実行スレッドが同じ臨界領域で同時に実行される可能性がある場合、これがプログラムに含まれるバグです.もしこのような状況が確かに発生したら、私たちはそれを競争条件(race conditions)と呼ぶ.同時発生を回避し、競合条件を防止することを同期(synchronization)と呼ぶ.
[同時実行の原因]
ユーザ空間が同期を必要とするのは、ユーザプログラムがスケジューラによって優先され、再スケジューリングされるためである.カーネルには、同時実行の可能性がある理由と同様のものがあります.
割り込み:割り込みはほとんどいつでも非同期で発生します.つまり、現在実行中のコードをいつでも中断します.
ソフトブレークとtasklet:カーネルはいつでもソフトブレークとtaskletを起動またはスケジューリングし、現在実行中のコードをブレークすることができます.
カーネルプリエンプト:カーネルにプリエンプト性があるため、カーネル内のタスクが別のタスクにプリエンプトされる可能性があります.
スリープとユーザー空間との同期:カーネルで実行されるプロセスがスリープする可能性があります.これにより、スケジューラが起動し、新しいユーザープロセスの実行がスケジュールされます.
シンメトリプロセッサ:2つ以上のプロセッサがコードを同時に実行できます.
[同期が必要なコード]
カーネルコードを作成するときは、次の質問をします.
このデータはグローバルですか?現在のスレッドを除いて、他のスレッドはアクセスできますか?
このデータはプロセスコンテキストと中断の上下文種で共有されますか?2つの異なる割り込みプロセッサで共有しますか?
プロセスがデータにアクセスするときにプリエンプトされる可能性はありますか?スケジューリングされた新しいプログラムは同じデータにアクセスしますか?
現在のプロセスは、一部のリソースでスリープ(ブロック)されているのではないでしょうか.そうであれば、共有データはどのような状態になりますか.
データの暴走を防ぐにはどうすればいいですか?
もしこの関数がまた別の処理でスケジューリングされたらどうなりますか?
コードが同時脅威から遠ざかるようにするにはどうすればいいですか?
簡単に言えば、ほとんどのカーネルグローバル変数と共有データにアクセスするには、何らかの形式の同期方法が必要です.
[デッドロック]
デッドロックの生成には、1つ以上の実行スレッドと1つ以上のリソースが必要です.各スレッドは1つのリソースを待っていますが、すべてのリソースが占有されています.すべてのスレッドは互いに待機していますが、すでに占有されているリソースは解放されません.したがって、どのリソースも継続できません.これは、デッドロックの発生を意味します.
Example:スレッドが2つとロックが2つあります
スレッド1スレッド2
取得ロックA取得ロックB
ロックBを取得しようとするロックAを取得しようとする
ウエイトロックBウエイトロックA
[原子操作]
原子操作は、命令が原子的に実行されることを保証し、実行プロセスが中断されないことを保証することができる.カーネルは、2つの原子操作インタフェースを提供します.1つのグループは整数に対して操作され、もう1つのグループは個別のビットに対して操作されます.
原子の整数タイプtypedef struct {
intcounter;
} atomic_t;
[スピンロック]
スピンロック(spin lock)は、最大1つの実行可能スレッドによってしか保持できません.実行スレッドが既に保持されている(いわゆる競合)スピンロックを取得しようとすると、スレッドは常に忙しいサイクル−回転−ロックの再利用を待機する.
spinlock構造体:typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
競合するスピンロックは、そのスレッドがロックの再利用を待つ間にスピンを要求し、特にプロセッサ時間を浪費することであり、この動作はスピンロックの要点である.だからスピンロックは長時間持つべきではない.スピンロックを持つ時間は、2回のコンテキスト切り替えを完了する時間よりも小さいことが望ましい.
スピンロックは処理プログラムを中断するために使用できますが、信号量はできません.信号量は睡眠をもたらします.
錠を使うときは必ず薬を処方し、的確性を持たなければならない.コードではなくデータを保護する必要があることを知っておく必要があります.
[信号量]
Linuxの信号量は睡眠ロックです.使用できない(すでに占有されている)信号量を取得しようとするタスクがある場合、信号量は待機キューに進み、スリープさせます.このとき、プロセッサは自由を取り戻し、他のコードを実行することができます.保有する信号量が利用可能(解放される)場合、待機キューにあるタスクは起動され、信号量が得られる.
semaphore構造体:struct semaphore {
spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
使用信号量に注意すべき点:
競合信号量のプロセスは、ロックが再利用可能になるのを待つ間に睡眠をとるため、信号量はロックが長時間保持される場合に適している.
逆に、ロックが短時間で保持されている場合、使用信号量があまり適切ではありません.睡眠、メンテナンス待ちキュー、および起動にかかるコストは、ロックが占有するすべての時間よりも長い可能性があるからです.
実行スレッドは、ロックが競合するとスリープするため、割り込みコンテキストではスケジューリングできないため、プロセスコンテキストでのみ信号量ロックを取得することができる.
他のプロセスが同じ信号量を取得しようとしたときにデッドロックしないため、信号量を持っている間に睡眠をとることができます.
信号量を占有しながらスピンロックを占有することはできません.信号量を待つと睡眠をとる可能性があるので、スピンロックを持っているときは睡眠を許さないからです.
[反発体]
Linuxの最新のlinuxカーネルでは、反発体mutexは反発を実現する特定の睡眠ロックです.Mutexはカーネル内でデータ構造mutexに対応しており,その挙動は使用カウント1の信号量と類似しているが,操作インタフェースはより簡単で実現もより効率的であり,使用制限がより強い.
mutex構造体:struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
struct task_struct *owner;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
const char *name;
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
Mutex使用制限:
いつでもmutexを持つタスクは1つしかありません.つまり、mutexの使用カウントは永遠に1です.
mutexに鍵をかける者は、再ロックを担当する必要があります.あるコンテキストではmutexをロックすることはできませんが、別のコンテキストではロックを解除することはできません.この制限により、mutexはカーネルとユーザ空間の複雑な同期シーンに適していない.最も一般的な方法は、同じコンテキストでロックとロック解除です.
再帰的に鍵をかけたり解除したりすることは許されません.つまり、同じロックを再帰的に持つことはできません.同じように、すでに解除されたmutexをロックすることはできません.
Mutexは中断または下半部で使用できません.
Mutexは公式APIでしか管理できません
[信号量と反発体]
反発ロックと信号量はよく似ており,カーネル内の両者が共存すると混同される.幸いなことに、それらの標準的な使用方法には簡単な規範があります.mutexの制約があなたの使用を妨げない限り、信号量よりもmutexを優先します.新しいコードを書くときは、特別な場合にのみ信号量を使用する必要があります.そのため、mutexを優先することをお勧めします.
[スピンロックと反発体]
スピンロックがいつ使用されるか、反発体または信号量がいつ使用されるかを知ることは、優れたコードを記述するのに重要ですが、多くの場合、中断コンテキストではスピンロックしか使用できず、タスクの睡眠中には反発体しか使用できないため、あまり考慮する必要はありません.
需要
推奨ロック方法
低コストロック
スピンロックの優先使用
短期ロック
スピンロックの優先使用
長期ロック
反発体を優先的に使用
割り込みコンテキストのロック
スピンロックの使用
下半部ロック
スピンロックの使用
鍵を持つには睡眠が必要です
反発体の使用
typedef struct {
intcounter;
} atomic_t;
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
struct semaphore {
spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
struct task_struct *owner;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
const char *name;
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};