- 論壇徽章:
- 0
|
本來發(fā)在LINUX內(nèi)核版的,借借這里的人氣。
這是我在一個論壇上偶然看到的,在討論中有人提出LINUX系統(tǒng)中的fork()函數(shù)行為不夠清晰。
比如在一個多線程的其中一個線程中執(zhí)行fork()函數(shù),是應(yīng)該復(fù)制整個進(jìn)程(即所有線程)還是僅僅復(fù)制當(dāng)前調(diào)用fork()的線程。當(dāng)然他對
LINUX不夠熟悉。但是他提出了一個很有意思的例子來說明fork()的不足。
這里先把幾個概念理一下:
多線程:由于LINUX并沒有顯式支持進(jìn)程和線程的區(qū)別,使用了所謂的輕量級進(jìn)程表示線程,所以這里的多線程表示的是共享虛擬地址空間(和信號處理所需的數(shù)據(jù)結(jié)構(gòu)以及諸如文件等等,不過這里共享虛擬地址空間所引起的問題最大)。也就是通常使用fork()函數(shù)時指定CLONE_VM以及相關(guān)的flag,或者使用clone(),或者使用pthread庫創(chuàng)建的進(jìn)程。這些所有的調(diào)用最后都是使用do_fork(),外加flag的特定限制,特別的使用CLONE_VM指定新進(jìn)程(線程)使用當(dāng)前進(jìn)程的虛擬地址空間,包括頁目錄和頁表。
也就是說,這些多線程共用一個虛擬地址空間,包括頁表和頁目錄。問題也就開始出現(xiàn)了。
現(xiàn)在舉一個例子:假如一個SMP系統(tǒng)上面有若干個多線程,t1,t2,...tn,它們使用兩個全局變量glob1, glob2進(jìn)行某方面的同步,需要使用諸如信號量之類的鎖進(jìn)行互斥控制。在絕大多數(shù)情況下,都是沒有問題的,F(xiàn)在假設(shè)線程t1執(zhí)行了fork()希望創(chuàng)建一個進(jìn)程(而不是線程,即新創(chuàng)建的子進(jìn)程盡管初始的虛擬地址空間跟父進(jìn)程完全一樣,但是使用了COW技術(shù)保證它們有自己獨(dú)立的虛擬地址空間)。在現(xiàn)在的內(nèi)核中,do_fork()調(diào)用具體函數(shù)將父進(jìn)程的mm_struct以及vma鏈表復(fù)制給子進(jìn)程,同時也將父進(jìn)程的頁目錄和頁表復(fù)制給子進(jìn)程的頁目錄和頁表。但是由于內(nèi)核無法完全控制父進(jìn)程的頁目錄和頁表(盡管它擁有控制互斥的自旋鎖和信號量,其他CPU上內(nèi)核執(zhí)行路徑是已經(jīng)無法修改訪問父進(jìn)程的mm和頁表,頁目錄。但是其他CPU上在用戶態(tài)運(yùn)行的線程t2,t3,..tn都可以訪問頁目錄和頁表。
假設(shè)這個多線程中有這樣一個臨界區(qū)代碼:
...........
mutex_lock()
glob1 += 2;
glob2 -= 2;
mutex_unlock();
...........
也就是說glob1和glob2要嗎都不改變,要嗎都需要改變,也就是原子操作。
假設(shè)glob1和glob2屬于同一個頁(那么也屬于同一個物理頁)。當(dāng)內(nèi)核在執(zhí)行t1的do_fork時,對于glob1和glob2所在的物理頁作COW,具體一點(diǎn)就是把指向該物理頁的pte設(shè)為只讀。如果其他CPU上某個線程ti正在臨界區(qū),那么就有可能出現(xiàn)以下執(zhí)行序列:
ti: mutex_lock();
glob1 += 2;
t1: 內(nèi)核將指向該物理頁的pte置為只讀(COW)
ti: glob2 -= 2; //在這里,由于現(xiàn)在的pte是只讀的,會發(fā)生缺頁異常,COW將使得glob2的修改只對t1,t2,...tn可見,
//而子進(jìn)程中的glob2沒有被修改
mutex_unlock();
在剛創(chuàng)建的子進(jìn)程返回到用戶態(tài)時,它的glob1和glob2處在不一致狀態(tài)?! |
|