免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 4773 | 回复: 10
打印 上一主题 下一主题

[C] 请教vfork()的问题。 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-04-16 12:41 |只看该作者 |倒序浏览
OS作业中要用到vfork这个函数,实现父进程,子进程交替输出:
代码如下
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
&nbsp;&nbsp;&nbsp;&nbsp;pid_t pid_1;
&nbsp;&nbsp;&nbsp;&nbsp;int status,i;

&nbsp;&nbsp;&nbsp;&nbsp;for(i=0;i<5;i++) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if((pid_1=vfork())==0) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;execv("./pibboy",NULL);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(0);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("current");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf ("[%d]\n",pid_1);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;waitpid(pid_1,&status,0);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;return 0;
}


#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include<time.h>
int main()
{
&nbsp;&nbsp;&nbsp;&nbsp;time_t timep;
&nbsp;&nbsp;&nbsp;&nbsp;int i;
&nbsp;&nbsp;&nbsp;&nbsp;for(i=0;i<3;i++){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sleep(1);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time (&timep);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("%s",ctime(&timep));
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;return 0;
}



man了一下vfork,说再vfork以后,父进程应该被阻塞,直到子进程exit或者_exit退出。
我代码的目的是子进程先输出,再父进程输出,轮流五次。
可是结果是父进程先输出,子进程在输出。不知道是什么地方出问题?所以上来问下大家,还有,是首次发贴,有什么不对的请指出。
我之前google了半天,讲vfork的都是强调父进程会阻塞,直到子进程退出,还看了很多代码,也不知道自己错再哪里了。
ps: 不要说用fork来实现之类的,我用fork实现国一个了,想再用vfork实现一次。

论坛徽章:
0
2 [报告]
发表于 2009-04-16 13:00 |只看该作者
把printf()移到wait后面呢

论坛徽章:
0
3 [报告]
发表于 2009-04-16 13:02 |只看该作者

回复 #2 lanying_wzw 的帖子

恩,成功了,能跟我讲一下为什么么?谢谢了。

论坛徽章:
0
4 [报告]
发表于 2009-04-16 13:28 |只看该作者

回复 #1 kingstarlcq 的帖子

LZ你的问题不是vfork不vfork的问题...

vfork阻塞父进程是为什么? 是因为vfork后父子进程使用的是完全相同的mm, 也就是说使用的是完全相同的虚拟内存空间, 包括栈也相同. 所以两个进程不能同时运行, 否则栈就乱掉了... 所以vfork后, 父进程是阻塞的.

exec系列函数调用后, 子进程的mm(old_mm)需要释放掉, 不再与父进程共用了, 于是父进程可能在子进程执行exec的过程中就结束阻塞了, 不用等到子进程结束.

waitpid是用来等待进程结束的, 这个地方会阻塞. 但这里的阻塞与vfork的阻塞不是一回事.

论坛徽章:
0
5 [报告]
发表于 2009-04-16 13:36 |只看该作者
所谓阻塞,是在子进程调用exec之前父进程阻塞。因为父子进程共享同一地址空间,所有必需有一方必须阻塞,否则就乱套了。而当调用了exec后,子进程有了自己的地址空间,父进程就没必要阻塞了。

论坛徽章:
0
6 [报告]
发表于 2009-04-16 14:34 |只看该作者

回复 #5 mcemil 的帖子

有点糊涂了,vfork的子进程使用的是父进程的空间,而exec调用的的程序是覆盖调用进程的空间,那岂不是把父进程给覆盖了,我是这样分析,越来越糊涂了

论坛徽章:
0
7 [报告]
发表于 2009-04-16 14:48 |只看该作者
google "linux 写时拷贝"

[ 本帖最后由 mcemil 于 2009-4-16 14:52 编辑 ]

论坛徽章:
0
8 [报告]
发表于 2009-04-16 15:15 |只看该作者

回复 #7 mcemil 的帖子

子进程往往要调用一种 exec函数以执行另一个程序。当进程调用一种 e x e c函数时,该进程完全由新程序代换,而新程序则从其 m a i n函数开始执行。因为调用exec并不创建新进程,所以前后的进程 ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
vfork:子进程于父进程同地址
子进程:exec("XXXX"),XXX不是覆盖了父进程么?
看了下写时拷贝是为了节省开辟新空间的代价,好像不能避免这个问题。

论坛徽章:
0
9 [报告]
发表于 2009-04-16 15:35 |只看该作者
exec函数需要更新当前进程的页表,但这些页表属于父进程,于是触发进程地址空间的缺页异常,内核此时就会为子进程分配属于他的页表。所以,你可以简单的理解为,在exec在替换了当前进程的正文、数据、堆和栈段前已经为其分配了新的地址空间.

论坛徽章:
0
10 [报告]
发表于 2009-04-16 16:25 |只看该作者
Copy-on-Write
在早期的Unix系统中,fork操作很简单,甚至可以说很幼稚。调用fork()时,内核复制所有内部数据结构,复制进程的页表项、然后对父进程的地址空间执行复制,一页一页的复制到新的子进程地址空间中。但这种一页一页的复制相当耗时,至少从内核的角度来说。

现代的Unix系统则更加优化。像Linux这样的现代Unix系统采用Copy-on-Write(COW)页,而不是复制父进程的整个地址空间。

Copy-on-Write是一种懒惰优化策略,设计用来减轻复制资源时的开销。设想很简单:如果多个消费者请求读取他们自己的资源拷贝,则不需要对资源进行重复拷贝。实际上,每个消费者可以拥有一个指向相同资源的指针。只要没有消费者修改自己那份资源“拷贝”,则等同于对资源进行互斥访问,同时避免了复制资源的开销。如果某个消费者确实要修改自己的资源拷贝,在那个时候,将会透明地复制一份资源拷贝给需要修改的消费者。然后这个消费者就可以修改自己的资源拷贝,而其它消费者继续共享原始未变化的资源。Copy-on-Write因此得名:复制只发生在写入时。

最主要的优点是如果一个消费者从不修改自己的资源拷贝,那就不会进行实际的拷贝操作。因此,只要进程不修改自己的地址空间,就不需要拷贝整个地址空间。在fork结束后,父进程与子进程看上去拥有自己单独的地址空间,而实际上它们共享父进程的原始页——而这又可能与它的父进程或其它子进程共享。

内核的实现很简单。页面在内核的页面相关数据结构中被标识为只读和写入时复制。如果某个进程尝试修改某个页,将产生一个页面错误。然后内核处理页面错误,透明地复制这个页。在这个时候,当前进程的页面的写入时复制属性被清除,从此不再共享。

由于现代机器架构在内存管理单元(MMU)对写入时复制提供了硬件层的支持,写入时复制实现起来非常简单。

写入时复制在fork操作时有一个更大的优点。因为很大一部分fork都紧跟着一个exec,复制父进程的地址空间到子进程地址空间经常是完全浪费时间:如果子进程立即执行一个新的二进制镜像,它原来的地址空间将全部擦去。写入时复制优化了这种情况。


vfork()

在写入时复制页面出现之前,Unix设计者已经关注fork紧跟着exec时的地址空间复制浪费问题。因此BSD开发者在3.0BSD公开了vfork()系统调用:
#include <sys/types.h>
#include <unistd.h>
pid_t vfork (void);

成功调用vfork()和fork()的行为一样,除了子进程必须立即发起一个exec函数,或者通过调用_exit()退出(下一节讨论)。vfork()系统调用避免了地址空间和页表复制,通过挂起父进程直到子进程终止或者执行新的二进制镜像。在此之间,父进程和子进程共享地址空间和页表项(虽然没有写入时复制的语义)。实际上,在调用vfork()期间唯一做的事情是复制内核内部数据结构。因此,子进程不能修改地址空间内的任何内存。

vfork()系统调用是一个历史遗物,永远不应该被Linux实现。应该指出的是,尽管fork()使用了写入时复制,vfork()也比fork()更快,因为vfork()不需要复制页表项。无论如何,写入时复制的采用,削弱了fork()其它变体的需要。实际上直到Linux内核2.2.0版本,vfork()只是fork()的简单封装。由于对vfork()的需求远远小于对fork()的需求,这样一种vfork()实现是切实可行的。

严格地说,没有哪种vfork()实现是无bug的:考虑exec调用失败的情况!父进程将被挂起不确定时间,直到子进程决定自己该怎么做或者直到子进程退出。

引自《Linux System Programming》

[ 本帖最后由 雨过白鹭洲 于 2009-4-16 16:27 编辑 ]
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP