多个进程同时写一个文件的问题

0oo0
多个进程同时写一个文件的问题

创建一组进程,在创建之前,父进程就已经open了一个文件,所以随后的子进程继承了这些fd资源,共享同一偏移量,那为什么程序执行完了以后,文件的输出跟预期的不一样呢?具体请看代码:

[table=95%][tr][td][font=FixedSys][color=#000000][color=#0000CC]#[/color][color=#FF0000]include[/color] [color=#0000CC]<[/color]stdio[color=#0000CC].[/color]h[color=#0000CC]>[/color]
[color=#0000CC]#[/color][color=#FF0000]include[/color] [color=#0000CC]<[/color]sys[color=#0000CC]/[/color]stat[color=#0000CC].[/color]h[color=#0000CC]>[/color]
[color=#0000CC]#[/color][color=#FF0000]include[/color] [color=#0000CC]<[/color]unistd[color=#0000CC].[/color]h[color=#0000CC]>[/color]
[color=#0000CC]#[/color][color=#FF0000]include[/color] [color=#0000CC]<[/color][color=#FF0000]string[/color][color=#0000CC].[/color]h[color=#0000CC]>[/color]
[color=#0000CC]#[/color][color=#FF0000]include[/color] [color=#0000CC]<[/color]stdlib[color=#0000CC].[/color]h[color=#0000CC]>[/color]
[color=#0000CC]#[/color][color=#FF0000]include[/color] [color=#0000CC]<[/color]fcntl[color=#0000CC].[/color]h[color=#0000CC]>[/color]
[color=#0000FF]int[/color] main [color=#0000CC]([/color][color=#0000FF]int[/color] argc[color=#0000CC],[/color] [color=#0000FF]char[/color][color=#0000CC]*[/color] argv[color=#0000CC][[/color][color=#0000CC]][/color][color=#0000CC])[/color]
[color=#0000CC]{[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]pid_t[/color] child_pid[color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000FF]int[/color] fd[color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000FF]char[/color] buf[color=#0000CC][[/color]1000[color=#0000CC]][/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000FF]int[/color] n[color=#0000CC],[/color] i[color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000FF]if[/color] [color=#0000CC]([/color]argc [color=#0000CC]![/color][color=#0000CC]=[/color] 3[color=#0000CC])[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000CC]{[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]fprintf[/color][color=#0000CC]([/color][color=#FF0000]stderr[/color][color=#0000CC],[/color] [color=#FF00FF]"Usage: ./a.out num filename"[/color][color=#0000CC])[/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]exit[/color][color=#0000CC]([/color]1[color=#0000CC])[/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000CC]}[/color]

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;n [color=#0000CC]=[/color] [color=#FF0000]atoi[/color][color=#0000CC]([/color]argv[color=#0000CC][[/color]1[color=#0000CC]][/color][color=#0000CC])[/color][color=#0000CC];[/color] [color=#FF9900]/*number of process */[/color]

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fd [color=#0000CC]=[/color] [color=#FF0000]open[/color][color=#0000CC]([/color]argv[color=#0000CC][[/color]2[color=#0000CC]][/color][color=#0000CC],[/color] O_RDWR [color=#0000CC]|[/color] O_CREAT[color=#0000CC],[/color] S_IRUSR [color=#0000CC]|[/color] S_IWUSR [color=#0000CC]|[/color] S_IRGRP [color=#0000CC]|[/color] S_IROTH[color=#0000CC])[/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000FF]if[/color] [color=#0000CC]([/color]fd [color=#0000CC]=[/color][color=#0000CC]=[/color] [color=#0000CC]-[/color]1[color=#0000CC])[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000CC]{[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]fprintf[/color][color=#0000CC]([/color][color=#FF0000]stderr[/color][color=#0000CC],[/color] [color=#FF00FF]" open error!/n"[/color][color=#0000CC])[/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]exit[/color][color=#0000CC]([/color]1[color=#0000CC])[/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000CC]}[/color]

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000FF]for[/color] [color=#0000CC]([/color]i [color=#0000CC]=[/color] 0[color=#0000CC];[/color] i [color=#0000CC]<[/color] n[color=#0000CC];[/color] i[color=#0000CC]+[/color][color=#0000CC]+[/color][color=#0000CC])[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000CC]{[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000FF]if[/color] [color=#0000CC]([/color][color=#0000CC]([/color]child_pid [color=#0000CC]=[/color] fork[color=#0000CC]([/color][color=#0000CC])[/color][color=#0000CC])[/color] [color=#0000CC]>[/color] 0[color=#0000CC])[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000FF]break[/color][color=#0000CC];[/color]

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000CC]}[/color]

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]sprintf[/color][color=#0000CC]([/color]buf[color=#0000CC],[/color] [color=#FF00FF]"i = %d/t process id = %d/n"[/color][color=#0000CC],[/color] i[color=#0000CC],[/color][color=#0000CC]([/color][color=#0000FF]long[/color][color=#0000CC])[/color]getpid[color=#0000CC]([/color][color=#0000CC])[/color][color=#0000CC])[/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]write[/color][color=#0000CC]([/color]fd[color=#0000CC],[/color] buf[color=#0000CC],[/color] [color=#FF0000]strlen[/color][color=#0000CC]([/color]buf[color=#0000CC])[/color][color=#0000CC])[/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]sprintf[/color][color=#0000CC]([/color]buf[color=#0000CC],[/color] [color=#FF00FF]"parent id = %d/t , child id = %d/t i = %d/n"[/color][color=#0000CC],[/color] [color=#0000CC]([/color][color=#0000FF]long[/color][color=#0000CC])[/color]getppid[color=#0000CC]([/color][color=#0000CC])[/color][color=#0000CC],[/color] [color=#0000CC]([/color][color=#0000FF]long[/color][color=#0000CC])[/color]child_pid[color=#0000CC],[/color] i[color=#0000CC])[/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]write[/color][color=#0000CC]([/color]fd[color=#0000CC],[/color] buf[color=#0000CC],[/color] [color=#FF0000]strlen[/color][color=#0000CC]([/color]buf[color=#0000CC])[/color][color=#0000CC])[/color][color=#0000CC];[/color]

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#FF0000]close[/color][color=#0000CC]([/color]fd[color=#0000CC])[/color][color=#0000CC];[/color]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[color=#0000FF]return[/color] 1[color=#0000CC];[/color]
[color=#0000CC]}[/color]
[/color][/font][/td][/tr][/table]

我的预期结果是,在要写入的文件中,应该有4个进程对文件写入相应的信息,即i = 0, i = 1, i = 2, i = 3这四对信息
但是实际的执行结果,只有i = 3 和 i = 0这两对信息
为什么i = 1 和 i = 2的write没有写入到对应的文件中呢?
我的执行命令是  ./a.out 3 a.test

最后a.test的内容是:

i = 3    process id = 6422
parent id = 6421         , child id = 0  i = 3
i = 0    process id = 6419
parent id = 14334        , child id = 6420       i = 0


请各位帮忙,谢谢!

cugb_cat
for (i = 0; i < n; i++)
        {
                if ((child_pid = fork()) > 0)
                        break;

        }
应该为if((child_pid=fork())) == 0) break;吧?
还有,文件指针好像是不共享,描述符是继承关系,但到了子进程中,子进程和父进程就没多大关系了。

[[i] 本帖最后由 cugb_cat 于 2008-2-13 16:43 编辑 [/i]]

yang_crystal
回复 #2 cugb_cat 的帖子

粗心了吧

0oo0
[quote]原帖由 [i]cugb_cat[/i] 于 2008-2-13 16:28 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7957124&ptid=1052406][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]
for (i = 0; i < n; i++)
        {
                if ((child_pid = fork()) > 0)
                        break;

        }
应该为if((child_pid==fork())) == 0) break;吧?
还有,文件指针好像是 ... [/quote]


我原本的意图就是需要
for (i = 0; i < n; i++)
{
        if ((child_pid = fork()) > 0)
                break;
}
即父进程创建了子进程,自己就break出for循环,然后进行write操作
相当于创建一个进程链,每一个链表后面的进程都是前一个进程的子进程

另外,你说到的:“描述符是继承关系,但到了子进程中,子进程和父进程就没多大关系了。”怎么理解呢?

我觉得如果是父进程open了一个fd,那子进程和父进程在内核空间的系统文件表里都是共享同一个入口
只是在用户空间的文件描述符表里是不同的入口,但它们指向的系统文件表是同一个入口,所以也共享文件偏移量这些属性吧?

请指教

cugb_cat
[quote]原帖由 [i]0oo0[/i] 于 2008-2-13 16:42 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7957191&ptid=1052406][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]



我原本的意图就是需要
for (i = 0; i < n; i++)
{
        if ((child_pid = fork()) > 0)
                break;
}
即父进程创建了子进程,自己就break出for循环,然后进行write操作
相当于创建 ... [/quote]
刚才试了一下,文件偏移确实是共用的,而且我在ubuntu上跑你的程序,结果确实是4个,没出现你说的现象。
./a.out 3 /tmp/tmp
  1 i = 3    process id = 8215
  2 parent id = 8214     , child id = 0  i = 3
  3 i = 2    process id = 8214
  4 parent id = 8213     , child id = 8215   i = 2
  5 i = 1    process id = 8213
  6 parent id = 8212     , child id = 8214   i = 1
  7 i = 0    process id = 8212
  8 parent id = 7955     , child id = 8213   i = 0

0oo0
顶楼的程序,我是在FC6上执行了,运行了几遍,都是上述结果
然后我再RH9上运行,运行了3次,结果都不一样
有的只有i = 0,i = 1, i = 2的输出
有的只有i = 0, i = 2的输出

这个程序与平台有关?为什么呢?

思一克
你在write()时候printf()输出一个标记:

printf("seek = %d/n", lseek(fd, 1, 0));

可能是因为2个write同时,一个覆盖了另一个?

jackissuper
回复 #1 0oo0 的帖子

i try at FC 8 , all the thing is ok.

0oo0
[quote]原帖由 [i]思一克[/i] 于 2008-2-13 22:10 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7958161&ptid=1052406][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]
你在write()时候printf()输出一个标记:

printf("seek = %d/n", lseek(fd, 1, 0));

可能是因为2个write同时,一个覆盖了另一个? [/quote]

你的意思是说:在每次write之前都把文件偏移量移到文件末尾?
那这样的话,似乎应该是lseek(fd, 1, 2)?

看样子应该是write之间进行覆盖了

一定要同步进程才行吗?有没有简易的办法可以解决呢?
谢谢!

思一克
用append方式打开实验一下

0oo0
[quote]原帖由 [i]思一克[/i] 于 2008-2-14 10:05 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7958974&ptid=1052406][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]
用append方式打开实验一下 [/quote]

APPEND方式打开的话是OK的
谢谢BZ!

多进程同时写日志文件的方法也是这样实现的吗?

思一克
是的!

0oo0
谢谢BZ!

r2r4
2楼cugb_cat的解答是正确的,我页来凑热闹,补充一点点

首先说明,代码导致写文件时发生覆盖.每个子进程都写了两遍.父进程至写了一遍.毕竟是长辈~~~
那楼主遇见的记录缺失情况就很正常了.(这必然是因为覆盖了嘛,并且是多次覆盖,不然数据哪里去了? ):)
看代码流程:
for (i = 0; i < n; i++)
        {
                if ((child_pid = fork()) > 0)
                        break;

        }
write()

fork后返回,不管是父进程还是子进程都要write().
那就看谁先write()了.

先补充一点知识:
    文件io过程中涉及的三项数据结构,涉及用户空间和内核空间.
我也不知细节,现大体意思一下:
1. 进程表项(用户空间)
2. 文件表(用户空间)
3. v节点(内核空间)

进程表项   中相关的就是文件描述符,open()得到的fd0,fd1...  (理解为,fd0指向文件表)
文件表     (当它是一个数据结构吧) 中存在文件状态标志,当前文件位移量,还有v节点指针(指向v节点)
v节点表    :v节点信息,i节点信息,当前文件长度(v节点表,细节暂不讨论,就当它与磁盘读写相关).


    2楼兄弟所言极是,子进程出现后,就和父进程没什么关系了,毕竟大家都是进程,又没有指明以线程方式来创建.
那么父子进程的进程表项和文件表被子进程继承,然后各自都不管了,各自独立(这里在楼主的代码里需要多考虑),那它们的文件偏移量也各自独立.

楼主的代码必然导致写覆盖,那为何又会有正确结果?

    第二点:
1.unix系统创建子进程的过程.(同样意思一下,不涉及细节)
为子进程创建PCB,然后再为其分配资源.
理论上是把父进程的所有资源做个完全copy给子进程,但是实际实现时有些出入.
(linux自称创建进程及其快速,所以它们的线程并不是在内核级别实现的,而是ptherad库来实现.)
对于创建进程,用写时copy技术.如果子进程运行时候并没有对自己的内存(就当作时变量)写值,那内核就不给它分配内存,直到它要写.此时发生缺页异常,系统中断处理,为它分配内存页.

2.子进程出现后,父子进程地位平等,但是linux一般尽量让子进程先运行.
看楼主代码;
    下面运行过程为假设:
1).第一次fork()后,子进程运行,子进程的 文件表(用户空间) 指示的当前文件位移量为0,它写入长度为L的信息.此时子进程文件偏移量L,子进程挂起.而父进程此时还是0.然后父进程运行,从0偏移写,把子进程的被容覆盖,然后,父进程去找GOD,从系统消失.此时文件留下的只有父进程的信息.

2).子进程投入运行,fork(),注意,此时文件偏移量为L, 重复1过程.留下第二个进程的信息.

