Linux啟動新進程的幾種方法及比較
有時候,我們需要在自己的程序(進程)中啟動另一個程序(進程)來幫助我們完成一些工作,那么我們需要怎么才能在自己的進程中啟動其他的進程呢?在Linux中提供了不少的方法來實現(xiàn)這一點,下面就來介紹一個這些方法及它們之間的區(qū)別。
一、system函數(shù)調(diào)用
system函數(shù)的原型為:
#include <stdlib.h> int system (const char *string);
它的作用是,運行以字符串參數(shù)的形式傳遞給它的命令并等待該命令的完成。命令的執(zhí)行情況就如同在shell中執(zhí)行命令:sh -c string。如果無法啟動shell來運行這個命令,system函數(shù)返回錯誤代碼127;如果是其他錯誤,則返回-1。否則,system函數(shù)將返回該命令的退出碼。
注意:system函數(shù)調(diào)用用一個shell來啟動想要執(zhí)行的程序,所以可以把這個程序放到后臺中執(zhí)行,這里system函數(shù)調(diào)用會立即返回。
可以先先下面的例子,源文件為new_ps_system.c,代碼如下:
#include <stdlib.h> #include <stdio.h> int main() { printf("Running ps with system\n"); //ps進程結(jié)束后才返回,才能繼續(xù)執(zhí)行下面的代碼 system("ps au");// 1 printf("ps Done\n"); exit(0); }
該程序調(diào)用ps程序打印所有與本用戶有關(guān)的進程,最后才打印ps Done。運行結(jié)果如下:
如果把注釋1的語句改為:system("ps au &");則system函數(shù)立即返回,不用等待ps進程結(jié)束即可執(zhí)行下面的代碼。所以你看到的輸出,ps Done可能并不是出現(xiàn)在最后一行,而是在中間。
一般來說,使用system函數(shù)不是啟動其他進程的理想手段,因為它必須用一個shell來啟動需要的程序,即在啟動程序之前需要先啟動一個shell,而且對shell的環(huán)境的依賴也很大,因此使用system函數(shù)的效率不高。
二、替換進程映像——使用exec系列函數(shù)
exec系列函數(shù)由一組相關(guān)的函數(shù)組成,它們在進程的啟動方式和程序參數(shù)的表達方式上各有不同。但是exec系列函數(shù)都有一個共同的工作方式,就是把當前進程替換為一個新進程,也就是說你可以使用exec函數(shù)將程序的執(zhí)行從一個程序切換到另一個程序,在新的程序啟動后,原來的程序就不再執(zhí)行了,新進程由path或file參數(shù)指定。exec函數(shù)比system函數(shù)更有效。
exec系列函數(shù)的類型為:
#include <unistd.h> char **environ; int execl (const char *path, const char *arg0, ..., (char*)0); int execlp(const char *file, const char *arg0, ..., (char*)0); int execle(const char *path, const char *arg0, ..., (char*)0, char *const envp[]); int execv (const char *path, char *const argv[]); int execvp(cosnt char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]);
這類函數(shù)可以分為兩大類,execl、execlp和execle的參數(shù)是可變的,以一個空指針結(jié)束,而execv、execvp和execve的第二個參數(shù)是一個字符串數(shù)組,在調(diào)用新進程時,argv作為新進程的main函數(shù)的參數(shù)。而envp可作為新進程的環(huán)境變量,傳遞給新的進程,從而變量它可用的環(huán)境變量。
承接上一個例子,如果想用exec系統(tǒng)函數(shù)來啟動ps進程,則這6個不同的函數(shù)的調(diào)用語句為:
注:arg0為程序的名字,所以在這個例子中全為ps。
char *const ps_envp[] = {"PATH=/bin:usr/bin", "TERM=console", 0}; char *const ps_argv[] = {"ps", "au", 0}; execl("/bin/ps", "ps", "au", 0); execlp("ps", "ps", "au", 0); execle("/bin/ps", "ps", "au", 0, ps_envp); execv("/bin/ps", ps_argv); execvp("ps", ps_argv); execve("/bin/ps", ps_argv, ps_envp);
下面我給出一個完整的例子,源文件名為new_ps_exec.c,代碼如下:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { printf("Running ps with execlp\n"); execlp("ps", "ps", "au", (char*)0); printf("ps Done"); exit(0); }
運行結(jié)果如下:
細心的話,可以發(fā)現(xiàn),最后的ps Done并沒有輸出,這是偶然嗎?并不是,因為我們并沒有再一次返回到程序new_ps_exec.exe上,因為調(diào)用execlp函數(shù)時,new_ps_exec.exe進程被替換為ps進程,當ps進程結(jié)束后,整個程序就結(jié)束了,并沒有回到原來的new_ps_exec.exe進程上,原本的進程new_ps_exec.exe不會再執(zhí)行,所以語句printf("ps Done");根本沒有機會執(zhí)行。
注意,一般情況下,exec函數(shù)是不會返回的,除非發(fā)生錯誤返回-1,由exec啟動的新進程繼承了原進程的許多特性,在原進程中已打開的文件描述符在新進程中仍將保持打開,但任何在原進程中已打開的目錄流都將在新進程中被關(guān)閉。
三、復(fù)制進程映像——fork函數(shù)
1、fork函數(shù)的應(yīng)用
exec調(diào)用用新的進程替換當前執(zhí)行的進程,而我們也可以用fork來復(fù)制一個新的進程,新的進程幾乎與原進程一模一樣,執(zhí)行的代碼也完全相同,但新進程有自己的數(shù)據(jù)空間、環(huán)境和文件描述符。
fork函數(shù)的原型為:
#include <sys/type.h> #include <unistd.h> pid_t fork();
注:在父進程中,fork返回的是新的子進程的PID,子進程中的fork返回的是0,我們可以通過這一點來判斷父進程和子進程,如果fork調(diào)用失敗,它返回-1.
繼承上面的例子,下面我給出一個調(diào)用ps的例子,源文件名為new_ps_fork.c,代碼如下:
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid = fork(); switch(pid) { case -1: perror("fork failed"); exit(1); break; case 0: //這是在子進程中,調(diào)用execlp切換為ps進程 printf("\n"); execlp("ps", "ps", "au", 0); break; default: //這是在父進程中,輸出相關(guān)提示信息 printf("Parent, ps Done\n"); break; } exit(0); }
輸出結(jié)果為:
我們可以看到,之前在第二點中沒有出現(xiàn)的ps Done是打印出來了,但是順序卻有點不對,這是因為,父進程先于子程序執(zhí)行,所以先輸出了Parent, ps Done,那有沒有辦法讓它在子進程輸出完之后再輸出,當然有,就是用wait和waitpid函數(shù)。注意,一般情況下,父進程與子進程的生命周期是沒有關(guān)系的,即便父進程退出了,子進程仍然可以正常運行。
2、等待一個進程
wait函數(shù)和waitpid函數(shù)的原型為:
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc); pid_t waitpid(pid_t pid, int *stat_loc, int options);
wait用于在父進程中調(diào)用,讓父進程暫停執(zhí)行等待子進程的結(jié)束,返回子進程的PID,如果stat_loc不是空指針,狀態(tài)信息將被寫入stat_loc指向的位置。
waitpid等待進程id為pid的子進程的結(jié)束(pid為-1,將返回任一子進程的信息),stat_loc參數(shù)的作用與wait函數(shù)相同,options用于改變waitpid的行為,其中有一個很重要的選項WNOHANG,它的作用是防止waippid調(diào)用者的執(zhí)行掛起。如果子進程沒有結(jié)束或意外終止,它返回0,否則返回子進程的pid。
改變后的程序保存為源文件new_ps_fork2.c,代碼如下:
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid = fork(); int stat = 0; switch(pid) { case -1: perror("fork failed"); exit(1); break; case 0: //這是在子進程中,調(diào)用execlp切換為ps進程 printf("\n"); execlp("ps", "ps", "au", 0); break; default: //這是在父進程中,等待子進程結(jié)束并輸出相關(guān)提示信息 pid = wait(&stat); printf("Child has finished: PID = %d\n", pid); //檢查子進程的退出狀態(tài) if(WIFEXITED(stat)) printf("Child exited with code %d\n", WEXITSTATUS(stat)); else printf("Child terminated abnormally\n"); printf("Parent, ps Done\n"); break; } exit(0); }
輸出為:
可以看到這次的輸出終于正常了,Parent的輸出也在子進程的輸出之后。
總結(jié)——三種啟動新進程方法的比較
首先是最簡單的system函數(shù),它需要啟動新的shell并在新的shell是執(zhí)行子進程,所以對環(huán)境的依賴較大,而且效率也不高。同時system函數(shù)要等待子進程的返回才能執(zhí)行下面的語句。
exec系統(tǒng)函數(shù)是用新的進程來替換原先的進程,效率較高,但是它不會返回到原先的進程,也就是說在exec函數(shù)后面的所以代碼都不會被執(zhí)行,除非exec調(diào)用失敗。然而exec啟動的新進程繼承了原進程的許多特性,在原進程中已打開的文件描述符在新進程中仍將保持打開,但需要注意,任何在原進程中已打開的目錄流都將在新進程中被關(guān)閉。
fork則是用當前的進程來復(fù)制出一個新的進程,新進程與原進程一模一樣,執(zhí)行的代碼也完全相同,但新進程有自己的數(shù)據(jù)空間、環(huán)境變量和文件描述符,我們通常根據(jù)fork函數(shù)的返回值來確定當前的進程是子進程還是父進程,即它并不像exec那樣并不返回,而是返回一個pid_t的值用于判斷,我們還可以繼續(xù)執(zhí)行fork后面的代碼。感覺用fork與exec系列函數(shù)就能創(chuàng)建很多需的進程。
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
相關(guān)文章
shell腳本實現(xiàn)快速生成xml格式sitemap實例分享
這篇文章主要介紹了shell腳本實現(xiàn)快速生成xml格式sitemap實例分享,只是本文的腳本首先需要一個創(chuàng)建好的URL集合文件,也就是數(shù)據(jù)源才可以生成,需要的朋友可以參考下2014-12-12