歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> 學習Linux >> [APUE]進程控制(中),apue進程控制

[APUE]進程控制(中),apue進程控制

日期:2017/3/3 17:40:00   编辑:學習Linux

[APUE]進程控制(中),apue進程控制

[APUE]進程控制(中),apue進程控制


一、wait和waitpid函數

當一個進程正常或異常終止時會向父進程發送SIGCHLD信號。對於這種信號系統默認會忽略。調用wait/waidpid的進程可能會:

  • 阻塞(如果其子進程都還在運行);
  • 立即返回子進程的終止狀態(如果一個子進程已經終止正等待父進程存取其終止狀態);
  • 出錯立即返回(如果它沒有任何子進程);
    如果進程由於收到SIGCHLD信號而調用wait,則可期望wait會立即返回。但是在任一時刻調用則進程可能阻塞。
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
返回值: 成功返回進程ID, 出錯-1.

這兩個函數區別:

  • wait如果在子進程終止前調用則會阻塞,而waitpid有一選項可以使調用者不阻塞。
  • waitpid並不等待第一個終止的子進程--它有多個選項,可以控制它所等待的進程。

如果調用者阻塞而且它有多個子進程,則在其一個子進程終止時,wait就立即返回。因為wait返回子進程ID,所以調用者知道是哪個子進程終止了。
參數statloc是一個整型指針。如果statloc不是一個空指針,則終止狀態就存放到它所指向的單元內。如果不關心終止狀態則將statloc設為空指針。
這兩個函數返回的整型狀態由實現定義。其中某些位表示退出狀態(正常退出),其他位則指示信號編號(異常返回),有一位指示是否產生了一個core文件等等。POSIX.1規定終止狀態用定義在<sys/wait.h>中的各個宏來查看。有三個互斥的宏可用來取得進程終止的原因,它們的名字都已WIF開始。基於這三個宏中哪一個值是真,就可選用其他宏(這三個宏之外的其他宏)來取得終止狀態、信號編號等。

下面的程序中pr_exit函數使用上表中的宏以打印進程的終止狀態。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>

void pr_exit(int status)
{
    if (WIFEXITED(status)) {
        printf("normal termination, exit status=%d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("abnormal termination, signal number = %d\n", WTERMSIG(status), 
    #ifdef WCOREDUMP
        WCOREDUMP(status) ? "(core file generated)" : "");
    #else
        "");
    #endif
    } else if (WIFSTOPPED(status)) {
        printf("child stopped, signal number = %d\n", WSTOPSIG(status));
    }
}

int main(void)
{
    pid_t pid;
    int status;
    
    if ((pid = fork()) < 0) {
        fprintf(stderr, "fork error");
    } else if (pid == 0) {
        exit(7);
    }
    
    if (wait(&status) != pid) {
        fprintf(stderr, "wait error");
    }
    
    pr_exit(status);
    
    if ((pid = fork()) < 0) {
        fprintf(stderr, "fork error");
    } else if (pid == 0) {
        abort();
    }
    
    if (wait(&status) != pid) {
        fprintf(stderr, "wait error");
    }
    
    pr_exit(status);
    
    if ((pid = fork()) < 0) {
        fprintf(stderr, "fork error");
    } else if (pid == 0) {
        status /= 0;
    }
    
    if (wait(&status) != pid) {
        fprintf(stderr, "wait error");
    }
    
    pr_exit(status);
    
    return 0;
}

編譯運行結果:

wait是只要有一個子進程終止就返回,waitpid可以指定子進程等待。對於waitpid的pid參數:

  • pid == -1, 等待任一子進程。這時waitpid與wait等效。
  • pid > 0, 等待子進程ID為pid。
  • pid == 0, 等待其組ID等於調用進程的組ID的任一子進程。
  • pid < -1 等待其組ID等於pid的絕對值的任一子進程。

對於wait,其唯一的出錯是沒有子進程(函數調用被一個信號中斷,也可能返回另一種出錯)。對於waitpid, 如果指定的進程或進程組不存在,或者調用進程沒有子進程都能出錯。 options參數使我們能進一步控制waitpid的操作。此參數或者是0,或者是下表中常數的逐位或運算。

二、 競態條件

當多個進程都企圖對某共享數據進行某種處理,而最後的結果又取決於進程運行的順序,則我們認為這發生了競態條件(race condition)。如果在fork之後的某種邏輯顯式或隱式地依賴於在fork之後是父進程先運行還是子進程先運行,那麼fork函數就會是競態條件活躍的孽生地。
如果一個進程希望等待一個子進程終止,則它必須調用wait函數。如果一個進程要等待其父進程終止,則可使用下列形式的循環:

