Linux程序

tenzzZ發表於2024-05-02

程式與程序

程式:是可執行檔案,其本質是是一個檔案,程式是靜態的,同一個程式可以執行多次,產生多個程序

程序:它是程式的一次執行過程,當應用程式被載入到記憶體中執行之後它就稱為了一個程序,程序是動態的,程序的生命週期是從程式執行到程式退出

父子程序:當一個程序A透過frok()函式建立出程序B,A為B的父程序,B為A的子程序

程序號(PID):也稱程序識別符號,是一個非負整數,用於唯一標識系統下某一個程序。

  • pid=0:交換程序,pid=1:init程序,

  • linux中可以使用ps -aux檢視系統中的所有程序,可配合管道進行使用 ps -aux | grep xxx。

  • pid_t getpid(void):該系統呼叫用於獲取當前程序的pid。

  • pid_t getppid(void):用於獲取父程序的pid。

程序的建立

一個現有的程序可以呼叫 fork()函式建立一個新的程序, 呼叫 fork()函式的程序稱為父程序,由 fork()函
數建立出來的程序被稱為子程序(child process) , fork()函式原型如下所示(fork()為系統呼叫)。

應用場景:

  • 在諸多的應用中,建立多個程序是任務分解時行之有效的方法,譬如,某一網路伺服器程序可在監聽客戶端請求的同時,為處理每一個請求事件而建立一個新的子程序,與此同時,伺服器程序會繼續監聽更多的客戶端連線請求。
  • 一個程序要執行不同的程式。 譬如在程式 app1 中呼叫 fork()函式建立了子程序,此時子程序是要
    去執行另一個程式 app2,也就是子程序需要執行的程式碼是 app2 程式對應的程式碼,子程序將從 app2
    程式的 main 函式開始執行。這種情況,通常在子程序從 fork()函式返回之後立即呼叫 exec 族函式
    來實現,關於 exec 函式將在後面內容向大家介紹。

fork():

作用:用於建立一個子程序,原型如下:

#include <unistd.h>
pid_t fork(void);

理解 fork()系統呼叫的關鍵在於,完成對其呼叫後將存在兩個程序,一個是原程序(父程序)、另一個
則是建立出來的子程序,並且每個程序都會從 fork()函式的返回處繼續執行,會導致呼叫 fork()返回兩次值,
子程序返回一個值、父程序返回一個值 。

#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    pid = getpid();

    fork();     //建立一個子程序,子程序從這開始執行

    printf("pid = %d\r\n",pid);     //父子程序都會執行該語句
    return 0;
}

fork()呼叫成功後,會在父程序中返回子程序的 PID,而在子程序中返回值是 0;如果呼叫失敗,父進
程返回值-1,不建立子程序,並設定 errno。

fork返回值總結:父子程序都會從fork()函式的返回處繼續執行。

  • 父程序:返回子程序PID,呼叫失敗返回-1
  • 子程序:返回0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    pid = fork();
    switch(pid)
    {
        case -1:
            perror("fork error");
            exit(-1);
        case 0:
            printf("這是子程序列印的資訊:父程序pid = %d,子程序pid = %d\r\n",getppid(),getpid());
            _exit(0);   //子程序使用_exit退出
        default:
            printf("這是父程序列印的資訊:父程序pid = %d,子程序pid = %d\r\n",getpid(),pid);
            exit(0);
    }
}

vfork()

也是用於建立子程序,返回值與fork()一樣。原型如下:

#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void)

vfork與fork區別:

  • vfork直接使用父程序儲存空間,不複製。
    • vfork()與 fork()一樣都建立了子程序,但 vfork()函式並不會將父程序的地址空間完全複製到子程序中,因為子程序會立即呼叫 exec(_exit) ,於是也就不會引用該地址空間的資料。不過在子程序呼叫 exec 或_exit 之前,它在父程序的空間中執行、 子程序共享父程序的記憶體。這種最佳化工作方式的實現提高的效率; 但如果子程序修改了父程序的資料(除了 vfork 返回值的變數)、進行了函式呼叫、或者沒有呼叫 exec 或_exit 就返回將可能帶來未知的結果
  • vfork保證子程序先執行,當子程序呼叫exit退出後,父程序才執行。
    • vfork()保證子程序先執行, 子程序呼叫 exec 之後父程序才可能被排程執行。
      雖然 vfork()系統呼叫在效率上要優於 fork(),但是 vfork()可能會導致一些難以察覺的程式 bug,所以儘量避免使用 vfork()來建立子程序,雖然 fork()在效率上並沒有 vfork()高,但是現代的 Linux 系統核心已經採用了寫時複製技術來實現 fork(),其效率較之於早期的 fork()實現要高出許多,除非速度絕對重要的場合,

