exec族函式詳解及迴圈建立子程序
前言:之前也知道exec族函式,但沒有完全掌握,昨天又重新學習了一遍,基本完全掌握了,還有一些父子程序和迴圈建立子程序的問題,還要介紹一下環境變數,今天分享一下。
一、環境變數
先介紹下環境的概念和特性,再舉例子吧。
環境變數,是指在作業系統中用來指定作業系統執行環境的一些引數。通常具備以下特徵:
① 字串(本質) ② 有統一的格式:名=值[:值] ③ 值用來描述程序環境資訊。
儲存形式:與命令列引數類似。char *[]陣列,陣列名environ,內部儲存字串,NULL作為哨兵結尾。
使用形式:與命令列引數類似。
引入環境變量表:須宣告環境變數。extern char ** environ;
環境變數跟很多東西有關係,例如接下來的exec族函式,這也是為什麼要先介紹下環境變數的原因,對理解exec族函式很有幫助;例如,Linux是什麼樣的系統?多使用者多工開源系統,每個使用者的登入資訊環境變數都會記錄。舉例一下常用的環境變數:
-
PATH
可執行檔案的搜尋路徑。ls命令也是一個程式,執行它不需要提供完整的路徑名/bin/ls,然而通常我們執行當前目錄下的程式a.out卻需要提供完整的路徑名./a.out,這是因為PATH環境變數的值裡面包含了ls命令所在的目錄/bin,卻不包含a.out所在的目錄。PATH環境變數的值可以包含多個目錄,用:號隔開。在Shell中用echo命令可以檢視這個環境變數的值:
$ echo $PATH
-
SHELL
當前Shell,它的值通常是/bin/bash。
-
TERM
當前終端型別,在圖形介面終端下它的值通常是xterm,終端型別決定了一些程式的輸出顯示方式,比如圖形介面終端可以顯示漢字,而字元終端一般不行。
-
LANG
語言和locale,決定了字元編碼以及時間、貨幣等資訊的顯示格式。
-
HOME
當前使用者主目錄的路徑,很多程式需要在主目錄下儲存配置檔案,使得每個使用者在執行該程式時都有自己的一套配置
介紹跟環境變數相關的函式:
char *getenv(const char *name); //獲取環境變數
int setenv(const char *name, const char *value, int overwrite); //新增或改變環境變數
int unsetenv(const char *name); //刪除
二、fork函式及迴圈建立子程序
先說一個問題,學會fork並寫程式時,可能都會遇到一個問題如下:
./a.out的輸出跑到終端上了,想過為什麼?接下來我會解釋這個問題。
1.fork函式
原型如下:
pid_t fork(void);
很好理解建立一個子程序,但需要真正理解這個函式需要理解:執行一次返回兩次,就是有兩個返回值,如下:
(1)返回子程序的pid
(2)返回0
2.getpid、getppid函式
兩個函式原型,如下:
pid_t getpid(void); //獲取當前程序ID pid_t getppid(void); //獲取父程序ID
3.fork建立子程序
主要建立一個子程序,並列印當前程序和父程序ID,並且打下了當前父程序的父程序ID,想一下父程序的父程序ID會是多少呢?程式如下:
#include<stdio.h> #include <unistd.h> #include <stdlib.h> int main(void) { pid_t pid; pid = fork(); if (pid == -1 ) { perror("fork"); exit(1); } else if (pid > 0) { //parent //sleep(1); //保證子程序先執行 printf("I'm parent pid = %d, parentID = %d\n", getpid(), getppid()); } else if (pid == 0) { //child printf("child pid = %d, parentID = %d\n", getpid(), getppid()); } return 0; }View Code
程式很簡單不再解釋,但要說明幾個問題,結果如下:
看到結果知道了父程序也有父程序的ID,並查詢一下它,是bash其實就是shell,shell通過某種方式創子程序(就是我們程式中的父程序),然後子程序再建立子程序。
對了,還有一個問題,有一個sleep函式,註釋是:確保子程序先執行,父子程序的執行順序是由CPU的排程演算法決定,但為啥我註釋了sleep,還是父程序先執行。說點題外話吧,APUE(unix環境高階程式設計)的作者做過實驗,98%的概率的都是父程序先執行。
4.迴圈建立子程序
接下來怎麼建立多個子程序,直接給正確的程式吧,先演示一下有些問題的程式碼,如下:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(int argc, char *argv[]) { int n = 5, i; //預設建立5個子程序 if (argc == 2) { n = atoi(argv[1]); } for (i = 0; i < n; i++) //出口1,父程序專用出口 if (fork() == 0) break; //出口2,子程序出口,i不自增 if (n == i) { //sleep(n); printf("I am parent, pid = %d\n", getpid()); } else { //sleep(i); printf("I'm %dth child, pid = %d\n", i+1, getpid()); } return 0; }View Code
演示結果:
會出現最開始的問題:輸出跑到終端上。接下來解釋為什麼會出現這個問題?
原因:shell、父程序和子程序都搶奪CPU,shell當父程序執行return 0,認為父程序執行完了,返回到終端,當子程序還沒執行完,就輸出到終端了。
三、exec族函式
其實有七種以exec開頭的函式,統稱exec函式:
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg,..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[],char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]); //真的系統呼叫
主要函式紅色部分的說明:
l (list) 命令列引數列表
p (path) 搜素file時使用path變數
v (vector) 使用命令列引數陣列
e (environment) 使用環境變數陣列,不使用程序原有的環境變數,設定新載入程式執行的環境變數
1.execlp函式
載入一個程序,藉助PATH環境變數
引數說明:
file:檔名,通過PATH環境變數查詢
arg:命令列引數,要強掉一下,第一個arg相當於arg[0],並要以NULL結尾
...:可變引數
通過呼叫ls來舉例:
execlp("ls","ls","-l",NULL);
其實,可以試一下第二個標紅的引數,隨便寫也不會有錯誤的,說明核心並不使用第二個引數。
程式示例如下:
#include <stdlib.h> #include <unistd.h> #include <stdio.h> int main(int argc, char *argv[]) { printf("========================\n"); char *argvv[] = {"ls", "-l", "-F", "R", "-a", NULL}; pid_t pid = fork(); if (pid == 0) { //execl("/bin/ls", "ls", "-l","-F", "-a", NULL); //execv("/bin/ls", argvv); execlp("ls","ls","-l","-F","-a",NULL); perror("execlp"); exit(1); } else if (pid > 0) { sleep(1); printf("parent\n"); } return 0; }View Code
演示結果就不展示了,可以自己在終端手動輸入命令,進行對照。
2.execl函式
載入一個程序, 通過 路徑+程式名 來載入。
跟execlp的主要區別在於不是通過環境變數獲取了,相對路徑也是可以的。
上面程式註釋部分有這個程式。
3.execv函式
int execv(const char *path, char *const argv[]);
注意“v”使用命令列引數。
上面程式註釋部分有這個程式。
就不一一舉例了,有興趣可以自己試一下。
總結:有問題,歡迎及時評論、交流與學習。