進(jìn)程與子進(jìn)程

討論幾種父、子進(jìn)程退出時相互產(chǎn)生的影響,同時也整理一下進(jìn)程與子進(jìn)程之間的關(guān)系。

孤兒進(jìn)程

(沒爹)
孤兒進(jìn)程,顧名思義,子進(jìn)程還在世的時候父進(jìn)程卻結(jié)束了。那么孤兒進(jìn)程沒了父進(jìn)程,是不是就被孤立了呢?不會的,我們還需要了解到1號進(jìn)程——init進(jìn)程,在初始化unix系統(tǒng)的時候,會創(chuàng)建一個init進(jìn)程。然后由init進(jìn)程創(chuàng)建終端,而終端進(jìn)程隨著用戶的接入,會啟動更多的進(jìn)程,以此類推。在這整個系統(tǒng)中,所有的進(jìn)程都屬于以init為根的一棵樹。當(dāng)某個父進(jìn)程終止,子進(jìn)程就會被init進(jìn)程收養(yǎng)。在這些孤兒進(jìn)程結(jié)束時,init進(jìn)程會回收他們的退出信息,保證他們不一直成為僵尸進(jìn)程。

以下是如何創(chuàng)建孤兒進(jìn)程的例子

def create_orphan():
    """  
    :return:
    """
    cur_pid = os.getpid()
    pgid = os.getpgrp()
    print('parent group id', pgid)
    print('-----fork before-----')
    # 在父進(jìn)程的堆棧中,c_id 為父進(jìn)程的編號,在子進(jìn)程的堆棧中,值為0。
    # fork將進(jìn)程信息寫入進(jìn)程信息表,寫時復(fù)制父進(jìn)程的各種數(shù)據(jù),同時設(shè)置好新進(jìn)程的各種數(shù)據(jù)
    # 其中子進(jìn)程的執(zhí)行內(nèi)容設(shè)置為fork的后一句,并將進(jìn)程推送到就緒態(tài)的隊列中,等待調(diào)度器調(diào)度執(zhí)行。
    c_id = os.fork()

    print('-----fork after-----')

    if c_id == 0: # 子進(jìn)程
        ppid = os.getppid()
        print(os.getpid(), 'kill', ppid)
        os.kill(ppid, 9)
        os.system('ps aux|grep {}'.format(ppid))
        print(os.getpid(), '[killed p] ', ppid)  # 殺死父進(jìn)程以后被init進(jìn)程接管。
        print('[killed p] now ppid is ', os.getppid())
        now_ppid = os.getpgrp()
        print('child group id {}'.format(now_ppid))
        os.killpg(now_ppid, 9)
        print(os.getpid(), '[kill group]', now_ppid)  # 整個組死掉了
        print('end...')
    else: # 父進(jìn)程
        print('i am ', os.getpid())

流程輸出

        parent group id 45381
        -----fork before-----
        -----fork after-----
        i am  45381
        -----fork after-----
        45382 kill 45381
        [1]    45381 killed     python process.py
        (dl) (dl) async ? ? echoocking       45387   0.0  0.0  4268036    808 s003  S     1:20PM   0:00.00 grep 45381
        echoocking       45383   0.0  0.0  4268616   1112 s003  S     1:20PM   0:00.00 sh -c ps aux|grep 45381
        45382 [killed p]  45381
        [killed p] now ppid is  1
        child group id 45381

流程解釋

    獲取group id
    打印fork前的提示語
    執(zhí)行fork
    由于當(dāng)前是父進(jìn)程在執(zhí)行,所以進(jìn)入c_id != 0 的流程。執(zhí)行完后父進(jìn)程等待子進(jìn)程,父進(jìn)程被掛起,
    子進(jìn)程被調(diào)度執(zhí)行,子進(jìn)程執(zhí)行fork后的語句
    進(jìn)入c_id == 0 的流程
    執(zhí)行kill 父進(jìn)程,此時子進(jìn)程的父進(jìn)程id已經(jīng)變?yōu)?,表示該進(jìn)程已由init進(jìn)程進(jìn)行接管
    查看orphan進(jìn)程的父進(jìn)程,其父進(jìn)程號依舊可以查詢到,并且與進(jìn)程組號相同
    使用進(jìn)程組號 執(zhí)行killpg,向整組發(fā)送kill信號
    可以看到整個進(jìn)程組全部退出,后續(xù)的打印也就沒有輸出。

