1. 程式人生 > >exec族函式詳解及迴圈建立子程序

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”使用命令列引數。

  上面程式註釋部分有這個程式。

  就不一一舉例了,有興趣可以自己試一下。

  總結:有問題,歡迎及時評論、交流與學習。