gcc编译优化对vfork的影响

vfork

Posted by lwk on December 25, 2019

最近在看linux系统编程的书,书名

No.Starch.Press.The.Linux.Programming.Interface.A.Linux.and.UNIX.System.Programming.Handbook,推荐给对linux系统编程感兴趣的同学(这本书也是身边的同事推荐的)

看到进程创建章节中关于vfork的系统调用。 image 其中书中关于vfork的解释是:子进程修改了变量之后,会在父进程中体现出来。

Vfork示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int

main(int argc, char *argv[])

{
   int istack = 222;
   switch(vfork())
   {  
       case 0:
           sleep(3);

           write(STDOUT_FILENO, "Child executing\n", 16);

           istack *= 3;

           printf("child istack=%d\n", istack);

           _exit(EXIT_SUCCESS);

 

       default:

           write(STDOUT_FILENO, "Parent executing\n", 17);

           printf("parent istack=%d\n", istack);

           exit(EXIT_SUCCESS);

   }  

}

编译完成执行之后,意想不到的事情发生了: image 父进程打印出的istack竟然是222!!!

奇怪,为什么不是666呢?和书上说的不一致,难道是书上有错误?可能性比较小。

难道和gcc版本有关系?找了个版本比较低的机器重新编译执行,问题还在,说明和gcc没关系。

对于有经验的同学可能很快就猜出问题出现在哪里了。是的,gcc编译选项导致的问题!

Gcc编译时有很多编译选项,这里不做介绍。

Gcc编译加入-O2优化选项: image

Gcc编译不加任何优化选项:

image

为什么会出现这样的问题呢?

让我们看下编译后的汇编代码

不加任何优化选项,对应的汇编代码:

子进程对istack=666存放在%rbp,并且父进程也是通过%rbp打印出istack image

image

加入-O2后对应的汇编代码:

从优化后的汇编代码可以看到,子进程将istack=666存入%esi,但父进程最终将istack=222存入%esi,完全和子进程里的istack无关。这也是为什么加入了编译优化选项之后产生的结果(编译器会想方设法的帮你把程序优化到极致)

image

image

这里的根因是是栈变量被优化到寄存器中了,直接不使用内存了,而寄存器内容是不共享的。man vfork可以看到manual举了个例子正好对应这个:” The use of vfork() was tricky: for example,not modifying data in the parent process depended on knowing which variableswere held in a register.”