父子資料共享

使用fork()建立子程序後,子程序會把父程序中的資料複製一份;該實驗中,子程序對a進行了+10操作,但是不影響父程序中的a。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    int a = 10;
    pid = fork();										//fork建立子程序
    switch(pid)
    {
        case -1:
            perror("fork error");
            exit(-1);
        case 0:
            a+=10;										//子程序中改變資料
            printf("我是子程序: a = %d\r\n",a);
            exit(0);
        default:
            printf("我是父程序: a = %d\r\n",a);
            exit(0);
    }
}

使用vfork()建立子程序,子程序不會複製父程序中的資料,而是直接使用父程序中的資料。子程序中更改資料也影響父程序中的資料。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    int a = 10;
    pid = vfork();			//vfork建立子程序
    switch(pid)
    {
        case -1:
            perror("fork error");
            exit(-1);
        case 0:
            a+=10;			//子程序中改變資料
            printf("我是子程序: a = %d\r\n",a);
            exit(0);
        default:
            printf("我是父程序: a = %d\r\n",a);
            exit(0);
    }
}

父子競爭

呼叫 fork()之後,子程序成為了一個獨立的程序,可被系統排程執行,而父程序也繼續被系統排程執行,
那麼誰先訪問cpu呢?答案是不確定的,父子程序執行順序不確定。

從測試結果可知,雖然絕大部分情況下,父程序會先於子程序被執行,但是並不排除子程序先於父程序
被執行的可能性。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    pid = fork();
    switch(pid)
    {
        case -1:
            perror("fork error");
            exit(-1);
        case 0:
            printf("我是子程序\r\n");
            exit(0);
        default:
            printf("我是父程序\r\n");
            exit(0);
    }
}

程序退出

程序退出包括正常退出和異常退出:

正常退出

  • main()函式中透過 return 語句返回來終止程序;
  • 應用程式中呼叫 exit()函式終止程序;
  • 應用程式中呼叫exit()或_Exit()終止程序;
  • 補充:程序中最後一個執行緒返回,程序也會退出。最後一個執行緒呼叫pthrea_exit();

異常退出

  • 應用程式中呼叫 abort()函式終止程序;

  • 程序接收到一個訊號,譬如 SIGKILL 訊號。

般使用 exit()庫函式而非exit()系統呼叫 ,原因在於 exit()最終也會透過exit()終止程序,但在此之前,它將會完成一些其它的工作, exit()函式會執行的動作如下

  1. 如果程式中註冊了程序終止處理函式,那麼會呼叫終止處理函式。
  2. 重新整理 stdio 流緩衝區 。
  3. 執行_exit()系統呼叫。
#include <stdlib.h>
void exit(int status);		//傳入狀態碼,用於標識為啥退出,0表示正常退出,-1表示異常退出

監視子程序

就是等待子程序退出。對於許多需要建立子程序的程序來說,有時設計需要監視子程序的終止時間以及終止時的一些狀態資訊,在某些設計需求下這是很有必要的。

wait()

系統呼叫 wait()可以等待程序的任一子程序終止,同時獲取子程序的終止狀態資訊,wait()函式的作用除了獲取子程序的終止狀態資訊之外,更重要的一點,就是回收子程序的一些資源,俗稱為子程序“收屍” , 其函式原型如下所示:

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);

引數介紹:

  • status: 引數 status 用於存放子程序終止時的狀態資訊,引數 status 可以為 NULL,表示不接收子程序
    終止時的狀態資訊。
  • 返回值: 若成功則返回終止的子程序對應的程序號;失敗則返回-1。

