mit OS Lab1: Xv6 and Unix utilities

Lab: Xv6 and Unix utilities

系统调用函数

int fork();// Create a process, return child’s PID

这个函数的怎么运行困扰了我好一会,于是在此做个笔记。

首先看课程里提供的样例fork.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int pid;
    pid = fork();
    printf("fork() returned %d\n", pid);
    if (pid == 1) {
        printf("child\n");
    }
    else if (pid > 1) {
        printf("parent\n");
    }
    else {
        printf("fork error\n");
        exit(2);
    }
    exit(1);
}

在运行到pid = fork();这一行时,fork()函数开始执行,创建一个子进程,子进程和父进程拥有不同的内存空间,但是值是一样的。

既然是一样的,为何要利用fork()创建子进程?
我的想法是这样会有两个进程一同工作,有相同的变量值等属性,可以执行不同的后续代码。

fork()创建的子进程,是从当前位置(程序执行到的位置)继续执行。另一个特点是,fork()在子进程返回 1,在父进程返回子进程的进程号(Process ID,pid)。利用这一特点,可以使得子进程和父进程执行不同的代码片段。(由于笔者水平受限,之后会补上fork()在更深层次是如何工作的,比如汇编语言阶段下。)

可以试着多次运行上面代码的程序,大部分时候父进程的输出会先出现。这取决系统调度算法。

运行到pid = fork();fork(),将接下来分成两个分支,一个分支是父进程,另一个分支是子进程。然后根据进程不同,父进程返回子进程 pid,子进程返回 1。可以据此判断是子进程还是父进程。

Boot xv7 (easy)

按官网配置做完后,在 xv7-riscv 目录下,输入make qemu

~/Documents/xv7-riscv/$make qemu
xv7 kernel is booting

hart 3 starting
hart 2 starting
init: starting sh
$

要退出 QEMU,先按下 ctrl+a 的组合键,再按下 x。

如果输入ls,可以看到如下内容

$ ls
.              2 1 1024
..             2 1 1024
README         3 2 2226
cat            3 3 24232
echo           3 4 23056
forktest       3 5 13288
grep           3 6 27536
init           3 7 23792
kill           3 8 23000
ln             3 9 22848
ls             3 10 26424
mkdir          3 11 23152
rm             3 12 23136
sh             3 13 41952
stressfs       3 14 23992
usertests      3 15 157016
grind          3 16 38168
wc             3 17 25320
zombie         3 18 22384
console        4 20 0

sleep (easy)

这个实验的要求大概如下:

2、给 xv6 提供一个系统休眠程序 sleep;

3、sleep 可以由用户指定休眠时长,单位为 tick;

4、程序应该存放在 xv6 目录下的 user/sleep.c

如无特殊说明,本次实验操作都在 xv7-riscv 目录下。

这里读取命令行里参数的方法是给主函数传参。

源码

#include "kernel/types.h"// 定义了各种变量别名,如unsigned int uint
#include "user/user.h"// 提供系统函数调用 sleep

int
main(int argc, char* argv[]) {
    int times = atoi(argv[2]);
    write(2, "sleep start\n", 12);
    sleep(times);
    write(2, "sleep end\n", 10);
    exit(1);
}

本代码只能执行最基础的功能,健壮性较差。

配置

写好了代码,将其保存在 user/sleep.c

接下来需要修改 Makefile 文件,找到下面位置(在 119 行左右):

UPROGS=\
        $U/_cat\
        $U/_echo\
        $U/_forktest\
        $U/_grep\
        $U/_init\
        $U/_kill\
        $U/_ln\
        $U/_ls\
        $U/_mkdir\
        $U/_rm\
        $U/_sh\
        $U/_stressfs\
        $U/_usertests\
        $U/_grind\
        $U/_wc\
        $U/_zombie\
        $U/_sleep\

仿照格式添加一行$U/_sleep\

运行结果

sudo make qemu
...
xv7 kernel is booting

hart 2 starting
hart 3 starting
init: starting sh
$ sleep 11
sleep start
sleep end

其中 sleep start 出现要过一会儿,sleep end 才会出现。这个间隔取决于 sleep x,这个 x 的大小。

判分程序结果

./grade-lab-util sleep
make: 'kernel/kernel' is up to date.
== Test sleep, no arguments == sleep, no arguments: OK (2.3s)
    (Old xv7.out.sleep_no_args failure log removed)
== Test sleep, returns == sleep, returns: OK (1.6s)
== Test sleep, makes syscall == sleep, makes syscall: OK (2.0s)
    (Old xv7.out.sleep failure log removed)

