# 6.6 指令和内存访问排序

人们很自然地会想到程序是按照源代码语句出现的顺序执行的。然而，许多编译器和中央处理器为了获得更高的性能而不按顺序执行代码。如果一条指令需要许多周期才能完成，中央处理器可能会提前发出指令，这样它就可以与其他指令重叠，避免中央处理器停顿。例如，中央处理器可能会注意到在顺序指令序列A和B中彼此不存在依赖。CPU也许首先启动指令B，或者是因为它的输入先于A的输入准备就绪，或者是为了重叠执行A和B。编译器可以执行类似的重新排序，方法是在源代码中一条语句的指令发出之前，先发出另一条语句的指令。

编译器和CPU在重新排序时需要遵循一定规则，以确保它们不会改变正确编写的串行代码的结果。然而，规则确实允许重新排序后改变并发代码的结果，并且很容易导致多处理器上的不正确行为。CPU的排序规则称为内存模型（memory model）。

例如，在`push`的代码中，如果编译器或CPU将对应于第4行的存储指令移动到第6行`release`后的某个地方，那将是一场灾难：

```c
l = malloc(sizeof *l);
l->data = data;
acquire(&listlock);
l->next = list;
list = l;
release(&listlock);
```

如果发生这样的重新排序，将会有一个窗口期，另一个CPU可以获取锁并查看更新后的`list`，但却看到一个未初始化的`list->next`。

为了告诉硬件和编译器不要执行这样的重新排序，xv6在`acquire`(***kernel/spinlock.c***:22) 和`release`(***kernel/spinlock.c***:47)中都使用了`__sync_synchronize()`。`__sync_synchronize()`是一个内存障碍：它告诉编译器和CPU不要跨障碍重新排序`load`或`store`指令。因为xv6在访问共享数据时使用了锁，xv6的`acquire`和`release`中的障碍在几乎所有重要的情况下都会强制顺序执行。第9章讨论了一些例外。