MIT6.S081/6.828 实验1:Lab Unix Utilities

Mit6.828/6.S081 fall 2019的Lab1是Unix utilities,主要内容为行使xv6的系统挪用实现sleep、pingpong、primes、find和xargs等工具。本文对各程序的实现思绪及xv6的系统挪用流程举行详细先容。

前言

在实验之前,推荐阅读一下官网LEC1中提供的资料。其中Introduction是对该课程的的概述,examples则是几个系统编程的样例,这两部分快速浏览一遍即可。对于xv6 book的第一章,则建议稍微仔细地阅读一遍,特别是对fork()、exec()、pipe()、dup()这几个系统挪用的先容,会在后面实验中用到。

实验环境搭建参考上一篇文章。进入xv6-riscv-fall19项目后可以看到两个对照主要的目录:kernel为xv6内核源码,内里除了os事情的焦点代码(如历程调剂),另有向外提供的接口(system call);user中则是用户程序,如我们熟悉的ls,echo下令等。本次实验的目的就是在user中增添用户程序,借助kernel中提供的system call来实现所需的功效。

实验思绪

每一个Lab需要在对应的分支编写代码,进入xv6-riscv-fall19目录下,使用git checkout util切换到util分支,即可最先编写我们的程序。下面主要提供实现思绪,详细实验代码请参考Github

实验完成后使用make grade可以执行单元测试举行评分,会以gdb-server模式启动qemu,并在gradelib.py中模拟gdb-client对我们的程序举行测试。若是在make grade时报错Timeout! Failed to connect to QEMU,可以将gradelib.py的325行改为self.sock.connect(("127.0.0.1", port))

sleep

sleep功效为使历程睡眠若干个时钟周期(xv6中一个tick为100ms),首先建立user/sleep.c源文件,引入user.h头文件,系统挪用和工具函数都界说在该文件里。焦点代码如下:

sleep(atoi(argv[1]));

完成编写后,在Makefile的UPROGS中追加一行$U/_sleep\。输入make qemu举行编译,乐成后进入shell,输入sleep 10,若是历程睡眠了约莫1s,则示意程序编写准确。

pingpong

功效是父历程通过管道向子历程发送1字节,子历程收到后向父历程回复1字节。

由于管道是单向流动的,以是两次挪用pipe()建立两个管道,划分对应两个偏向。使用fork()建立子历程,在子历程中先从管道1read()再向管道2write(),父历程中则与之相反。

primes

primes的功效是输出2~35之间的素数,实现方式是递归fork历程并使用管道链接,形成一条pipeline来对素数举行过滤。

每个历程收到的第一个数p一定是素数,后续的数若是能被p整除则之间抛弃,若是不能则输出到下一个历程,详细先容可参考文档。伪代码如下:

void primes() {
  p = read from left         // 从左边吸收到的第一个数一定是素数
  if (fork() == 0): 
    primes()                 // 子历程,进入递归
  else: 
    loop: 
      n = read from left     // 父历程,循环吸收左边的输入  
      if (p % n != 0): 
        write n to right     // 不能被p整除则向右输出   
}

还需要注重两点:

  • 文件描述符溢出: xv6限制fd的局限为0~15,而每次pipe()都市建立两个新的fd,若是不实时关闭不需要的fd,会导致文件描述符资源用尽。这里使用重定向到尺度I/O的方式来制止天生新的fd,首先close()关闭尺度I/O的fd,然后使用dup()复制所需的管道fd(会自动复制到序号最小的fd,即关闭的尺度I/O),随后对pipe两侧fd举行关闭(此时只会移除描述符,不会关闭现实的file工具)。

  • pipeline关闭: 在完成素数输出后,需要依次退出pipeline上的所有历程。在退出父历程前关闭其尺度输入fd,此时read()将读取到eof(值为0),此时同样关闭子历程的尺度输入fd,退出历程,这样历程链上的所有历程就可以退出。

    系统化学习多线程(一)

find

find功效是在目录中匹配文件名,实现思绪是递归搜索整个目录树。

使用open()打开当前fd,用fstat()判断fd的type,若是是文件,则与要找的文件名举行匹配;若是是目录,则循环read()到dirent结构,获得其子文件/目录名,拼接获得当前路径后进入递归挪用。注重对于子目录中的...不要举行递归。

xargs

xargs的功效是将尺度输入转为程序的下令行参数。可配合管道使用,让原本无法吸收尺度输入的下令可以使用尺度输入作为参数。

凭据lab中的使用例子可以看出,xv6的xargs每次回车都市执行一次下令并输出效果,直到ctrl+d时竣事;而linux中的实现则是一直吸收输入,收到ctrl+d时才执行下令并输出效果。

思绪是使用两层循环读取尺度输入:

  • 内层循环依次读取每一个字符,凭据空格举行参数支解,将参数字符串存入二维数组中,当读取到’\n’时,退出当前循环;当吸收到ctrl+d(read返回的长度<0)时退出程序。
  • 外层循环对每一行输入fork()出子历程,挪用exec()执行下令。注重exec吸收的二维参数数组argv,第一个参数argv[0]必须是该下令自己,最后一个参数argv[size-1]必须为0,否则将执行失败。

xv6系统挪用流程

Lab中对system call的使用很简单,看起来和通俗函数挪用并没有什么区别,但现实上的挪用流程是较为庞大的。我们很容易发生一些疑问:系统挪用的整个生命周期详细是什么样的?用户历程和内核历程之间是若何切换上下文的?系统挪用的函数名、参数和返回值是若何在用户历程和内核历程之间通报的?

1.用户态挪用

在用户空间,所有system call的函数声明写在user.h中,挪用后会进入usys.S执行汇编指令:将对应的系统挪用号(system call number)置于寄存器a7中,并执行ecall指令举行系统挪用,其中函数参数存在a0~a5这6个寄存器中。ecall指令将触发软中止,cpu会暂停对用户程序的执行,转而执行内核的中止处置逻辑,陷入(trap)内核态。

2.上下文切换

中止处置在kernel/trampoline.S中,首先举行上下文的切换,将user历程在寄存器中的数据save到内存中(珍爱现场),并restore(恢复)kernel的寄存器数据。内核中会维护一个历程数组(最多容纳64个历程),存储每个历程的状态信息,proc结构体界说在proc.h,这也是xv6对PCB(Process Control Block)的实现。用户程序的寄存器数据将被暂时保存到proc->trapframe结构中。

3.内核态执行

完成历程切换后,挪用trap.c/usertrap(),接着进入syscall.c/syscall(),在该方式中凭据system call number拿到数组中的函数指针,执行系统挪用函数。函数参数从用户历程的trapframe结构中获取(a0~a5),函数执行的效果则存储于trapframe的a0字段中。完成挪用后同样需要历程切换,先save内核寄存器到trapframe->kernel_*,再将trapframe中暂存的user历程数据restore到寄存器,重新回到用户空间,cpu从中止处继续执行,从寄存器a0中拿到函数返回值。

至此,系统挪用完成,共履历了两次历程上下文切换:用户历程 -> 内核历程 -> 用户历程,同时伴随着两次CPU事情状态的切换:用户态 -> 内核态 -> 用户态。

实验代码:https://github.com/zhayujie/xv6-riscv-fall19

原文链接:https://zhayujie.com/mit6828-lab-util.html

原创文章,作者:时事新闻,如若转载,请注明出处:https://www.28ru.com/archives/8975.html