天天看點

程序控制(上):程序建立,程序等待,程序終止

程序建立

程序建立被定義為通過父程序建立子程序的過程。

fork函數

函數原型:pid_t fork(void);

特點:

1.fork函數調用一次,傳回兩次兩次傳回值得差別分别是子程序當中的傳回值為0,父程序當中的傳回值為建立子程序的ID(将ID傳回給父程序的原因是沒有函數可以使父程序得到子程序的ID,這樣會便于管理);

2.子程序被建立出來後,子程序是父程序的副本(子程序獲得父程序資料空間,堆,棧的副本<一定注意是副本>);

3.父程序和子程序隻共享正文段(.text);

4.由于fork之後經常跟随者exec(程式加載函數),是以現在很多作業系統的實作并不執行一個父程序的副本,而是使用了寫時拷貝(這些區域由父子程序所共享,而且核心将這些區域的通路權限設定為隻讀權限,如果父程序和子程序中的任意一個區域試圖被修改,則核心會為修改區域産生一個副本)。

5.fork之後父程序和子程序的執行順序是随機的,取決于核心所使用的排程算法。

6.關于檔案共享,這部分内容和檔案描述符有關大家可以看看下面這篇部落格:http://blog.csdn.net/bit_clearoff/article/details/54565044

7.父程序和子程序的差別:

- fork的傳回值不同

- 程序ID,父程序ID不同

- 子程序不繼承父程序設定的檔案鎖

- 子程序未處理的鬧鐘被清楚

- 子程序未處理的信号集,被設定為空集

函數vfork

vfork函數也是建立程序,但是與fork函數不相同的是:

1.vfork建立一個新程序,而新程序的目的是exec一個新程式;

2.vfork并不将父程序的位址空間完全複制到子程序中;

3.子程序在調用exec函數或者exit函數之前,子程序在父程序的空間中運作;

4.vfork函數保證子程序先運作,在它調用exec或exit函數之後父程序才會被恢複運作。

程序終止

程序有五種正常終止方式和三種異常終止方式,他們分别是:

五種正常終止方式

1.在main函數内執行return 語句。它等效于調用exit;

2.調用exit函數,exit函數由C庫定義,其操作包含調用各種終止處理程式,關閉所有标準I/O流等。詳細的大家可以看看下面這篇部落格:

http://blog.csdn.net/bit_clearoff/article/details/54425362

3.調用_exit函數,exit函數和_exit函數的不用地方就是它為程序提供了一種無需終止運作終止處理程式或信号處理程式而終止的辦法。

4.程序的最後一個線程在其啟動例程中執行return語句.(這個現在大家先了解即可,關于線程的知識後面會詳細分析)

5.程序的最後一個線程調用pthread_exit函數。

三種異常終止方式

1.調用absort。

2.當程序接收到某些信号時。

3.最後一個線程對“取消請求作出響應”。

不管程序以上面的何種方式進行終止,都會執行核心中的同一段代碼,這段代碼為相應的程序關閉所有打開的描述符,釋放程序所擁有的位址空間。

另外,對任意一種終止情形,父程序都希望得到所終止的子程序的相關退出資訊,對于三個終止函數,都采用了輸出型參數status得到子程序的退出狀态和資訊;在異常情況中,核心産生一個訓示其異常終止原因的終止狀态,另外,父程序可以用wait函數和waitpid函數取得子程序的終止狀态。

上面提到的都是子程序先被終止,然後父程序回收子程序的相關狀态,這時被終止的子程序被稱為僵死程序(注意父程序此時還沒有被終止);

程式控制(上):程式建立,程式等待,程式終止

但是如果父程序在子程序被終止前先被終止,此時我們稱子程序為孤兒程序,這時子程序的父程序會變成ID号為1的init程序,這時孤兒程序的領養機制,我們來看相關代碼:

程式控制(上):程式建立,程式等待,程式終止

程序等待

關于程序等待我們首先應該想到這兩個函數,wait和waitpid函數都是用來提供給父程序檢測子程序的狀态的,但兩種函數也是有差別的,我們先來看看wait函數:

wait函數

1.函數原型:pid_t wait(int* status);

其中,status參數是前面提到的父程序用來監測子程序退出狀态的輸出型參數,我們将會在下面進項講解;另外wait函數的傳回值為pid_t ,可想而知,它傳回的是一個程序辨別符;

2.調用wait函數時會發生什麼?

- 如果有子程序再運作,那麼目前父程序就處于阻塞狀态(可以了解為什麼事情都沒有做,一直在檢測子程序是否在運作);

- 如果子程序都已經終止,那麼wait可立即獲得子程序的終止狀态(退出碼,退出資訊),子程序的終止狀态是展現在status參數上的,另外wait還會傳回所終止的子程序的辨別符;

- 如果目前程序沒有任何子程序,那麼wait會立即出錯傳回(此時傳回值為-1);

- 如果有一個子程序終止,那麼wait便傳回;

程式控制(上):程式建立,程式等待,程式終止

下面我們來了解一下waitpid函數:

waitpid函數

1.函數原型:pid _t waitpid(pid_t pid,int *status,int options);

參數解釋

pid為監測子程序的辨別符;

status:子程序的終止狀态資訊,如果不是空指針,則終止程序的終止資訊就存放在它所指向的單元内,不關心終止狀态可以将status制成NULL;

options:指定參數,預設情況下waitpid與wait做的事情都是一樣的,為阻塞式監測子程序終止狀态;當options=WNOHANG時,此時為非阻塞方式,就是監測時如果子程序沒有終止,調用者可以做其他事情,另外還有兩個參數分别為WCONTINUED和WUNTRACED,這兩個參數是跟作業控制有關的,我們在那部分是在介紹。

