【发布时间】: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);
问题:
- 既然这段代码明确地处理内存排序和原子性,那么 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() 有什么作用?从论坛中的一些 cmets 看来,您似乎可以在此处发布 smp_rmb(),但在某些架构上这是不必要的(x86)且过于昂贵,因此创建 smp_read_barrier_depends() 来选择性地执行此操作......也就是说,我不太明白为什么 smp_rmb() 是必要的!
- smp_mb() 是否可以保证在它之前的所有读取在它之后的写入之前完成?
【问题讨论】:
标签: linux concurrency circular-buffer memory-barriers