fork出的子进程和父进程 避免僵死进程

一、fork后的父子进程

由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id。将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程id。对子进程来说,之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的pid;也可以调用getppid()来获取父进程的id。(进程id 0总是由交换进程使用,所以一个子进程的进程id不可能为0 )。

fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置(两进程的程序计数器pc值相同,也就是说,子进程是从fork返回处开始执行的),但有一点不同,如果fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。
可以这样想象,2个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。这也是fork为什么叫fork的原因

至于那一个最先运行,可能与操作系统(调度算法)有关,而且这个问题在实际应用中并不重要,如果需要父子进程协同,可以通过原语的办法解决。

[cpp]  view plain copy
  1. include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <stdlib.h>  
  4. #include <errno.h>  
  5.   
  6. int main(void)  
  7. {  
  8.         pid_t pid=fork();  
  9.         if(pid==0)  
  10.         {  
  11.                 int j ;  
  12.                 for(j=0;j<10;j++)  
  13.                 {  
  14.                         printf("child: %d\n",j);  
  15.                         sleep(1);  
  16.                 }  
  17.         }  
  18.         else if (pid>0)  
  19.         {  
  20.                 int i;  
  21.                 for(i=0;i<10;i++)  
  22.                 {  
  23.                         printf("parent: %d\n",i);  
  24.                         sleep(1);  
  25.                 }  
  26.         }  
  27.         else  
  28.         {  
  29.                 fprintf(stderr,"can't fork ,error %d\n",errno);  
  30.                 exit(1);  
  31.         }  
  32.         printf("This is the end !");  
  33. }  

二、fork出的子进程和父进程的继承关系  

fork出来的子进程,基本上除了进程号之外父进程的所有东西都有一份拷贝,基本就意味着不是全部,下面我们要说的是子进程从父进程那里继承了什么东西,什么东西没有继承。还有一点需要注意,子进程得到的只是父进程的拷贝,而不是父进程资源的本身。

由子进程自父进程继承到:     
1.进程的资格(真实(real)/有效(effective)/已保存(saved)用户号(UIDs)和组号(GIDs))
2.环境(environment)
3.堆栈
4.内存
5.打开文件的描述符(注意对应的文件的位置由父子进程共享,这会引起含糊情况)
6.执行时关闭(close-on-exec) 标志 (译者注:close-on-exec标志可通过fnctl()对文件描述符设置,POSIX.1要求所有目录流都必须在exec函数调用时关闭。更详细说明,参见《UNIX环境高级编程》 W. R. Stevens, 1993, 尤晋元等译(以下简称《高级编程》), 3.13节和8.9节)
7.信号(signal)控制设定
8.nice值 (译者注:nice值由nice函数设定,该值表示进程的优先级,数值越小,优先级越高)
进程调度类别(scheduler class)(译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级,根据进程调度类别和nice值,进程调度程序可计算出每个进程的全局优先级(Global process prority),优先级高的进程优先执行)
8.进程组号
9.对话期ID(Session ID) (译者注:译文取自《高级编程》,指:进程所属的对话期(session)ID, 一个对话期包括一个或多个进程组, 更详细说明参见《高级编程》9.5节)
10.当前工作目录
11.根目录 (译者注:根目录不一定是“/”,它可由chroot函数改变)
12.文件方式创建屏蔽字(file mode creation mask (umask))(译者注:译文取自《高级编程》,指:创建新文件的缺省屏蔽字)
13.资源限制
14.控制终端

子进程所独有:

进程号
1.不同的父进程号(译者注:即子进程的父进程号与父进程的父进程号不同, 父进程号可由getppid函数得到)
2.自己的文件描述符和目录流的拷贝(译者注:目录流由opendir函数创建,因其为顺序读取,顾称“目录流”)
3.子进程不继承父进程的进程,正文(text), 数据和其它锁定内存(memory locks)(译者注:锁定内存指被锁定的虚拟内存页,锁定后,4.不允许内核将其在必要时换出(page out),详细说明参见《The GNU C Library Reference Manual》 2.2版, 1999, 3.4.2节)
5.在tms结构中的系统时间(译者注:tms结构可由times函数获得,它保存四个数据用于记录进程使用中央处理器 (CPU:Central Processing Unit)的时间,包括:用户时间,系统时间, 用户各子进程合计时间,系统各子进程合计时间)
6.资源使用(resource utilizations)设定为0
8.阻塞信号集初始化为空集(译者注:原文此处不明确,译文根据fork函数手册页稍做修改)
9.不继承由timer_create函数创建的计时器
10.不继承异步输入和输出

