函数 —— fork()分叉函数
閱讀目錄
- fork()運行時做的事情
- 父子進程文件共享問題
- fork()函數(shù)在底層中做了什么?
- vfork和fork的之間的比較:
記得以前初次接觸fork()函數(shù)的時候,一直被“printf”輸出多少次的問題弄得比較暈乎。不過,“黃天不負留心人"。哈~ 終于在學習進程和進程創(chuàng)建fork相關(guān)知識后,總算是大致摸清了其中的來龍去脈。廢話不多講,下面來談談本人的一點小小積累
?一個現(xiàn)有的進程可以調(diào)用fork函數(shù)創(chuàng)建一個新進程。原型如下:
fork()系統(tǒng)調(diào)用會通過復制一個現(xiàn)有進程來創(chuàng)建一個全新的進程.?進程被存放在一個叫做任務隊列的雙向循環(huán)鏈表當中.鏈表當中的每一項都是類型為task_struct成為進程描述符的結(jié)構(gòu).也就是我們寫過的進程PCB.?
小知識:內(nèi)核通過一個位置的進程標識值或PID來標識每一個進程.同時其最大值默認為32768,short int短整型的最大值.?他就是系統(tǒng)中允許同時存在的進程最大的數(shù)目.可以去linux下的proc目錄中尋找一個 pid_max的文件,并打開它加以驗證. 如
fork()運行時做的事情
首先我們來看一段代碼,不過這里會有一點奇怪的現(xiàn)象:
/*************************************************************************> File Name: 1.c> Author: tp> Mail: > Created Time: Mon 07 May 2018 07:57:28 PM CST************************************************************************/#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main( void) {printf("change world!\n");pid_t pid = fork();if( pid == -1) {perror("fork"),exit(1); }printf( "pid=%d, returnVal=%d\n", getpid(), pid);sleep( 1);exit(0); } ~這段代碼的運行結(jié)果,大家如果像我當時不了解fork的時候,一定會以為輸出結(jié)果是兩個"change world!",然后2個printf里面的內(nèi)容.?因為
我們復制出來了兩個一模一樣的進程,那么他們就應該做同樣的事情. But!!!?我們看運行結(jié)果:
?
結(jié)果并非我們想的那樣,這個時候我們就需要知道fork出子進程之后,程序的運行細節(jié)。可以來一張圖幫助我們理解:
?
?
一般來說,在fork之后是父進程先執(zhí)行還是子進程先執(zhí)行是不確定的.這取決于內(nèi)核所使用的調(diào)度算法.如果要求父,子進程之間相互同步.則要求某種形式的進程間通信.?好了我們繼續(xù),當進程調(diào)用fork后,當控制轉(zhuǎn)移到內(nèi)核中的fork代碼后,內(nèi)核會做4件事情:
?
1.分配新的內(nèi)存塊和內(nèi)核數(shù)據(jù)結(jié)構(gòu)給子進程
2.將父進程部分數(shù)據(jù)結(jié)構(gòu)內(nèi)容(數(shù)據(jù)空間,堆棧等)拷貝至子進程
3.添加子進程到系統(tǒng)進程列表當中
4.fork返回,開始調(diào)度器調(diào)度
為什么fork成功調(diào)用后返回兩個值??
由于在復制時復制了父進程的堆棧段,所以兩個進程都停留在fork函數(shù)中,等待返回。所以fork函數(shù)會返回兩次,一次是在父進程中返回,另一次是在子進程中返回,這兩次的返回值不同,
其中父進程返回子進程pid,這是由于一個進程可以有多個子進程,但是卻沒有一個函數(shù)可以讓一個進程來獲得這些子進程id,那談何給別人你創(chuàng)建出來的進程。而子進程返回0,這是由于子進程可以調(diào)用getppid獲得其父進程進程ID,但這個父進程ID卻不可能為0,因為進程ID0總是有內(nèi)核交換進程所用,故返回0就可代表正常返回了。
從fork函數(shù)開始以后的代碼父子共享,既父進程要執(zhí)行這段代碼,子進程也要執(zhí)行這段代碼.(子進程獲得父進程數(shù)據(jù)空間,堆和棧的副本.?但是父子進程并不共享這些存儲空間部分. (即父,子進程共享代碼段.)。現(xiàn)在很多實現(xiàn)并不執(zhí)行一個父進程數(shù)據(jù)段,堆和棧的完全復制.?而是采用寫時拷貝技術(shù)(不懂可以戳進去看一看).這些區(qū)域有父子進程共享,而且內(nèi)核地他們的訪問權(quán)限改為只讀的.如果父子進程中任一個試圖修改這些區(qū)域,則內(nèi)核值為修改區(qū)域的那塊內(nèi)存制作一個副本,?也就是如果你不修改我們一起用,你修改了之后對于修改的那部分內(nèi)容我們分開各用個的.
?父子進程文件共享問題
/*************************************************************************> File Name: 2.c> Author: tp> Mail: > Created Time: Mon 07 May 2018 12:40:39 PM CST************************************************************************/#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h>int set = 110; int main( void) {printf( "before fork\n");pid_t pid = fork( );if( pid < 0){ perror(" fork"),exit( 1);}if( pid == 0){++set;printf( "son pid=%d, %d\n", getpid(), set);}else{sleep( 1);printf( "parent pid=%d , %d\n", getpid( ), set);}exit( 0); }看一下結(jié)果:
?
科普小知識:
1、getpid()函數(shù)功能:取得進程識別碼; ?其返回值:目前進程的進程ID
2、fork()函數(shù)返回值: 若成功調(diào)用一次則返回兩個值,子進程返回0,父進程返回子進程ID;否則,出錯返回-1
不難注意到??“before fork”這句話只是被打印了一次,這個從上面的例子,這不難理解;與此同時子進程中的set的值被改變了。此時再進行一個重定向操作會發(fā)生什么
?
出現(xiàn)很神奇的現(xiàn)象! 這個時候打印了出了兩次“before fork”,不僅僅是如此,上述針對父進程的標準輸出執(zhí)行重定向操作還導致了子進程也執(zhí)行重定向操作。
透過現(xiàn)象看本質(zhì),來細細分析一下。針對打印兩次“before fork”,首先,先要知道標準IO庫是是帶緩沖的,而像printf這種直接輸出到標準輸出時,這個緩沖區(qū)是由換行符刷新的;而當執(zhí)行了重定向操作,這里就是將標準輸出重定向到文件,文件就不會立即去刷新緩沖區(qū)(全緩沖的方式);好,由于在fork之前調(diào)用了一次printf,但fork之后,該行數(shù)據(jù)仍存留在緩沖區(qū)中,然后父進程數(shù)據(jù)空間被復制到子進程中,該行數(shù)據(jù)去也被復制了過去,這樣父子進程都各自帶有該行內(nèi)容的緩沖區(qū)了,相當于子進程緩沖區(qū)添加了一行“before fork”,然后在每個進程exit之后,每個緩沖區(qū)的內(nèi)容就被寫到了相應的文件中。
再一個就是,在重定向父進程的標準輸出時,子進程標準輸出也被重定向。這就源于父子進程會共享所有的打開文件。 因為fork的特性就是將父進程所有打開文件描述符復制到子進程中。當父進程的標準輸出被重定向,子進程本是寫到標準輸出的時候,此時自然也改寫到那個對應的地方;與此同時,在父進程等待子進程執(zhí)行時,子進程被改寫到文件show.out中,然后又更新了與父進程共享的該文件的偏移量;那么在子進程終止后,父進程也寫到show.out中,同時其輸出還會追加在子進程所寫數(shù)據(jù)之后,這也就解釋了上面為什么“before fork”會在一個文件中打印兩次。
在fork之后處理文件描述符一般又以下兩種情況:
1.父進程等待子進程完成。此種情況,父進程無需對其描述符作任何處理。當子進程終止后,它曾進行過讀,寫操作的任一共享描述符的文件偏移已發(fā)生改變。
2.父子進程各自執(zhí)行不同的程序段。這樣fork之后,父進程和子進程各自關(guān)閉它們不再使用的文件描述符,這樣就避免干擾對方使用的文件描述符了。這類似于網(wǎng)絡(luò)服務進程。
?
同時父子進程也是有區(qū)別的:它們不僅僅是兩個返回值不同;它們各自的父進程也不同,父進程的父進程是ID不變的;還有子進程不繼承父進程設(shè)置的文件鎖,子進程未處理的信號集會設(shè)置為空集等不同
fork()函數(shù)在底層中做了什么??
linux平臺通過clone()系統(tǒng)調(diào)用實現(xiàn)fork(). fork(),vfork()和clone()庫函數(shù)都根據(jù)各自需要的參數(shù)標志去調(diào)用clone(),然后由clone()去調(diào)用
do_fork().?再然后do_fork()完成了創(chuàng)建中的大部分工作,他定義在kernel/fork.c當中.該函數(shù)調(diào)用copy_process().?然后重點來了,我們看看這個
copy_process函數(shù)到底做了那些事情???我畫一張圖幫我們理解:
?
?
vfork和fork的之間的比較:
vfork()的誕生是在fork()還沒有寫時拷貝的時候,因為那個時候創(chuàng)建一個子進程的成本太大了,如果一下子創(chuàng)建好多了那么程序的效率一定會下降.?然后就有人提出了vfork(). vfork的實現(xiàn)原理非常簡單,就是子進程,父進程完全公用一個資源.?就是是有人修改了內(nèi)容,甚至main()函數(shù)退出了也不會新開辟一個空間. 所以這里里會有問題的,如果你的一個子進程沒有使用exit()退出,那么程序就會出現(xiàn)段錯誤.?不相信可以去試一試~?
為什么會出現(xiàn)段錯誤??
在函數(shù)棧上面,子進程運行結(jié)束了,main的函數(shù)棧被子進程釋放了,然后父進程在使用的時候,就訪問不到了,一旦vfork出子進程,退出的時候需要使用exit來結(jié)束.
?
vfork和fork之間的區(qū)別:
?
1.fork父子進程交替運行,vfork保證子進程先運行,父進程阻塞,直到子進程結(jié)束(或子進程調(diào)用了exec或exit).
2.fork實現(xiàn)了寫時拷貝. 而vfork直接讓父子進程共用公用資源,避免多開辟空間拷貝,
3,vfork必須使用exit或者excl退出.
4.就算是fork使用了寫時拷貝,也沒有vfork性能高.
5.每個系統(tǒng)上的vfork都有問題,推薦不要使用.
?
參考連接:https://www.cnblogs.com/tp-16b/p/9005079.html#_label0
? ? ? ? ? ? ? ? ? ?https://baike.baidu.com/item/getpid/4475877?fr=aladdin
總結(jié)
以上是生活随笔為你收集整理的函数 —— fork()分叉函数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sqlite库 —— 为什么要使用 SQ
- 下一篇: 如何解决 “ 段错误(吐核) ” ???