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]
[color=#FF0000]pid_t[/color] child_pid[color=#0000CC];[/color]
[color=#0000FF]int[/color] fd[color=#0000CC];[/color]
[color=#0000FF]char[/color] buf[color=#0000CC][[/color]1000[color=#0000CC]][/color][color=#0000CC];[/color]
[color=#0000FF]int[/color] n[color=#0000CC],[/color] i[color=#0000CC];[/color]
[color=#0000FF]if[/color] [color=#0000CC]([/color]argc [color=#0000CC]![/color][color=#0000CC]=[/color] 3[color=#0000CC])[/color]
[color=#0000CC]{[/color]
[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]
[color=#FF0000]exit[/color][color=#0000CC]([/color]1[color=#0000CC])[/color][color=#0000CC];[/color]
[color=#0000CC]}[/color]
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]
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]
[color=#0000FF]if[/color] [color=#0000CC]([/color]fd [color=#0000CC]=[/color][color=#0000CC]=[/color] [color=#0000CC]-[/color]1[color=#0000CC])[/color]
[color=#0000CC]{[/color]
[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]
[color=#FF0000]exit[/color][color=#0000CC]([/color]1[color=#0000CC])[/color][color=#0000CC];[/color]
[color=#0000CC]}[/color]
[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]
[color=#0000CC]{[/color]
[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]
[color=#0000FF]break[/color][color=#0000CC];[/color]
[color=#0000CC]}[/color]
[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]
[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]
[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]
[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]
[color=#FF0000]close[/color][color=#0000CC]([/color]fd[color=#0000CC])[/color][color=#0000CC];[/color]
[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之间进行覆盖了
一定要同步进程才行吗?有没有简易的办法可以解决呢?
谢谢!
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!
多进程同时写日志文件的方法也是这样实现的吗?
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位移参数,这个当然也是进程之间独立的:)