系統呼叫 wait()將執行如下動作: 一次 wait()呼叫只能處理一次。

  • 呼叫 wait()函式,如果其所有子程序都還在執行,則 wait()會一直阻塞等待,直到某一個子程序終
    止;

  • 如果程序呼叫 wait(),但是該程序並沒有子程序, 也就意味著該程序並沒有需要等待的子程序, 那 麼 wait()將返回錯誤,也就是返回-1、並且會將 errno 設定為 ECHILD。

  • 如果程序呼叫 wait()之前, 它的子程序當中已經有一個或多個子程序已經終止了,那麼呼叫 wait()
    也不會阻塞,而是會立即替該子程序“收屍” 、處理它的“後事” 。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <errno.h>
    int main(void)
    {
        int status;
        int ret;
        int i;
        /* 迴圈建立 3 個子程序 */
        for (i = 1; i <= 3; i++) 
        {
            switch (fork()) 
            {
                case -1:
                    perror("fork error");
                    exit(-1);
                case 0:
                    /* 子程序 */
                    printf("子程序<%d>被建立\n", getpid());
                    sleep(i);
                    _exit(i);
                default:
                    /* 父程序 */
                    break;      //跳出switch
            }
        }
        sleep(1);
        printf("~~~~~~~~~~~~~~\n");
        for (i = 1; i <= 3; i++) 
        {
            ret = wait(&status);
            if (-1 == ret) 
            {
                if (ECHILD == errno) 
                {
                    printf("沒有需要等待回收的子程序\n");
                    exit(0);
                }
                else 
                {
                    perror("wait error");
                    exit(-1);
                }
            }
            printf("回收子程序<%d>, 終止狀態<%d>\n", ret,
            WEXITSTATUS(status));
        }
    
        exit(0);
    }
    

waitpid()

用 wait()系統呼叫存在著一些限制,這些限制包括如下:

  • 如果父程序建立了多個子程序,使用 wait()將無法等待某個特定的子程序的完成,只能按照順序等待下一個子程序的終止,一個一個來、誰先終止就先處理誰;
  • 如果子程序沒有終止,正在執行,那麼 wait()總是保持阻塞,有時我們希望執行非阻塞等待,是否有子程序終止,透過判斷即可得知;
  • 使用 wait()只能發現那些被終止的子程序,對於子程序因某個訊號(譬如 SIGSTOP 訊號)而停止(注意,這裡停止指的暫停執行),或是已停止的子程序收到 SIGCONT 訊號後恢復執行的情況就無能為力了

函式原型:

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

引數介紹:

  • pid: 引數 pid 用於表示需要等待的某個具體子程序,關於引數 pid 的取值範圍如下:
    • 如果 pid 大於 0,表示等待程序號為 pid 的子程序;
    • 如果 pid 等於 0,則等待與呼叫程序(父程序)同一個程序組的所有子程序;
    • 如果 pid 小於-1,則會等待程序組識別符號與 pid 絕對值相等的所有子程序;
    • 如果 pid 等於-1,則等待任意子程序。 wait(&status)與 waitpid(-1, &status, 0)等價。
  • status: 與 wait()函式的 status 引數意義相同。
  • options: 引數 options 是一個位掩碼,可以包括 0 個或多個標誌: WNOHANG;WUNTRACED ;WCONTINUED
  • 返回值: 返回值與 wait()函式的返回值意義基本相同,在引數 options 包含了 WNOHANG 標誌的情況
    下,返回值會出現 0。

waitid()

waitid()與 waitpid()類似,不過 waitid()提供了更多的擴充套件功能,具體的使用方法筆者便不再介紹,大家有興趣可以自己透過 查閱資料進行學習 。

孤兒程序

父程序先於子程序結束,也就是意味著,此時子程序變成了一個“孤兒”,我們把這種程序就稱為孤兒
程序。

在 Linux 系統當中,所有的孤兒程序都自動成為 init 程序(程序號為 1)的子程序。

殭屍程序

程序結束之後,通常需要其父程序為其“收屍”,回收子程序佔用的一些記憶體資源,父程序透過呼叫
wait()(或其變體 waitpid()、 waitid()等)函式回收子程序資源,歸還給系統 。

