“你還有什麼要說的嗎?沒有的話我就要動手了”,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老哥手起刀落,一切都消失了···
【完】
趣話計算機底層技術,喜歡的話,記得三連哦~