1. 程式人生 > >linux程式設計之程序控制

linux程式設計之程序控制

(一)fork

  #include <unistd.h>
  pid_t fork(void);

返回值:有兩個,一個是子程序的ID,另一個為零。當返回值為0時,進入子程序,大於0進入父程序,返回-1時建立程序錯誤。

子程序與父程序的比較:
(1)子程序與父程序的程序ID不同。且父程序ID不同。
(2)記憶體佈局: 子程序是父程序的副本,其中子程序的資料空間,堆,棧是父程序的副本,但是共享真正文段。
(3)執行順序:子程序與父程序執行順序不確定,取決於核心所使用的排程演算法。
(4)檔案共享:父程序所有開啟的檔案描述符都會賦值到子程序,父程序與子程序每個相同的開啟檔案描述符共享一個檔案表項。即子程序操作檔案直接影響父程序對該檔案的操作,最明顯的是檔案偏移量收到影響。其圖如下所示:

例如:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int x = 33,y = 44;

    pid_t pid;

    if( (pid = fork()) == -1 )
    {
        perror("fork err\n");
        exit(EXIT_FAILURE);
    }
    else if( pid == 0 )
    {
        x ++;
        y ++;
        printf("child : x = %d  y = %d\n",x,y);
        exit( EXIT_SUCCESS);
    }
    wait();
    printf("parent: x = %d y = %d\n",x,y);

    return 0;
}
fork的使用場景:

(1)父程序希望複製自己,使得父程序和子程序同時執行不同的程式碼段,即在網路服務中是最常見的,父程序等待客戶端的服務請求,當請求到來時,父程序fork一個子程序來處理子程序的請求,這時父程序可以繼續等待下一個服務請求。
(2)使得子程序執行一個不同的程式,需要fork一個子程序,在此子程序中執行exec執行新的程式。

(二)vfork函式

 #include <sys/types.h>
 #include <unistd.h>
 pid_t vfork(void);

返回值為:與fork幾乎相同

與fork函式的不同:

(1)vfork一般是建立一個程序來執行exec函式,即執行一個新的程式。


(2)vfork中,子程序不將父程序的地址空間完全複製到子程序中,其子程序在父程序空間中執行。
(3)vfork保證子程序先執行,父程序等待子程序執行完成後在再執行。
(4)vfork子程序的資源與父程序是共用的,因此父程序中變數經過子程序的對其的改變則父程序也會改變。
例如:

//注意與上個fork的例子進行比較
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int x = 33,y = 44;
    pid_t pid;

    if( (pid = vfork() ) == -1 )
    {
        perror("fork err\n");
        exit(EXIT_FAILURE);
    }
    else if( pid == 0 )
    {
        x++;
        y++;
        printf("child : x = %d y = %d\n",x,y);
        exit(0);
    }
    wait();
    printf("parent : x = %d y = %d\n",x,y);

    return 0;
}

(三)wait函式

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

   pid_t wait(int *status);

該函式的都是等待子程序的退出,如果成功返回退出程序的ID,如果出錯返回-1。

(1)呼叫wait函式
  • 如果所有的子程序都在執行,則此時父程序阻塞。
  • 如果一個子程序終止,等待的父程序立即獲取其終止狀態並立即返回。
  • 如果沒有任何子程序則出錯返回。
(2)獲得終止狀態
  • WIFEXITED(status) 判斷子程序是否正常終止。若為正常終止則是真,可以執行WEXITSTATUS(status)來獲得子程序傳送給exit或者_exit引數低8位。
  • WIFSIGNALED(status) 判斷是否為異常終止,若為異常終止則為真,可以用WTERMSIG(status)獲得子程序終止的訊號編號。
  • WIFSTOPPED(status) 判斷子程序暫停,暫停則是真。可以使用WSTOPSIG(status)獲得子程序暫停的訊號編號。

例如:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.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));
    }
    else if( WIFSTOPPED(status) )//子程序暫停
    {
        printf("child stopped ,signal number = %d\n",WSTOPSIG(status));
    }
}
int main()
{
    pid_t pid;
    int status;
    if( ( pid = fork()) == -1 )
    {
        perror("fork err\n");
        exit(EXIT_FAILURE);
    }
    else if( pid == 0 )
    {
        exit(7);
    }
    if( wait(&status) != pid )
    {
        perror("wait err\n");
    }
    pr_exit(status); 

    if( ( pid = fork()) == -1 )
    {
        perror("fork err\n");
        exit(EXIT_FAILURE);
    }
    else if( pid == 0 )
    {
        abort();
    }
    if( wait(&status) != pid )
    {
        perror("wait err\n");
    }
    pr_exit(status);   
    if( ( pid = fork()) == -1 )
    {
        perror("fork err\n");
        exit(EXIT_FAILURE);
    }
    else if( pid == 0 )
    {
        status = status / 0;
    }
    if( wait(&status) != pid )
    {
        perror("wait err\n");
    }
    pr_exit(status);
    return 0;
}