3).重复2过程
.....至退出
这样的结果,就是楼主预期的.

    然后再看子进程挂起时机.
从头开始:
如果子进程写完以后不肯罢休,继续fork()自己的进程,那么情况怎样?
如果子进程的子进程页不肯罢休,那又怎样?
如此,楼主的遭遇...不难理解了.

还没完......
如果父进程fork()后,自己先运行了呢?
子进程继承到的文件偏移量是多少? 文件结果如何?:P

[[i] 本帖最后由 r2r4 于 2008-2-20 22:05 编辑 [/i]]

r2r4
10楼 思一克 兄 append...

在此append和write并不是原子操作,即使是原子操作,也不从根本解决问题...:em17:
但是它大大降低了临界条件,得到正确结果的可能性就大了很多.
具体还是要看父子进程运行顺序,如果每个进程的两次write调用都之间都有挂起操作,那么结果更丰富..

楼主看大家写fork()==0很不爽..但是fork()> 0 也不是那么简单额...:em21:

r2r4
貌似有点不对....

从以上分析来看,即使是最初的父进程被挂起到最后才写文件,那文件中应该只有一条记录啊,
为何楼主有两条..,并且最后一条是i=0 ??父进程??:em14: :em14:

莫非楼主把结果粘错了?
再跑跑看?

