关于父子进程共享文件的学习研究

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


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