日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

神奇的vfork

發(fā)布時(shí)間:2023/11/30 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 神奇的vfork 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
一段神奇的代碼

在論壇里看到下面一段代碼:
int createproc();
int main()
{
pid_t pid=createproc();
printf("%d\n", pid);
exit(0);
}
int createproc()
{
pid_t pid;
if(!(pid=vfork())) {
printf("child proc:%d\n", pid);
return pid;
}
else return -1;
}
輸出結(jié)果:
child proc:0
0
child proc:0
Killed

? ? ? 感覺(jué)非常奇怪,為什么vfork以后,父子進(jìn)程都走了“子進(jìn)程”的分支呢?

? ? ? 什么是vfork?

? ? ? 什么是vfork,網(wǎng)絡(luò)上介紹它的文檔很多,隨便一搜就是一大堆。簡(jiǎn)單來(lái)說(shuō),vfork和fork完成了基本上相同的功能,把進(jìn)程做了一次復(fù)制,變成兩個(gè)進(jìn)程。
? ? ? 在shell中,執(zhí)行命令時(shí),shell程序就是通過(guò)“復(fù)制”形成了父子進(jìn)程。子進(jìn)程生成后,執(zhí)行exec系列函數(shù),載入新的可執(zhí)行文件,開(kāi)始執(zhí)行。
由于復(fù)制完成后,子進(jìn)程馬上就要載入新的程序來(lái)運(yùn)行了,在此之前從父進(jìn)程那里復(fù)制來(lái)的內(nèi)存空間都不需要了。所以,“復(fù)制”過(guò)程中,復(fù)制內(nèi)存空間是件費(fèi)力不討好的事情。
所以,fork有了“寫(xiě)時(shí)復(fù)制”技術(shù)?!皬?fù)制”的時(shí)候內(nèi)存并沒(méi)有被復(fù)制,而是共享的。直到父子進(jìn)程之一去寫(xiě)某塊內(nèi)存時(shí),它才被復(fù)制。(內(nèi)核先將這些內(nèi)存設(shè)為只讀,當(dāng)它們被寫(xiě)時(shí),CPU出現(xiàn)訪存異常。內(nèi)核捕捉異常,復(fù)制空間,并改屬性為可寫(xiě)。)

? ? ?上面說(shuō)到的內(nèi)存空間是實(shí)際存儲(chǔ)用戶數(shù)據(jù)的空間,利用“寫(xiě)時(shí)復(fù)制”避免了干前面提到的那件費(fèi)力不討好的事情。
? ? ? 但是,“寫(xiě)時(shí)復(fù)制”其實(shí)還是有復(fù)制,進(jìn)程的mm結(jié)構(gòu)、頁(yè)表都還是被復(fù)制了(“寫(xiě)時(shí)復(fù)制”也必須由這些信息來(lái)支撐。否則內(nèi)核捕捉到CPU訪存異常,怎么區(qū)分這是“寫(xiě)時(shí)復(fù)制”引起的,還是真正的越權(quán)訪問(wèn)呢?)。
? ? ?而vfork就把事情做絕了,所有有關(guān)于內(nèi)存的東西都不復(fù)制了,父子進(jìn)程的內(nèi)存是完全共享的。但是這樣一來(lái)又有問(wèn)題了,雖然用戶程序可以設(shè)計(jì)很多方法來(lái)避免父子進(jìn)程間的訪存沖突。但是關(guān)鍵的一點(diǎn),父子進(jìn)程共用著棧,這可不由用戶程序控制的。一個(gè)進(jìn)程進(jìn)行了關(guān)于函數(shù)調(diào)用或返回的操作,則另一個(gè)進(jìn)程的調(diào)用棧(實(shí)際上就是同一個(gè)棧)也被影響了。這樣的程序沒(méi)法運(yùn)行下去。

? ? ?所以,vfork有個(gè)限制,子進(jìn)程生成后,父進(jìn)程在vfork中被內(nèi)核掛起,直到子進(jìn)程有了自己的內(nèi)存空間(exec**)或退出(_exit)。并且,在此之前,子進(jìn)程不能從調(diào)用vfork的函數(shù)中返回(同時(shí),不能修改棧上變量、不能繼續(xù)調(diào)用除_exit或exec系列之外的函數(shù),否則父進(jìn)程的數(shù)據(jù)可能被改寫(xiě))。
盡管限制很多,但并不妨礙實(shí)現(xiàn)前面提到的關(guān)于shell程序的那個(gè)“需求”。

? 問(wèn)題的思考

? ? ? 說(shuō)到這里,可以看出文章開(kāi)頭的那段代碼是存在問(wèn)題的了。子進(jìn)程不但調(diào)用了printf,還從createproc函數(shù)中返回了。
? ? ? 但是,子進(jìn)程的違規(guī)為什么會(huì)使父進(jìn)程走上“child proc”這條路呢?父進(jìn)程在子進(jìn)程退出前被阻塞在vfork里面,vfork的返回值是如何變成0的呢

? ? ?前面一直在說(shuō)vfork,其實(shí)它是兩個(gè)東西,庫(kù)(libc)函數(shù)vfork和系統(tǒng)調(diào)用vfork用戶程序調(diào)用的是庫(kù)函數(shù),而庫(kù)函數(shù)再去調(diào)用系統(tǒng)調(diào)用。用戶程序中幾乎所有的系統(tǒng)調(diào)用都是通過(guò)庫(kù)函數(shù)去調(diào)用的。因?yàn)椴煌w系結(jié)構(gòu)下(甚至相同體系結(jié)構(gòu)),系統(tǒng)調(diào)用的指令和參數(shù)傳遞規(guī)則都可能不同,這些細(xì)節(jié)被庫(kù)函數(shù)隱藏了。

