fork()的调用
fork()会创建一个子进程,子进程复制父进程的虚拟空间,但父进程与子进程是两个相互独立的进程。也就是说子进程的虚拟空间是独立的。linux会使用写时复制技术,即使用时再复制:每个进程的虚拟空间中都有相应的代码段,数据段,堆,栈等,fork()之后两个进程使用同一个物理空间,而虚拟空间不同,当父子进程中有更改相应段的行为时,再为子进程相应段分配自己的物理空间
fork()是一个一次调用,两次返回的函数
来看一个最简单的例子:
void fork0()
{
if (fork() == 0) {
printf("Hello from child\n");
}
else {
printf("Hello from parent\n");
}
}
运行结果:
结果确实是一次调用,两次返回。但是父进程与子进程会不会相互影响呢(相互独立)?
void fork1()
{
int x=1;
pid_t pid =fork();
if(pid==0){
printf("Child has x=%d\n",++x);
}
else{
printf("parent has x=%d\n",--x);
}
printf("Bye from process %d with x=%d\n", getpid(), x)
}
运行结果:
父进程的x=0 (1->0)
子进程的x=2(1->2)
由此可见子进程的数据段与父进程的代码段是相互独立的
下面是fork1()的汇编代码,通过汇编我们更好的理解一下fork()的使用
00000000004009ec <fork1>:
4009ec: 55 push %rbp
4009ed: 48 89 e5 mov %rsp,%rbp
4009f0: 48 83 ec 10 sub $0x10,%rsp //得到栈空间
4009f4: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp) //x=1
4009fb: e8 b0 fe ff ff callq 4008b0 <fork@plt> //调用fork()
400a00: 89 45 fc mov %eax,-0x4(%rbp) //pid=fork()的返回值
400a03: 83 7d fc 00 cmpl $0x0,-0x4(%rbp) //比较pid==0
400a07: 75 1a jne 400a23 <fork1+0x37>//pid!=0,跳转到else
400a09: 83 45 f8 01 addl $0x1,-0x8(%rbp) //x++
400a0d: 8b 45 f8 mov -0x8(%rbp),%eax
400a10: 89 c6 mov %eax,%esi
400a12: bf c3 15 40 00 mov $0x4015c3,%edi
400a17: b8 00 00 00 00 mov $0x0,%eax
400a1c: e8 bf fd ff ff callq 4007e0 <printf@plt>//printf("Child has x=%d\n",++x);
400a21: eb 18 jmp 400a3b <fork1+0x4f>//跳转到400a3b
400a23: 83 6d f8 01 subl $0x1,-0x8(%rbp) //else:x--
400a27: 8b 45 f8 mov -0x8(%rbp),%eax
400a2a: 89 c6 mov %eax,%esi
400a2c: bf d5 15 40 00 mov $0x4015d5,%edi
400a31: b8 00 00 00 00 mov $0x0,%eax //返回值为0
400a36: e8 a5 fd ff ff callq 4007e0 <printf@plt>调用printf("parent has x=%d\n",--x)
400a3b: e8 80 fd ff ff callq 4007c0 <getpid@plt>//调用getpid()
400a40: 89 c1 mov %eax,%ecx
400a42: 8b 45 f8 mov -0x8(%rbp),%eax
400a45: 89 c2 mov %eax,%edx
400a47: 89 ce mov %ecx,%esi
400a49: bf e8 15 40 00 mov $0x4015e8,%edi
400a4e: b8 00 00 00 00 mov $0x0,%eax
400a53: e8 88 fd ff ff callq 4007e0 <printf@plt>//printf("Bye from process %d with x=%d\n", getpid(), x)
400a58: 90 nop
400a59: c9 leaveq
400a5a: c3 retq //返回
以上都是一个fork()调用,如果出现多个fork()调用呢
void fork3()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("L2\n");
fork();
printf("Bye\n");
}
运行结果:
这些结果是如何得到的,我们可以画进程图:
这里的一个节点代表调用一次fork()
再看两个有趣的小例子:
例1:
void fork10()
{
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
exit(100+i); /* Child */
}
for (i = 0; i < N; i++) { /* Parent */
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
简单分析一下第一个for循环
分析其生成了五个子进程
运行结果:
第一次运行
如果还不太明白可以看一下它的汇编:
0000000000400c83 <fork10>:
400c83: 55 push %rbp
400c84: 48 89 e5 mov %rsp,%rbp
400c87: 48 83 ec 40 sub $0x40,%rsp
400c8b: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
400c92: 00 00
400c94: 48 89 45 f8 mov %rax,-0x8(%rbp)
400c98: 31 c0 xor %eax,%eax
400c9a: c7 45 d8 00 00 00 00 movl $0x0,-0x28(%rbp)
400ca1: eb 2e jmp 400cd1 <fork10+0x4e>
400ca3: e8 08 fc ff ff callq 4008b0 <fork@plt>
400ca8: 89 c2 mov %eax,%edx
400caa: 8b 45 d8 mov -0x28(%rbp),%eax
400cad: 48 98 cltq
400caf: 89 54 85 e0 mov %edx,-0x20(%rbp,%rax,4)
400cb3: 8b 45 d8 mov -0x28(%rbp),%eax
400cb6: 48 98 cltq
400cb8: 8b 44 85 e0 mov -0x20(%rbp,%rax,4),%eax
400cbc: 85 c0 test %eax,%eax
400cbe: 75 0d jne 400ccd <fork10+0x4a>
400cc0: 8b 45 d8 mov -0x28(%rbp),%eax
400cc3: 83 c0 64 add $0x64,%eax
400cc6: 89 c7 mov %eax,%edi
400cc8: e8 b3 fb ff ff callq 400880 <exit@plt>
400ccd: 83 45 d8 01 addl $0x1,-0x28(%rbp)
400cd1: 83 7d d8 04 cmpl $0x4,-0x28(%rbp)
400cd5: 7e cc jle 400ca3 <fork10+0x20>
400cd7: c7 45 d8 00 00 00 00 movl $0x0,-0x28(%rbp)
400cde: eb 60 jmp 400d40 <fork10+0xbd>
400ce0: 48 8d 45 d4 lea -0x2c(%rbp),%rax
400ce4: 48 89 c7 mov %rax,%rdi
400ce7: e8 b4 fb ff ff callq 4008a0 <wait@plt>
400cec: 89 45 dc mov %eax,-0x24(%rbp)
400cef: 8b 45 d4 mov -0x2c(%rbp),%eax
400cf2: 89 45 c0 mov %eax,-0x40(%rbp)
400cf5: 8b 45 c0 mov -0x40(%rbp),%eax
400cf8: 83 e0 7f and $0x7f,%eax
400cfb: 85 c0 test %eax,%eax
400cfd: 75 29 jne 400d28 <fork10+0xa5>
400cff: 8b 45 d4 mov -0x2c(%rbp),%eax
400d02: 89 45 d0 mov %eax,-0x30(%rbp)
400d05: 8b 45 d0 mov -0x30(%rbp),%eax
400d08: 25 00 ff 00 00 and $0xff00,%eax
400d0d: c1 f8 08 sar $0x8,%eax
400d10: 89 c2 mov %eax,%edx
400d12: 8b 45 dc mov -0x24(%rbp),%eax
400d15: 89 c6 mov %eax,%esi
400d17: bf e0 16 40 00 mov $0x4016e0,%edi
400d1c: b8 00 00 00 00 mov $0x0,%eax
400d21: e8 ba fa ff ff callq 4007e0 <printf@plt>
400d26: eb 14 jmp 400d3c <fork10+0xb9>
400d28: 8b 45 dc mov -0x24(%rbp),%eax
400d2b: 89 c6 mov %eax,%esi
400d2d: bf 10 17 40 00 mov $0x401710,%edi
400d32: b8 00 00 00 00 mov $0x0,%eax
400d37: e8 a4 fa ff ff callq 4007e0 <printf@plt>
400d3c: 83 45 d8 01 addl $0x1,-0x28(%rbp)
400d40: 83 7d d8 04 cmpl $0x4,-0x28(%rbp)
400d44: 7e 9a jle 400ce0 <fork10+0x5d>
400d46: 90 nop
400d47: 48 8b 45 f8 mov -0x8(%rbp),%rax
400d4b: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
400d52: 00 00
400d54: 74 05 je 400d5b <fork10+0xd8>
400d56: e8 75 fa ff ff callq 4007d0 <__stack_chk_fail@plt>
400d5b: c9 leaveq
400d5c: c3 retq
第二次运行
第三次运行
三次运行结果都有些许差别,这说明每个子进程是相互独立的,其结束时间不一定与其生成顺序有关,同样回收时也就与生成顺序没有一定关系
对比例2:
void fork11()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
exit(100+i); /* Child */
for (i = N-1; i >= 0; i--) {
pid_t wpid = waitpid(pid[i], &child_status, 0);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
运行结果:
这个的输出恰与子进程的生成顺序有关,主要差别在于wait()和waitpid()这两个函数的使用
具体可以看一下我的另一篇:wait和waitpid的使用与区别
wait和waitpid一般和fork配套使用,用来阻塞父进程,直到子进程结束:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.