Linux啓動新進程的幾種方法及比較

 

Linux啓動新進程的幾種方法及比較


Linux多進程創建新進程
有時候,我們需要在自己的程序(進程)中啓動另一個程序(進程)來幫助我們完成一些工作,那麼我們需要怎麼才能在自己的進程中啓動其他的進程呢?在Linux中提供了不少的方法來實現這一點,下面就來介紹一個這些方法及它們之間的區別。

一、system函數調用
system函數的原型爲:
  1. #include <stdlib.h>  
  2. int system (const char *string);  
它的作用是,運行以字符串參數的形式傳遞給它的命令並等待該命令的完成。命令的執行情況就如同在shell中執行命令:sh -c string。如果無法啓動shell來運行這個命令,system函數返回錯誤代碼127;如果是其他錯誤,則返回-1。否則,system函數將返回該命令的退出碼。

注意:system函數調用用一個shell來啓動想要執行的程序,所以可以把這個程序放到後臺中執行,這裏system函數調用會立即返回。

可以先先下面的例子,源文件爲new_ps_system.c,代碼如下:
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3.   
  4. int main()  
  5. {  
  6.     printf("Running ps with system\n");  
  7.     //ps進程結束後才返回,才能繼續執行下面的代碼  
  8.     system("ps au");// 1  
  9.     printf("ps Done\n");  
  10.     exit(0);  
  11. }  
該程序調用ps程序打印所有與本用戶有關的進程,最後纔打印ps Done。運行結果如下:


如果把註釋1的語句改爲:system("ps au &");則system函數立即返回,不用等待ps進程結束即可執行下面的代碼。所以你看到的輸出,ps Done可能並不是出現在最後一行,而是在中間。

一般來說,使用system函數不是啓動其他進程的理想手段,因爲它必須用一個shell來啓動需要的程序,即在啓動程序之前需要先啓動一個shell,而且對shell的環境的依賴也很大,因此使用system函數的效率不高。

二、替換進程映像——使用exec系列函數
exec系列函數由一組相關的函數組成,它們在進程的啓動方式和程序參數的表達方式上各有不同。但是exec系列函數都有一個共同的工作方式,就是把當前進程替換爲一個新進程,也就是說你可以使用exec函數將程序的執行從一個程序切換到另一個程序,在新的程序啓動後,原來的程序就不再執行了,新進程由path或file參數指定。exec函數比system函數更有效。

exec系列函數的類型爲:
  1. #include <unistd.h>  
  2.   
  3. char **environ;  
  4.   
  5. int execl (const char *path, const char *arg0, ..., (char*)0);  
  6. int execlp(const char *file, const char *arg0, ..., (char*)0);  
  7. int execle(const char *path, const char *arg0, ..., (char*)0, char *const envp[]);  
  8.   
  9. int execv (const char *path, char *const argv[]);  
  10. int execvp(cosnt char *file, char *const argv[]);  
  11. int execve(const char *path, char *const argv[], char *const envp[]);  
這類函數可以分爲兩大類,execl、execlp和execle的參數是可變的,以一個空指針結束,而execv、execvp和execve的第二個參數是一個字符串數組,在調用新進程時,argv作爲新進程的main函數的參數。而envp可作爲新進程的環境變量,傳遞給新的進程,從而變量它可用的環境變量。

承接上一個例子,如果想用exec系統函數來啓動ps進程,則這6個不同的函數的調用語句爲:
注:arg0爲程序的名字,所以在這個例子中全爲ps。

  1. char *const ps_envp[] = {"PATH=/bin:usr/bin""TERM=console", 0};  
  2. char *const ps_argv[] = {"ps""au", 0};  
  3.   
  4. execl("/bin/ps""ps""au", 0);  
  5. execlp("ps""ps""au", 0);  
  6. execle("/bin/ps""ps""au", 0, ps_envp);  
  7.   
  8. execv("/bin/ps", ps_argv);  
  9. execvp("ps", ps_argv);  
  10. execve("/bin/ps", ps_argv, ps_envp);  
下面我給出一個完整的例子,源文件名爲new_ps_exec.c,代碼如下:
  1. #include <unistd.h>  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4.   
  5. int main()  
  6. {  
  7.     printf("Running ps with execlp\n");  
  8.     execlp("ps""ps""au", (char*)0);  
  9.     printf("ps Done");  
  10.     exit(0);  
  11. }  
運行結果如下:

細心的話,可以發現,最後的ps Done並沒有輸出,這是偶然嗎?並不是,因爲我們並沒有再一次返回到程序new_ps_exec.exe上,因爲調用execlp函數時,new_ps_exec.exe進程被替換爲ps進程,當ps進程結束後,整個程序就結束了,並沒有回到原來的new_ps_exec.exe進程上,原本的進程new_ps_exec.exe不會再執行,所以語句printf("ps Done");根本沒有機會執行。

注意,一般情況下,exec函數是不會返回的,除非發生錯誤返回-1,由exec啓動的新進程繼承了原進程的許多特性,在原進程中已打開的文件描述符在新進程中仍將保持打開,但任何在原進程中已打開的目錄流都將在新進程中被關閉。

