最近在做MIT操作系统的实验,在做到primes实验的时候,遇到了一个问题,纠缠了好久。
这是题目的描述,大致意思就是需要我们采取多进程的方式,利用埃式筛法筛选出2-35内的素数。
我的想法就是,由parent进程调用函数创建子进程,每个子进程被传入不同的素数,以筛去他的整数倍。同时,各个子进程之间就如同手拉手的关系,下一个子进程从上一个子进程中筛选之后的整数池中读取数字,作为筛选的基数,以此类推。
因此定义如下函数
void stage(int left[],int p)
该函数专门由parent进程调用,left传入文件描述符,指向的是当前筛选整数进程的底层缓冲池。p传入的是筛选出来的质数。
在main里面,创建一个子进程1,它相当于第一个筛选的进程,但是它实际上只是机械的将2-35写入缓冲池中。
然后父进程读取数字2,开始第一个stage,打印2,并且创建新的pipe以及子进程,来筛选2的整数倍,此时,parent进程的读取的缓冲池应该是这个新建的进程,依次递推调用。
下面是我原来的完整代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void stage(int left[],int p){
int right[2];
pipe(right);//每打开一个新的子进程,都会开一个新的缓冲区,将筛选后的整数写入里面
printf("prime %d\n",p);
if(fork()==0){
int n;
close(left[1]);//关闭无用写端
close(right[0]);//不需要right读端
while(read(left[0],&n,sizeof(int))>0){
if(n%p!=0){
write(right[1],&n,sizeof(int));
}
}
close(left[0]);//关闭读端
close(right[1]);//关闭写端
exit(0);
}
else{
close(left[0]);
close(left[1]);//关闭parent进程与先前的写入缓冲区的联系,不需要从里面读了,从子进程的缓冲区中读
close(right[1]);
int i;
if(read(right[0],&i,sizeof(int))>0)//从子进程的缓冲区读一个数,再次stage
{
stage(right,i);
}
close(right[0]);
wait(0);
}
return;
}
int main(){
int n=2;
int p[2];
pipe(p);
if(fork()==0){
close(p[0]);//关闭读端
for(int i=n;i<=35;i++){
write(p[1],&i,sizeof(int));//该进程专门否则向p指向的缓冲区中写2-35
}
close(p[1]);
exit(0);
}
else{
int buf;
close(p[1]);//不需要写端
if(read(p[0],&buf,sizeof(int))>0)
{
stage(p,buf);//这个parent进程专门负责打印prime和打开子进程筛选
}
close(p[0]);
wait(0);
}
exit(0);
}
我在代码中添加了详细的注释,希望你能理解我的代码,同时,也想让你尽量的跳入我的思维,觉得这份代码没问题。
在运行代码后,会发现,始终只会输出prime 2,此后,代码就运行完成了。
出现这个问题的原因就在于,在linux或者mit的教学os xv6中,文件描述符的分配规则是
永远找当前进程中最小、未使用、已经关闭的数字分配!
我的这份代码中,出现了大量的重复关闭的动作。
在main的parent进程中,在stage之前,我首先关闭了p[1]写端,此时,在调用stage的时候,传入了参数p,此时,p仍然记录着先前分配的文件描述符的值。
进入stage,我们要分配新的文件描述符给right,此时p[1]属于未分配状态,可以分配,此时,我们的right[0]也就是我们的读端,它会被分配一个文件描述符,他的值就是p[1]。
在stage的父进程中,我为了切断与原缓冲池的联系,再次关闭了 p[0],p[1](他们作为参数传入了left),此时,会导致right[0]被错误关闭,我们的parent进程也就不能再读取数据,开启stage了。
这是我在gdb环境下追踪的变量值:
可见,新分配的right[0]=p[1],这也就导致了后续的错误关闭。
正确的做法是,我们保证每个进程在关闭描述符的时候,只关闭一次,同时stage不必传入left[1],因为用不到。
下面是修正后的代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void stage(int left_read,int p){
int right[2];
pipe(right);//每打开一个新的子进程,都会开一个新的缓冲区,将筛选后的整数写入里面
printf("prime %d\n",p);
if(fork()==0){
int n;
close(right[0]);
while(read(left_read,&n,sizeof(int))>0){
if(n%p!=0){
write(right[1],&n,sizeof(int));
}
}
close(left_read);
close(right[1]);
exit(0);
}
else{
close(right[1]);
int i;
if(read(right[0],&i,sizeof(int))>0)//从子进程的缓冲区读一个数,再次stage
{
stage(right[0],i);
}
close(right[0]);
wait(0);
}
return;
}
int main(){
int n=2;
int p[2];
pipe(p);
if(fork()==0){
close(p[0]);
for(int i=n;i<=35;i++){
write(p[1],&i,sizeof(int));//该进程专门否则向p指向的缓冲区中写2-35
}
close(p[1]);
exit(0);
}
else{
int buf;
close(p[1]);//由父进程关闭写端
if(read(p[0],&buf,sizeof(int))>0)
{
stage(p[0],buf);//这个parent进程专门负责打印prime和打开子进程筛选
}
close(p[0]);
wait(0);
}
exit(0);
}
这是我第一次用并发的方式编程,所以可能遇到的问题有些幼稚。
希望对大家有所帮助!