有一篇文章在http://lwn.net/Articles/378262/上描述了Linux内核的循环缓冲区实现。 我有一些疑问:
这里是“制片人”:
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(&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);
针对“消费者”的问题:
对于生产者:
spin_lock()
是为了防止两个生产者试图同时修改队列。 ACCESS_ONCE
可以防止重新排序,也可以防止编译器稍后重新加载值。 ( 有关LWN上的ACCESS_ONCE
的一篇文章 ,进一步扩展了这一点) head
值。 消费者:
smp_read_barrier_depends()
是一个数据依赖障碍,它是一个读取障碍的较弱形式(见2 )。 在这种情况下的效果是确保在将buffer->tail
用作buffer[tail]
的数组索引之前将其读取。 smp_mb()
在这里是一个完整的内存屏障,确保所有的读写都是由这一点提交的。 其他参考:
(注:我并不完全确定我在制作人和消费者中的5个答案,但我相信他们是事实的一个公平的近似值,我强烈推荐阅读关于记忆障碍的文档页面,因为它更多比我能写的任何东西都要全面。)