最近在看linux系统编程的书,书名
No.Starch.Press.The.Linux.Programming.Interface.A.Linux.and.UNIX.System.Programming.Handbook,推荐给对linux系统编程感兴趣的同学(这本书也是身边的同事推荐的)
看到进程创建章节中关于vfork的系统调用。 其中书中关于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);
}
}
编译完成执行之后,意想不到的事情发生了: 父进程打印出的istack竟然是222!!!
奇怪,为什么不是666呢?和书上说的不一致,难道是书上有错误?可能性比较小。
难道和gcc版本有关系?找了个版本比较低的机器重新编译执行,问题还在,说明和gcc没关系。
对于有经验的同学可能很快就猜出问题出现在哪里了。是的,gcc编译选项导致的问题!
Gcc编译时有很多编译选项,这里不做介绍。
Gcc编译加入-O2优化选项:
Gcc编译不加任何优化选项:
为什么会出现这样的问题呢?
让我们看下编译后的汇编代码
不加任何优化选项,对应的汇编代码:
子进程对istack=666存放在%rbp,并且父进程也是通过%rbp打印出istack
加入-O2后对应的汇编代码:
从优化后的汇编代码可以看到,子进程将istack=666存入%esi,但父进程最终将istack=222存入%esi,完全和子进程里的istack无关。这也是为什么加入了编译优化选项之后产生的结果(编译器会想方设法的帮你把程序优化到极致)
这里的根因是是栈变量被优化到寄存器中了,直接不使用内存了,而寄存器内容是不共享的。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.”