三、複製進程映像——fork函數
1、fork函數的應用
exec調用用新的進程替換當前執行的進程,而我們也可以用fork來複制一個新的進程,新的進程幾乎與原進程一模一樣,執行的代碼也完全相同,但新進程有自己的數據空間、環境和文件描述符。

fork函數的原型爲:
  1. #include <sys/type.h>  
  2. #include <unistd.h>  
  3.   
  4. pid_t fork();  
注:在父進程中,fork返回的是新的子進程的PID,子進程中的fork返回的是0,我們可以通過這一點來判斷父進程和子進程,如果fork調用失敗,它返回-1.

繼承上面的例子,下面我給出一個調用ps的例子,源文件名爲new_ps_fork.c,代碼如下:
  1. #include <unistd.h>  
  2. #include <sys/types.h>  
  3. #include <stdio.h>  
  4. #include <stdlib.h>  
  5.   
  6. int main()  
  7. {  
  8.     pid_t pid = fork();  
  9.     switch(pid)  
  10.     {  
  11.     case -1:  
  12.         perror("fork failed");  
  13.         exit(1);  
  14.         break;  
  15.     case 0:  
  16.         //這是在子進程中,調用execlp切換爲ps進程  
  17.         printf("\n");  
  18.         execlp("ps""ps""au", 0);  
  19.         break;  
  20.     default:  
  21.         //這是在父進程中,輸出相關提示信息  
  22.         printf("Parent, ps Done\n");  
  23.         break;  
  24.     }  
  25.     exit(0);  
  26. }  
輸出結果爲:


我們可以看到,之前在第二點中沒有出現的ps Done是打印出來了,但是順序卻有點不對,這是因爲,父進程先於子程序執行,所以先輸出了Parent, ps Done,那有沒有辦法讓它在子進程輸出完之後再輸出,當然有,就是用wait和waitpid函數。注意,一般情況下,父進程與子進程的生命週期是沒有關係的,即便父進程退出了,子進程仍然可以正常運行。

2、等待一個進程
wait函數和waitpid函數的原型爲:
  1. #include <sys/types.h>  
  2. #include <sys/wait.h>  
  3.   
  4. pid_t wait(int *stat_loc);  
  5. pid_t waitpid(pid_t pid, int *stat_loc, int options);  
wait用於在父進程中調用,讓父進程暫停執行等待子進程的結束,返回子進程的PID,如果stat_loc不是空指針,狀態信息將被寫入stat_loc指向的位置。

waitpid等待進程id爲pid的子進程的結束(pid爲-1,將返回任一子進程的信息),stat_loc參數的作用與wait函數相同,options用於改變waitpid的行爲,其中有一個很重要的選項WNOHANG,它的作用是防止waippid調用者的執行掛起。如果子進程沒有結束或意外終止,它返回0,否則返回子進程的pid。

改變後的程序保存爲源文件new_ps_fork2.c,代碼如下:
  1. #include <unistd.h>  
  2. #include <sys/types.h>  
  3. #include <stdio.h>  
  4. #include <stdlib.h>  
  5.   
  6. int main()  
  7. {  
  8.     pid_t pid = fork();  
  9.     int stat = 0;  
  10.     switch(pid)  
  11.     {  
  12.     case -1:  
  13.         perror("fork failed");  
  14.         exit(1);  
  15.         break;  
  16.     case 0:  
  17.         //這是在子進程中,調用execlp切換爲ps進程  
  18.         printf("\n");  
  19.         execlp("ps""ps""au", 0);  
  20.         break;  
  21.     default:  
  22.         //這是在父進程中,等待子進程結束並輸出相關提示信息  
  23.         pid = wait(&stat);  
  24.         printf("Child has finished: PID = %d\n", pid);  
  25.         //檢查子進程的退出狀態  
  26.         if(WIFEXITED(stat))  
  27.             printf("Child exited with code %d\n", WEXITSTATUS(stat));  
  28.         else  
  29.             printf("Child terminated abnormally\n");  
  30.         printf("Parent, ps Done\n");  
  31.         break;  
  32.     }  
  33.     exit(0);  
  34. }  
輸出爲:

可以看到這次的輸出終於正常了,Parent的輸出也在子進程的輸出之後。

總結——三種啓動新進程方法的比較
首先是最簡單的system函數,它需要啓動新的shell並在新的shell是執行子進程,所以對環境的依賴較大,而且效率也不高。同時system函數要等待子進程的返回才能執行下面的語句。

exec系統函數是用新的進程來替換原先的進程,效率較高,但是它不會返回到原先的進程,也就是說在exec函數後面的所以代碼都不會被執行,除非exec調用失敗。然而exec啓動的新進程繼承了原進程的許多特性,在原進程中已打開的文件描述符在新進程中仍將保持打開,但需要注意,任何在原進程中已打開的目錄流都將在新進程中被關閉。

fork則是用當前的進程來複製出一個新的進程,新進程與原進程一模一樣,執行的代碼也完全相同,但新進程有自己的數據空間、環境變量和文件描述符,我們通常根據fork函數的返回值來確定當前的進程是子進程還是父進程,即它並不像exec那樣並不返回,而是返回一個pid_t的值用於判斷,我們還可以繼續執行fork後面的代碼。感覺用fork與exec系列函數就能創建很多需的進程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章