關於父子進程共享文件的學習研究

在《unix環境高級編程》中關於文件共享:

父子進程共享同一個文件的偏移量!!!”

考慮下面情況:一個進程fork了一個子進程,然後等待子進程終止。假定,作爲普通處理的一部分,父進程和子進程都向標準輸出進行寫操作。如果父進程的標準輸出已經重定向,那麼子進程寫到該標準輸出時,它將更新與該系統共享的文件偏移量。在這個例子中,當父進程等待子進程時,子進程寫到標準輸出;而在子進程終止後,父進程也寫到標準輸出上,並且知道其輸出會追加在子進程所寫數據之後。

如果父進程和子進程寫同一描述符指向的文件,但又沒有任何形式的同步,那麼他們的輸出就會相互混合。



#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>




int main(int argc,char **argv)
{
        pid_t child;
        char c;
        int fd;
        child = fork();


        fd = open("./Hello",O_RDONLY);
        if(-1 == child)
        {
                perror("fork fail:");
                _exit(-1);
        }


        if(0 == child)
        {
                while(read(fd,&c,1))
                {
                        write(STDOUT_FILENO,&c,1);
                        sleep(1);
                }
        }


        if(child)
        {
                while(read(fd,&c,1))
                {
                        write(STDOUT_FILENO,&c,1);
                        sleep(1);
                }
        }


        return 0;
}


以上爲實驗代碼,讀取的文件中內容是Hello World!!!

然而輸出結果是 HHeelllloo    WWoorrlldd!!!!!!

這不應該啊,按照書中所說的話,那麼可能會出現亂序打印,但不可能出現重複打印的情況啊。

於是去問度娘,結果找到了一個有相同問題的同學。

他給了一份解釋:

我用的是unix center的一臺機器:x4100.unix-center.net,查不到它是單核的還是多核的。
剛纔又把程序分別在單核的機器上和多核的機器上試了一下,

發現單核的機器上的輸出沒有相同偏移量的字母被多次讀取的現象,而在多核的機器上運行結果類似與unix center機器上的效果

也會出現相同偏移量的字母被多次讀取的現象,所以猜測unix center機器應該是一臺多核的機器。

這樣一來,問題的答案也很清楚了,read調用應該是原子操作,在單核CPU下不可能有read操作在同時進行,

每次read完以後都會正確設置文件偏移量,所以沒有問題,多核CPU的情況下可能會出現2個CPU在同時運行父子進程,

也就是父子進程同時執行read調用,所以有時出現了同一個字母被讀取了兩次的現象。得到的一個教訓是,

在多核系統下做開發,要仔細考慮併發控制的問題。本想輸出CPU的信息來驗證,但找了半天沒找到得到CPU信息的API,

暫時算了,有哪位知道哪個API可以得到CPU信息,歡迎告訴我,謝謝大家!

可惜我沒有單核電腦做實驗驗證。。。。


然後,後面是他請教的高手的一份答案:

下面的kernel郵件列表中說用戶態程序若出現多線程/多進程共享文件描述符進行操作的情況,

程序自己必須使用同步機制來將請求序列化,除了

以 O_APPEND模式進行的write操作(POSIX標準要求這種操作是原子化的)以外kernel不會爲程序提供任何同步保障

內核主要開發人員對此似乎早就達成了共識……


solaris、freebsd和linux在多線程/進程共享文件描述符操作時的內部實現不一樣,

solaris在write()時是序列化的,freebsd對read()和write()都進行了序列化,

linux對read()和write()都沒有序列化。實際在freebsd 6.x上運行同樣的程序也確實沒有發現類似的問題。

另外在32-bit架構上共享文件描述符還有一個潛在的競態條件,因爲文件偏移量是64-bit的, 

32-bit架構上要更新偏移量就需要至少2條指令,

對於搶佔式的linux內核來說很有可能在偏移量更新中途發生了進程/線程切換,

導致偏移量出現部分更新的不一致數據;64-bit架構上則消除了這個競態條件,但read()/write()操作的非序列化競態條件仍然存在。

好吧,看來意思已經很明瞭。。。。。LINUX好像並沒有序列化。。。

而且按照他的意思,32爲CPU在更新文件偏移量時需要兩條指令,也就是說可能還沒更新完成,就有了線程/進程切換。但是64位架構就沒有這種問題了!


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