multiprocessing Process popen_fork

以下是multiprocessing的popen_fork的實現(xiàn),和上述例子非常相似。


    def _launch(self, process_obj):
        code = 1
        parent_r, child_w = os.pipe()  # 不過為什么要創(chuàng)建pipe呢? pipe獲得了兩個文件描述符。
        self.pid = os.fork()
        if self.pid == 0:
            try:
                os.close(parent_r) 
                if 'random' in sys.modules:
                    import random
                    random.seed()
                code = process_obj._bootstrap()
            finally:
                os._exit(code)
        else:
            os.close(child_w)
            util.Finalize(self, os.close, (parent_r,))
            self.sentinel = parent_r

進(jìn)程創(chuàng)建過程

在Unix,子進(jìn)程是父進(jìn)程的拷貝,其地址空間是父進(jìn)程地址空間的副本,不可寫的內(nèi)存部分是共享的,例如程序代碼。被修改的變量等通過寫時復(fù)制進(jìn)行修改。父子進(jìn)程擁有相同的內(nèi)存映像、打開的文件描述符,環(huán)境變量等。

子進(jìn)程執(zhí)行fork后的程序代碼。所以multiprocessing里的進(jìn)程創(chuàng)建后設(shè)置執(zhí)行的內(nèi)容,就是在fork之后,程序計數(shù)器值為fork 后一句的語句編號。

孤兒進(jìn)程總結(jié)

父進(jìn)程被終止,子進(jìn)程轉(zhuǎn)為孤兒進(jìn)程, 結(jié)束整組進(jìn)程可以殺死孤兒進(jìn)程?;蛘叩却舆M(jìn)程自己結(jié)束,結(jié)束后的數(shù)據(jù)回收由init進(jìn)程接管,所以孤兒進(jìn)程不會對系統(tǒng)造成過多問題。

ps:此時如果你用ctrl+c,是無法結(jié)束子進(jìn)程的,因為他的終端已經(jīng)成了1號進(jìn)程,必須找到其進(jìn)程號,kill 進(jìn)程號,來結(jié)束。

僵尸進(jìn)程???♂?

(有爹,爹不管)
父進(jìn)程創(chuàng)建,由父進(jìn)程創(chuàng)建子進(jìn)程,當(dāng)子進(jìn)程退出以后,大部分的資源被釋放,但是還是會有例如pid, 存在時間的記錄等資源沒有被釋放。所以當(dāng)子進(jìn)程退出后,子進(jìn)程會先變成僵尸進(jìn)程,然后由父進(jìn)程進(jìn)行剩余的清理工作。當(dāng)父進(jìn)程沒有對子進(jìn)程進(jìn)行清理工作的話,子進(jìn)程就會維持僵尸進(jìn)程的狀態(tài)。

過多僵尸進(jìn)程會 pid 不夠用。。以及系統(tǒng)資源會一直被占用。

僵尸進(jìn)程可以用 ps aux 這個命令來觀察。

[root@linux ~]# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1   1740   540 ?        S    Jul25   0:01 init [3]
root         2  0.0  0.0      0     0 ?        SN   Jul25   0:00 [ksoftirqd/0]
root         3  0.0  0.0      0     0 ?        S<   Jul25   0:00 [events/0]
.....中間省略.....
root      5881  0.0  0.3   5212  1204 pts/0    S    10:22   0:00 su
root      5882  0.0  0.3   5396  1524 pts/0    S    10:22   0:00 bash
root      6142  0.0  0.2   4488   916 pts/0    R+   11:45   0:00 ps aux

