Linux進程

来源:https://www.cnblogs.com/tenzzz/p/18170604
-Advertisement-
Play Games

title: 進程 cover: https://img2.imgtp.com/2024/04/30/ZamtkUJE.jpg tags: - 進程 - linux categories: linux系統編程 程式與進程 程式:是可執行文件,其本質是是一個文件,程式是靜態的,同一個程式可以運行多次, ...


程式與進程

程式:是可執行文件,其本質是是一個文件,程式是靜態的,同一個程式可以運行多次,產生多個進程

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

父子進程:當一個進程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;
}


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • VMware虛擬機安裝Centos-7.9 創作不易,點贊關註一下吧 1.安裝VMware Workstation Pro 大家根據自己的實際情況安裝合適版本的VMware Workstation Pro,具體的安裝推薦及各版本的下載鏈接大家可以看我之前發佈的一篇博客:VMware Workstat ...
  • 前言 新版WSL2已經支持鏡像模式網路 可以將WSL2的IP固定為與主機相同 鏡像模式網路 但是在啟用後WSL2中Docker運行的服務本機無法訪問 issues 10494 結合上述issues給出自己的使用新的幫助大家避坑 環境 Win11 23H2win + r > winver WSL 2. ...
  • 參考 Ubuntu installation on a RISC-V virtual machine using a server install image and QEMU 用到的文件 fw_jump.bin u-boot.bin ubuntu-22.04.3-preinstalled-serv ...
  • 安裝環境編譯qemu 1. PC啟動 打開兩個視窗,在第一個視窗中 make qemu-gdb,會啟動內核,但在執行第一個指令之前停下; 在第二個視窗中make gdb,實時觀察第一個視窗中的執行過程。 從這裡可以觀察到: IBM PC 在物理地址 0x000ffff0 開始執行, 位於為 ROM ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...