如果子程序先於父程序結束,此時父程序還未來得及給子程序“收屍”,那麼此時子程序就變成了一個
殭屍程序。

另外一種情況,如果父程序並沒有呼叫 wait()函式然後就退出了,那麼此時 init 程序將會接管它的子程序並
自動呼叫 wait(), 故而從系統中移除殭屍程序。

系統中存在大量的殭屍程序,它們勢必會填滿核心程序表,從而阻礙新程序的建立。

需要注意的是,殭屍程序是無法透過訊號將其殺死的,即使是“一擊必殺”訊號 SIGKILL 也無法將其殺死,那麼這種情況下,只能殺死殭屍程序的父程序(或等待其父程序終止),這樣 init 程序將會接管這些殭屍程序,從而將它們從系統中清理掉!

總結

  1. 孤兒程序:父程序先結束【爹先掛了】,init養父(程序號為1) 收養,並被重新設定為其子程序
  2. 殭屍程序:子程序終止,但父程序沒有使用wait或waitpid收集其資源【爹不管】
  3. 守護程序:在後臺執行,不與任何終端關聯的程序【後臺天使】

守護程序

守護程序(Daemon) 也稱為精靈程序,是執行在後臺的一種特殊程序,它獨立於控制終端並且週期性
地執行某種任務或等待處理某些事情的發生, 主要表現為以下兩個特點:

  • 長期執行。系統啟動開始執行,除非強制終止,不然直到系統關機才停止執行。普通程序是使用者登入或者程式執行時建立,到使用者登出或程式退出時終止。但守護程序不受使用者登入或登出的影響,一直執行。
  • 與終端脫離。在 Linux 中,系統與使用者互動的介面稱為終端,每一個從終端開始執行的程序都
    會依附於這個終端 。

守護程序 Daemon,通常簡稱為 d,一般程序名後面帶有 d 就表示它是一個守護程序。 TTY列中是?號,表示沒有控制終端,也就是守護程序。

exec簇函式

當子程序的工作不再是執行父程序的程式碼段,而是執行另一個新程式的程式碼(另一個可執行程式),那麼這個時候子程序可以透過 exec 函式來實現執行另一個新的程式 。

說到這裡,為什麼需要在子程序中執行新程式?其實這個問題非常簡單,雖然可以直接在子程序分支編寫子程序需要執行的程式碼,但是不夠靈活,擴充套件性不夠好,直接將子程序需要執行的程式碼單獨放在一個可執行檔案中不是更好嗎, 所以就出現了 exec 操作 。

exec 族函式中的庫函式都是基於系統呼叫 execve()而實現的,雖然引數各異、但功能相同, 包括: execl()、 execlp()、 execle()、 execv()、execvp()、 execvpe(), 它們的函式原型如下所示:

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, 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[]);

execve()

execve()可以將新程式載入到某一程序的記憶體空間執行,使用新的程式替換舊的程式,而程序的棧、資料、以及堆資料會被新程式的相應部件所替換,然後從新程式的 main()函式開始執行。 函式原型如下:

#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]); 

返回值: execve 呼叫成功將不會返回;失敗將返回-1,並設定 errno。

引數介紹:

  • filename: 指向需要載入當前程序空間的新程式的路徑名,既可以是絕對路徑、也可以是相對路徑。
  • argv: 指定了傳遞給新程式的命令列引數。是一個字串陣列, 該陣列對應於 main(int argc, char*argv[])函式的第二個引數 argv,且格式也與之相同,是由字串指標所組成的陣列,以 NULL 結束。argv[0]對應的便是新程式自身路徑名。
  • envp: 引數 envp 也是一個字串指標陣列, 指定了新程式的環境變數列表, 引數 envp 其實對應於新程式的 environ 陣列,同樣也是以 NULL 結束,所指向的字串格式為 name=value。

使用試列:

下列execve()函式的使用並不是它真正的應用場景, 通常由 fork()生成的子程序對 execve()的呼叫最為頻繁,也就是子程序執行 exec 操作; 試列 中的 execve 用法在實際的應用不常見,這裡只是給大家進行演示說明。