while(getppid() != 1)
    sleep(1);

這種形式的循環(稱為定期詢問(polling))的問題是它浪費了CPU時間,因為調用者每隔1秒都被喚醒,然後進行條件測試。
為了避免競態條件和定期詢問,在多個進程之間需要有某種形式的信號機制。在UNIX中可以使用信號機制,各種形式的進程間通信(IPC)也可使用。
在父、子進程的關系中,常常有以下情況:在fork之後,父、子進程都有一些事情要做。例如:父進程可能以子進程ID更新日志文件中的一個記錄,而子進程則可能要為父進程創建一個文件。在本例中,要求每個進程在執行完它的一套初始化操作後要通知對方,並且在繼續運行之前,要等待另一方完成其初始化操作。這種情況可以描述為如下:

TELL_WAIT();

if ((pid = fork()) < 0) {
    err_sys("fork error");
} else if (pid == 0) {
    TELL_PARENT(getppid());
    WAIT_PARENT();
    exit(0);
}

TELL_CHILD(pid);
WAIT_CHILD();
exit(0);

三、exec函數

當進程調用exec函數時,該進程完全由新進程代換,而新程序則從其main函數開始執行。因為調用exec並不創建新進程,所以前後的進程ID不會改變。exec只是用另一個程序替換了當前進程的正文、數據、堆和棧段。

#include <unistd.h>

int execl(const char *pathname, const char *arg0, ... /* (char *) 0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *) 0 */);
int execvp(const char *filename, char *const argv[]);
返回值:出錯-1,若成功不返回

這些函數之間的第一個區別是前四個取路徑名作為參數,後兩個取文件名作為參數。當制定filename作為參數時:

  • 如果filename中包含/,則就將其視為路徑名。
  • 否則按PATH環境變量。

如果excelp和execvp中的任意一個使用路徑前綴中的一個找到了一個可執行文件,但是該文件不是機器可執行代碼文件,則就認為該文件是一個shell腳本,於是試著調用/bin/sh,並以該filename作為shell的輸入。
第二個區別與參數表的傳遞有關(l 表示表(list),v 表示矢量(vector))。函數execl、execlp和execle要求將新程序的每個命令行參數都說明為一個單獨的參數。這種參數表以空指針結尾。另外三個函數execv,execvp,execve則應先構造一個指向個參數的指針數組,然後將該數組地址作為這三個函數的參數。
最後一個區別與向新程序傳遞環境表相關。以 e 結尾的兩個函數excele和exceve可以傳遞一個指向環境字符串指針數組的指針。其他四個函數則使用調用進程中的environ變量為新程序復制現存的環境。
六個函數之間的區別:

每個系統對參數表和環境表的總長度都有一個限制。當使用shell的文件名擴充功能產生一個文件名表時,可能會收到此值的限制。例如,命令:

grep _POSIX_SOURCE /usr/include/*/*.h

在某些系統上可能產生下列形式的shell錯誤。

arg list too long

執行exec後進程ID沒改變。除此之外,執行新程序的進程還保持了原進程的下列特征:

  • 進程ID和父進程ID。
  • 實際用戶ID和實際組ID。
  • 添加組ID。
  • 進程組ID。
  • 對話期ID。
  • 控制終端。
  • 鬧鐘尚余留的時間。
  • 當前工作目錄。
  • 根目錄。
  • 文件方式創建屏蔽字。
  • 文件鎖。
  • 進程信號屏蔽。
  • 未決信號。
  • 資源限制。
  • tms_utime,tms_stime,tms_cutime以及tms_ustime值。

對打開文件的處理與每個描述符的exec關閉標志值有關。進程中每個打開描述符都有一個exec關閉標志。若此標志設置,則在執行exec時關閉該文件描述符,否則該描述符仍打開。除非特地用fcntl設置了該標志,否則系統的默認操作是在exec後仍保持這種描述符打開。
POSIX.1明確要求在exec時關閉打開目錄流。這通常是由opendir函數實現的,它調用fcntl函數為對應於打開目錄流的描述符設置exec關閉標志。
在exec前後實際用戶ID和實際組ID保持不變,而有效ID是否改變則取決於所執行程序的文件的設置-用戶-ID位和設置-組-ID位是否設置。如果新程序的設置-用戶-ID位已設置,則有效用戶ID變成程序文件的所有者的ID,否則有效用戶ID不變。對組ID的處理方式與此相同。

在很多UNIX實現中,這六個函數只有一個execve是系統調用。另外5個是庫函數

http://xxxxxx/Linuxjc/1186728.html TechArticle

Copyright © Linux教程網 All Rights Reserved