编译乱序和执行乱序
理解Linux内核的锁机制, 还需要理解编译器和处理器的特点。 比如下面一段代码, 写端申请一个新的struct foo
结构体并初始化其中的a、 b、 c, 之后把结构体地址赋值给全局gp指针:
struct foo {
int a;
int b;
int c;
};
struct foo *gp = NULL;
/* . . . */
p = kmalloc(sizeof(*p), GFP_KERNEL);
p->a = 1;
p->b = 2;
p->c = 3;
gp = p;
而读端如果简单做如下处理, 则程序的运行可能是不符合预期的:
p = gp;
if (p != NULL) {
do_something_with(p->a, p->b, p->c);
}
有两种可能的原因会造成程序出错, 一种可能性是编译乱序, 另外一种可能性是执行乱序。
关于编译方面, C语言顺序的“p->a=1; p->b=2; p->c=3; gp=p; ”的编译结果的指令顺序可能是gp的赋值指令发
生在a、 b、 c的赋值之前。 现代的高性能编译器在目标码优化上都具备对指令进行乱序优化的能力。 编译器可以
对访存的指令进行乱序, 减少逻辑上不必要的访存, 以及尽量提高Cache命中率和CPU的Load/Store单元的工作
效率。 因此在打开编译器优化以后, 看到生成的汇编码并没有严格按照代码的逻辑顺序, 这是正常的。
解决编译乱序问题, 需要通过barrier() 编译屏障进行。 我们可以在代码中设置barrier() 屏障, 这个屏障可以
阻挡编译器的优化。 对于编译器来说, 设置编译屏障可以保证屏障前的语句和屏障后的语句不乱“串门”。
比如, 下面的一段代码在e=d[4095]与b=a、 c=a之间没有编译屏障:
int main(int argc, char *argv[])
{
int a = 0, b
理解Linux内核的锁机制, 还需要理解编译器和处理器的特点。 比如下面一段代码, 写端申请一个新的struct foo
结构体并初始化其中的a、 b、 c, 之后把结构体地址赋值给全局gp指针:
struct foo {
int a;
int b;
int c;
};
struct foo *gp = NULL;
/* . . . */
p = kmalloc(sizeof(*p), GFP_KERNEL);
p->a = 1;
p->b = 2;
p->c = 3;
gp = p;
而读端如果简单做如下处理, 则程序的运行可能是不符合预期的:
p = gp;
if (p != NULL) {
do_something_with(p->a, p->b, p->c);
}
有两种可能的原因会造成程序出错, 一种可能性是编译乱序, 另外一种可能性是执行乱序。
关于编译方面, C语言顺序的“p->a=1; p->b=2; p->c=3; gp=p; ”的编译结果的指令顺序可能是gp的赋值指令发
生在a、 b、 c的赋值之前。 现代的高性能编译器在目标码优化上都具备对指令进行乱序优化的能力。 编译器可以
对访存的指令进行乱序, 减少逻辑上不必要的访存, 以及尽量提高Cache命中率和CPU的Load/Store单元的工作
效率。 因此在打开编译器优化以后, 看到生成的汇编码并没有严格按照代码的逻辑顺序, 这是正常的。
解决编译乱序问题, 需要通过barrier() 编译屏障进行。 我们可以在代码中设置barrier() 屏障, 这个屏障可以
阻挡编译器的优化。 对于编译器来说, 设置编译屏障可以保证屏障前的语句和屏障后的语句不乱“串门”。
比如, 下面的一段代码在e=d[4095]与b=a、 c=a之间没有编译屏障:
int main(int argc, char *argv[])
{
int a = 0, b