return val:傳回值與wait函數相同

2.與wait函數的不同點:

  • 在一個子程序終止前,wait使調用者阻塞,而waitpid有一個選項可以使調用者不發生阻塞;
  • waitpid并不等待在其調用之後的第一個終止子程序,它有若幹選項,并可以控制子程序
  • waitpid可以等待一個特定的程序,通過pid參數進行設定;

3.status的構成:我們現在不關心status參數的高16位,在低十六位中,次低8位反應了子程序終止時的退出碼,低8位反應了子程序發生異常終止時的傳回狀态。

下面我們就來寫段代碼分析一下waitpid和status:

用信号殺死子程序(子程序不正常傳回的情況)

程式控制(上):程式建立,程式等待,程式終止

子程序正常傳回(有結束碼)

程式控制(上):程式建立,程式等待,程式終止

4.檢查wait和waitpid所傳回的終止狀态的宏

在上面我們是通過移位操作來擷取到子程序傳回的終止狀态資訊status,其實,Linux還為使用者提供了四種宏定義,用來檢查子程序所傳回的終止狀态。

WIFEXITED(status):表示若為正常終止子程序傳回的狀态,則為真;即該宏定義拿到status的低8位,如果低8位為0則表示子程序正常傳回,如果低八位不為0則表示子程序異常傳回;

WEXITSTATUS(status):這個宏相當于讀取子程序的退出碼,如果WIFEXITED(status)不為真,則這個宏的傳回值将毫無意義;

WIFSIGNALED(status):這個宏表示若為異常終止子程序的傳回狀态,則傳回真并接收到一個不捕捉的信号,他可以和WTERMSIG(status)結合使用,後者為擷取子程序的終止的信号編号。

WIFSTOPPED(status):這個宏是用來判斷若目前子程序的傳回狀态為暫停狀态,則為真。對于這種情況,可以結合WSTOPSIG(status),擷取使子程序暫停的信号編号,這個宏的用法和上面的很相似;

WIFCONTINUED(status):這個宏常用在作業控制暫停後已經繼續的子程序傳回了狀态,則為真,現在我們先不讨論這個宏的使用;

我寫段代碼來使用一下前兩個宏定義,大家可以試着使用下其他宏:

int id=fork();
    int count=;
    if(id==)
    {
        //child proc
        while(count--)
        {
            sleep();
            printf("CHild proc.id:: %d\n",getpid());
        }
        exit();
    }
    else{
        //father proc
        int status=;
        int ret=waitpid(id,&status,);    //option = 
        if(ret>){
            if(WIFEXITED(status))
            {
                printf("child proc return normal,exit code:%d\n",\
                        WEXITSTATUS(status));
                printf("Child proc dead success,ret=%d,status=%d\n",\
                    (status>>)&,(status&));
            }
            else{
                //retuen val is false
                printf("child proc return unnormal\n");
                if(WIFSIGNALED(status))
                {
                  printf("child proc is killed by signal,signal code is %d\n",WTERMSIG(status));
                }
                else{
                    printf("child proc is not killed bu signal\n");
                }
            }
        }
        else{
            printf("Child proc dead false,ret=%d\n",ret);
        }
        printf("Status::%d\n",status);
    }
           

5.關于waitpid的pid參數問題

pid==-1:表示等待任意子程序,相當于wait函數;

pid>0:表示等待程序ID與pid相等的程序;

pid==0:表示等在程序組ID等于調用程序組ID的任一子程序;

pid<-1:表示等待組ID等于pid絕對值的任一子程序;

6.關于waitpid函數的option參數問題

option參數可以使我們能進一步控制waitpid的操作,下面我們就來看一看:

option==0:表示執行waitpid函數的預設版本,将會導緻調用者阻塞;

option==WNOHANG:表示由pid指定的子程序并不是立即能夠傳回的,可以使waitpid函數為非阻塞狀态,此時如果沒有子程序ID傳回,那麼這個函數的傳回值為0;

下面我們來看看waitpid函數怎樣實作非阻塞檢測子程序功能:

代碼如下:

int id=fork();
    int count=;
    if(id==){
        //child proc
        while(count--){
            printf("I am child proc,ID:%d\n",getpid());
            sleep();
        }
        exit();        //exit code 
    }
    else{
        // father proc
        int status=;
        int ret=;
        do{
            printf("Do other thing and check child proc status\n");
            sleep();
        }while((ret=waitpid(id,&status,WNOHANG))==);
        if(ret>){
            printf("child proc dead\n");
            if(WIFEXITED(status)){
                printf("child proc dead normal.exit code:%d\n"\
                        ,WEXITSTATUS(status));
            }
            else{
                if(WIFSIGNALED(status)){
                    printf("child proc is killed by signal,signal code is \
                            %d\n",WTERMSIG(status));
                }
                else{
                    printf("child proc is killed by other things\n");
                }
            }
        }

    }
           

總結

這一篇我們提到了程序建立,程序等待,程序終止等程序控制方面的内容,關于程序控制,大家可以去看看《UNIX環境進階程式設計中的第八章》.另外,在程序控制中,我們如果沒有特殊需要,盡量不要使用vfork函數,因為vfork函數和fork函數與父程序共用一塊位址空間,如果子程序沒有調用exit或者加載程式的話,另外在提取子程序的終止狀态時,我們應該盡量使用宏來代替移位操作。

繼續閱讀