MIT操作系统实验lab1(案例:primes(质数筛选)附代码、详解)

1.题目描述:在xv6上使用管道实现“质数筛选”, 输出2~35之间的而所有质数。请将代码写在user/primes.c文件中。运行效果应该如下:

2. 实验原理:

先附上参考链接。

(1)官方网址:https://pdos.csail.mit.edu/6.828/2020/index.html

(2)xv6 book:https://pdos.csail.mit.edu/6.828/2020/xv6/book-riscv-rev1.pdf

(3)原理概念:

管道(pipe)是一种最基本的进程间通信机制。管道分为 读出端 和 写入端 两个部分,进程可以向写端写入数据,也可以从读端读出数据。通过pipe系统调用,内核会为用户进程创建管道,同时返回两个文件描述符,用以描述管道的读写端,例如:

int p[2];
int ret;
ret = pipe(p); /*正常创建后,p[1]为管道写入端,p[0]为管道读出端*/ 

 通过文件描述符,程序可以按读写文件的形式读写管道,例如:

int write = write(p[1], buffer, n);
int read = read(p[0], buffer, n);

 进程通常只持有某个管道的读出端或者写入端,因此使用的时候需要将另一端关闭,例如(一般通过fork()开启一个新的进程,父进程和子进程分别实现对管道流的读和写):

/* 子进程读管道,父进程写管道 */
int ret = fork();
if (ret == 0) { 
    /* 子进程 */
    close(p[1]); // 关闭写端
    ...
    read(...);
    ...
    close(p[0]); // 读取完成,关闭读端
} else if (ret>0) { 
    /* 父进程 */
    close(p[0]); // 关闭读端
    ...
    write(...);
    ...
    close(p[1]); // 写入完成,关闭写端
}

(注意:为什么要专门提及管道和父子进程的概念呢??因为质数筛选过程中,筛选后的数字由上一级流向下一级的操作是通过:父进程通过管道写筛选后的数据,子进程从管道读筛选后的数据实现的!) 

质数筛选的思想:

使用系统调用pipe创建管道,使用系统调用fork创建子进程。主进程将所有数据(例2~11)输入到管道的左侧,第一个子进程从管道读出并筛选出2,排除掉2的倍数,其他数字再写入下一管道;第二个子进程读出并筛选出3,排除掉3的倍数,其他数字写入到下一管道;第三个子进程读出筛选出5,依次类推……如下图所示:

即每一次筛选,从当前未被筛选的第一个数字开始,排除掉它的倍数,再将剩余的数字写入管道。

(4)实现思路: 

1.设置一个长度为 36 的数组 nums(0~35),并对其进行初始化。 其中 nums[i]=’1’表示已经对数 i 进行判断其是否为质数;对于 nums[i]=’0’,反之。

2.main 函数中,父进程向下一级传递 nums 数组,子进程调用 prime 函数继续对传入的 nums 数组进行筛选。wait()保证数据传输的顺序不会混乱。(最终在 primes 函数中 exit(0)结束)

 3.primes质数筛选函数过程中,做了三件事:找出当前nums数组中的第一质数(未被判断即nums[i]=='0'的数);筛去该质数的倍数;通过进程,将筛选后的数组传入下一级。

(注意:primes利用了递归的思想。代码段if(target==0){exit(0)}处可以视为递归函数退出的条件,可以理解为:如果当前nums数组均筛选完毕,则target不会变化仍为0,此时质数筛选过程完毕,退出。

代码实现:

#include "kernel/types.h"
#include "user.h"

#define MAXNUM 36

void prime(int read_p,int write_p){
    char nums[MAXNUM];
    read(read_p,nums,MAXNUM);//读取上一级传过来的数组
    int target = 0;
    for(int i=0;i<MAXNUM;i++){
        //不断对数字进行判断,找出当前第一个质数
        if(nums[i]=='0'){
            target = i;
            break;//跳出循环
        }
    }
    if(target==0){
        exit(0);
    }
    printf("prime %d\n",target);
    nums[target]='1';
    //筛去当前质数的公倍数
    for(int i=0;i<MAXNUM;i++){
        if(i%target==0 ){
            nums[i] = '1';
        }
    }
    

    //创建子进程,进行流水
    int pid  = fork();
    if(pid>0){
        //当前进程
        write(write_p,nums,MAXNUM);
        wait(0);
    }
    if(pid==0){
        //当前进程的子进程
        prime(read_p,write_p);
        wait(0);
    }
}

int
main(int argc,int *argv[]){
    //创建一个数组,用于判断质数
    //下标i表示要判断的数字,num[i]=1表示已经判断,num[i]=0表示未判断
    char nums[MAXNUM];
    //数组的初始化
    memset(nums,'0',sizeof(nums));

    //管道的创建
    int p[2];
    pipe(p);

    int pid = fork();
    //父进程写nums数组
    if(pid>0){
        nums[0] = '1';
        nums[1] = '1';//0,1已经判断
        write(p[1],nums,MAXNUM);
        wait(0);
    }
    //子进程调用prime函数进行下一级的判断
    if(pid==0){
        prime(p[0],p[1]);//递归地使用相同的管道,减少内存损耗,通过wait()实现父子进程顺序执行
        wait(0);
    }
    exit(0);
}

 

猜你喜欢

转载自blog.csdn.net/m0_56603583/article/details/127378976