6.6 指令和内存访问排序

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

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

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

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不要跨障碍重新排序loadstore指令。因为xv6在访问共享数据时使用了锁,xv6的acquirerelease中的障碍在几乎所有重要的情况下都会强制顺序执行。第9章讨论了一些例外。

copyright by duguosheng all right reserved,powered by Gitbook该文件修订时间: 2021-08-19 13:55:14

results matching ""

    No results matching ""