1. 程式人生 > 其它 >C語言建立子程序

C語言建立子程序

技術標籤:Linuxc語言linux

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表示要等待的程序的pidstat_loc表示要儲存程序返回狀態的地方,options是用於控制該函式行為的選項,其中一個常用的選項是WNOHANG,它的作用是防止呼叫程式在等待另一個程序的時候被掛起。