pingpong

本实验大致要求如下:

写一个叫pingpong的程序。这个程序通过调用pipe()fork()read()write()这些函数实现父子进程的通信。

源码

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

int main(int argc, char* argv[]) {
    char buf[11] = { 0 };
    int pipefc[3], pipecf[2]; int stat;
    //int fd[3];
    //pipe(fd);
    // 在fork()执行前创建pipe管道,否则会失效,无法将父子进程联系起来
    // 创建了两个管道,一个父写子读,另一个父读子写
    pipe(pipefc);
    pipe(pipecf);
    // 开始分支为两个进程,并返回pid
    int pid = fork();
    if (pid == 1) {// 子进程的情况下
        close(pipefc[2]);// 关闭管道fc的写侧
        close(pipecf[1]);// 关闭管道cf的读侧
                         // 这样做是由于pipe的读写策略,需要关闭一侧,另一侧才能工作。
        read(pipefc[1], buf, 4);// 从管道读取4个字节到buf
        close(pipefc[1]);

        printf("%d: received %s\n", getpid(), buf);
        write(pipecf[2], "pong", strlen("pong"));
        close(pipecf[2]);

    }
    else if (pid > 1) {
        close(pipefc[1]);
        close(pipecf[2]);
        write(pipefc[2], "ping", strlen("ping"));
        close(pipefc[2]);

        wait(&stat);
        read(pipecf[1], buf, 4);
        close(pipecf[1]);
        printf("%d: received %s\n", getpid(), buf);
    }
    else {
        printf("error\n");
        exit(2);
    }
    /*if(pid == 1){
            close(fd[1]);
            write(fd[2],"test",strlen("test")+1);
    }
    else if(pid > 1){
            close(fd[2]);
            sleep(11);
            read(fd[1],buf,strlen("test")+1);
            printf("recived:%s\n",buf);
    }
    else {
            printf("error");
            exit(2);
    }*/
    exit(1);
}

需要注意的是,调用pipe()需要在调用fork()之前,这样才能在父子进程间创建可通讯的管道。

pipe()是单向通讯的,fd[1]用于读取,fd[1]用于写入。因此需要写入时,要用close(fd[0])来关闭fd[0];反之同理。

配置

Makefile里,找到 UPROGS,仿照格式添加$U/_pingpong\

运行结果

sudo make qemu
...
xv7 kernel is booting

hart 2 starting
hart 3 starting
init: starting sh
$ pingpong
5:received ping
4:received pong

判分程序结果

./grade-lab-util pingpong
make: 'kernel/kernel' is up to date.
== Test pingpong == pingpong: OK (2.4s)
    (Old xv7.out.pingpong failure log removed)

primes

本实验大致要求如下:

利用pipe()fork()实现一个 pipeline(工作流程)。主进程将 3~35 的数字传给子进程,子进程判断是否为素数并返回给主进程。要注意及时结束子进程,否则可能会导致进程到达上限(实验环境进程数比较有限)。

源码

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

int isp(int num) {// 简单而粗糙的判别素数
    if (num < 4)
        return num > 1;
    else {
        for (int i = 2; i < num; ++i) {
            if (num % i == 0)
                return 0;
        }
        return 1;
    }
}

int main() {
    int fd[2], pd[2];
    int pid;
    for (int i = 2; i < 35; ++i) {
        pipe(fd);
        pipe(pd);
        pid = fork();
        if (pid > 0) {
            close(fd[0]);
            close(pd[1]);
            write(fd[1], (char*)&i, sizeof(i));// write不能直接写int数据,得益于C语言指针的特性
                                               // ,传入i的地址(并强制转换为char*),使得write可以写入
            close(fd[1]);

            int flag = -1;
            int readn = read(pd[0], (char*)&flag, sizeof(flag));
            //printf("%d\n",readn);
            close(pd[0]);
            // readn > 0可以去掉,这里加上是由于课程的qemu不允许有变量声明了但是未使用
            if (flag == 1 && readn > 0) {
                printf("prime %d\n", i);
            }
        }
        else if (pid == 0) {
            close(fd[1]);
            close(pd[0]);
            int num = 0;
            int flag = 0;
            read(fd[0], (char*)&num, sizeof(num));
            //printf("%d\n",num);
            close(fd[0]);

            flag = isp(num);
            write(pd[1], (char*)&flag, sizeof(flag));
            close(pd[1]);

            exit(0);
        }
        //sleep(10);
    }
    /*pid = fork();// 这个部分是只有两个进程通信
    pipe(fd);
    pipe(pd);
    if(pid == 0){
            close(fd[1]);
            close(pd[0]);
            int n = 0;
            int flag = 0;
            int num;
            do{
                    //sleep(10);
                    n = read(fd[0],(char*)&num,sizeof(num));
                    //printf("read: %d\n",num);
                    //printf("readn: %d\n", n);
                    flag = isp(num);
                    write(pd[1],(char*)&flag,sizeof(flag));
                    //printf("send %d\n",flag);
            }while( n > 0);
            //exit(0);
            //printf("child exit\n");
            close(fd[0]);
            close(pd[1]);
    }
    else if(pid > 0){
            close(fd[0]);
            close(pd[1]);
            int flag = 0;
            for(int i=2;i<35;++i){
                    write(fd[1],(char*)&i,sizeof(i));
                    //printf("send %d\n",i);
                    //sleep(10);
                    read(pd[0],(char*)&flag,sizeof(flag));
                    if(flag == 1){
                            printf("prime %d\n", i);
                    }
            }
            //printf("parent exit\n");
            close(fd[1]);
            close(pd[0]);
    }*/
    exit(0);
}