PiscesSTAR
回复 #14 r2r4 的帖子

"第一次fork()后,子进程运行,子进程的 文件表(用户空间) 指示的当前文件位移量为0,它写入长度为L的信息.此时子进程文件偏移量L,子进程挂起.而父进程此时还是0.然后父进程运行,从0偏移写,把子进程的被容覆盖,然后,父进程去找GOD,从系统消失.此时文件留下的只有父进程的信息."

在Linux中
    进程中的打开文件表对应struct files_struct
而文件表项对应struct file(f_pos字段表示当前文件指针)
而在fork时,子进程有了父进程struct files_struct的副本,
但是struct file是共享的,那么也共享文件指针


asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t count)
{
        struct file *file;
        ssize_t ret = -EBADF;
        int fput_needed;

        file = fget_light(fd, &fput_needed);
        if (file) {
                loff_t pos = file_pos_read(file);      
我理解的是真正的问题在这里,可能是由于进程调度导致父子进程看到的文件指针出现了不一致
                ret = vfs_write(file, buf, count, &pos);
                file_pos_write(file, pos);
                fput_light(file, fput_needed);
        }

       return ret;
}

r2r4
感谢 PiscesSTAR 给出详细代码

struct file {
struct file                   *f_next, **f_pprev;
struct dentry                *f_dentry;
struct file_operations    *f_op;
mode_t                       f_mode;
loff_t                         f_pos;
unsigned int                f_count, f_flags;
unsigned long              f_reada, f_ramax, f_raend, f_ralen, f_rawin;
struct fown_struct        f_owner;
unsigned long              f_version;
void                           *private_data;
};

file_pos_read(file)
取到的f_pos,父子进程相互独立的.
file_pos_write(file, pos)更新file结构的pos位移参数,这个当然也是进程之间独立的:)