【问题标题】:Understanding Linux Kernel Circular Buffer了解 Linux 内核循环缓冲区
【发布时间】:2013-01-17 16:08:31
【问题描述】:

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

针对“消费者”的问题:

  1. smp_read_barrier_depends() 有什么作用?从论坛中的一些 cmets 看来,您似乎可以在此处发布 smp_rmb(),但在某些架构上这是不必要的(x86)且过于昂贵,因此创建 smp_read_barrier_depends() 来选择性地执行此操作......也就是说,我不太明白为什么 smp_rmb() 是必要的!
  2. smp_mb() 是否可以保证在它之前的所有读取在它之后的写入之前完成?

【问题讨论】:

    标签: linux concurrency circular-buffer memory-barriers


    【解决方案1】:

    对于制作人:

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

    消费者:

    1. smp_read_barrier_depends() 是一个数据依赖屏障,它是一种较弱的读取屏障形式(请参阅2)。这种情况下的效果是确保在将buffer->tail 用作buffer[tail] 中的数组索引之前先读取它。
    2. smp_mb() 这里是一个完整的内存屏障,确保所有读取和写入都在此时提交。

    其他参考资料:

    (注意:我不完全确定我对生产者 5 和消费者 1 的答案,但我相信它们是事实的合理近似值。我强烈建议阅读有关内存障碍的文档页面,因为它比我在这里写的任何东西都更全面。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-10
      • 1970-01-01
      • 2017-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-29
      • 2014-04-01
      相关资源
      最近更新 更多