シケーンスロック
シケーンスロックは、読み手より書き手を優先させる処理(タイマ更新なの)で使用されるロックです。クリティカルパスの通常ロックは、読み手/書き手がロックを取得すると、他のプロセスが読み手/書き手にかかわらず、そこでウエイトします。リードロック/ライトロックがありますが、これも読み手がロック取得下では、書き手はウエイトされます。
シーケンスロックは、読み手がクリティカルパスを実行していても、書き手はそのパスをウエイトする事なしに、実行できるというものです。ただし書き手が、ロックを取得している場合は、当然ですがウエイトされます。上記の機能を実装するために、読み手は、クリティカルパスを実行した後に、その間に書き手がそのパスを実行したかどうかをチェックします。もし実行していたら、読み手は再度クリティカルパスを実行します。
シーケンスロック構造体は、書き手の競合を防ぐためのスピンロックと、読み手が、そのパスを書き手が動作したかどうかのフラグを有しています。
シーケンスロックは、読み手がクリティカルパスを実行していても、書き手はそのパスをウエイトする事なしに、実行できるというものです。ただし書き手が、ロックを取得している場合は、当然ですがウエイトされます。上記の機能を実装するために、読み手は、クリティカルパスを実行した後に、その間に書き手がそのパスを実行したかどうかをチェックします。もし実行していたら、読み手は再度クリティカルパスを実行します。
シーケンスロック構造体は、書き手の競合を防ぐためのスピンロックと、読み手が、そのパスを書き手が動作したかどうかのフラグを有しています。
typedef struct { unsigned sequence; spinlock_t lock; } seqlock_t;書き手はまず、write_seqlock()でロックを取得します。これは別の書き手との競合を防ぐためです。読み手は、このロックを取得しません。そしてsequence++とします。sequenceの初期値は0です。なお、ロックを解除するときものインクリメントします。すなわち、sequenceが奇数の場合、書き手がそのパスを実行しているということです。
static inline void write_seqlock(seqlock_t *sl) { spin_lock(&sl->lock); ++sl->sequence; smp_wmb(); }書き手が終了した時に、write_sequnlock()をコールします。これはスピンロックを解除して、sequence++とします。これでsequenceは偶数となりました。
static inline void write_sequnlock(seqlock_t *sl) { smp_wmb(); sl->sequence++; spin_unlock(&sl->lock); }読み手は、read_seqbegin()をコールします。まずsequenceが奇数かどうかチェックします。奇数なら書き手がその区間を実行中で、奇数になるまでループします。なおsequenceはスピンロックでないと言うことです。
static __always_inline unsigned read_seqbegin(const seqlock_t *sl) { unsigned ret; repeat: ret = ACCESS_ONCE(sl->sequence); if (unlikely(ret & 1)) { cpu_relax(); goto repeat; } smp_rmb(); return ret; }読み手はそのパスを終了した時に、read_seqretry()をコールし、読み手がそのパスを実行中に、書き手がそのパスを実行していないかチェックする必要があります。それはread_seqbegin()で取得したsequenceが、終了時のそれと同じかどうか判断します。
static __always_inline int read_seqretry(const seqlock_t *sl, unsigned start) { smp_rmb(); return unlikely(sl->sequence != start); }getnstimeofday()はチィック値がタイムを返します。チィック値を読み出す前に、read_seqbegin(&xtime_lock)でsequenceを取得し、終了時それを引数として、read_seqretry()をコールしています。それが同じでないなら、再度ループで同じ処理をすることになります。
void getnstimeofday(struct timespec *ts) { unsigned long seq; s64 nsecs; WARN_ON(timekeeping_suspended); do { seq = read_seqbegin(&xtime_lock); *ts = xtime; nsecs = timekeeping_get_ns(); nsecs += arch_gettimeoffset(); } while (read_seqretry(&xtime_lock, seq)); timespec_add_ns(ts, nsecs); }