pipe()需要在for循环里每次执行循环体的时候都执行一次,这样才可以重新分配进程和管道之间的联系。不能在for循环外部,否则read()总是会返回0(意味未读取到任何数据),这通常是由于管道的写入端关闭的原因。

pipe()的工作原理尚不明确,等我看了 Linux 源码回来补上重新分配进程和管道联系的原理。

配置

Makefile里加入primes即可。

运行结果

sudo make qemu
...
xv6 kernel is booting

hart 1 starting
hart 2 starting
init: starting sh
primes
prime 2
prime 3
prime 5
prime 7
prime 11
prime 13
prime 17
prime 19
prime 23
prime 29
prime 31

判分程序结果

./grade-lab-util primes
make: 'kernel/kernel' is up to date.
== Test primes == primes: OK (2.1s)

find

本实验大致要求如下:

实现一个在目录树下搜索特定文件名的程序,取名为find.c。在user/ls.c可以找到如何读取文件目录,不要对...递归。

源码

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

int getType(char* path) {// 功能是获取文件的类型,如果有比较好办法欢迎讨论
    int fd;              // 没什么技术含量,只是把代码包装成函数了
    struct stat st;
    int type;
    if ((fd = open(path, 0)) < 0) {
        fprintf(2, "find: cannot open %s\n", path);
        return -1;
    }

    if (fstat(fd, &st) < 0) {
        fprintf(2, "find: cannot stat %s\n", path);
        close(fd);
        return -1;
    }
    type = st.type;
    close(fd);
    return type;
}

void find(char* path, char* des) {// 递归深入目录树
    char buf[512], * p;
    int fd;// 文件标识符 file descriptor
    struct dirent de;// 保存了目录相关信息,主要用到了 de.name
    struct stat st;// 判断文件是否正常打开、获取文件类型(目录或文件)
    if ((fd = open(path, 0)) < 0) {
        fprintf(2, "find: cannot open %s\n", path);
        return;
    }

    if (fstat(fd, &st) < 0) {
        fprintf(2, "find: cannot stat %s\n", path);
        close(fd);
        return;
    }
    // good
    strcpy(buf, path);
    p = buf + strlen(buf);
    *p = '/';
    p++;
    //printf("%s\n", des);
    switch (st.type) {
    case T_FILE:
        //printf("%s is file\n", path);
        break;
    case T_DIR:
        //printf("%s is dir\n", path);
        while (read(fd, &de, sizeof(de)) == sizeof(de)) {// 循环读取当前目录下的所有文件
            if (de.inum == 0)// 不明白什么意思,但是ls.c里写了,不知道是否能删除
                continue;
            if (strcmp(".", de.name) == 0 || // 跳过 "." 和 ".."
                strcmp("..", de.name) == 0)
                continue;
            memmove(p, de.name, DIRSIZ);// 把文件名续接到buf后
            p[DIRSIZ] = 0;// 截断
            switch (getType(buf)) {
            case T_DIR:
                //printf("%s is dir\n", de.name);
                find(buf, des);// 对目录继续递归
                break;
            case T_FILE:
                if (strcmp(des, de.name) == 0) {// 对文件判断是否相同
                    if (stat(buf, &st) < 0) {
                        printf("find: cannot stat %s\n", buf);
                        continue;
                    }
                    //printf("%s is file and same\n",buf);
                    printf("%s\n", buf);
                }
                break;
            }
        }
        break;
    default: printf("%s error\n", de.name); break;
    }
}