testAPP程式中透過呼叫execve函式來執行newAPP程式

							/*testAPP.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    char *arg_arr[5];
    char *env_arr[5] = {"NAME=app", "AGE=25",
    "SEX=man", NULL};		//設定newAPP程式中的環境變數
    if (2 > argc)
    	exit(-1);
    arg_arr[0] = argv[1];
    arg_arr[1] = "Hello";
    arg_arr[2] = "World";
    arg_arr[3] = NULL;		//必須以NULL結束
    execve(argv[1], arg_arr, env_arr);
    
    perror("execve error");		//execve成功退出後,該程式也結束,並不會執行這些程式碼
    exit(-1);
}
								/*newAPP.c*/
#include <stdio.h>
#include <stdlib.h>
extern char **environ;			//對應testAPP中的env_arr

int main(int argc, char *argv[])
{
    char **ep = NULL;
    int j;
    for (j = 0; j < argc; j++)
    {
        printf("argv[%d]: %s\n", j, argv[j]);	//列印傳遞過來的引數
    }
    puts("env:");
    for (ep = environ; *ep != NULL; ep++)
    {
        printf(" %s\n", *ep);		//列印環境變數
    }
    exit(0);
} 

execl()

函式原型:

int execl(const char *path, const char *arg, ... );

引數介紹:

  • path:指向新可執行程式的路徑,可以是相對和絕對路徑

  • 引數列表:把引數列表依次排列,使用可變引數形式傳遞,本質上也是多個字串,以 NULL 結尾。

    execl("./newApp", "./newApp", "Hello", "World", NULL);
    

execv

函式原型:

int execv(const char *path, char *const argv[]);

引數介紹:

  • path:指向新可執行程式的路徑,可以是相對和絕對路徑。

  • argv:指定了傳遞給新程式的命令列引數。是一個字串陣列, 該陣列對應於 main(int argc, char*argv[])函式的第二個引數 argv,且格式也與之相同,是由字串指標所組成的陣列,以 NULL 結束。argv[0]對應的便是新程式自身路徑名。

    char *arg_arr[5];
    arg_arr[0] = "./newApp";
    arg_arr[1] = "Hello";
    arg_arr[2] = "World";
    arg_arr[3] = NULL;
    execv("./newApp", arg_arr);
    

execlp()execvp()

int execlp(const char *file, const char *arg, ... );
int execvp(const char *file, char *const argv[]);

execlp()和 execvp()在 execl()和 execv()基礎上加了一個 p,這個 p 其實表示的是 PATH。execl()和execv()要求提供新程式的路徑名,而 execlp()和 execvp()則允許只提供新程式檔名,系統會在由
環境變數 PATH 所指定的目錄列表中尋找相應的可執行檔案,如果執行的新程式是一個 Linux 命令,這將很有用; 當然, execlp()和 execvp()函式也相容相對路徑和絕對路徑的方式。

execle()與execvpe()

int execle(const char *path, const char *arg, ... );
int execvpe(const char *file, char *const argv[], char *const envp[]);

execle()和 execvpe()這兩個函式在命名上加了一個 e,這個 e 其實表示的是 environment 環境變數,
意味著這兩個函式可以指定自定義的環境變數列表給新程式, 引數envp與系統呼叫execve()的envp
引數相同,也是字串指標陣列 。

//execvpe 傳參
char *env_arr[5] = {"NAME=app", "AGE=25","SEX=man", NULL};
char *arg_arr[5];
arg_arr[0] = "./newApp";
arg_arr[1] = "Hello";
arg_arr[2] = "World";
arg_arr[3] = NULL;
execvpe("./newApp", arg_arr, env_arr);
// execle 傳參
execle("./newApp", "./newApp", "Hello", "World", NULL, env_arr);

使用exec簇函式執行ls命令

execl

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    execl("/bin/ls", "ls", "-a", "-l", NULL);
    perror("execl error");
    exit(-1);
}

execv

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    char *arg_arr[5];
    arg_arr[0] = "ls";
    arg_arr[1] = "-a";
    arg_arr[2] = "-l";
    arg_arr[3] = NULL;
    execv("/bin/ls", arg_arr);
    perror("execv error");
    exit(-1);
}