在fork()/execve()过程中,假如子进程完结时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用waitpid()期待子进程完结,又没有显式疏忽该信号,则子进程成为僵尸进程,无法正常完结,此时即便是root身份kill-9也不能杀死僵尸进程。弥补方法是杀死僵尸进程的父进程(僵尸进程的父进程必定存在),僵尸进程成为"孤儿进程",过继给1号进程init,init始终会担任清算僵尸进程。

  僵尸进程是指的父进程已经退出,而该进程dead之后没有进程接收,就成为僵尸进程.(zombie)进程

  怎么发生僵尸进程的:

  一个进程在调用exit命令完结自己的性命的时分,其实它并没有真正的被烧毁,而是留下一个称为僵尸进程(Zombie)的数据结构(体系调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完整烧毁)。在Linux进程的状况中,僵尸进程

  是十分特别的一种,它已经废弃了简直一切内存空间,没有任何可履行代码,也不能被调度,仅仅在进程列表中保存一个地位,记录该进程的退

  出状况等信息供其余进程搜集,除此之外,僵尸进程不再占有任何内存空间。它须要它的父进程来为它收尸,假如他的父进程没安装SIGCHLD信

  号处理函数调用wait或waitpid()期待子进程完结,又没有显式疏忽该信号,那么它就始终维持僵尸状况,假如这时父进程完结了,那么init进程主动

  会接手这个子进程,为它收尸,它还是能被清除的。然而假如假如父进程是一个循环,不会完结,那么子进程就会始终维持僵尸状况,这就是为什么体系中有时会有很多的僵尸进程。

  Linux体系对运行的进程数量有限制,假如发生过多的僵尸进程占用了可用的进程号,将会招致新的进程无法生成。这就是僵尸进程对体系的最大伤害。

  僵尸进程实例:

  /*-----zombie1.c-----*/

  #include "sys/types.h"

  #include "sys/wait.h"

  #include "stdio.h"

  #include "unistd.h"

  int main(int argc, char* argv[])

  {

  while(1)

  {

  pid_t chi = fork();

  if(chi == 0)

  {

  execl("/bin/bash","bash","-c","ls",NULL);

  }

  sleep(2);

  }

  会不停地发生僵死进程ls;

  /*-----zombie2.c-----*/

  #include <stdio.h>

  #include<sys/types.h>

  main()

  {

  if(!fork())

  {

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

  exit(0);

  }

  /*wait();*/

  /*waitpid(-1,NULL,0);*/

  sleep(60);

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

  exit(0);

  }

  60s内会始终发生僵尸进程,晓得父进程exit(0);

  假如在调用wait/waitpid来为子进程收尸,就不会发生僵尸进程了。

  PS:运行例子,先gcc zombie1.c -o zombie编译,而后运行zombie;

  而后可以可用ps -ef来检查能否发生了僵尸进程。

  怎么检查僵尸进程:

  应用命令ps,可以看到有标记为Z的进程就是僵尸进程。

  怎么来清除僵尸进程:

  1.改写父进程,在子进程死后要为它收尸。具体做法是接收SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,履行 waitpid()函数为子进程收尸。这是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD音讯,只管对的默许处理是疏忽,假如想响应这个音讯,可以设置一个处理函数。

  2.把父进程杀掉。父进程死后,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会担任清算僵尸进程.它发生的一切僵尸进程也跟着消逝。

  在Linux中可以用

  ps auwx

  发现僵尸进程

  a all w/ tty, including other users 一切窗口和终端,包含其余用户的进程

  u user-oriented 面向用户(用户友爱)

  -w,w wide output 宽格式输入

  x processes w/o controlling ttys

  在僵尸进程后面 会标注

  ps axf

  看进程树,以树形方法现实进程列表

  ps axm

  会把线程列出来,在linux下进程和线程是对立的,是轻量级进程的两种方法。

  ps axu

  显示进程的具体状况

  ===========================================

  killall

  kill -15

  kill -9

  个别都不能杀掉 defunct进程

  用了kill -15,kill -9以后 之后反而会多出更多的僵尸进程

  kill -kill pid

  fuser -k pid

  可以斟酌杀死他的parent process,

  kill -9 他的parent process

  =========================================== 一个已经终止,然而其父进程尚未对其进行善后处理(获取终止子进程的有关信息、开释它仍占用的资源)的进程被称为僵死进程(Zombie Process)。

  避免zombie的方法:

  1)在SVR4中,假如调用signal或sigset将SIGCHLD的配置设置为疏忽,则不会发生僵死子进程。另外,运用SVR4版的 sigaction,则可设置SA_NOCLDWAIT标记以避免子进程僵死。

  Linux中也可运用这个,在一个程序的开始调用这个函数

  signal(SIGCHLD,SIG_IGN);

  2)调用fork两次。程序8 - 5 完成了这一点。

  3)用waitpid期待子进程返回.

  ===========================================

  zombie进程是僵死进程。避免它的方法,一是用wait,waitpid之类的函数取得

  进程的终止状况,以开释资源。另一个是fork两次

  ===========================================

  defunct进程只是在process table里还有一个记录,其余的资源没有占用,除非你的体系的process个数的限制已经快超越了,zombie进程不会有更多的害处。

  可以惟一的方法就是reboot体系可以清除zombie进程。

  ===========================================

  任何程序都有僵尸状况,它占用一点内存资源(也就是进程表里还有一个记录),仅仅是表象而已不用畏惧。假如程序有问题有时机遇见,处理少量量僵尸简单有效的方法是重起。kill是无任何后果的

  fork与zombie/defunct"

  在Unix下的一些进程的运作方法。当一个进程死亡时,它并不是完整的消逝了。进程终止,它不再运行,然而还有一些残留的小货色期待父进程发出。这些残留的货色包含子进程的返回值和其余的一些货色。当父进程 fork()一个子进程后,它必需用 wait() 或许 waitpid() 期待子进程退出。正是这个 wait() 举措来让子进程的残留物消逝。

  自然的,在上述规矩之外有个例外:父进程可以疏忽 SIGCLD 软中止而不用要 wait()。可以这样做到(在支撑它的体系上,比方Linux):

  main()

  {

  signal(SIGCLD, SIG_IGN); /* now I don't have to wait()! */

  .

  .

  fork();

  fork();

  fork(); /* Rabbits, rabbits, rabbits! */

  }

  如今,子进程死亡时父进程没有 wait(),通罕用 ps 可以看到它被显示为“”。它将永远维持这样 直到 父进程 wait(),或许按以下方法处理。

  这里是你必需晓得的另一个规矩:当父进程在它wait()子进程之前死亡了(假如它没有疏忽 SIGCLD),子进程将把 init(pid1)进程作为它的父进程。假如子进程工作得很好并可以掌握,这并不是问题。但假如子进程已经是defunct,咱们就有了一点小费事。看,原先的父进程不可以再 wait(),因为它已经灭亡了。这样,init 怎么晓得 wait() 这些zombie 进程。

  答案:不可预感的。在一些体系上,init周期性的损坏掉它一切的defunct进程。在另外一些体系中,它干脆拒绝成为任何defunct进程的父进程,而是立刻消灭它们。假如你运用上述体系的一种,可以写一个简单的循环,用属于init的defunct进程填满进程表。这大约不会令你的体系管理员很愉快吧?

  你的义务:肯定你的父进程不要疏忽 SIGCLD,也不要 wait() 它 fork() 的一切进程。不过,你也未必 要总是这样做(比方,你要起一个 daemon 或是别的什么货色),然而你必需警惕编程,假如你是一个 fork()的老手。另外,也不要在心理上有任何约束。

  总结:

  子进程成为 defunct 直到父进程 wait(),除非父进程疏忽了 SIGCLD 。

  更进一步,父进程没有 wait() 就灭亡(仍假如父进程没有疏忽 SIGCLD )的子进程(运动的或许 defunct)成为 init 的子进程,init 用重手段处理它们。



  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值