转自:http://chhaj5236.blog.163.com/blog/static/1128810812013327102727881/
参考资料[1]对vfork进行了较为详细的描述:vfork()函数和fork()一样会创建一个新进程,所不同的是vfork()创建的子进程与父进程共享地址空间,且父进程会被阻塞,直到子进程调用exec()家族的某个函数,或调用_exit()。由于父子进程共享相同的地址空间,(准确地说是子进程)一定不要从调用vfork()的函数中返回,否则会破坏父进程的堆栈。同时,在执行exec()和_exit()之前,也最好不要调用任何对父进程状态有影响的操作,如改变父进程某个变量的值,或使用exit()退出子进程(exit()退出时会关闭I/O缓存,意味着父进程也会失去I/O缓存)等。
本文的主要目的就是理清参考文献[2]中提到的例子,即如果子进程从调用vfork()的函数中返回,会发生什么?为了更加清晰地说明这个问题,将文中提及的代码修改为如下所示:
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int createproc();
int main(){
pid_t pid;
pid = createproc();
printf("pid: %d, ret_pid: %d\n", getpid(), pid);
exit(0);
}
int createproc(){
pid_t pid;
pid = vfork();
printf("after vfork - pid: %d, ret_pid: %d\n", getpid(), pid);
if(!pid){
printf("pid: %d, parent: %d\n", getpid(), getppid());
//execl("/bin/ls", "ls", "-l", NULL); //正常情况下,应该调用exec()家族的一个函数,载入新的程序
return pid;
}
else return -1;
}
after vfork - pid: 29327, ret_pid: 0
pid: 29327, parent: 29326
pid: 29327, ret_pid: 0
after vfork - pid: 29326, ret_pid: 29327
after vfork - pid: 29328, ret_pid: 0
pid: 29328, parent: 29326
Segmentation fault
80484d5: e8 2d 00 00 00 call 8048507 <_Z10createprocv>
80484da: 89 44 24 1c mov %eax,0x1c(%esp)
经过上面两步操作后,堆栈的状态大致如①所示(其实vfork()的返回地址保存在ecx寄存器中,后面再详细说明,暂时认为其存放在堆栈中易于理解)。由于此时父进程29326阻塞,操作系统将为父进程保存包括esp等寄存器在内的进程上下文。而子进程29327从vfork()返回,esp将函数返回地址0x8048513弹出到指令寄存器,并执行相应的指令,也就是将vfork()的返回值0赋给pid,之后输出第一句“after vfork - pid: 29327, ret_pid: 0”。由于是子进程(pid==0),所以还会输出第二句“pid: 29327, parent: 29326”。此时子进程执行return pid,这意味着之前被createproc压入堆栈的数据会在return pid执行之前弹出以恢复现场,此时堆栈的状态如②所示。从图中可以看出,子进程29327执行return后的返回地址为当前esp所指内容,意味着将弹出并执行0x80484da所对应的指令,即main()函数中将createproc()的返回值赋给pid。我们发现由于堆栈共享,子进程由createproc()创建,但最终返回到main()函数。指令接着往下执行,所以输出的第三句依然属于子进程29327:“pid: 29327, ret_pid: 0”,注意:在exit()之前的函数调用将改变堆栈中的数据,如压入参数等操作,如图③所示。最后子进程29327执行exit(0)退出,从反汇编代码<main>中最后两行可以看出,在前面将0x80484da弹出堆栈后,esp所指栈顶被赋值为0以传递exit参数,之后调用再调用exit()函数,正如之前所说,call exit的下一条指令地址将被压入堆栈,但这条语句已经是<main>的最后一条指令,它的下一条指令为<_Z10createprocv>的第一条指令,所以将返回地址0x8048507压入堆栈,如图④所示。804850e: e8 ad fe ff ff call 80483c0 <vfork@plt>
8048513: 89 45 f4 mov %eax,-0xc(%ebp)
80484fb: c7 04 24 00 00 00 00 movl $0x0,(%esp)8048502: e8 99 fe ff ff call 80483a0 <exit@plt>
08048507 < _Z10createprocv >:8048507: 55 push %ebp
000983f0 <__vfork>:
983f0: 59 pop %ecx
983f1: 65 8b 15 6c 00 00 00 mov %gs:0x6c,%edx
983f8: 89 d0 mov %edx,%eax
983fa: f7 d8 neg %eax
......
9840e: cd 80 int $0x80
98410: 51 push %ecx
......
==============================反汇编代码================================