了解Linux内核循环缓冲区

dic*_*oce 9 linux concurrency circular-buffer memory-barriers

有一篇文章在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);
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 由于此代码显式处理内存排序和原子性,spin_lock()的重点是什么?
  2. 到目前为止,我的理解是ACCESS_ONCE停止编译器重新排序,是吗?
  3. produce_item(item)是否只发出与该项关联的所有写入操作?
  4. 我相信smp_wmb()保证produce_item(item)中的所有写入都在它之后的"发布"写入之前完成.真正?
  5. 我获得此代码的页面上的注释似乎暗示在更新头索引后通常需要smp_wmb(),但wake_up(消费者)执行此操作,因此没有必要.真的吗?如果是这样的话?

这是"消费者":

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);
Run Code Online (Sandbox Code Playgroud)

特定于"消费者"的问题:

  1. smp_read_barrier_depends()做什么?从论坛中的一些评论来看,你似乎可以在这里发布一个smp_rmb(),但是在某些架构上这是不必要的(x86)而且太贵了,所以创建了smp_read_barrier_depends()以便可选地执行此操作......那就是说,我真的不明白为什么smp_rmb()是必要的!
  2. smp_mb()是否保证在写入之前完成所有读取之后呢?

Has*_*kun 7

对于制片人:

  1. spin_lock()是为了防止两个生产者同时尝试修改队列.
  2. ACCESS_ONCE确实可以防止重新排序,它还可以防止编译器稍后重新加载该值.(有一篇关于ACCESS_ONCELWN 的文章进一步扩展了这一点)
  3. 正确.
  4. 也正确.
  5. 在唤醒消费者之前需要(隐含的)写屏障,否则消费者可能看不到更新的head值.

消费者:

  1. smp_read_barrier_depends()是一个数据依赖障碍,它是一种较弱的读取障碍形式(见2).在这种情况下的效果是确保buffer->tail在将其用作数组索引之前读取buffer[tail].
  2. smp_mb() 这是一个完整的内存屏障,确保在这一点上提交所有读写操作.

其他参考:

(注意:我不完全确定我在制作人中的5和消费者的答案,但我相信他们是事实的近似.我强烈建议阅读有关内存障碍的文档页面,因为它更多比我在这里写的任何内容都要全面.)