1. 程式人生 > >Linux應用程式設計學習記錄(五)

Linux應用程式設計學習記錄(五)

        現在來學習一下關於程序的一些操作。

1.  使用fork()函式建立程序

A)函式一定是在程式中被呼叫的,而呼叫fork函式的程式在執行中是一個程序。在這個程序中,執行fork的效果,是把自己完完全全複製一遍。這個新的程序是原來程序的子程序,他倆構成了父子程序的關係。並且是同時執行的,具體誰先誰後要由排程演算法來決定。

B)子程序是由父程序將自己複製一份而產生的,因此不僅程式邏輯與父程序一致(因為程式碼完全一致),就連執行狀態都與父程序一致。子程序被創建出來後,會以為自己就是父程序,而繼續執行fork後面的程式碼。這就麻煩了,我們其實是希望程式能夠識別出自己是不是子程序的。換句話說,我們創建出新的程序,不是為了幹一件與父程序一模一樣的工作,我們需要在fork之後有分支!這就需要用到fork函式的返回值了。

C)關於fork函式的返回

        如果函式執行成功,那麼就意味著子程序建立成功。這時候函式不僅會向父程序返回一個值,也會向子程序返回一個值。向父程序返回的是子程序的PID號,向子程序返回的是0。如果執行失敗,即子程序建立失敗,那麼此時只會向父程序返回數值,為一個小於0的錯誤碼。

        對於父程序很好理解,它執行了fork函式,然後檢查自己剛剛執行的fork函式的返回值,再對返回值進行判斷。對於子程序,它一開始以為自己就是父程序,它也檢查“自己剛剛執行的fork函式的返回值”,結果發現是0。(以為自己是爸爸,結果讓所有人大吃一驚..... )

D)說了那麼多,貼一段程式碼最實在了:

#include <stdio.h> #include <unistd.h> #include <stdlib.h>

int main(int argc, char *argv[]) {     pid_t pid;     pid = fork();     if (pid == 0)         printf("I am child, my PID is %d\n", getpid());     else if (pid > 0)         printf("I am parent, my PID is %d\n", getpid());     else         printf("create new process failed!\n");     return 0; }

在父程序中,會進入pid > 0的分支,而在子程序中,會進入pid == 0的分支。

2.  終止程序

A)程序終止分為正常終止和異常終止兩大類。

        正常終止的方式有:在main函式中,用return返回;或者呼叫類exit函式。

        對於異常終止方式有:呼叫abort函式;或者收到一個訊號終止。

B)正常終止方式中,在main函式中使用return函式返回的實質是自行呼叫exit類函式來終止程序。

        在main函式中使用return 0,等效於exit(0)。

C)異常終止方式中,abort函式是通過SIGABRT訊號來實現的。

3.  exec族函式

        這是一系列函式,具體包含6個。它們的功能是一致的,只是具體用法不同。

A)該函式的使用背景如下

        當我們用fork函式建立新程序後,只能通過對fork函式的返回值進行判斷來搞一些分支操作。在分支中,可以做一些差異化處理。但本質上,子程序與父程序的程式碼是一模一樣的。如果只能這樣用多程序,實在是太不靈活了。那麼我們能不能在建立了新的程序後,讓它去執行另一個程式呢?當然是可以的,這就要使用exec族函數了。它可以將子程序所執行的程式完全替換成新程式。來一張圖吧:假設要子程序執行的程式是/bin目錄下名為ls的程式。

        注意看,執行exec函式後,子程序的PID和PPID號都沒有變,只是執行的程式碼換掉了。

B)exec族函式的返回值

        exec族函式只有在執行失敗的情況下才會有返回值,返回值是-1。如果一切正常,不發生返回。

C)來具體看看exec族的6個函式吧

        注意觀察這些函式的名字,會發現它們都是在exec的後面,加上了l、p、e、v這些字母。他們分別代表了不同的含義,為了方便記憶,下面來分析一下。

首先,來看看字母 l 和 v 。對於所有的exec族函式,在命名時必須從 l 和 v 中選一個。它倆代表執行函式時引數的傳遞方式。

什麼是引數傳遞?呼叫執行某個程式,其實就好像是你在命令列中輸入   

./可執行檔案  引數1 引數2

        有些程式沒有引數,直接執行。有些函式卻需要引數,那麼這個引數怎麼傳遞給程式呢?這就涉及到引數的傳遞方式了。exec族函式支援兩種引數傳遞方式

其一,引數列表形式,即以列舉的形式一個個把引數列出來,注意最後一個引數必須是NULL。

        比如,execl("函式名","引數1","引數2"...NULL);

其二,引數向量形式,這就有點像main函式的第二個引數char *argv[],它是由若干字元型指標構成的陣列,每個指標其實都指向著一個引數,當然這些引數被當成字串處理,取走的是字串的首地址。

上述兩種方式,對應到函式名上就是加l或者加v的區別:加l就是列表,加v就是向量。

其次,來看看字母 p 。它決定了程式檔案的查詢方式。如果exec族函式的命名中帶p,則你可以只寫程式名,不寫它所在的目錄,系統會自動在PATH環境變數所指定的各個目錄下去搜索你指定的程式。如果exec族函式的命名中不帶p,則你必須把程式的路徑寫的清清楚楚。

        當然,使用帶有字母p命名的exec族函式,也可以在呼叫函式時寫清楚路徑。這麼一看的話,帶字母p的exec函式豈不是萬金油?

最後,來看看字母 e 。它決定了新程序的環境變數,如果不帶字母e。則新程序繼承了environ這個全域性變數所指的環境變數。而如果你希望用新的環境變數來替代,那麼就使用帶字母e的exec族函式。

        這時exec函式的宣告中會有char *const envp[]這樣一個引數,它與main函式的第三個引數一樣,都是由若干個字元型指標所構成的陣列。環境變數的傳遞方式也是類似的。