Linux カーネルには、スピン ロック、読み取り/書き込みロック、セマフォ、RCU ロックなど、多くのロック メカニズムがあります。この記事では、読み取り/書き込みロックに似たロック メカニズムである順次ロック (seqlock) を紹介します。
シーケンシャル ロックは、読み取り/書き込みロックと同様、読み取りを増やし、書き込みを減らし、処理を高速化するためのロック メカニズムです。順次ロックと読み取り/書き込みロックの違いは、読み取り/書き込みロックの読み取りロックは書き込みロックをブロックしますが、順次ロックの読み取りロックは書き込みロックをブロックしないことです。
リードロック原理
読み取りロックが書き込みロックをブロックするのを防ぐために、読み取りロックは実際にはロック操作を実行しません。では、クリティカル セクション データを読み取るときに、読み取りロックはどのようにして他のプロセスによってデータが変更されるのを防ぐのでしょうか?
この問題を解決するために、順次ロックはバージョン番号と同様のメカニズムを使用します序号
。シリアル番号は増加するだけで減少しないカウンタであり、次のコードに示すようにシーケンシャル ロック オブジェクトの定義からわかります。
typedef struct {
struct seqcount seqcount; // 序号
spinlock_t lock; // 自旋锁,写锁上锁时使用
} seqlock_t;
read_seqbegin()
クリティカル セクション データを読み取る前に、まず関数を呼び出して読み取りロックを取得する必要があります 。read_seqbegin()
関数の中心的なロジックは、シーケンシャル ロックのシーケンス番号を読み取ることです。コードは次のようになります。
static inline unsigned read_seqbegin(const seqlock_t *sl)
{
unsigned ret;
repeat:
// 读取顺序锁的序号
ret = sl->sequence;
// 如果序号是单数,需要重新获取
if (unlikely(ret & 1)) {
...
goto repeat;
}
...
return ret;
}
上記のコードからわかるように、read_seqbegin()
この関数はシーケンシャル ロックのシーケンス番号を取得するだけで、ロック操作は実行しないため、読み取りロックは書き込みロックをブロックしません。
注: シリアル番号が奇数の場合に再取得が必要な理由は、書き込みロックの実装原理を分析する際に説明されます。
読み取りロックはロック操作を実行しないため、クリティカル セクションのデータを読み取るときにデータが変更された場合はどうなりますか? 答えは、クリティカル セクションを終了するときに、現在の順次ロック シーケンス番号と以前に読み取られたシーケンス番号を比較して、一貫性があるかどうかを確認することです。一貫性がある場合はデータが変更されていないことを意味し、そうでない場合はデータが変更されたことを意味します。データが変更された場合、クリティカル セクションのデータを再読み込みする必要があります。
関数を使用して read_seqretry()
シリアル番号が一致しているかどうかを比較できるため、読み取りロックの正しい使用法は次のコードに示すとおりです。
do {
// 获取顺序锁序号
unsigned seq = read_seqbegin(&seqlock);
// 读取临界区数据
...
} while (read_seqretry(&seqlock, seq)); // 对比序号是否一致?
read_seqretry()
関数の実装は非常に簡単で、次のようになります。
static inline unsigned
read_seqretry(const seqlock_t *sl, unsigned start)
{
...
return sl->sequence != start;
}
上記のコードからわかるように、read_seqretry()
この関数は現在のシリアル番号が以前に読み取られたシリアル番号と一致するかどうかを単純に比較します。
書き込みロックの原理
上記の分析から、読み取りロックは、前後のシリアル番号が一致しているかどうかを比較することで、データが変更されたかどうかを判断していることがわかります。では、シリアル番号はいつ変更されたのでしょうか? 答えは、書き込みロックを取得するときです。
書き込みロックの取得は write_seqlock()
関数を通じて実装され、その実装は比較的単純で、コードは次のとおりです。
static inline void write_seqlock(seqlock_t *sl)
{
spin_lock(&sl->lock);
sl->sequence++;
...
}
write_seqlock()
この関数はまずスピン ロックを取得し (そのため、書き込みロックと書き込みロックは相互に排他的です)、次にシーケンス番号に 1 を加えます。そのため、クリティカルセクションのデータを変更する前に、ライトロックによりシーケンス番号の値が増加してしまうため、リードロックの前後で2回取得したシーケンス番号に不整合が発生します。この状況を次の図で説明します。
シークロック原理
Information Direct: Linux カーネル ソース コード テクノロジ学習ルート + ビデオ チュートリアル カーネル ソース コード
Learning Express: Linux カーネル ソース コード メモリ チューニング ファイル システム プロセス管理 デバイス ドライバー/ネットワーク プロトコル スタック
クリティカルセクションの読み取り前後で取得したシーケンス番号の値が一致しない場合、データが変更されていることを意味し、変更されたデータを再度読み取る必要があることがわかります。
書き込みロックの解除も非常に簡単で、コードは次のとおりです。
static inline void write_sequnlock(seqlock_t *sl)
{
...
s->sequence++;
spin_unlock(&sl->lock);
}
ロックを解除するには、シリアル番号に 1 を追加してからスピン ロックを解除する必要もあります。
write_seqlock()
関数や 関数はシリアル番号に1を加算するため write_sequnlock()
、ロック解除後のシリアル番号の値は偶数である必要があります。
読み取りロックを分析したところ、シリアル番号が奇数の場合、シリアル番号が偶数になるまでシリアル番号が再取得されることがわかりました。シーケンス番号が奇数の場合は、データが更新中であることを意味するためである。このときクリティカルセクションの値を読み取っても意味がないので、更新が完了するまで待ってから読み込む必要があります。
原著者: Linux カーネルについて