STAT:該程序目前的狀態(tài),主要的狀態(tài)有:
R :該程序目前正在運作,或者是可被運作;
S :該程序目前正在睡眠當(dāng)中(可說是idle 狀態(tài)啦!),但可被某些訊號(signal) 喚醒。
T :該程序目前正在偵測或者是停止了;
Z :該程序應(yīng)該已經(jīng)終止,但是其父程序卻無法正常的終止他,造成zombie (疆尸) 程序的狀態(tài)

守護進(jìn)程 (自動孤兒)

在linux里,守護進(jìn)程其實就是服務(wù)對應(yīng)的 默默的在后臺跑著的程序。
一般來說 守護進(jìn)程沒有任何存在的父進(jìn)程(即PPID=1),成為守護進(jìn)程的方式是父進(jìn)程創(chuàng)建完子進(jìn)程以后,立即退出,由init接管子進(jìn)程(碰瓷init, init內(nèi)心也是崩潰的)。上述也叫脫殼。

這里是python 的守護進(jìn)程的實現(xiàn):Sander Marechal's code sample is superior to the original

python multiprocessing daemon vs linux daemon

父進(jìn)程退出,kill所有daemon進(jìn)程。和linux的守護進(jìn)程是倆概念。這里的daemon模式 只是普通的子進(jìn)程,當(dāng)非守護進(jìn)程(父進(jìn)程)退出的時候,daemon進(jìn)程也會被退出的。在python里,daemonic processes are not allowed to have children。

以下是python設(shè)置子進(jìn)程daemon的例子;

   flush_key_process = Process(target=self.flush_redis_key_gap_time, name='{}_monitor'.format(self.key_name))
   flush_key_process.daemon = True
   flush_key_process.start()

總結(jié): 子進(jìn)程 vs 父進(jìn)程

  • 當(dāng)父進(jìn)程意外退出時,子進(jìn)程會如何
    子進(jìn)程會變成孤兒進(jìn)程,被init接管,子進(jìn)程退出后的clean工作也由init進(jìn)程完成。
  • 當(dāng)唯一的子進(jìn)程退出時,父進(jìn)程會如何
    父進(jìn)程清理子進(jìn)程退出后的資源。如果父進(jìn)程是阻塞等待的話,那么父進(jìn)程會解除阻塞,繼續(xù)執(zhí)行。
  • 當(dāng)python multiprocessing 里以守護進(jìn)程運行的時候,父進(jìn)程退出,子進(jìn)程會如何
    會被kill。

其他

kill -9 能否在程序中被捕捉,然后執(zhí)行一些清理操作?

The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon receiving this signal.

答案是并不能,原因??。

程序與資源管理
什麼是 daemon 與服務(wù) (service)
僵尸進(jìn)程與孤兒進(jìn)程
multiprocessing.Process.daemon
python sigkill catching strategies
現(xiàn)代操作系統(tǒng)
linux系統(tǒng)編程之進(jìn)程:父進(jìn)程查詢子進(jìn)程的退出,wait,waitpid

最后編輯于
?著作權(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)容

  • 進(jìn)程 操作系統(tǒng)背景知識 顧名思義,進(jìn)程即正在執(zhí)行的一個過程。進(jìn)程是對正在運行程序的一個抽象。 進(jìn)程的概念起源于操作...
    go以恒閱讀 1,026評論 0 2
  • 一. 操作系統(tǒng)概念 操作系統(tǒng)位于底層硬件與應(yīng)用軟件之間的一層.工作方式: 向下管理硬件,向上提供接口.操作系統(tǒng)進(jìn)行...
    月亮是我踢彎得閱讀 6,188評論 3 28
  • 1.內(nèi)存的頁面置換算法 (1)最佳置換算法(OPT)(理想置換算法):從主存中移出永遠(yuǎn)不再需要的頁面;如無這樣的...
    杰倫哎呦哎呦閱讀 3,607評論 1 9
  • 又來到了一個老生常談的問題,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個問題開始,來談?wù)劜?..
    tangsl閱讀 4,332評論 0 23
  • 聽弦斷,斷那三千癡纏。墜花湮,湮沒一朝風(fēng)漣。花若憐,落在誰指尖…… 一花一世界,一葉一追尋。一曲一場嘆,一生為一人!
    無音額閱讀 1,369評論 5 13

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