怎么在Linux中使用fork()函數(shù)

怎么在Linux中使用fork()函數(shù)?相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

創(chuàng)新互聯(lián)成立于2013年,我們提供高端重慶網(wǎng)站建設(shè)公司、成都網(wǎng)站制作成都網(wǎng)站設(shè)計(jì)、網(wǎng)站定制、成都營(yíng)銷網(wǎng)站建設(shè)微信平臺(tái)小程序開(kāi)發(fā)、微信公眾號(hào)開(kāi)發(fā)、成都網(wǎng)站推廣服務(wù),提供專業(yè)營(yíng)銷思路、內(nèi)容策劃、視覺(jué)設(shè)計(jì)、程序開(kāi)發(fā)來(lái)完成項(xiàng)目落地,為廣告設(shè)計(jì)企業(yè)提供源源不斷的流量和訂單咨詢。

 一、fork()函數(shù)

在操作系統(tǒng)的基本概念中進(jìn)程是程序的一次執(zhí)行,且是擁有資源的最小單位和調(diào)度單位(在引入線程的操作系統(tǒng)中,線程是最小的調(diào)度單位)。在Linux系統(tǒng)中 創(chuàng)建進(jìn)程有兩種方式:一是由操作系統(tǒng)創(chuàng)建,二是由父進(jìn)程創(chuàng)建進(jìn)程(通常為子進(jìn)程)。系統(tǒng)調(diào)用函數(shù)fork()是創(chuàng)建一個(gè)新進(jìn)程的唯一方式,當(dāng)然 vfork()也可以創(chuàng)建進(jìn)程,但是實(shí)際上其還是調(diào)用了fork()函數(shù)。fork()函數(shù)是Linux系統(tǒng)中一個(gè)比較特殊的函數(shù),其一次調(diào)用會(huì)有兩個(gè)返 回值,下面是fork()函數(shù)的聲明:

#include <unistd.h>

// On success, The PID of the process is returned in the parent,
 and 0 is returned in the child. On failure,
// -1 is returned in the parent, no child process is created, 
and errno is set appropriately.
pid_t fork (void);

當(dāng)程序調(diào)用fork()函數(shù)并返回成功之后,程序就將變成兩個(gè)進(jìn)程,調(diào)用fork()者為父進(jìn)程,后來(lái)生成者為子進(jìn)程。這兩個(gè)進(jìn)程將執(zhí)行相同的程序文本, 但卻各自擁有不同的棧段、數(shù)據(jù)段以及堆??截悺W舆M(jìn)程的棧、數(shù)據(jù)以及棧段開(kāi)始時(shí)是父進(jìn)程內(nèi)存相應(yīng)各部分的完全拷貝,因此它們互不影響。從性能方面考慮,父 進(jìn)程到子進(jìn)程的數(shù)據(jù)拷貝并不是創(chuàng)建時(shí)就拷貝了的,而是采用了寫時(shí)拷貝(copy-on -write)技術(shù)來(lái)處理。調(diào)用fork()之后,父進(jìn)程與子進(jìn)程的執(zhí)行順序是我們無(wú)法確定的(即調(diào)度進(jìn)程使用CPU),意識(shí)到這一點(diǎn)極為重要,因?yàn)樵谝恍┰O(shè)計(jì)不好的程序中會(huì)導(dǎo)致資源競(jìng)爭(zhēng),從而出現(xiàn)不可預(yù)知的問(wèn)題。下圖為寫時(shí)拷貝技術(shù)處理前后的示意圖:

怎么在Linux中使用fork()函數(shù)

在Linux系統(tǒng)中,常常存在許多對(duì)文件的操作,fork()的執(zhí)行將會(huì)對(duì)文件操作帶來(lái)一些小麻煩。由于子進(jìn)程會(huì)將父進(jìn)程的大多數(shù)數(shù)據(jù)拷貝一份,這樣在文 件操作中就意味著子進(jìn)程會(huì)獲得父進(jìn)程所有文件描述符的副本,這些副本的創(chuàng)建方式類似于dup()函數(shù)調(diào)用,因此父、子進(jìn)程中對(duì)應(yīng)的文件描述符均指向相同的 打開(kāi)的文件句柄,而且打開(kāi)的文件句柄包含著當(dāng)前文件的偏移量以及文件狀態(tài)標(biāo)志,所以在父子進(jìn)程中處理文件時(shí)要考慮這種情況,以避免文件內(nèi)容出現(xiàn)混亂或者別 的問(wèn)題。下圖為執(zhí)行fork()調(diào)用后文件描述符的相關(guān)處理及其變化:

怎么在Linux中使用fork()函數(shù)

二、線程

與進(jìn)程類似,線程(thread)是允許應(yīng)用程序并發(fā)執(zhí)行多個(gè)任務(wù)的一種機(jī)制。一個(gè)進(jìn)程中可以包含多個(gè)線程,同一個(gè)程序中的所有線程均會(huì)獨(dú)立執(zhí)行,且共享 同一份全局內(nèi)存區(qū)域,其中包括初始化數(shù)據(jù)段(initialized data),未初始化數(shù)據(jù)段(uninitialized data),以及堆內(nèi)存段(heap segment)。在多處理器環(huán)境下,多個(gè)線程可以同時(shí)執(zhí)行,如果線程數(shù)超過(guò)了CPU的個(gè)數(shù),那么每個(gè)線程的執(zhí)行順序?qū)⑹菬o(wú)法確定的,因此對(duì)于一些全局共 享數(shù)據(jù)據(jù)需要使用同步機(jī)制來(lái)確保其的正確性。

在系統(tǒng)中,線程也是稀缺資源,一個(gè)進(jìn)程能同時(shí)創(chuàng)建多少個(gè)線程這取決于地址空間的大小和內(nèi)核參數(shù),一臺(tái)機(jī)器可以同時(shí)并發(fā)運(yùn)行多少個(gè)線程也受限于CPU的數(shù) 目。在進(jìn)行程序設(shè)計(jì)時(shí),我們應(yīng)該精心規(guī)劃線程的個(gè)數(shù),特別是根據(jù)機(jī)器CPU的數(shù)目來(lái)設(shè)置工作線程的數(shù)目,并為關(guān)鍵任務(wù)保留足夠的計(jì)算資源。如果你設(shè)計(jì)的程 序在背地里啟動(dòng)了額外的線程來(lái)執(zhí)行任務(wù),那這也屬于資源規(guī)劃漏算的情況,從而影響關(guān)鍵任務(wù)的執(zhí)行,最終導(dǎo)致無(wú)法達(dá)到預(yù)期的性能。很多程序中都存在全局對(duì) 象,這些全局對(duì)象的初始化工作都是在進(jìn)入main()函數(shù)之前進(jìn)行的,為了能保證全局對(duì)象的安全初始化(按順序的),因此在程序進(jìn)入main()函數(shù)之前 應(yīng)該避免線程的創(chuàng)建,從而杜絕未知錯(cuò)誤的發(fā)生。

三、fork()與多線程

在程序中fork()與多線程的協(xié)作性很差,這是POSIX系列操作系統(tǒng)的歷史包袱。因?yàn)殚L(zhǎng)期以來(lái)程序都是單線程的,fork()運(yùn)轉(zhuǎn)正常。當(dāng)20世紀(jì)90年代初期引入線程之后,fork()的適用范圍就大為縮小了。

在多線程執(zhí)行的情況下調(diào)用fork()函數(shù),僅會(huì)將發(fā)起調(diào)用的線程復(fù)制到子進(jìn)程中。(子進(jìn)程中該線程的ID與父進(jìn)程中發(fā)起fork()調(diào)用的線程ID是一樣的,因此,線程ID相同的情況有時(shí)我們需要做特殊的處理。)也就是說(shuō)不能同時(shí)創(chuàng)建出于父進(jìn)程一樣多線程的子進(jìn)程。其他線程均在子進(jìn)程中立即停止并消失,并且不會(huì)為這些線程調(diào)用清理函數(shù)以及針對(duì)線程局部存儲(chǔ)變量的析構(gòu)函數(shù)。這將導(dǎo)致下列一些問(wèn)題:

1. 雖然只將發(fā)起fork()調(diào)用的線程復(fù)制到子進(jìn)程中,但全局變量的狀態(tài)以及所有的pthreads對(duì)象(如互斥量、條件變量等)都會(huì)在子進(jìn)程中得以保留, 這就造成一個(gè)危險(xiǎn)的局面。例如:一個(gè)線程在fork()被調(diào)用前鎖定了某個(gè)互斥量,且對(duì)某個(gè)全局變量的更新也做到了一半,此時(shí)fork()被調(diào)用,所有數(shù) 據(jù)及狀態(tài)被拷貝到子進(jìn)程中,那么子進(jìn)程中對(duì)該互斥量就無(wú)法解鎖(因?yàn)槠洳⒎窃摶コ饬康膶僦鳎?,如果再試圖鎖定該互斥量就會(huì)導(dǎo)致死鎖,這是多線程編程中最不 愿意看到的情況。同時(shí),全局變量的狀態(tài)也可能處于不一致的狀態(tài),因?yàn)閷?duì)其更新的操作只做到了一半對(duì)應(yīng)的線程就消失了。fork()函數(shù)被調(diào)用之后,子進(jìn)程 就相當(dāng)于處于signal handler之中,此時(shí)就不能調(diào)用線程安全的函數(shù)(用鎖機(jī)制實(shí)現(xiàn)安全的函數(shù)),除非函數(shù)是可重入的,而只能調(diào)用異步信號(hào)安全(async- signal-safe)的函數(shù)。fork()之后,子進(jìn)程不能調(diào)用:

  1. malloc(3)。因?yàn)閙alloc()在訪問(wèn)全局狀態(tài)時(shí)會(huì)加鎖。

  2. 任何可能分配或釋放內(nèi)存的函數(shù),包括new、map::insert()、snprintf() &hellip;&hellip;

  3. 任何pthreads函數(shù)。你不能用pthread_cond_signal()去通知父進(jìn)程,只能通過(guò)讀寫pipe(2)來(lái)同步。

  4. printf()系列函數(shù),因?yàn)槠渌€程可能恰好持有stdout/stderr的鎖。

  5. 除了man 7 signal中明確列出的“signal安全”函數(shù)之外的任何函數(shù)。

2. 因?yàn)椴⑽磮?zhí)行清理函數(shù)和針對(duì)線程局部存儲(chǔ)數(shù)據(jù)的析構(gòu)函數(shù),所以多線程情況下可能會(huì)導(dǎo)致子進(jìn)程的內(nèi)存泄露。另外,子進(jìn)程中的線程可能無(wú)法訪問(wèn)(父進(jìn)程中)由其他線程所創(chuàng)建的線程局部存儲(chǔ)變量,因?yàn)椋ㄗ舆M(jìn)程)沒(méi)有任何相應(yīng)的引用指針。

由于這些問(wèn)題,推薦在多線程程序中調(diào)用fork()的唯一情況是:其后立即調(diào)用exec()函數(shù)執(zhí)行另一個(gè)程序,徹底隔斷子進(jìn)程與父進(jìn)程的關(guān)系。由新的進(jìn)程覆蓋掉原有的內(nèi)存,使得子進(jìn)程中的所有pthreads對(duì)象消失。

對(duì)于那些必須執(zhí)行fork(),而其后又無(wú)exec()緊隨其后的程序來(lái)說(shuō),pthreads API提供了一種機(jī)制:fork()處理函數(shù)。利用函數(shù)pthread_atfork()來(lái)創(chuàng)建fork()處理函數(shù)。pthread_atfork()聲明如下:

#include <pthread.h>

// Upon successful completion, pthread_atfork() shall return a value 
of zero; otherwise, an error number shall be returned to indicate the error.
// @prepare 新進(jìn)程產(chǎn)生之前被調(diào)用
// @parent  新進(jìn)程產(chǎn)生之后在父進(jìn)程被調(diào)用
// @child    新進(jìn)程產(chǎn)生之后,在子進(jìn)程被調(diào)用
int pthread_atfork (void (*prepare) (void), void (*parent) (void), void 
(*child) (void));

該函數(shù)的作用就是往進(jìn)程中注冊(cè)三個(gè)函數(shù),以便在不同的階段調(diào)用,有了這三個(gè)參數(shù),我們就可以在對(duì)應(yīng)的函數(shù)中加入對(duì)應(yīng)的處理功能。同時(shí)需要注意的是,每次調(diào)用pthread_atfork()函數(shù)會(huì)將prepare添加到一個(gè)函數(shù)列表中,創(chuàng)建子進(jìn)程之前會(huì)(按與注冊(cè)次序相反的順序)自動(dòng)執(zhí)行該函數(shù)列表中函數(shù)。parent與child也會(huì)被添加到一個(gè)函數(shù)列表中,在fork()返回前,分別在父子進(jìn)程中自動(dòng)執(zhí)行(按注冊(cè)的順序)。

看完上述內(nèi)容,你們掌握怎么在Linux中使用fork()函數(shù)的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

名稱欄目:怎么在Linux中使用fork()函數(shù)
網(wǎng)頁(yè)鏈接:http://muchs.cn/article6/jpehig.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站維護(hù)、網(wǎng)站內(nèi)鏈、用戶體驗(yàn)、服務(wù)器托管、建站公司軟件開(kāi)發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

小程序開(kāi)發(fā)