fork()的调用

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()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.

还有fork()的其他的代码及运行结果可以从下方下载

https://pan.baidu.com/s/1swYDzk7bQ5vUdw7r5RAVVg

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值