進程關(guān)系,父子是否生死相依

眾所周知,進程通常不是憑空獨立的出現(xiàn)的,在類Unix系統(tǒng)中,所有的其他進程都是從 進程0 fork 出來的,每個進程都會擁有多個子進程。那么,想要弄清楚父進程和子進程的關(guān)系,我們首先要了解 fork 究竟經(jīng)歷了什么過程。

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環(huán)境下則會看到:

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).

可以看到基本內(nèi)容大同小異,簡單進行翻譯一下:調(diào)用fork會創(chuàng)建一個當(dāng)前進程的精確副本進程,這個被創(chuàng)建出的副本進程被稱作子進程而調(diào)用fork的進程則稱為父進程。既然被稱作精確副本,看來子進程和父進程是相同的,其實也不盡然。

比如,子進程將會擁有一個自己的進程標識符也就是所謂的pid。同時,根據(jù)父進程的不同,子進程的父進程id也不一樣。

由于現(xiàn)代操作系統(tǒng)的寫時復(fù)制機制,即使我們知道每個進程都擁有自己獨立的地址空間,其實指向的物理內(nèi)存是和父進程相同的(代碼段,數(shù)據(jù)段,堆棧都指向父親的物理空間),只有子進程修改了其中的某個值時(通常會先調(diào)度運行子進程),才會給子進程分配新的物理內(nèi)存,并把根據(jù)情況把新的值或原來的值復(fù)制給子進程的內(nèi)存。

由此可見,父子進程其實有相當(dāng)?shù)莫毩⑿?,并不會相互影響?/p>

那么既然沒有相互影響,那么父子進程會不會有依賴關(guān)系了呢?比如,關(guān)閉了父進程,子進程還會存在嗎?

既然提出了這個問題,正如手術(shù)刀既能治病救人也能殺人滅口,想要了解殺死進程之后發(fā)生什么,首先要了解的是我們殺死進程的刀——kill。

kill

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

$man kill

只摘取linux環(huán)境下的內(nèi)容:

KILL(1)                             User Commands                             KILL(1)

NAME
       kill - send a signal to a process

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

kill聽起來是殺死什么東西的意思,但手冊上卻寫著它只是發(fā)送一個信號給進程。其實這個信號指的是Linux標準信號。

Linux標準信號

信號是進程間通信的形式,Linux支持64種標準信號。而其中32種是傳統(tǒng)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

其中可能與關(guān)閉進程有關(guān)的信號是INT、QUIT、TERMKILL。讓我們來一一分析一下。

  • SIGINT:其實我們平時在使用終端運行某個軟件的時候,如果這個軟件會持續(xù)運行而不再顯示shell提示符,那么我們通常關(guān)閉這個程序是使用^C(就是Ctrl+C),其實就是向當(dāng)前運行的進程發(fā)送了一個SIGINT。通知前臺進程組停止進程。也就是會中斷前臺運行的所有進程。
  • SIGQUIT:與SIGINT其實相似,通過終端鍵入^\來發(fā)送,相當(dāng)于錯誤信號,會讓進程產(chǎn)生core文件。
  • SIGTERM:當(dāng)我們不加任何參數(shù)直接調(diào)用kill時,則會發(fā)送這個信號給進程,這個關(guān)閉請求并非強制,它會被阻塞,通常會等待一個程序正常退出。
  • SIGKILL:這個就厲害了,這也是為什么關(guān)不掉一個程序,網(wǎng)上通常會教你kill -9,它發(fā)送一個強制的關(guān)閉信號,不可被忽略。但正是因為這樣,程序難以進行自我清理,而且會產(chǎn)生僵尸進程。

很顯然,由于前三者關(guān)閉進程的“人性化”導(dǎo)致出問題的情況及其有限,接下來我們將要對這個兇殘的kill -9做做文章。

在繼續(xù)討論之前,我們先來補充一點知識:

僵尸進程和孤兒進程

進程和現(xiàn)實與眾不同的是,進程的世界通常是“白發(fā)人送黑發(fā)人“,父進程在調(diào)用子進程之后,通常會在子進程結(jié)束之后進行一些后續(xù)處理。

但我們知道,父進程的運行和子進程的結(jié)束通常是不可能同時進行的,那父進程也不可能知道子進程是如何結(jié)束的,那么,父進程如何為子進程”收尸“呢。

原來每個進程在結(jié)束自己之前通常會調(diào)用exit()命令,資源即使早就全部釋放了,但進程號,運行時間,退出狀態(tài)卻會因此命令而保留,等到父進程調(diào)用了waitpid()時,才會釋放這些內(nèi)容。如果父進程不調(diào)用waitpid(),則子進程的信息永遠不會釋放,這就是所謂的僵尸進程。

除非,父進程在子進程exit之前就已經(jīng)關(guān)閉,子進程便不會變?yōu)榻┦M程,這是因為,每次一個進程結(jié)束時,系統(tǒng)都會自動掃描一下這個進程的子進程,如果這個進程有子進程,此時這些子進程被稱作孤兒進程,便會把這些進程轉(zhuǎn)交給init接管。這些子進程結(jié)束后,自然init作為”繼父“進程會以某種機制waitpid()(收尸)的。

讓我們先來構(gòu)造一個父子關(guān)系程序。

#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程序調(diào)用了fork()來創(chuàng)建了一個子程序。父子程序都用一個死循環(huán)來卡住.

運行效果如下:

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

使用^C可以看到,發(fā)送了SIGINT信號,關(guān)閉了父進程和子進程。這是由于SIGINT將會發(fā)送給所有依賴于當(dāng)前終端的進程,自然前臺所有的進程都關(guān)閉了。

重新運行,并使用$ps -ef觀看進程列表。

此時使用$pstree -p [父進程的pid]則會看到父子進程的關(guān)系。此時無論向父進程發(fā)送任何退出信號,都會讓子進程變?yōu)楣聝哼M程。

那么,怎樣才能讓我的子進程隨著父進程愉快地結(jié)束呢?

讓我們改寫一下代碼:

#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);
}

這時,殺死父進程,子進程會向自己發(fā)送一個SIGKILL信號,從而父子進程都被關(guān)閉了??梢姼缸舆M程之間的生命相依聯(lián)系,就是通過prctl來維系的。這也是程序員可以控制的關(guān)系。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 又來到了一個老生常談的問題,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個問題開始,來談?wù)劜?..
    tangsl閱讀 4,310評論 0 23
  • 本篇轉(zhuǎn)自:http://hongjiang.info/why-kill-2-cannot-stop-tomcat/...
    在路上的碼農(nóng)一枚閱讀 1,047評論 0 1
  • 1 進程介紹 1.1 進程和程序 所謂進程是由正文段(text)、用戶數(shù)據(jù)段(user segment)以及系統(tǒng)數(shù)...
    瘋狂小王子閱讀 1,343評論 0 7
  • 1.韓麗楓。 她是高中時期班里男生最喜歡的女生——長相甜美,唱歌好聽,愛畫畫,會彈吉他吹口琴拉手風(fēng)琴。。。 也是班...
    高小花0218閱讀 598評論 0 0
  • 時光會帶走一切,一切的一切。 所以,一個人該留下點什么呢? 還是不留什么,只為了現(xiàn)世快樂一些? 沒有答案!只是可以...
    夜語山林閱讀 311評論 1 1

友情鏈接更多精彩內(nèi)容