那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()─取得目前行程的PID;getppid()─在子行程中取的父行程的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只能放小圖啊?? 超不清楚的....
沒有留言:
張貼留言