今日のLKML: テストしてテストしてからbitを立てる

こういうpatchが投稿されている

[10/12] writeback: only allow one inflight and pending full flush - Patchwork

full flushが一度に1つまでしか走らないようにしたいね、というものなのだけど、中身は本題には関係はない

patchの中に以下のようなコードがある

+       if (test_bit(WB_start_all, &wb->state))
+               return;
+
+       set_bit(WB_start_all, &wb->state); 

 "wb->state"のWB_start_allのbitが立っていたらreturnして、立ってなかったら立てる、というもの。このWB_start_allで"full flush"の排他をしていることになる。

厳密には、これは排他になっていない。1つ目のスレッドがtest_bit()してから、set_bit()するまでの間に、2つ目のスレッドがtest_bit()でbitを見にいくことがある。その場合、2つのスレッドが同時に"critical section"に入ってしまう。

ここを厳密な排他にするには、test_and_set_bit()を使う。

linux/atomic.h at master · torvalds/linux · GitHub

もちろんそんなことはkernel developerには、わかっている。patchのコメントは以下のように書いてある

It doesn't matter if we race a little bit on this, so use the faster separate test/set bit variants. 

 もともとfull syncが2つ以上一緒に走ると重いよね、ぐらいのものなのでここで厳密な排他は必要ないのだ。

こういうatomicityが必要でない場合には、test_bit()とset_bit()とを分けた方がいい時もある。

test_and_set_bit()を使うと必ずbitを立てる、すなわち値を更新してしまう。すると、cache lineがdirtyになる。頻繁に呼ばれ、多くの場合にbitが立っているような場所では、この「cache lineを汚すかどうか」が大きな違いになってくる。

 

で、頻繁に呼ばれるところで無駄にcache lineを汚さずにtest_and_set_bitしたいな〜となると以下のようなコードを書くことになる

if (test_bit(bit, addr) || test_and_set_bit(bit, addr))
...

 スレッドはpatchの内容をすっかり離れて、こうした"idiom"のdocumentないし、それもあわせてなんか関数作っとく?という話になる。そこで関数の名前の話になり、"test_and_test_and_set_bit"がいいとか、"test_then_test_and_set_bit"がいいとか、patchの話どうしたみたいな感じになっていってる。

まあ以下のように解説があるぐらい知られている話なのだけど、関数名として見ると"test and test and set bit"ってなんかむずっとする名前だよな・・・と思った次第

Test and test-and-set - Wikipedia