进程关系,父子是否生死相依

众所周知,进程通常不是凭空独立的出现的,在类Unix系统中,所有的其他进程都是从 进程0 fork 出来的,每个进程都会拥有多个子进程。那么,想要弄清楚父进程和子进程的关系,我们首先要了解 fork 究竟经历了什么过程。

fork

我们可以看一看fork的官方文档。

$man fork

Linux下将会看到:

FORK(2)                       Linux Programmer's Manual                       FORK(2)

NAME
       fork - create a child process

SYNOPSIS
       #include <unistd.h>

       pid_t fork(void);

DESCRIPTION
       fork()  creates  a  new  process  by duplicating the calling process.  The new
       process is referred to as the child process.  The calling process is  referred
       to as the parent process.

       The  child  process  and the parent process run in separate memory spaces.  At
       the time of fork() both memory spaces have the same content.   Memory  writes,
       file  mappings  (mmap(2)),  and unmappings (munmap(2)) performed by one of the
       processes do not affect the other.

The child process is an exact duplicate of the parent process except  for  the
       following points:

       *  The child has its own unique process ID, and this PID does not match the ID
          of any existing process group (setpgid(2)).

       *  The child's parent process ID is the same as the parent's process ID.

       *  The child does not inherit its  parent's  memory  locks  (mlock(2),  mlock‐
          all(2)).

       *  Process   resource   utilizations  (getrusage(2))  and  CPU  time  counters
          (times(2)) are reset to zero in the child.

       *  The child's set of pending signals is initially empty (sigpending(2)).

       *  The  child  does  not  inherit  semaphore  adjustments  from   its   parent
          (semop(2)).

       *  The  child does not inherit process-associated record locks from its parent
          (fcntl(2)).  (On the  other  hand,  it  does  inherit  fcntl(2)  open  file
          description locks and flock(2) locks from its parent.)

       *  The  child does not inherit timers from its parent (setitimer(2), alarm(2),
          timer_create(2)).

       *  The child does not inherit outstanding asynchronous I/O operations from its
          parent  (aio_read(3),  aio_write(3)),  nor does it inherit any asynchronous
          I/O contexts from its parent (see io_setup(2)).


而在Unix环境下则会看到:

FORK(2)                     BSD System Calls Manual                    FORK(2)

NAME
     fork -- create a new process

SYNOPSIS
     #include <unistd.h>

     pid_t
     fork(void);

DESCRIPTION
     fork() causes creation of a new process.  The new process (child process)
     is an exact copy of the calling process (parent process) except for the
     following:

           o   The child process has a unique process ID.

           o   The child process has a different parent process ID (i.e., the
               process ID of the parent process).

           o   The child process has its own copy of the parent's descriptors.
               These descriptors reference the same underlying objects, so
               that, for instance, file pointers in file objects are shared
               between the child and the parent, so that an lseek(2) on a
               descriptor in the child process can affect a subsequent read or
               write by the parent.  This descriptor copying is also used by
               the shell to establish standard input and output for newly cre-
               ated processes as well as to set up pipes.

           o   The child processes resource utilizations are set to 0; see
               setrlimit(2).

可以看到基本内容大同小异,简单进行翻译一下:调用fork会创建一个当前进程的精确副本进程,这个被创建出的副本进程被称作子进程而调用fork的进程则称为父进程。既然被称作精确副本,看来子进程和父进程是相同的,其实也不尽然。

比如,子进程将会拥有一个自己的进程标识符也就是所谓的pid。同时,根据父进程的不同,子进程的父进程id也不一样。

由于现代操作系统的写时复制机制,即使我们知道每个进程都拥有自己独立的地址空间,其实指向的物理内存是和父进程相同的(代码段,数据段,堆栈都指向父亲的物理空间),只有子进程修改了其中的某个值时(通常会先调度运行子进程),才会给子进程分配新的物理内存,并把根据情况把新的值或原来的值复制给子进程的内存。

由此可见,父子进程其实有相当的独立性,并不会相互影响。

那么既然没有相互影响,那么父子进程会不会有依赖关系了呢?比如,关闭了父进程,子进程还会存在吗?

既然提出了这个问题,正如手术刀既能治病救人也能杀人灭口,想要了解杀死进程之后发生什么,首先要了解的是我们杀死进程的刀——kill

kill

首先自然是先查看kill的手册:

$man kill

只摘取linux环境下的内容:

KILL(1)                             User Commands                             KILL(1)

NAME
       kill - send a signal to a process

SYNOPSIS
       kill [options] <pid> [...]

kill听起来是杀死什么东西的意思,但手册上却写着它只是发送一个信号给进程。其实这个信号指的是Linux标准信号。

Linux标准信号

信号是进程间通信的形式,Linux支持64种标准信号。而其中32种是传统Unix的信号。可以通过

$kill -l

来查看。

HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS

