在《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位架構就沒有這種問題了!