execlp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    execlp("ls", "ls", "-a", "-l", NULL);
    perror("execlp error");
    exit(-1);
}

execvp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    char *arg_arr[5];
    arg_arr[0] = "ls";
    arg_arr[1] = "-a";
    arg_arr[2] = "-l";
    arg_arr[3] = NULL;
    execvp("ls", arg_arr);
    perror("execvp error");
    exit(-1);
}

execle

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern char **environ;
int main(void)
{
    execle("/bin/ls", "ls", "-a", "-l", NULL, environ);
    perror("execle error");
    exit(-1);
}

execvpe

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern char **environ;
int main(void)
{
    char *arg_arr[5];
    arg_arr[0] = "ls";
    arg_arr[1] = "-a";
    arg_arr[2] = "-l";
    arg_arr[3] = NULL;
    execvpe("ls", arg_arr, environ);
    perror("execvpe error");
    exit(-1);
}

system函式

使用 system()函式可以很方便地在我們的程式當中執行任意 shell 命令 。system()函式其內部的是透過呼叫 fork()、execl()以及 waitpid()這三個函式來實現它的功能。

函式原型:

#include <stdlib.h>
int system(const char *command);

返回值:

  • 當引數 command 為 NULL, 如果 shell 可用則返回一個非 0 值,若不可用則返回 0;針對一些非
    UNIX 系統,該系統上可能是沒有 shell 的,這樣就會導致 shell 不可能;如果 command 引數不為
    NULL,則返回值從以下的各種情況所決定。

  • 如果無法建立子程序或無法獲取子程序的終止狀態,那麼 system()返回-1;

  • 如果子程序不能執行 shell,則 system()的返回值就好像是子程序透過呼叫_exit(127)終止了;

  • 如果所有的系統呼叫都成功, system()函式會返回執行 command 的 shell 程序的終止狀態。

引數介紹:

  • command: 指向需要執行的 shell 命令,以字串的形式提供,譬如"ls -al"、 "echo
    HelloWorld"等 。

使用示例:將需要執行的命令透過引數傳遞給main函式,main函式里呼叫system來執行該命令。

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    int ret;
    if (2 > argc)
    	exit(-1);
    ret = system(argv[1]);
    if (-1 == ret)
    {
        fputs("system error.\n", stderr);
    }
    else 
    {
        if (WIFEXITED(ret) && (127 == WEXITSTATUS(ret)))
        	fputs("could not invoke shell.\n", stderr);
	}
	exit(0);
}

popen函式

也是用於執行shell命令的函式,與system相比,popen能夠將執行命令後得到的資料透過管道進行讀出或寫入。

popen() 函式透過建立一個管道,呼叫 fork 產生一個子程序,執行一個 shell 以執行命令來開啟一個程序。

這個程序必須由 pclose() 函式關閉,而不是 fclose() 函式。pclose() 函式關閉標準 I/O 流,等待命令執行結束,然後返回 shell 的終止狀態。如果 shell 不能被執行,則 pclose() 返回的終止狀態與 shell 已執行 exit 一樣。也就是,popen建立管道,執行shell命令將檔案流中的某些資料讀出。

函式原型:

#include <stdio.h>//標頭檔案
FILE *popen(const char *command, const char *type);

補充:FILE指標,相當於檔案描述符fd,作為檔案控制代碼。

引數介紹:

  • command:是一個指向以 NULL 結束的 shell 命令字串的指標。命令將被傳到 bin/sh 並使用 -c 標誌,shell 將執行這個命令,比如sh -c ls。
  • type:“r” 則檔案指標連線到 command 的標準輸出;如果 type 是 “w” 則檔案指標連線到 command 的標準輸入。

使用示例:透過呼叫popen去執行ls -l命令,把結果給fp,透過fread讀取

#include <stdio.h>
#include <unistd.h>

int main(int argc ,char **argv){
	
	char ret[1024] = {0};
	FILE *fp;
    
	fp = popen("ls -l","r");

	//size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
	int nread = fread(ret,1,1024,fp);
	printf("read ret %d byte, %s\n",nread,ret);
	
	return 0;
}

相關文章