其中可能与关闭进程有关的信号是INTQUITTERMKILL。让我们来一一分析一下。

  • SIGINT:其实我们平时在使用终端运行某个软件的时候,如果这个软件会持续运行而不再显示shell提示符,那么我们通常关闭这个程序是使用^C(就是Ctrl+C),其实就是向当前运行的进程发送了一个SIGINT。通知前台进程组停止进程。也就是会中断前台运行的所有进程。
  • SIGQUIT:与SIGINT其实相似,通过终端键入^\来发送,相当于错误信号,会让进程产生core文件。
  • SIGTERM:当我们不加任何参数直接调用kill时,则会发送这个信号给进程,这个关闭请求并非强制,它会被阻塞,通常会等待一个程序正常退出。
  • SIGKILL:这个就厉害了,这也是为什么关不掉一个程序,网上通常会教你kill -9,它发送一个强制的关闭信号,不可被忽略。但正是因为这样,程序难以进行自我清理,而且会产生僵尸进程。

很显然,由于前三者关闭进程的“人性化”导致出问题的情况及其有限,接下来我们将要对这个凶残的kill -9做做文章。

在继续讨论之前,我们先来补充一点知识:

僵尸进程和孤儿进程

进程和现实与众不同的是,进程的世界通常是“白发人送黑发人“,父进程在调用子进程之后,通常会在子进程结束之后进行一些后续处理。

但我们知道,父进程的运行和子进程的结束通常是不可能同时进行的,那父进程也不可能知道子进程是如何结束的,那么,父进程如何为子进程”收尸“呢。

原来每个进程在结束自己之前通常会调用exit()命令,资源即使早就全部释放了,但进程号,运行时间,退出状态却会因此命令而保留,等到父进程调用了waitpid()时,才会释放这些内容。如果父进程不调用waitpid(),则子进程的信息永远不会释放,这就是所谓的僵尸进程

除非,父进程在子进程exit之前就已经关闭,子进程便不会变为僵尸进程,这是因为,每次一个进程结束时,系统都会自动扫描一下这个进程的子进程,如果这个进程有子进程,此时这些子进程被称作孤儿进程,便会把这些进程转交给init接管。这些子进程结束后,自然init作为”继父“进程会以某种机制waitpid()(收尸)的。

让我们先来构造一个父子关系程序。

#include <stdio.h>
#include <unistd.h>
int main (void) {
    pid_t pid;
    int count = 0;
    pid = fork ();
    if (pid < 0) printf("Error!\n");
    else if (pid == 0) {
        printf("I'm a child\n");
        while(1);
        exit (0);
    }
    else {
        printf("I'm the father, and my son's pid is %d \n",pid);
        while(1);
    }
    exit(0);
}

可见,这个C程序调用了fork()来创建了一个子程序。父子程序都用一个死循环来卡住.

运行效果如下:

I'm the father, and my son's pid is 40211 
I'm a child

使用^C可以看到,发送了SIGINT信号,关闭了父进程和子进程。这是由于SIGINT将会发送给所有依赖于当前终端的进程,自然前台所有的进程都关闭了。

重新运行,并使用$ps -ef观看进程列表。

此时使用$pstree -p [父进程的pid]则会看到父子进程的关系。此时无论向父进程发送任何退出信号,都会让子进程变为孤儿进程。

那么,怎样才能让我的子进程随着父进程愉快地结束呢?

让我们改写一下代码:

#include <signal.h>
#include <sys/prctl.h>
#include <stdio.h>
#include <unistd.h>
int main (void) {
    pid_t pid;
    int count = 0;
    pid = fork ();
    if (pid < 0) printf("Error!\n");
    else if (pid == 0) {
        printf("I'm a child\n");
        while(1) {
            prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
        }

        exit (0);
    }
    else {
        printf("I'm the father, and my son's pid is %d \n",pid);
        while(1) {
        }
    }
    exit(0);
}

这时,杀死父进程,子进程会向自己发送一个SIGKILL信号,从而父子进程都被关闭了。可见父子进程之间的生命相依联系,就是通过prctl来维系的。这也是程序员可以控制的关系。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,004评论 0 23
  • 本篇转自:http://hongjiang.info/why-kill-2-cannot-stop-tomcat/...
    在路上的码农一枚阅读 711评论 0 1
  • 1 进程介绍 1.1 进程和程序 所谓进程是由正文段(text)、用户数据段(user segment)以及系统数...
    疯狂小王子阅读 1,193评论 0 7
  • 1.韩丽枫。 她是高中时期班里男生最喜欢的女生——长相甜美,唱歌好听,爱画画,会弹吉他吹口琴拉手风琴。。。 也是班...
    高小花0218阅读 468评论 0 0
  • 时光会带走一切,一切的一切。 所以,一个人该留下点什么呢? 还是不留什么,只为了现世快乐一些? 没有答案!只是可以...
    夜语山林阅读 269评论 1 1