Linuxカーネルの循環バッファの実装について説明している記事がhttp://lwn.net/Articles/378262/にあります。いくつか質問があります:
これが「プロデューサー」です。
spin_lock(&producer_lock);
unsigned long head = buffer->head;
unsigned long tail = ACCESS_ONCE(buffer->tail);
if (CIRC_SPACE(head, tail, buffer->size) >= 1) {
/* insert one item into the buffer */
struct item *item = buffer[head];
produce_item(item);
smp_wmb(); /* commit the item before incrementing the head */
buffer->head = (head + 1) & (buffer->size - 1);
/* wake_up() will make sure that the head is committed before
* waking anyone up */
wake_up(consumer);
}
spin_unlock(&producer_lock);
質問:
- このコードはメモリオーダリングとアトミック性を明示的に扱っているので、spin_lock()のポイントは何ですか?
- これまでのところ、ACCESS_ONCEはコンパイラの並べ替えを停止するというのが私の理解ですが、本当ですか?
- produce_item(item)は、アイテムに関連付けられたすべての書き込みを発行するだけですか?
- smp_wmb()は、それに続く「公開」書き込みの前に、produce_item(item)へのすべての書き込みが完了することを保証すると思います。本当ですか?
- このコードを入手したページの解説は、ヘッドインデックスを更新した後、通常はsmp_wmb()が必要になることを示唆しているようですが、wake_up(consumer)はこれを行うため、必要ありません。本当?もしそうなら、なぜですか?
これが「消費者」です。
spin_lock(&consumer_lock);
unsigned long head = ACCESS_ONCE(buffer->head);
unsigned long tail = buffer->tail;
if (CIRC_CNT(head, tail, buffer->size) >= 1) {
/* read index before reading contents at that index */
smp_read_barrier_depends();
/* extract one item from the buffer */
struct item *item = buffer[tail];
consume_item(item);
smp_mb(); /* finish reading descriptor before incrementing tail */
buffer->tail = (tail + 1) & (buffer->size - 1);
}
spin_unlock(&consumer_lock);
「消費者」に固有の質問:
- smp_read_barrier_depends()は何をしますか?フォーラムのいくつかのコメントから、ここでsmp_rmb()を発行できたようですが、一部のアーキテクチャでは、これは不要(x86)で高すぎるため、オプションでこれを行うためにsmp_read_barrier_depends()が作成されました...なぜsmp_rmb()が必要なのか本当に理解できません!
- smp_mb()は、それ以前のすべての読み取りが、その後の書き込みの前に完了することを保証するためにありますか?