? ? ? 前面提到,父進(jìn)程被掛起在vfork中,這是指的系統(tǒng)調(diào)用vfork。在系統(tǒng)調(diào)用中,進(jìn)程使用的是內(nèi)核棧(每個(gè)進(jìn)程有著自己獨(dú)有的內(nèi)核棧)。此時(shí),父進(jìn)程在內(nèi)核里面是安全的,隨便子進(jìn)程怎么違規(guī)。內(nèi)核會(huì)保證系統(tǒng)調(diào)用vfork的完整性,系統(tǒng)調(diào)用的返回值也不會(huì)有問(wèn)題(它是通過(guò)寄存器傳回用戶空間的,跟棧無(wú)關(guān))。
? ? ?而vfork的返回值變成0的問(wèn)題,則是在庫(kù)函數(shù)vfork中產(chǎn)生的。既然子進(jìn)程已經(jīng)違規(guī)了,庫(kù)函數(shù)沒(méi)辦法保證程序的正確性。而庫(kù)函數(shù)vfork是否返回0也是不確定的,可能不同版本的libc、不同的程序上下文、不同的系統(tǒng)、等等、都會(huì)有不同的返回值(或者就直接“段錯(cuò)誤”了)。還有可能是,父進(jìn)程中庫(kù)函數(shù)vfork并沒(méi)有返回0,但是棧上的返回地址被改寫(xiě)了,從函數(shù)createproc返回,返回到printf("child proc")這句話去了。

? 再深入一點(diǎn)

? ? ? vfork后,庫(kù)函數(shù)沒(méi)法保證子進(jìn)程在進(jìn)行函數(shù)調(diào)用或返回的操作后程序還正常,但是庫(kù)函數(shù)vfork本身就是一個(gè)函數(shù)呀,從系統(tǒng)調(diào)用vfork返回后,庫(kù)函數(shù)vfork接著又返回了。這時(shí),程序的正確性又是如何保證的呢?

關(guān)于函數(shù)調(diào)用,一般而言:調(diào)用前-調(diào)用者將需要傳遞的參數(shù)放到棧上;調(diào)用時(shí)-調(diào)用者使用call指令,該指令自動(dòng)將返回地址入棧;調(diào)用后,在被調(diào)用的函數(shù)中,第一件事是做調(diào)用棧的調(diào)整,如createproc函數(shù)如是做:
08048487 <createproc>:
8048487:?????? 55????????????????????? push?? %ebp
8048488:?????? 89 e5???????????????? mov??? %esp,%ebp
804848a:?????? 83 ec 28??????????? sub??? $0x28,%esp
......
其中ESP是當(dāng)前棧的指針,而EBP是上一層調(diào)用棧的指針。調(diào)用棧調(diào)整之前,EBP保存著上上一層棧的指針,這個(gè)值不能丟,需要放在棧上,以便函數(shù)返回時(shí)恢復(fù)。

每層調(diào)用都有自己的調(diào)用棧,“深”的調(diào)用不會(huì)影響到之前的調(diào)用棧。所以,vfork后子進(jìn)程調(diào)用其他函數(shù)應(yīng)該是沒(méi)有問(wèn)題的(但是可能會(huì)改寫(xiě)掉屬于父進(jìn)程的某些數(shù)據(jù),造成邏輯上的錯(cuò)誤),只要它不從調(diào)用vfork的函數(shù)中返回就行了。
但是,庫(kù)函數(shù)vfork本身卻不是這樣做的。在這個(gè)函數(shù)中沒(méi)有使用棧上的內(nèi)存空間,它沒(méi)有去進(jìn)行調(diào)用棧的切換,如:
000983f0 <__vfork>:
983f0:?????? 59????????????????????? pop??? %ecx
983f1:?????? 65 8b 15 6c 00 00 00??? mov??? %gs:0x6c,%edx
983f8:?????? 89 d0?????????????????? mov??? %edx,%eax
983fa:?????? f7 d8?????????????????? neg??? %eax
......
9840e:?????? cd 80?????????????????? int??? $0x80
98410:?????? 51????????????????????? push?? %ecx
......

所以父進(jìn)程在庫(kù)函數(shù)中運(yùn)行時(shí),不用擔(dān)心棧上的數(shù)據(jù)已經(jīng)被子進(jìn)程修改(它根本不去使用棧上的數(shù)據(jù))。
然而call/ret指令卻不得不使用棧(因?yàn)榉祷氐刂纷詣?dòng)會(huì)被CPU放在棧上),如果子進(jìn)程在vfork后調(diào)用其他函數(shù),會(huì)使得父進(jìn)程在進(jìn)入庫(kù)函數(shù)vfork時(shí)通過(guò)call指令在棧上留下的“返回地址”被擦掉。
事情的確是這樣。于是庫(kù)函數(shù)vfork為了解決這個(gè)問(wèn)題,做了一些手腳,它并沒(méi)有讓棧上的“返回地址”一直留在棧上。注意上面的匯編代碼,進(jìn)入庫(kù)函數(shù)vfork的第一條指令就是“pop %ecx”,把放在棧上的“返回地址”彈到了ECX中去,保存起來(lái)。然后在系統(tǒng)調(diào)用vfork返回后(int 0x80是用于系統(tǒng)調(diào)用的指令),再“push %ecx”,把“返回地址”放回去。

轉(zhuǎn)載于:https://www.cnblogs.com/wangfengju/archive/2013/05/11/6173100.html

總結(jié)

以上是生活随笔為你收集整理的神奇的vfork的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。