一次fork引發的慘案!

“你還有什麼要說的嗎?沒有的話我就要動手了”,kill程序最後問道。

這一次,我沒有再回答。

只見kill老哥手起刀落,我短暫的一生就這樣結束了···

 

 

我是一個網絡程序,一直以來都運行在Windows系統上,日子過得很舒服。可前段時間,程序員告訴我要把我移植到Linux系統下運行,需要對我大動手術,我平靜的生活就這樣被打破了。

來到這個叫Linux的地方運行,一切對我都很陌生,沒有了熟悉的C盤、D盤和E盤,取而代之的是各種各樣的目錄。

/bin
/boot
/etc
/dev
/mnt
/opt
/proc
/home
/usr
/usr64
/var
/sys
...

這裏很有意思,一切都是文件,硬件設備是文件、管道是文件、網絡套接字也是文件,搞得我很不適應。

這些都還好,我都還能接受,但直到今天···

 

奇怪的fork

今天早上,我收到了一個網絡請求,需要完成一個功能,這個工作比較耗時,我準備創建一個子進程,讓我的小弟去完成。

這是我第一次在Linux系統上創建進程,有點摸不着北,看了半天,只看到程序員在我的代碼裏寫了一個fork函數:

pid_t pid=fork();
if ( pid > 0 ) {
    ···
} else if( pid == 0 ) {
    ···
} else {
    ···
}

我晃晃悠悠的來到fork函數的門前,四處觀察。

“您是要創建進程嗎?”,fork函數好像看出了我的來意。

“是的,我是第一次在這裏創建進程,以前我在Windows那片兒的時候,都是調用CreateProcess,但這裏好像沒有叫這個名字的函數···”

fork函數聽後笑了起來,說道:“別找了,我就是負責創建進程的函數”

“你?fork不是叉子的意思嗎,好端端的幹嘛取這麼個名字?”,我一邊說,一邊朝fork函數走去。

fork沒有理會我的問題,只是說道:“您這邊稍坐一下,我要跟內核通信一下,讓內核創建一個子進程”

這下我倒是明白他的意思,像創建進程這種操作,都是由操作系統內核中的系統調用來完成的,而像fork這些我們可以直接調用的函數只是應用層的接口而已,這跟以前在Windows上是一樣的。

 

不過我突然反應過來,着急問道:“唉,我還沒告訴你要創建的進程參數呢,你怎麼知道要啓動哪個程序?”

fork撲哧一下笑出了聲,不過並沒有回答我的問題。

人生地不熟的,我也沒好再多問,只好耐心等待,等待期間我竟然睡着了。

 

 

“醒醒”,不知過了多久,fork函數叫醒了我:“創建完成了,請拿好,這是進程號pid”,說完給了我一個數字。

我攤開一看,居然寫了一個大大的0!

 

“怎麼搞的,創建失敗了?”,我問到。

“沒有啊,您就是剛剛創建的子進程”

“啥?你是不是搞錯了,我就是專程來創建子進程的,我自己怎麼會是子進程?”

fork函數又笑了,“我沒有搞錯,您其實已經不是原來的你了,而是一個複製品,是內核剛剛複製出來的”

 

“複製品?什麼意思?”,我越聽越懵!

“每個進程在內核中都是一個task_struct結構,剛纔您睡着期間,內核在創建進程的時候,把內核中原來的你的task_struct複製了一份,還創建了一個全新的進程地址空間和堆棧,現在的你和原來的你除了極少數地方不一樣,基本上差不多”

 

 

“那原來的我呢?去哪裏了”

“他已經變成你的父進程了,我是一個特殊的函數,一次調用會返回兩次,在父進程和子進程中都會返回。在原來的進程中,我把你的進程號給了他,而我返回給你0,就表示你現在就是子進程”

原來是這樣,我大受震撼,這簡直顛覆我的認知,居然還有如此奇特的函數,調用一次,就變成了兩個進程,思考之間,我忽然有些明白這個函數爲什麼要叫fork的原因了。

 

寫時拷貝

“您是剛來咱們這裏吧,可能還不太熟悉,慢慢就習慣了”