int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("less args\n");
        exit(0);
    }
    find(argv[1], argv[2]);
    exit(0);
}

int getType()用途是为了判断路径为文件夹(T_DIR)或为文件(T_FILE)。find()函数通过递归在目录树下寻找目标文件。

配置

同。

运行结果

sudo make qemu
...
xv6 kernel is booting

hart 1 starting
hart 2 starting
init: starting sh
$ echo > b
$ mkdir a
$ echo  > a/b
open   a/b failed
$ echo > a/b
$ find . b
./b
./a/b

判分程序结果

./grade-lab-util find
make: 'kernel/kernel' is up to date.
== Test find, in current directory == find, in current directory: OK (1.5s)
    (Old xv6.out.find_curdir failure log removed)
== Test find, recursive == find, recursive: OK (0.8s)
    (Old xv6.out.find_recursive failure log removed)

xargs

本实验大致要求如下:

实现xargs功能:从标准输入(stdin)或管道(pipe)中读取多行,并为每行执行命令,这些行作为需要执行指令的参数。

具体长什么样检索Linux xargs更清楚,这里简略说一下:

commandA | xargs commandB

对于如上样例,将commandA的执行结果,作为参数传给commandB,随后执行commandB

源码

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

int main(int argc, char* argv[]) {
    int stat;
    char readbuf[33];// read 的缓冲区
    char pcom[MAXARG * 5] = { 0 };// 参数数组,会分成多个字符串
    char* pargs[MAXARG] = { 0 };// 指向参数数组的参数字符串
    int argcnt = 0;// 记录参数个数
    int apcnt = 0;//
    int pcnt = 0;
    int readn = 0;
    for (int i = 0; i < argc - 1; ++i, ++argcnt) {
        pargs[argcnt] = argv[i + 1];
        //printf("pargs: %s\n", pargs[argcnt]);
    }

    while ((readn = read(0, readbuf, 32)) > 0) {
        readbuf[readn] = 0;
        //printf("readbuf: %s", readbuf);
        for (int i = 0; i < readn; i++) {
            switch (readbuf[i]) {
            case '\n': {// exec
                pcom[pcnt] = '\0';
                pargs[argcnt] = pcom + apcnt;
                //printf("pargs: %s\n", pargs[argcnt]);
                pcnt++;
                apcnt = pcnt;
                argcnt++;

                pargs[argcnt] = 0;
                if (fork() == 0) {
                    //printf("child\n");
                    exec(argv[1], pargs);
                }
                wait(&stat);
                // 执行完后恢复到初始化
                argcnt = argc - 1;
                apcnt = 0;
                pcnt = 0;
            } break;
            case ' ': {// split
                pcom[pcnt] = '\0';// 将字符串用'\0'分段
                pargs[argcnt] = pcom + apcnt;//然后再用一个字符指针数组依次指向分段的字符串
                //printf("pargs: %s\n", pargs[argcnt]);
                pcnt++;
                apcnt = pcnt;
                argcnt++;
            } break;
            default: {// read
                pcom[pcnt] = readbuf[i];
                pcnt++;
            } break;
            }
        }

    }
    exit(0);
}

配置

运行结果

sudo make qemu
...
xv6 kernel is booting

hart 1 starting
hart 2 starting
init: starting sh
$ mkdir a
$ echo hello > a/b
$ mkdir c
$ echo hello > c/b
$ echo hello > b
$ find . b | xargs grep hello
hello
hello
hello

判分程序结果

./grade-lab-util xargs
make: 'kernel/kernel' is up to date.
== Test xargs == xargs: OK (1.2s)

Lab1 整体实验判分结果

== Test sleep, no arguments ==
$ make qemu-gdb
sleep, no arguments: OK (7.0s)
== Test sleep, returns ==
$ make qemu-gdb
sleep, returns: OK (0.9s)
== Test sleep, makes syscall ==
$ make qemu-gdb
sleep, makes syscall: OK (0.6s)
== Test pingpong ==
$ make qemu-gdb
pingpong: OK (0.7s)
== Test primes ==
$ make qemu-gdb
primes: OK (1.0s)
== Test find, in current directory ==
$ make qemu-gdb
find, in current directory: OK (1.2s)
== Test find, recursive ==
$ make qemu-gdb
find, recursive: OK (1.3s)
== Test xargs ==
$ make qemu-gdb
xargs: OK (0.9s)
== Test time ==
time: OK
Score: 100/100
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容