了解Linux内核循环缓冲区

时间:2013-01-17 16:08:32

标签: 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);

问题:

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

    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);
    

    特定于“消费者”的问题:

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

1 个答案:

答案 0 :(得分:7)

对于制作人:

  1. 此处的spin_lock()是为了防止两个生产者同时尝试修改队列。
  2. ACCESS_ONCE确实阻止了重新排序,它还阻止编译器稍后重新加载该值。 (an article about ACCESS_ONCE on LWN进一步扩展了这一点)
  3. 正确。
  4. 也正确。
  5. 在唤醒消费者之前需要(隐含)写入障碍,否则消费者可能看不到更新的head值。
  6. 消费者:

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

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