概述
在Linux系統(tǒng)中,通過(guò)創(chuàng)建新的進(jìn)程,我們可以實(shí)現(xiàn)多任務(wù)處理、并發(fā)執(zhí)行和資源隔離等功能。創(chuàng)建進(jìn)程的主要方法為:fork、vfork、clone。下面,我們將分別進(jìn)行介紹。
fork
fork是最常用的創(chuàng)建新進(jìn)程的方法。當(dāng)一個(gè)進(jìn)程調(diào)用fork時(shí),系統(tǒng)會(huì)創(chuàng)建一個(gè)新的子進(jìn)程。子進(jìn)程是調(diào)用進(jìn)程(即父進(jìn)程)的一個(gè)精確副本,但它有自己的獨(dú)立內(nèi)存空間、文件描述符等資源。fork使用寫(xiě)時(shí)拷貝技術(shù),以推遲或避免不必要的拷貝。在需要寫(xiě)入時(shí),才會(huì)復(fù)制地址空間。fork函數(shù)返回兩次:一次是在父進(jìn)程中返回子進(jìn)程的PID,另一次是在子進(jìn)程中返回0。fork函數(shù)的原型如下。
pid_t fork(void);
fork函數(shù)是一個(gè)無(wú)參函數(shù),調(diào)用時(shí)不需要傳遞任何參數(shù)。返回值取決于調(diào)用的結(jié)果和當(dāng)前進(jìn)程的狀態(tài),有以下三種情況。
1、父進(jìn)程。當(dāng)fork函數(shù)調(diào)用成功時(shí),父進(jìn)程會(huì)收到子進(jìn)程的PID。這個(gè)PID是一個(gè)唯一的正整數(shù),用于標(biāo)識(shí)子進(jìn)程。父進(jìn)程可以使用這個(gè)PID來(lái)監(jiān)控子進(jìn)程的狀態(tài),比如:通過(guò)wait或waitpid等函數(shù)等待子進(jìn)程結(jié)束。
2、子進(jìn)程。子進(jìn)程在調(diào)用fork函數(shù)后,會(huì)立即返回0。這是因?yàn)樽舆M(jìn)程需要知道自己是新創(chuàng)建的進(jìn)程,而0是一個(gè)特殊的返回值,專門(mén)用于標(biāo)識(shí)子進(jìn)程。子進(jìn)程從fork函數(shù)返回后,通常會(huì)執(zhí)行與父進(jìn)程不同的任務(wù),或者調(diào)用exec系列函數(shù)來(lái)執(zhí)行新的程序。
3、錯(cuò)誤處理。如果fork函數(shù)調(diào)用失敗,它會(huì)返回-1,并設(shè)置全局變量errno來(lái)表示具體的錯(cuò)誤原因。常見(jiàn)的錯(cuò)誤包括:系統(tǒng)資源不足、內(nèi)存不足等。
具體如何使用fork,可參考下面的示例代碼。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
// 創(chuàng)建子進(jìn)程
pid_t pid = fork();
if (pid < 0)
{
// 創(chuàng)建子進(jìn)程失敗
printf("Fork failed!\n");
return 1;
}
else if (pid == 0)
{
// 如果返回0,表示當(dāng)前代碼在子進(jìn)程中執(zhí)行
printf("Hello from child process\n");
}
else
{
// 如果返回正值,表示當(dāng)前代碼在父進(jìn)程中執(zhí)行,返回值為子進(jìn)程ID
printf("Hello from parent process. Child PID: %d\n", pid);
}
// 父子進(jìn)程都會(huì)執(zhí)行到這里
return 0;
}
vfork
vfork函數(shù)與fork類似,其函數(shù)原型如下。
pid_t vfork(void);
vfork函數(shù)與fork有一些重要的區(qū)別,主要有如下幾點(diǎn)。
1、內(nèi)存共享。
fork:創(chuàng)建的新進(jìn)程是父進(jìn)程的一個(gè)完全復(fù)制,子進(jìn)程擁有自己獨(dú)立的內(nèi)存空間、文件描述符等資源。子進(jìn)程和父進(jìn)程之間沒(méi)有任何內(nèi)存共享,因此子進(jìn)程可以安全地修改自己的內(nèi)存而不影響父進(jìn)程。
vfork:創(chuàng)建的新進(jìn)程與父進(jìn)程共享內(nèi)存,子進(jìn)程在自己的地址空間中運(yùn)行,但實(shí)際上與父進(jìn)程共享同一個(gè)內(nèi)存地址空間。子進(jìn)程不能修改任何數(shù)據(jù)結(jié)構(gòu),因?yàn)檫@些修改會(huì)影響到父進(jìn)程。因此,子進(jìn)程必須盡快調(diào)用exec系列函數(shù)來(lái)執(zhí)行新的程序,或者調(diào)用_exit函數(shù)退出。
2、父進(jìn)程的阻塞。
fork:父進(jìn)程和子進(jìn)程幾乎同時(shí)開(kāi)始執(zhí)行。父進(jìn)程在fork返回后可以立即繼續(xù)執(zhí)行,子進(jìn)程也從fork返回點(diǎn)開(kāi)始執(zhí)行。父進(jìn)程和子進(jìn)程之間的執(zhí)行順序是不確定的,取決于操作系統(tǒng)的調(diào)度策略。
vfork:父進(jìn)程在子進(jìn)程調(diào)用exec或_exit之前,會(huì)被阻塞。這意味著父進(jìn)程會(huì)暫停執(zhí)行,直到子進(jìn)程完成exec或_exit。這種設(shè)計(jì)減少了內(nèi)存開(kāi)銷,因?yàn)樽舆M(jìn)程不需要復(fù)制父進(jìn)程的整個(gè)內(nèi)存空間。
3、使用場(chǎng)景。
fork:適用于需要?jiǎng)?chuàng)建一個(gè)完全獨(dú)立的子進(jìn)程的場(chǎng)景。子進(jìn)程可以執(zhí)行與父進(jìn)程不同的任務(wù),或者調(diào)用exec系列函數(shù)來(lái)執(zhí)行新的程序。由于子進(jìn)程是父進(jìn)程的完全復(fù)制,因此fork比較消耗資源,特別是當(dāng)父進(jìn)程占用大量?jī)?nèi)存時(shí)。
vfork:適用于需要臨時(shí)借用父進(jìn)程的地址空間來(lái)執(zhí)行exec系列函數(shù)的場(chǎng)景。這種情況下,子進(jìn)程不需要長(zhǎng)時(shí)間運(yùn)行,只需要快速切換到新的程序。vfork更節(jié)省資源,因?yàn)樗恍枰獜?fù)制父進(jìn)程的內(nèi)存空間,但同時(shí)也帶來(lái)了更多的限制,因?yàn)樽舆M(jìn)程不能修改任何數(shù)據(jù)結(jié)構(gòu)。
clone
與fork和vfork不同,clone函數(shù)提供了更多的靈活性。它允許用戶指定哪些資源應(yīng)該被共享,從而可以創(chuàng)建線程或更輕量級(jí)的進(jìn)程。其函數(shù)原型如下。
int clone(int (*fn)(void *), void *child_stack, int flags,
void *arg, ... /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
fn:指向子進(jìn)程將要執(zhí)行的函數(shù)的指針,子進(jìn)程從這個(gè)函數(shù)開(kāi)始執(zhí)行。函數(shù)的返回值是一個(gè)整數(shù),通常用于表示子進(jìn)程的退出狀態(tài)。
child_stack:指向子進(jìn)程棧的指針,子進(jìn)程將使用這塊內(nèi)存作為其??臻g。通常,這塊內(nèi)存是從堆中分配的,且指向棧的頂部(即高地址)。
flags:一個(gè)位掩碼,用于指定子進(jìn)程的行為和資源共享方式,可取值為CLONE_VM、CLONE_FILES等。
arg:指向傳遞給fn函數(shù)的參數(shù)的指針。
ptid:可選參數(shù),指向一個(gè)變量,該變量將存儲(chǔ)子進(jìn)程的PID。
tls:可選參數(shù),指向線程局部存儲(chǔ)TLS描述符。
ctid:可選參數(shù),指向一個(gè)變量,該變量將接收子進(jìn)程的CTID(如果設(shè)置了CLONE_CHILD_SETTID標(biāo)志)。
由于clone函數(shù)提供了更多的選項(xiàng),因此使用起來(lái)也更加復(fù)雜。開(kāi)發(fā)者需要詳細(xì)了解各個(gè)標(biāo)志位的作用,并正確管理?xiàng)?臻g和其他資源。雖然可以直接使用clone創(chuàng)建線程,但這通常只在特定的高性能或低級(jí)系統(tǒng)編程場(chǎng)景中才會(huì)用到。