C語言建立子程序
C語言建立子程序
程式執行的時候,可以建立與自己關聯的子程序,建立了這個子程序之後,可以選擇等待這個子程序執行完畢,也可以讓子程序與自己並行執行,還可以終止自己轉而執行子程序。這些操作都是通過一系列相似而又有細微區別的庫函式來實現。下面對這些可以使用的庫函式進行介紹。
system()
它的函式定義如下:
#include <stdlib.h>
int system(const char * string);
這個程序會在系統中另外啟動一個shell,並在新的shell中執行引數string
給定的命令。如果無法啟動shell,這個函式會返回127
-1
,否則返回該命令的退出碼。
在程式中呼叫這個函式之後,呼叫程序會等待新shell中命令執行完成,之後在繼續執行。但是也可以在string
引數的命令後面加上&
讓命令後臺執行,這樣就能實現呼叫程序與新shell程序並行執行。
下面是一個例子:
#include <stdlib.h> #include <stdio.h> int main(void){ char * command = "ps -al"; //新shell中將要執行的命令 printf("Now running a new process.\n"); system(command); printf("Returned from the sub process.\n"); return 0; }
編譯並執行上面的程式會得到下面的輸出:
Now running a new process.
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 5691 5646 0 80 0 - 581762 poll_s pts/0 00:00:04 vim
0 S 1000 6424 6215 0 80 0 - 570 do_wai pts/1 00:00:00 a
0 S 1000 6425 6424 0 80 0 - 598 do_wai pts/1 00:00:00 sh
0 R 1000 6426 6425 0 80 0 - 2897 - pts/1 00:00:00 ps
Returned from the sub process.
可以看到程式執行到一半時,建立了一個子程序ps
,等到子程序執行完畢,才回到自己的程式繼續執行。
如果在執行的命令ps -al
後面加上&
,就會讓子程式後臺執行,從而呼叫程式與子程式就能並行執行。
exec
系列函式
上面的system()
需要啟動一個新shell,並在新的shell中執行子程式,這樣做的執行效率不高,並且因為不同的環境shell版本不一樣,所以也會造成相容性的問題。因此我們在需要建立子程序的時候使用exec
系列函式,而不是system()
。
exec
系列函式的作用都是結束本程式的執行,轉而執行另外一個程式。新建立的子程式集成了原來程序的資源,有著同樣的pid
。
新的程式預設狀態下會繼承已開啟的檔案,但是如果檔案使用fcntl()
開啟並且設定了FD_CLOEXEC
,則執行新程序的時候會關閉檔案。
下面是所有exec
系列函式的定義:
int execl(const char *pathname, const char *arg, ...)
int execv(const char *pathname, char *const argv[])
int execle(const char *pathname, const char *arg, ..., char *const envp[])
int execve(const char *pathname, char *const argv[], char *const envp[])
int execlp(const char *filename, const char *arg, ...)
int execvp(const char *filename, char *const argv[])
其中檔名的不同表示了傳遞引數的方式,查詢程式的方式以及是否可以設定環境變數不同。對於名字的前四個字母exec
都是統一的函式名,後面跟上l
表示按照引數列表的方式接受多個函式引數;v
表示利用字串陣列來接受多個引數;p
表示系統將通過搜尋PATH
環境變數來查詢程式;e
表示可以傳入環境變數。
下面是一個使用exec
系列函式從當前程序啟動ps
程式的例子,在這個例子中使用了上面的所有方法:
#include <unistd.h>
#include <stdio.h>
int main(void){
char * const ps_argv[] = {"ps", "-al", 0}; //就算使用陣列的形式傳入引數,也要用0表示引數的結尾
char * const ps_envp[] = {"PATH=/bin:/usr/bin", "TERM=console", 0};
//下面的函式只有第一個可以執行,剩餘的只是作為演示
execl("/bin/ps", "blahhh", "-al", 0);
//剩下的函式不會被執行
execlp("ps", "ps", "-af", 0);
execle("/bin/ps", "ps", "-af", 0, ps_envp);
execv("/bin/ps", ps_argv);
execvp("ps", ps_argv);
execve("/bin/ps", ps_argv, ps_envp);
}
關於這個程式,有幾個需要注意的地方:
- 程式執行了第一個
execl()
函式就會停止,轉而執行引數中指定程式,除非指定程式執行失敗,才會造成函式返回,從而繼續往下執行。
在往新程式中傳入引數的時候,注意第一個引數是不讀取的。在execl("/bin/ps", "blahh", "-al", 0);
中,第一個引數表示要執行的程式,而第二個引數是傳入程式的第一個引數,但這個引數不被讀取,所以這裡隨便輸入一也沒關係,程式會從第三個引數開始讀入程式的引數。這個規則對於用陣列讀取引數的函式也使用,在ps_argv
中第一個引數也不是-al
。 - 不管用函式引數還是陣列傳入引數,最後一個引數都要是
0
以表示結束。
fork()
函式
使用fork()
函式可以複製當前的程序。新建立的程序與原來的程序幾乎完全一樣,但是兩個程序都有自己的資料,環境和檔案描述符。並且新建立的程序是當前程序的子程序,基於這個特性,可以使用fork()
先建立一個與當前程序一模一樣的子程序,然後使用exec()
函式使子程序轉而執行另一個程式,就可以使另外一個子程序成為當前程序的子程序。
如果子程序建立失敗,fork()
函式會返回-1
,並且將錯誤碼儲存在errno
中,如果呼叫成功則返回子程序的PID
。因為子程序跟父程序執行的程式碼相同,所以在子程序中也會有一個fork()
函式,但是子程序中的fork()
函式不會建立另外一個子程序,因為如果這樣就會無限迴圈下去,取而代之子程序的fork()
函式不會建立任何子程序,同時返回0
,可以用這一點來判斷當前程序是一個父程序還是一個子程序。下面是一個例子:
#include <stdio.h>
#include <unistd.h>
int main(void){
int res = fork();
switch (res){
case -1:
printf("Failed to creat a new sub process.\n"); break;
case 0:
printf("This is a sub proccess.\n"); break;
default:
printf("This is a parent process.\n");
}
return 0;
}
等待一個程序
當使用fork()
函式建立一個子程序之後,子程序與父程序是並行執行的,如果想將父程序掛起知道子程序執行結束,可以使用wait()
函式。這個函式會讓父程序等待到子程序執行完畢之後在繼續執行。當子程序結束之後,這個函式會返回子程序的PID
,並且會將子程序返回的狀態碼儲存到指定位置stat_loc
,指定的位置只需要一個整型變數即可。
下面是該函式的定義:
#include <sys/wait.h>
#include <sys/types.h>
pid_t wait(int *stat_loc);
函式返回的狀態資訊被儲存在一個整型數裡,這個資料不是人能看得懂的,如果要檢視子程序結束時的狀態,可以使用sys/wait.h
中定義的巨集來實現,常用的巨集有:
巨集 | 說明 |
---|---|
WIFEXITED(stat_val) | 如果子程序正常結束,它就取一個非零值 |
WEXITSTATUS(stat_val) | 如果子程序正常結束,返回子程序的退出碼 |
WIFSIGNALED(stat_val) | 如果子程序因為一個未捕獲的訊號終止,返回非零值 |
WTERMSIG(stat_val) | 如果子程序因為一個未捕獲的訊號終止,返回訊號程式碼 |
WIFSTOPPED(stat_val) | 如果子程序意外終止,返回一個非零值 |
WSTOPSIG(stat_val) | 如果子程序意外終止,返回一個訊號程式碼 |
下面是一個例子:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(void){
int stat;
sleep(1);
pid_t pid = fork();
pid_t wt = wait(&stat);
if(pid){
printf("Contents in stat is: %d\n", stat);
printf("WIFEXITED = %d\n", WIFEXITED(stat));
}
return 0;
}
這個函式有另外一個加強版本,它會讓程式等待另外任意一個程式執行完畢。它的定義為:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int * stat_loc, int options);
這個函式的返回值跟wait()
函式一樣,但是引數卻不一樣,pid
表示要等待的程序的pid
,stat_loc
表示要儲存程序返回狀態的地方,options
是用於控制該函式行為的選項,其中一個常用的選項是WNOHANG
,它的作用是防止呼叫程式在等待另一個程序的時候被掛起。