有一篇文章在: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 停止编译器重新排序,对吗?
- producer_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() 有什么作用?从论坛中的一些评论来看,您似乎可以在此处发布 smp_rmb(),但在某些架构上,这是不必要的(x86)且过于昂贵,因此创建了 smp_read_barrier_depends() 来选择性地执行此操作......也就是说,我真的不明白为什么 smp_rmb() 是必要的!
- smp_mb() 是否可以保证它之前的所有读取在它之后的写入之前完成?