注意: 如果有多個子程序退出,而需要等待特定的子程序退出時,可以使用wait的返回值與某個期望的ID比較。

(三)waitpid函式

   #include <sys/types.h>
   #include <sys/wait.h>
   pid_t  waitpid(pid_t   pid,   int   *status,   int
   options);
  • pid == -1 等待任一個程序,此情況和wait等效。
  • pid > 0 等待程序ID與pid相同的程序
  • pid == 0 等待組ID等於呼叫程序組ID的任一個子程序。
  • pid < -1 等待組ID等於pid絕對值的任一個子程序
  • options:可以進一步控制waitpid的操作,一般為0

該函式的都是等待特定子程序的退出,如果成功返回退出程序的ID,如果出錯返回-1。

(1)呼叫waitpid函式
  • 等待特定的子程序終止。
  • 如果所有的子程序都在執行,則此時父程序阻塞。但是有一個選項可以使得不阻塞
(2)獲得終止狀態與wait相同
(四)exec函式
   #include <unistd.h>
   extern char **environ;
   int execl(const char *path, const char *arg, ...);
   int execv(const char *path, char *const argv[]);
   int execle(const char *path, const char *arg,    
              ..., char * const envp[]);
   int execlp(const  char  *file,  const  char  *arg,
   ...);
   int execvp(const char *file, char *const argv[]);
   int execvpe(const char *file, char *const argv[],
               char *const envp[]);
(1)當程序呼叫exec函式時,exec並不建立新的程序,前後ID不改變,該程序執行的程式被完全替代為exec新程式,新的程式從main函式開始執行,exec只是用磁碟上的一個新的程式替換了當前程序的證正文段,資料段,堆段和棧段。
(2)函式區別:
  • 字母p代表這些函式取filename作為引數。並且用PATH環境變數尋找可執行檔案。
  • 字母l表示函式取一個引數表,與字V互斥。
  • v表示該函式取argv[]向量。
  • 字母e表示函式取envp[]陣列,而不適用當前的環境。

例如:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char * env_init[] = {"USER=unkonwn","PATH=/tmp",NULL};
int main()
{
    pid_t pid;

    if( (pid = fork()) == -1 )
    {
        perror("fork err\n");
        exit(EXIT_FAILURE);
    }
    else if( pid == 0 )
    {
        if( (execle("/home/kaye/APUE/unit8/show","arg1","arg2","arg3",(char*)0,env_init)) < 0 )
        {
            perror("execle err\n");
            exit(1);
        }
    }
    if( waitpid( pid,NULL,0) != pid )
    {
        perror("waitpid fail\n");
        exit(3);
    }
    if( (pid = fork()) == -1 )
    {
        perror("fork err\n");
        exit(2);
    }
    else if( pid == 0 )
    {
        if( execlp("show","arg4","arg5","arg6",(char *)0)  < 0 )
        {//必須把該可執行檔案的路徑加入到環境變數中,這樣才能找到該可執行檔案
            perror("execlp err\n");
            exit(3);
        }
    }
    return 0;
}

(五)典型的程序

(1)孤兒程序

產生原因: 父程序終止,子程序會被Init程序收養,此時的子程序就是孤兒程序。

產生過程: 程序終止時,核心逐個檢查所有活動程序,以判斷他是否是正要終止程序的子程序,如果是,則該程序的父程序,ID就更改為1,這樣就保證了每個程序的都有一個父程序。

(2)僵死程序

產生原因:一個程序已經終止,但是其父程序,尚未對其進行善後處理(獲取終止子程序的有關資訊,釋放他仍佔用的資源)的程序被稱為僵死程序。父程序沒有等待取得子程序的終止狀態。

避免方法: 核心為每個終止子程序儲存了一定量的資訊,所以當終止程序的父程序呼叫wait或waitpid時,可以得到這些資訊,這些資訊包括程序ID,程序的終止狀態,以及程序使用的CPU時間總量。

注意:如果一個孤兒程序終止時會成為僵死程序麼?:其實是不會的,原因為init程序中的子程序終止時,init會呼叫一個wait函式取得其終止狀態,這樣可以防止產生僵死程序。

例如:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> 
int main()
{
    pid_t pid ;
    if( ( pid = fork() ) == -1 )
    {
        perror("fork err\n");
        exit(EXIT_FAILURE);
    }
    else if( pid == 0 )
    {
        printf("aaaa\n");
        exit(1);
    }
    else 
    {
        system("ps");//此時僵死程序
        wait();
        system("ps");//經過wait後,沒有僵死程序
        sleep (2);
    }
    return 0;
}