国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 操作系統(tǒng) > 正文

關(guān)于fork函數(shù)中的內(nèi)存復(fù)制和共享

2024-06-28 13:22:43
字體:
供稿:網(wǎng)友
關(guān)于fork函數(shù)中的內(nèi)存復(fù)制和共享

  原來剛剛開始做linux下面的多進(jìn)程編程的時候,對于下面這段代碼感到很奇怪,

 1 #include<unistd.h> 2 #include<stdio.h> 3 #include<string.h> 4 #include<stdlib.h> 5 #include<stdarg.h> 6 #include<errno.h> 7 #define LEN 2 8 void err_exit(char *fmt,...); 9 int main(int argc,char *argv[])10 {11     pid_t pid;12     int loop; 13 14     for(loop=0;loop<LEN;loop++)15     {16     if((pid=fork()) < 0)17         err_exit("[fork:%d]: ",loop);18     else if(pid == 0)19     {20        PRintf("Child process/n"); 21     }22     else23     {24         sleep(5);25     }26     }27 28     return 0;29 }

    為什么這段程序會創(chuàng)建3個子進(jìn)程,而不是兩個,為什么在第20行后面加上一個return 0;就創(chuàng)建的又是兩個子進(jìn)程了?原來一直搞不明白,后來了解了C語言程序的存儲空間布局以及在fork之后父子進(jìn)程是共享正文段(代碼段CS)之后才明白這其中的緣由!具體原理是啥,且容我慢慢道來!

    首先得明白一個東西就是C程序的存儲空間布局,如下圖所示:

  (原圖出自《UNIX環(huán)境高級編程》7.6節(jié))

    當(dāng)一個C程序執(zhí)行之后,它會被加載到內(nèi)存之中,它在內(nèi)存中的布局如上圖,分為這么幾個部分,環(huán)境變量和命令行參數(shù)、棧、堆、數(shù)據(jù)段(初始化和未初始化的)、正文段,下面挨個來說明這幾段分別代表了什么:

    環(huán)境變量和命令行參數(shù):這些指的就是Unix系統(tǒng)上的環(huán)境變量(比如$PATH)和傳給main函數(shù)的參數(shù)(argv指針?biāo)赶虻膬?nèi)容)。

    數(shù)據(jù)段:這個是指在C程序中定義的全局變量,如果沒有初始化,那么就存放在未初始化的數(shù)據(jù)段中,程序運(yùn)行時統(tǒng)一由exec賦值為0。否則就存放在初始化的數(shù)據(jù)段中,程序運(yùn)行時由exec統(tǒng)一從程序文件中讀取。(了解匯編的朋友們想必知道匯編語言中的數(shù)據(jù)段DS,這和匯編中的數(shù)據(jù)段其實(shí)是一個東西)。

    堆:這一部分主要用來動態(tài)分配空間。比如在C語言中用malloc申請的空間就是在這個區(qū)域申請的。

    正文段:C語言代碼并不是直接執(zhí)行的,而是被編譯成了機(jī)器指令才能夠在電腦上執(zhí)行,最終生成的機(jī)器指令就是存放在這個區(qū)域(匯編中的代碼段CS指的就是這片區(qū)域)。

    棧:個人感覺這是C程序內(nèi)存布局最關(guān)鍵的部分了。這個部分主要用來做函數(shù)調(diào)用。具體而言怎么說呢,程序剛開始棧中只有main這一個函數(shù)的內(nèi)容(即main的棧幀),如果main函數(shù)要調(diào)用func函數(shù),那么func函數(shù)的返回地址(main函數(shù)的地址),func函數(shù)的參數(shù),func函數(shù)中定義的局部變量,還有func函數(shù)的返回值等等這些都會被壓入棧中,這時棧中就多了func函數(shù)的內(nèi)容(func的棧幀)。然后func函數(shù)運(yùn)行完了之后再來彈棧,把它原來壓的內(nèi)容去掉(即清除掉func棧幀),此時棧中又只剩下了main的棧幀。(這片區(qū)域就是匯編中的棧段SS)

    OK,這就是C程序的存儲器布局。這里我聯(lián)想到另外一點(diǎn),就是全局變量和靜態(tài)變量是存儲在數(shù)據(jù)段中的,而局部變量是存儲在棧中的,棧中數(shù)據(jù)在函數(shù)調(diào)用完之后一彈棧就沒了,這就是為什么全局變量的生存周期比局部變量的生存周期要長的原因。

    了解了C程序在存儲器的布局之后,我們再來了解fork的內(nèi)存復(fù)制機(jī)制,關(guān)于這個,我們只需要了解一句話就夠了,“子進(jìn)程復(fù)制父進(jìn)程的數(shù)據(jù)空間(數(shù)據(jù)段)、棧和堆,父、子進(jìn)程共享正文段。”也就是說,對于程序中的數(shù)據(jù),子進(jìn)程要復(fù)制一份,但是對于指令,子進(jìn)程并不復(fù)制而是和父進(jìn)程共享。具體來看下面這段代碼(這是我在上面那段代碼上稍微添加了一點(diǎn)東西):

 1 /*  這個程序會創(chuàng)建3個子進(jìn)程,理解這句話,父子進(jìn)程復(fù)制數(shù)據(jù)段、棧、堆,共享正文段 2  * 3  */ 4 #include<unistd.h> 5 #include<stdio.h> 6 #include<string.h> 7 #include<stdlib.h> 8 #include<stdarg.h> 9 #include<errno.h>10 #define BUFSIZE 51211 #define LEN 212 void err_exit(char *fmt,...);13 int main(int argc,char *argv[])14 {15     pid_t pid;16     int loop; 17 18     for(loop=0;loop<LEN;loop++)19     {20     printf("Now is No.%d loop:/n",loop);21 22     if((pid=fork()) < 0)23         err_exit("[fork:%d]: ",loop);24     else if(pid == 0)25     {26        printf("[Child process]P:%d C:%d/n",getpid(),getppid()); 27     }28     else29     {30         sleep(5);31     }32     }33 34     return 0;35 }

    為什么上面那段代碼會創(chuàng)建三個子進(jìn)程?我們來具體分析一下它的執(zhí)行過程:

    首先父進(jìn)程執(zhí)行循環(huán),通過fork創(chuàng)建一個子進(jìn)程,然后sleep5秒。

    再來看父進(jìn)程創(chuàng)建的這個子進(jìn)程,這里我們記為子進(jìn)程1.子進(jìn)程1完全復(fù)制了這個父進(jìn)程的數(shù)據(jù)部分,但是需要注意的是它的正文段是和父進(jìn)程共享的。也就是說,子進(jìn)程1開始執(zhí)行代碼的部分并不是從main的 { 開始執(zhí)行的,而是主函數(shù)執(zhí)行到哪里了,它就接著執(zhí)行,具體而言就是它會執(zhí)行fork后面的代碼。所以子進(jìn)程1首先會打印出它的ID和它的父進(jìn)程的ID。然后繼續(xù)第二遍循環(huán),然后這個子進(jìn)程1再來創(chuàng)建一個子進(jìn)程,我們記為子進(jìn)程11,子進(jìn)程1開始sleep。

    子進(jìn)程11接著子進(jìn)程1執(zhí)行的代碼開始執(zhí)行(即fork后面),它也是打印出它的ID和父進(jìn)程ID(子進(jìn)程1),然后此時loop的值再加1就等于2了,所以子進(jìn)程2直接就返回了。

    那個子進(jìn)程1sleep完了之后也是loop的值加1之后變成了2,所以子進(jìn)程1也返回了!

    然后我們再返回去看父進(jìn)程,它僅僅循環(huán)了一次,sleep完之后再來進(jìn)行第二次循環(huán),這次又創(chuàng)建了一個子進(jìn)程我們記為子進(jìn)程2。然后父進(jìn)程開始sleep,sleep完了之后也結(jié)束了。

    那么那個子進(jìn)程2怎么樣了呢?它從fork后開始執(zhí)行,此時loop等于1,它打印完它的ID和父進(jìn)程ID之后,就結(jié)束循環(huán)了,整個子進(jìn)程2就直接結(jié)束了!

    這就是上面那段代碼的運(yùn)行流程,進(jìn)程間的關(guān)系如下圖所示:

    

    上圖中那個loop=%d就是當(dāng)這個進(jìn)程開始執(zhí)行的時候loop的值。上面那段代碼的運(yùn)行結(jié)果如下圖:

    

    這里這個3498進(jìn)程就是我們的主進(jìn)程,3499就是子進(jìn)程1,3500就是子進(jìn)程11,3501就是子進(jìn)程2。

    最后,我們再來回答一下我們開始的時候提出的那個問題,為什么在子進(jìn)程的處理部分“ if(pid == 0) ”最后加一個return 0,就會創(chuàng)建兩個子進(jìn)程了,就是因?yàn)樽舆M(jìn)程1運(yùn)行到這里直接就結(jié)束了,不再進(jìn)行第二遍循環(huán)了,所以就不會再去創(chuàng)建那個子進(jìn)程11了,所以最后一共就是創(chuàng)建了兩個子進(jìn)程啊!


發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 平凉市| 丰镇市| 厦门市| 滦南县| 靖安县| 陇西县| 景泰县| 巴林左旗| 蒙自县| 浦城县| 江陵县| 汾西县| 桦川县| 盐边县| 荥经县| 吉首市| 霍林郭勒市| 柳州市| 福清市| 鲁山县| 二连浩特市| 湖口县| 望谟县| 吴桥县| 大宁县| 明星| 大足县| 莎车县| 阳泉市| 明溪县| 鄂托克前旗| 新沂市| 娄烦县| 嘉荫县| 凯里市| 哈尔滨市| 东至县| 饶河县| 淮南市| 广东省| 稷山县|