MIT 6.S081 primes实验 一个由文件描述符导致的问题

0 阅读5分钟

最近在做MIT操作系统的实验,在做到primes实验的时候,遇到了一个问题,纠缠了好久。

这是题目的描述,大致意思就是需要我们采取多进程的方式,利用埃式筛法筛选出2-35内的素数。

5dc7564534821efa477290e70762660d.png

我的想法就是,由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,此后,代码就运行完成了。

01c7ef2ad51b3c0219e9b89824d97622.png

出现这个问题的原因就在于,在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环境下追踪的变量值:

daab5f5e21a142230b11e1a8b0405b27.png

95647c1a5bb381f79edbc503f2b5ea73.png

可见,新分配的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);
}

这是我第一次用并发的方式编程,所以可能遇到的问题有些幼稚。

希望对大家有所帮助!