孤兒進程和殭屍進程


title: 孤兒進程和殭屍進程
date: 2019-06-18 18:26:46
tags:
- Linux
categories:
- Linux

孤兒進程和殭屍進程的概念,回收進程資源(wait、waitpid)。

孤兒進程

孤兒進程:父進程先於子進程結束,則子進程程偉孤兒進程,子進程的父進程成爲 init 進程,稱爲 init 領養了這個孤兒進程。

init 進程會循環地 wait() 它的已經退出的子進程。這樣,當一個孤兒進程淒涼地結束了其生命週期的時候,init進程就會代表黨和政府出面處理它的一切善後工作。因此孤兒進程並不會有什麼危害。

栗子

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    pid_t ret = fork();
    if (ret == -1) {
        perror("fork");
        exit(-1);
    } else if (ret == 0) {
        // 子進程
        printf("1. i' am child, my father is: %d\n", getppid()); 
        sleep(5);
        printf("2. i' am child, my father is: %d\n", getppid());
    } else {
        // 父進程
        sleep(1);
        printf("i'm going to die.\n");
    }

    return 0;
}

孤兒進程被init領養

殭屍進程

殭屍進程:進程終止,父進程對其不管不顧,子進程殘留資源(PCB)存放於內核中,變成殭屍(Zombie)進程。

殭屍進程是不能使用 kill 命令清除掉的。因爲 kill 命令指示用來終止進程的,而殭屍進程已經終止。殺掉殭屍進程的方法就是 kill 掉他的父進程。

想到個栗子:武林大哥讓小弟去幹一件事,小弟在辦事途中意外掛了,但他留了一封血書信給大哥,告訴他大哥自己是怎麼掛的,讓大哥去給他報仇去。這封信就是PCB。但是如果大哥不在意這件事,這個 PCB 就會一直存在。

栗子

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    pid_t ret = fork();

    if (ret == -1) {
        perror("fork error");
        exit(-1);
    } else if (ret == 0) {
        // 子進程
        printf("child process : i'm going to die.\n");
    } else {
        // 父進程
        while (1) {
            printf("father process : pid = %d, and my son pid = %d\n", getpid(), ret);
            sleep(2);
        }
    }

    return 0;
}