“你們這效率也太高了吧,整個進程地址空間那麼大,居然這麼快就複製了一份!”

fork函數又笑了!難道我又說錯話了?

 

“進程的內存地址空間可沒有複製,你現在和父進程是共享的內存空間的”

“啥?共享?你剛纔不是說創建了新的進程空間和堆棧嗎?”

“您看到的內存地址空間是虛擬的,您的內存頁面和父進程的內存頁面實際上是映射的同一個物理內存頁,所以實際上是共享的喲”

 

 

“原來是這樣,可是弄成共享了,兩個進程一起用,豈不是要出亂子?”

“放心,內核把這些頁面都設置成了只讀,如果你們只是讀的話,不會有問題,但只要有一方嘗試寫入,就會觸發異常,內核發現異常後再去分配一個新的頁面讓你們分開使用。哦對了,這個叫寫時拷貝(COW) 機制”

 

 

“有點意思,你們倒是挺聰明的”

“沒辦法,儘量壓縮成本,提高創建進程的效率嘛,因爲進程中的很多內存頁面都只會去讀,如果全部無腦拷貝一份,那不是太浪費資源和時間了嗎”,fork函數說到。

“有道理,有道理”,我點了點頭,告別了fork函數,準備回去繼續工作。

 

消失的線程們

本以爲這奇怪的進程創建方式已經讓我大開眼界了,沒想到可怕的事情纔剛剛開始。

告別fork函數沒多久,我就卡在了一個地方沒法執行下去,原來,前面有一把鎖被別的線程佔用了,而我現在也需要佔用它。

這倒也不足爲奇,以往工作的時候,也經常碰到鎖被別的線程鎖定的情況,但這一次,我等了很久也一直不見有線程來釋放。

 

“喂,醒醒”

不知過了多久,我竟然又睡着了。

睜開眼睛,另一個程序站出現在了我的面前。

 

“你是?”

“你好,我是kill”

“kill?那個專門殺進程的kill程序?你來找我幹嘛”,我驚的一下睡意全無。

 

 

kill程序從背後拿出了兩個數字:9,1409

“你看,這是我收到的參數,1409是你的進程號PID,9表示要強制殺死你”

“啊?爲什麼?”,那一刻,我徹底慌了。

 

“可能是你卡死在這裏太久了吧,人類才啓動我來結束你的運行”,kill程序說到。

“是啊,不知道是哪個該死的線程佔用了這把鎖一直不釋放,我才卡在這裏”,我委屈的說到。

“哪裏有別的線程,我看了一下,你這進程就只有一個線程啊!”

 

“你看錯了吧?”,說完,我認真檢查了起來,居然還真只有一個線程了!我白等了這麼久!

“奇怪了,我明明是一個多線程的程序啊!”,我眉頭緊鎖。

“你仔細想想,剛纔有沒有發生什麼事情?”,kill程序問到。

 

“我就執行了一下fork,生成了一個子進程,哦對了,我就是那個子進程”

“難怪!”,kill程序恍然大悟。

“難怪什麼?

“fork那傢伙創建子進程的時候,只會複製當前的線程,其他線程不會被複制!”,Kill程序說完嘆了口氣,彷彿已經見怪不怪了。

“what?怎麼會這樣?其他線程沒複製,那豈不是要出亂子?”

 

kill程序不緊不慢地說道:“這都是歷史遺留問題了,早期都是單線程的程序,一個task_struct就是一個進程,fork這樣做是沒有問題的,後來出現了多線程技術,一個task_struct實際上是一個線程了,多個task_struct通過共享地址空間,成爲一個線程組,也就是進程,但fork仍然只複製當前的線程,就有了這個問題”

“我去,這坑爹的fork!”

“你不是第一個被坑的了!等着程序員把你重新改造下吧”

“唉···”,我長長的嘆了口氣。

 

“你還有什麼要說的嗎?沒有的話我就要動手了”,kill程序最後問道。

這一次,我沒有再回答。

只見kill老哥手起刀落,一切都消失了···

【完】

趣話計算機底層技術,喜歡的話,記得三連哦~

相關故事推薦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章