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