1. 程式人生 > >程序控制(上):程序建立,程序等待,程序終止

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

程序建立

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

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=10;
    if(id==0)
    {
        //child proc
        while(count--)
        {
            sleep(1);
            printf("CHild proc.id:: %d\n",getpid());
        }
        exit(11);
    }
    else{
        //father proc
        int status=0;
        int ret=waitpid(id,&status,0);    //option = 0
        if(ret>0){
            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>>8)&0xff,(status&0xff));
            }
            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=10;
    if(id==0){
        //child proc
        while(count--){
            printf("I am child proc,ID:%d\n",getpid());
            sleep(1);
        }
        exit(9);        //exit code 
    }
    else{
        // father proc
        int status=0;
        int ret=0;
        do{
            printf("Do other thing and check child proc status\n");
            sleep(1);
        }while((ret=waitpid(id,&status,WNOHANG))==0);
        if(ret>0){
            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或者載入程式的話,另外在提取子程序的終止狀態時,我們應該儘量使用巨集來代替移位操作。