[外鏈圖片轉存失敗(img-qDQDmxL3-1563258032185)(https://i.loli.net/2019/06/18/5d08c4500fafd63657.png)]

[外鏈圖片轉存失敗(img-MJDI7cZG-1563258032185)(https://i.loli.net/2019/06/18/5d08c4ea435af39607.png)]

回收

一個進程在終止的時候會關閉所有文件描述符,釋放在用戶控件分配的內存,但它的 PCB 還保留着,內核在其中保存了一些信息:如果進程是正常終止則保存着退出狀態,如果是異常終止則保存着導致該進程終止的信號是哪個。

這個進程的父進程可以調用 wait 或 waitpid 獲取這些信息,然後徹底清除掉這個進程。

一個進程的退出狀態可以在 shell 中用 $? 查看,因爲 shell 是它的父進程,當它終止時 shell 調用 wait 或 waitpid 得到它的退出狀態同時徹底清除掉這個進程佔用的資源。

當進程終止時,操作系統的隱式回收機制:1.關閉所有文件描述符;2.釋放用戶空間分配的內存。內核的 PCB 仍存在。其中保存該進程的退出狀態。(正常終止:退出值;異常終止:終止信號)

注意wait 和 waitpid 獲取退出狀態的時候並不是一個 int 都是退出的狀態。在網上找到的資料畫出來應該是下面這樣的,這個status 是由操作系統填充的,他並不真的、簡單的當做一個 int 類型。status 的低 16 位是真正我們關心的。當然也可以用宏獲取這個進程的退出狀態。下文中有

[外鏈圖片轉存失敗(img-tVx3Accq-1563258032186)(https://i.loli.net/2019/06/12/5d006eaa2202331296.png)]

wait

#include <sys/wait.h>
pid_t wait(int *stat_loc);

返回:
	成功:清理掉的子進程 ID
	失敗:-1(沒有子進程)

參數:輸出型參數,不關心子進程的退出狀態可以填 NULL
  • 阻塞式的等待子進程退出
  • 回收子進程殘留資源
  • 獲取子進程結束狀態
  • 一次 wait 只回收一個子進程

栗子1

直接回收,不關心子進程的退出碼。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    pid_t ret = fork();

    if (ret == -1) {
        perror("fork error");
        exit(-1);
    } else if (ret == 0) {
        // 子進程
        printf("child process : i'm going to die.\n");
    } else {
        // 父進程
        pid_t w_ret = wait(NULL);
        if (w_ret == -1) {
            perror("wait error");
            exit(-2);
        }
        
        while (1) {
            sleep(2);
        }
    }

    return 0;
}

[外鏈圖片轉存失敗(img-kL8gtlUq-1563258032186)(https://i.loli.net/2019/06/18/5d08ce0b3638042606.png)]

ps 查看,子進程已經被回收

栗子2

使用 wait 的參數 status 來保存進程的退出狀態。藉助紅函數來進一步判斷進程終止的具體原因。宏函數在 man page 中有詳細的介紹。

  1. WIFEXITED(status) 爲非0,進程正常結束『wait, if exited』

    WEXITSTATUS(status) 如上宏爲真,使用此宏,獲取進程退出狀態『wait, exit status』

  2. WIFSIGNALED(status) 爲非0,進程異常終止『wait, if signaled』

    WTERMSIG(status) 如上宏爲真,使用此宏,取得使進程終止的那個信號的編號『wait, term signaled』

  3. WIFSTOPPED(status) 爲非0,進程處於暫停狀態『wait, if stopped』

    WSTOPSIG(status) 如上宏爲真,使用此宏,取得使進程暫停的那個信號的編號『wait, stop signal』

    WIFCONTINUED(status) 爲真,進程暫停後已經繼續運行『wait, if continued』

/*
    調用 wait 回收子進程資源
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    pid_t ret = fork();

    if (ret == -1) {
        perror("fork error");
        exit(-1);
    } else if (ret == 0) {
        // 子進程
        printf("child process : i'm going to die.\n");
        exit(101);
    } else {
        // 父進程
        int status = 0;
        pid_t w_ret = wait(&status);
        if (w_ret == -1) {
            perror("wait error");
            exit(-2);
        }
        if (WIFEXITED(status)) {
            printf("child exit with %d\n", WEXITSTATUS(status));
        }

        while (1) {
            sleep(2);
        }
    }

    return 0;
}

[外鏈圖片轉存失敗(img-cLGBoV1a-1563258032187)(https://i.loli.net/2019/06/18/5d08d2a15f6d132781.png)]

栗子3

獲取讓子進程退出的信號,奧,對還有信號這個知識還不是很瞭解,看看書找找資料學習學習寫到博客裏。kill -l查看信號

/*
    調用 wait 回收子進程資源
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    pid_t ret = fork();

    if (ret == -1) {
        perror("fork error");
        exit(-1);
    } else if (ret == 0) {
        // 子進程
        printf("child process : i'm going to die.\n");
        sleep(100);
        exit(101);
    } else {
        // 父進程
        int status = 0;
        pid_t w_ret = wait(&status);
        if (w_ret == -1) {
            perror("wait error");
            exit(-2);
        }
        if (WIFEXITED(status)) {
            printf("child exit with %d\n", WEXITSTATUS(status));
        }
        if (WIFSIGNALED(status)) {
            printf("child exit by sig %d\n", WTERMSIG(status));
        }

        while (1) {
            sleep(2);
        }
    }

    return 0;
}

[外鏈圖片轉存失敗(img-EA5JaP0o-1563258032188)(https://i.loli.net/2019/06/18/5d08d418c1be347460.png)]

[外鏈圖片轉存失敗(img-59eO16ch-1563258032188)(https://i.loli.net/2019/06/18/5d08d436ef76b36645.png)]

waitpid

可以指定 pid 進行清理,可以不阻塞。

#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *stat_loc, int options);

參數:
	pid:
		> 0,回收指定的子進程
		-1 ,回收任意子進程
		0  ,回收和當前調用 waitpid 一個組的所有子進程
		< -1,回收指定進程組內的任意子進程
	status:
	 	輸出參數,子進程的退出狀態
  options:
  	WNOHANG,非阻塞方式 // would no hang, 不會掛起(不會阻塞, hang 懸掛;暫停,中止)
  	0 ,阻塞方式
  	nohang

返回:
	成功:返回清理掉的子進程pid
	失敗:-1(無子進程)
  0  :參數 options 爲 WNOHANG,且子進程正在運行
  
waitpid(-1, NULL, 0) == wait(NULL)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章