在《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位架构就没有这种问题了!