Linuxカーネル同期(四):シーケンスロック(seqlock)
5251 ワード
シーヶンスロック
第3章で述べた読み書きスピンロックは,読者に偏っている.カーネルは、書き込み者に偏ったロックを提供します.
このロックは簡単な読み書き共有のメカニズムを提供し、彼の設計は書く者に偏っており、どんな状況(複数の書く者が競争していない場合)、書く者には直接書く権利(覇道)があり、読者は?ここでは、書き込み者が入ると、このシーケンス値に1が加算され、読者が読み出し値の前後にそれぞれこの値をチェックすると、読み取り中(奇数は偶数)、書き込み者がデータを「改ざん」したかどうかを知るシーケンス値が提供され、もしあれば、再びspinの読み取りが行われ、データが完全に改ざんされるまで行われる.
シーケンスロック臨界領域は1つのwriter threadのみが入ることを許可し(複数のライター間で反発している)、臨界領域は1つのwriter threadのみが入ることを許可し、writer threadがない場合、reader threadは勝手に入ることができ、つまりreaderはreaderをブロックしない.臨界領域にreader threadしかない場合、writer threadはすぐに実行でき、待つことはありません
Writer threadの操作:
writer threadの場合、seqlockを取得する操作は次のとおりです.
(1)臨界領域にwriterが1つしか入らないことを保証するspin lockなどのロックを取得する.
(2)sequence counterプラス1
seqlockを解放するには、次の手順に従います.
(1)ロックを解除し、他のwriter threadが臨界領域に入ることを許可する.
(2)sequence counterプラス1(注意:マイナス1ではありませんよ、sequence counterは累積的なcounterです)
以上の操作から分かるように、臨界領域にwriter threadがない場合、sequence counterは偶数(sequence counterは0に初期化)であり、臨界領域にwriter threadが1つある場合(もちろん、1つしかない)、sequence counterは奇数である.
Reader threadの操作は次のとおりです.
(1)sequence counterの値を取得し、偶数であれば臨界領域に入ることができ、奇数であればwriterが臨界領域から離れるのを待つ(sequence counterが偶数になる).臨界領域に入るときのsequence counterの値をold sequence counterと呼ぶ.
(2)臨界領域に入り、データを読み取る
(3)sequence counterの値を取得し、old sequence counterに等しい場合はすべてOKであることを説明し、そうでなければstep(1)に戻る
適用シーン:
一般的にseqlockは次のように適用されます.
(1)read操作が比較的頻繁である
(2)write操作は少ないが、性能要求が高く、reader threadにブロックされたくない(write操作が少ないのは主にread sideの性能を考慮する)
(3)データ型は比較的簡単であるが,データへのアクセスは原子操作では保護できない.簡単な例を挙げて説明します.保護する必要があるデータがチェーンテーブルであると仮定すると、header--->A node->>B node->>C node->>nullです.reader threadがチェーンテーブルを遍歴する過程で、B nodeのポインタを一時変数xに付与すると、割り込みが発生し、reader threadがpreemptされる(seqlockの場合、readerはプリエンプトを禁止していないことに注意).これにより、他のcpu上で実行されるwriter threadは、B nodeのmemoryを解放するのに十分な時間がある(reader threadの一時変数xは、このメモリを指すことに注意).read threadが実行を再開し、xというポインタでメモリアクセスを行うと(例えばnextでC nodeを見つけようとする)、悲劇が起こります......
シーケンシャルロックの使用
定義#テイギ#
順序ロックを定義するには、次の2つの方法があります.
書き込み臨界領域:
リード臨界領域:
例:kernelでjiffies_64システムが起動してからtickの数が保存されており、そのデータへのアクセス(および他のjiffies関連データ)にはjiffies_が必要である.lockこのseq lock:
現在のtickを読みます.
カーネル更新現在のtick:
シーケンスロックの実装
1. seqlock_t構造:
2. write_seqlock/write_sequnlock
seqlockも実はspinlockに基づいていることがわかります.smp_wmbは書き込みメモリバリアであり,seq lockはsequence counterに基づいているため,この動作を保証しなければならない.
3. read_seqbegin:
writer threadがあればread_seqbegin関数には偶数になるまでpolling sequenc counterが絶えず存在し、この過程で制御しなければシステム全体の性能が損なわれる(ここでの性能は消費電力と速度を指す).したがって、polling中にcpu_がrelaxの呼び出しは、ARM 64のコード:
yield命令はハードウェアシステムに通知するために使用され、本cpuで実行される命令はpolling操作であり、それほど急迫していない.リソースの衝突があれば、本cpuは制御権を譲ることができる.
4. read_seqretry
startパラメータは、現在臨界領域を脱退しているsequenc counterよりも、臨界領域に入るときのsequenc counterのスナップショットであり、等しい場合はwriterが邪魔reader threadに入っていないことを示すと、快適に臨界領域を離れることができる.
もう一つ面白い論理問題があります.read_seqbeginはなぜパリティ判断を行うのですか?すべてをreadに押し付けるseqretryで判断してはいけませんか?つまり、なぜread_seqbeginはwriter threadがない場合に臨界領域に入るのですか?実はwriter threadも入ってもいいし、どうせread_seqretryでは,パリティおよび等しい判断を行い,論理の正確性を保証することができる.もちろん、そう思うのも正しいですが、performanceに欠けています.readerはwriter threadが臨界領域にあることを検出した後も、reader threadを入れて入ると、writer threadの追加のオーバーヘッド(cache miss)を招く可能性があります.そのため、最善の方法はread_seqbeginでブロックします.
参考文献:http://www.wowotech.net/kernel_synchronization/seqlock.html
第3章で述べた読み書きスピンロックは,読者に偏っている.カーネルは、書き込み者に偏ったロックを提供します.
このロックは簡単な読み書き共有のメカニズムを提供し、彼の設計は書く者に偏っており、どんな状況(複数の書く者が競争していない場合)、書く者には直接書く権利(覇道)があり、読者は?ここでは、書き込み者が入ると、このシーケンス値に1が加算され、読者が読み出し値の前後にそれぞれこの値をチェックすると、読み取り中(奇数は偶数)、書き込み者がデータを「改ざん」したかどうかを知るシーケンス値が提供され、もしあれば、再びspinの読み取りが行われ、データが完全に改ざんされるまで行われる.
シーケンスロック臨界領域は1つのwriter threadのみが入ることを許可し(複数のライター間で反発している)、臨界領域は1つのwriter threadのみが入ることを許可し、writer threadがない場合、reader threadは勝手に入ることができ、つまりreaderはreaderをブロックしない.臨界領域にreader threadしかない場合、writer threadはすぐに実行でき、待つことはありません
Writer threadの操作:
writer threadの場合、seqlockを取得する操作は次のとおりです.
(1)臨界領域にwriterが1つしか入らないことを保証するspin lockなどのロックを取得する.
(2)sequence counterプラス1
seqlockを解放するには、次の手順に従います.
(1)ロックを解除し、他のwriter threadが臨界領域に入ることを許可する.
(2)sequence counterプラス1(注意:マイナス1ではありませんよ、sequence counterは累積的なcounterです)
以上の操作から分かるように、臨界領域にwriter threadがない場合、sequence counterは偶数(sequence counterは0に初期化)であり、臨界領域にwriter threadが1つある場合(もちろん、1つしかない)、sequence counterは奇数である.
Reader threadの操作は次のとおりです.
(1)sequence counterの値を取得し、偶数であれば臨界領域に入ることができ、奇数であればwriterが臨界領域から離れるのを待つ(sequence counterが偶数になる).臨界領域に入るときのsequence counterの値をold sequence counterと呼ぶ.
(2)臨界領域に入り、データを読み取る
(3)sequence counterの値を取得し、old sequence counterに等しい場合はすべてOKであることを説明し、そうでなければstep(1)に戻る
適用シーン:
一般的にseqlockは次のように適用されます.
(1)read操作が比較的頻繁である
(2)write操作は少ないが、性能要求が高く、reader threadにブロックされたくない(write操作が少ないのは主にread sideの性能を考慮する)
(3)データ型は比較的簡単であるが,データへのアクセスは原子操作では保護できない.簡単な例を挙げて説明します.保護する必要があるデータがチェーンテーブルであると仮定すると、header--->A node->>B node->>C node->>nullです.reader threadがチェーンテーブルを遍歴する過程で、B nodeのポインタを一時変数xに付与すると、割り込みが発生し、reader threadがpreemptされる(seqlockの場合、readerはプリエンプトを禁止していないことに注意).これにより、他のcpu上で実行されるwriter threadは、B nodeのmemoryを解放するのに十分な時間がある(reader threadの一時変数xは、このメモリを指すことに注意).read threadが実行を再開し、xというポインタでメモリアクセスを行うと(例えばnextでC nodeを見つけようとする)、悲劇が起こります......
シーケンシャルロックの使用
定義#テイギ#
順序ロックを定義するには、次の2つの方法があります.
seqlock_t seqlock
seqlock_init(&seqlock)
DEFINE_SEQLOCK(seqlock)
書き込み臨界領域:
write_seqlock(&seqlock);
/* -------- ---------*/
write_sequnlock(&seqlock);
リード臨界領域:
unsigned long seq;
do {
seq = read_seqbegin(&seqlock);
/* ---------- ----------*/
} while (read_seqretry(&seqlock, seq));
例:kernelでjiffies_64システムが起動してからtickの数が保存されており、そのデータへのアクセス(および他のjiffies関連データ)にはjiffies_が必要である.lockこのseq lock:
現在のtickを読みます.
u64 get_jiffies_64(void)
{
do {
seq = read_seqbegin(&jiffies_lock);
ret = jiffies_64;
} while (read_seqretry(&jiffies_lock, seq));
}
カーネル更新現在のtick:
static void tick_do_update_jiffies64(ktime_t now)
{
write_seqlock(&jiffies_lock);
/* jiffies_64 */
write_sequnlock(&jiffies_lock);
}
シーケンスロックの実装
1. seqlock_t構造:
typedef struct {
struct seqcount seqcount;
spinlock_t lock;
} seqlock_t;
2. write_seqlock/write_sequnlock
static inline void write_seqlock(seqlock_t *sl)
{
spin_lock(&sl->lock);
sl->sequence++;
smp_wmb();
}
static inline void write_sequnlock(seqlock_t *sl)
{
smp_wmb();
s->sequence++;
spin_unlock(&sl->lock);
}
seqlockも実はspinlockに基づいていることがわかります.smp_wmbは書き込みメモリバリアであり,seq lockはsequence counterに基づいているため,この動作を保証しなければならない.
3. read_seqbegin:
static inline unsigned read_seqbegin(const seqlock_t *sl)
{
unsigned ret;
repeat:
ret = ACCESS_ONCE(sl->sequence); --- , sequenc counter
if (unlikely(ret & 1)) { ----- , writer thread
cpu_relax();
goto repeat; ---- writer, , polling sequenc counter
}
smp_rmb(); --- sequenc counter
return ret;
}
writer threadがあればread_seqbegin関数には偶数になるまでpolling sequenc counterが絶えず存在し、この過程で制御しなければシステム全体の性能が損なわれる(ここでの性能は消費電力と速度を指す).したがって、polling中にcpu_がrelaxの呼び出しは、ARM 64のコード:
static inline void cpu_relax(void)
{
asm volatile("yield" ::: "memory");
}
yield命令はハードウェアシステムに通知するために使用され、本cpuで実行される命令はpolling操作であり、それほど急迫していない.リソースの衝突があれば、本cpuは制御権を譲ることができる.
4. read_seqretry
static inline unsigned read_seqretry(const seqlock_t *sl, unsigned start)
{
smp_rmb();--- sequenc counter
return unlikely(sl->sequence != start);
}
startパラメータは、現在臨界領域を脱退しているsequenc counterよりも、臨界領域に入るときのsequenc counterのスナップショットであり、等しい場合はwriterが邪魔reader threadに入っていないことを示すと、快適に臨界領域を離れることができる.
もう一つ面白い論理問題があります.read_seqbeginはなぜパリティ判断を行うのですか?すべてをreadに押し付けるseqretryで判断してはいけませんか?つまり、なぜread_seqbeginはwriter threadがない場合に臨界領域に入るのですか?実はwriter threadも入ってもいいし、どうせread_seqretryでは,パリティおよび等しい判断を行い,論理の正確性を保証することができる.もちろん、そう思うのも正しいですが、performanceに欠けています.readerはwriter threadが臨界領域にあることを検出した後も、reader threadを入れて入ると、writer threadの追加のオーバーヘッド(cache miss)を招く可能性があります.そのため、最善の方法はread_seqbeginでブロックします.
参考文献:http://www.wowotech.net/kernel_synchronization/seqlock.html