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”使用命令行參數。

  上面程序注釋部分有這個程序。

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

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

  

  

 

  

  

 

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM