10.27.2015

Linux C Parent-Child Process

這一次要記錄下來的是作業系統中常見到的一個觀念─Parent-Child Process (父子行程)

那Parent-Child Process對自己而言,就如同fopen(), popen(), open()之後,我們都會記得要fclose(), pclose(), close();

同樣的道理,當我們fork()出一個子行程之後,我們在父行程中也要執行wait(), waitpid()來接收子行程的執行狀態碼;要不然,若是子行程比父行程提早結束,而父行程又沒有使用wait(), waitpid()來接收子行程的狀態,那子行程就很容易變成Zombie / Defunct,會一直占用住整個系統的資源,直到系統重新啟動為止。

雖然這樣的範例課本都有寫,但看課本寫的會有一種「我好像已經懂了」的錯覺,所以還是要想辦法生一個範例證明自己懂了!!

自己想要寫的範例需求如下:
1. 寫一個程式,要能夠fork()出父子行程;
2. 在子行程,秀出父行程的PID,和自己的PID;
3. 在父行程,秀出它的PID,和子行程的PID;
4. 為了呈現Zombie / Defunct的狀態,子行程要比父行程先行結束,各自sleep()5秒和10秒;
5. 父行程要接收子行程所回傳的狀態碼。
// myFork.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
 
int 
main()
{
    pid_t pid = -2, child_pid = -3;
    int nStatusVal = 0, nChildExitCode = 0;

    pid = fork();
    if (pid == 0) { // Child Process
        sleep(5);
        printf("Child Process (CPID): %d \n", getpid());
        printf("Child Process (PPID): %d \n", getppid());
        printf("\n");
        exit(6); // You could define your exit-status code....
    }
    else if (pid == -1) {
        perror("fork() Child Process failed (error)!! \n");
        exit(EXIT_FAILURE);
    }
    else { } // Parent Process

    sleep(10);
    child_pid = wait(&nStatusVal);
    printf("Parent Process (PPID): %d \n", getpid());
    printf("Parent Process (CPID): %d \n", child_pid);
    printf("\n");
//  printf ("nStatusVal = %d \n", nStatusVal); // for testing
    if (WIFEXITED(nStatusVal) != 0) {
        nChildExitCode = WEXITSTATUS(nStatusVal);
        switch (nChildExitCode) {
            case 6:
                printf("The Child Process EXIT-STATUS Code: %d \n", nChildExitCode);
                break;
            // You could define other EXIT-STATUS Code....
            default:
                break;
        }
    }
    else
        printf("Child terminated abnormally. \n");

    return 66;
}
程式碼一開始所宣告的:pid_t pid = -2; 自己並沒有用很正規的Initialization (初始化)方式寫成:pid_t pid = (pid_t)(-2); 因為在自己的Fedora Linux 13系統中,pid_t的資料型態就等同於int,標頭檔的定義部分如下:
/usr/include/unistd.h
#ifndef __pid_t_defined
typedef __pid_t pid_t
#define __pid_t_defined
#endif
/usr/include/bits/types.h
#define __S32_TYPE int
// ....
#define __STD_TYPE typedef
// ....
__STD_TYPE __PID_T_TYPE __pid_t
// ....
/usr/include/bits/typesizes.h
#define __PID_T_TYPE __S32_TYPE
自己把fork()的結果用if()-else if()-else{}的方式來區分─fork()出來的子行程要做甚麼、fork()失敗要做甚麼、父行程要做甚麼?? 自己也有看過Open Source的寫法,直接把子行程寫在if()裡面,完全不考慮fork()失敗的狀況。

在父子行程中,都分別使用了getpid()─取得目前行程的PIDgetppid()─在子行程中取的父行程的PID

在父行程中使用wait()函式,會回傳子行程的PID (child_pid);同時也會藉由Call by Reference的方式回傳子行程的結束狀態碼 (nStatusVal)。

這裡要先註明一下,子行程的結束狀態碼絕對不是指exit(6); 不信?? 可以把我註解掉的printf()那一行列印出來看看,這一部分自己要很誠實地說並不是很懂....

接下來我們要用WIFEXITED()和WEXITSTATUS()這兩個巨集 / 宏 / Micro來解譯子行程所回傳的結束狀態碼;但除了這兩個巨集之外,還有另外4個:

WIFEXITED(stat_val):如果子行程正常結束,回傳一個非0值。

WEXITSTATUS(stat_val):如果WIFEXITED()回傳一個非0值,就回傳子行程的結束碼 (exit(6); )。

WIFSIGNALED(stat_val):如果子行程因遇到Signal (訊號)而結束,回傳一個非0值。

WTERMSIG(stat_val):如果WIFSIGNALED()回傳一個非0值,就回傳訊號編號。

WIFSTOPPED(stat_val):如果子行程已經結束,回傳一個非0值。

WSTOPSIG(stat_val):如果WIFSTOPPED()回傳一個非0值,就回傳訊號編號。

所以結論就是,當我們fork()出子行程之後,記得在父行程中也要跟著使用wait(), WIFEXITED(), WEXITSTATUS()等相關的函式或巨集來分析子行程的狀態或是回傳值。

至於另外的後面四個巨集,目前還沒想到範例....(以後可能會有吧!!)

執行結果:

首先,我們開啟兩個終端機視窗。當程式開始執行的時候,因為子程序會等待5秒,父程序會等待10秒,所以還來的急在另一個終端機視窗輸入ps指令來查詢父子程序以及它們的PID;

當子程序印出訊息之後,隨之結束了子程序;再輸入一次ps指令查詢,發現子程序已經變成<defunct>狀態,因為父程序還要再等5秒才會執行wait();

當父程序列印出訊息之後,跟著結束;再用ps查詢,發現程序均正常結束,並沒有留下Zombie。

PS:我一直很好奇,為啥Google Blogger只能放小圖啊?? 超不清楚的....

沒有留言:

張貼留言