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