os-lab6笔记


lab6笔记

  • 实验目的

    pipe

    shell

    shell中设计pipe“|”的部分

  • 管道

    是进程间通信的另一种方式,显然是进程间单向通信的一种方式。

mos中pipe的使用与实现

首先分析Pipe这个结构体

struct Pipe {
    u_int p_rpos; // read position,下一个将要从管道读的数据的位置
                  // 只有读者可以更新 p_rpos
    u_int p_wpos; // write position,下一个将要向管道写的数据的位置
                  // 只有写者可以更新 p_wpos
    u_char p_buf[PIPE_SIZE]; // data buffer
                             // 这个 PIPE_SIZE 大小的缓冲区发挥的作用类似于环形缓冲区
                             // 所以下一个要读或写的位置 i 实际上是 i%PIPE_SIZE
    // 所以如果管道数据为空,即当 p_rpos >= p_wpos 时,应该进程切换到写者运行
};

这里就要分析管道的特殊作用。管道是一种只存在于内存中的文件(与磁盘中的文件啥的相对,linux一切皆文件)。这里会同步进行读和写,那么如何做到这一个呢,首先由于不希望进行通信,那么实际上会有同一块内存区域,然后为了考虑同步读和同步写,需要在内存区域中添加读和写的一个指针。

所以如果管道数据为空,即当 p_rpos >= p_wpos 时,应该进程切换到写者运行。

同时写者在写入的时候,也是将数据存入缓冲区,需要注意管道的缓冲区可能出现满溢的情况,所以写者必须得在p_wpos-p_rpos < PIPE_SIZE时方可运行,否则要一直挂起。

同时为了解决部分死锁的问题,需要知道管道的另一端是否已经关闭,当出现缓冲区为空或满的情况是,要根据另一端是否关闭来判断是否要返回。如果另一端已经关闭,进程返回0即可;如果没有关闭,则切换进程运行。

然后分析一下pipe函数的具体实现。父子进程的p[0] 和 p[1]访问到的内存区域应该是一致的。

那么在pipe中是如何实现的呢,

首先通过fd_allocsyscall_mem_alloc两个函数分别创建文件描述符并为文件描述符分配空间

然后给fd0对应的虚拟地址分配一页物理内存,再将fd1对应的虚拟地址映射到这一页物理内存。

注意这里创建文件描述符fd0 fd1和为文件描述符对应的虚拟地址分配物理空间时用到的权限位都是PTE_D|PTE_LIBRARYPTE_D可以理解,表示可写,PTE_LIBRARY实际上还表达了父子进程可以对这个共享页面直接进行写操作,也就是共享可写页面!

这里讲一下为什么先要分配这三个物理页面

这里需要分析一下fd_alloc的实现,这里的关键实际上是*fd = (struct Fd*)va;

也就是说只是找到了一个没有使用过的虚拟地址,但是如果需要使用这个物理地址,是需要对应到实际的物理页面的。同时fd也需要对应到实际的物理页面。

那么这里就需要创建三个页面了。

在MOS中,使用了_pipe_is_closed()来判断管道的另一端是否已经关闭。

这个是如何实现的呢,又是什么原理呢。

确定几点,管道只有一端读一端写,每一个匿名管道分配了三夜空间,一页是读数据的文件描述符fd0,一页是写数据的文件描述符fd1,同时还有一页被两个文件描述符共享的管道数据缓冲区pipe

那么显然有pageref(rfd)+pageref(wfd)=pageref(pipe)成立。

那么要判断另一端是否已经关闭实际上是判断另一端的pageref是否为0,那么实际上就是判断pageref(fd) == pageref(pipe)是否成立。

内核会对pages数组成员维护一个页引用变量pp_ref来记录指向该物理页的虚页数量。

看看pipe_close这个函数

这里进行了两个syscall_mem_unmap的操作

目前的unmap的顺序是,

先取消对pipe的映射,然后再取消对文件描述符的映射

在这个情况下我们考虑

pipe(p);
if (fork() == 0) {
    close(p[1]);
    read(p[0], buf, sizeof(buf));
} else {
    close(p[0]);
    write(p[1], "Hello", 5);
}

如果执行顺序是

  1. 子进程close(p[1]);

  2. 父进程close(p[0]);仅完成了取消p[0]pipe的映射

    此时每个页面的引用情况为:

    pageref(p[1]) = 1;

    pageref(p[0]) = 2;

    pageref(pipe) = 2;

  3. 子进程执行read,判断写端已经关闭,因为pageref(p[0]) == pageref(pipe),相当于认为pageref(p[1]) == 0

    因此斗胆猜测,如果判断写端关闭的时候把两个都加上是不是就没问题了(x

    那么此时

    rbuf = (char*)vbuf;
    for (int i = 0; i < n; i++) {
     while (p->p_rpos == p->p_wpos) {
         if (_pipe_is_closed(fd, p) || i > 0)
             return i;
         syscall_yield();
     }
     rbuf[i] = p->p_buf[p->p_rpos % PIPE_SIZE];
     p->p_rpos++;
    }

    那么此时就会发现p->p_rpos == p->p_wpos但是写端没有关闭,因此就会进行系统调用,然后父进程继续完成进行,搞定!

这里还解释一下把两个unmap的顺序调整后可行的原因。

这里考虑的完全是pageref

显然有pageref(pipe) >= pageref(fd),而先取消pipe的映射,可能导致应该是pageref(pipe)>pageref(fd)变成pageref(pipe)==pageref(fd),但是先取消fd的映射就不可能出现这个情况,因此不会误判另一端已经关闭。

然后就是读的时候也可能因为进程切换出现问题,这里好办,只要判断没有进行进程切换即可,也就是这个过程前后没有发生进程切换,那么用一个全局变量来判断即可,也就是env_runs,记录了某个进程run的次数

Shell

Shell程序需要完成的事情就是:不断读取用户的输入,根据命令创建对应的进程,运行对应的程序,同时实现进程间的通信。

通过简单的测试,发现ARGBEGIN ARGEND可以解析出sh -ix;sh -i; sh -x中的相应参数。

ARGBEGIN
ARGEND

readline详细注释版:

void readline(char* buf, u_int n)
{
    int r;
    for (int i = 0; i < n; i++) {
        if ((r = read(0, buf + i, 1)) != 1) {
            if (r < 0) {
                debugf("read error: %d\n", r);
            }
            exit();
        }
        // debugk_user("IN user/sh.c readline(), thel local i is %d",i);
        if (buf[i] == '\b' || buf[i] == 0x7f /*DEL (Delete) 符号*/) {
            // 因为之后会执行i++操作,表示如果已经在buf中有字符,则回退一个,否则留在原地
            if (i > 0) {
                i -= 2;
            } else {
                i = -1;
            }
            if (buf[i] != '\b') {
                // 这个表示将光标左移一位
                printf("\b");
            }
        }
        if (buf[i] == '\r' || buf[i] == '\n') {
            buf[i] = 0;
            return;
        }
    }
    // 理论上应该在上面的'\r' '\n'那里结束,没有结束说明太长了
    debugf("line too long\n");
    // 读完这一行,并将buf[0]置为'\0',表示为空字符串
    while ((r = read(0, buf, 1)) == 1 && buf[0] != '\r' && buf[0] != '\n') {
        ;
    }
    buf[0] = 0;
}

_gettoken函数的作用是:


文章作者: hugo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hugo !
  目录