6.S081-Lab: Xv6 and Unix utilities 操作系统实验

本文详细介绍了在Xv6操作系统中进行的实验室工作,涉及实现包括sleep、echo、rm在内的Unix实用程序。作者分享了对系统调用如sleep的学习过程,以及如何实现这些命令。此外,还探讨了如何使用pipe进行进程间通信,并通过primes、find和xargs等实验加深了对操作系统的理解。实验中涉及了C语言编程和对Xv6内核的理解,适合有Linux和Unix背景的读者参考学习。
摘要由CSDN通过智能技术生成

学习操作系统的时候,买了一本人民邮电出版社的-Operating Systems: Three Easy Pieces 中译版(操作系统导论)。
从书中学到了很多关于操作系统的思想,出现一个问题,思考策略,并比较策略之间的利弊。整本书读起来十分有趣,当然我不是在这里打广告。
当时只是学习了理论知识,书中所给的练习以及后面的实验都没有做过,当时在附录后面看到:xv6 实验,一直没来做。
这些天,从网上找到了 xv6 实验对应的课程 6.S081,课程的 lab 是重点。
这个 Lab 主要是 Shell 的一些命令实现,做完之后会体会到:原来这些命令是这样实现的!
希望我的文章能给大家带来些帮助!

Xv6-Lab-utilities

sleep (easy)

Some hints:

  • Before you start coding, read Chapter 1 of the xv6 book.
  • Look at some of the other programs in user/ (e.g., user/echo.c, user/grep.c, and user/rm.c) to see how you can obtain the command-line arguments passed to a program.
  • If the user forgets to pass an argument, sleep should print an error message.
  • The command-line argument is passed as a string; you can convert it to an integer using atoi (see user/ulib.c).
  • Use the system call sleep.
  • See kernel/sysproc.c for the xv6 kernel code that implements the sleep system call (look for sys_sleep), user/user.h for the C definition of sleep callable from a user program, and user/usys.S for the assembler code that jumps from user code into the kernel for sleep.
  • Make sure main calls exit() in order to exit your program.
  • Add your sleep program to UPROGS in Makefile; once you’ve done that, make qemu will compile your program and you’ll be able to run it from the xv6 shell.
  • Look at Kernighan and Ritchie’s book The C programming language (second edition) (K&R) to learn about C.

user 目录下的其他程序

按照实验指导给的步骤,先看看可执行命令对应的代码是怎样的,再添加自己编写的程序。

echo.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
   
  int i;

  for(i = 1; i < argc; i++){
   
    write(1, argv[i], strlen(argv[i]));
    if(i + 1 < argc){
   
      write(1, " ", 1);
    } else {
   
      write(1, "\n", 1);
    }
  }
  exit(0);
}

rm.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
   
  int i;

  if(argc < 2){
   
    fprintf(2, "Usage: rm files...\n");
    exit(1);
  }

  for(i = 1; i < argc; i++){
   
    if(unlink(argv[i]) < 0){
   
      fprintf(2, "rm: %s failed to delete\n", argv[i]);
      break;
    }
  }

  exit(0);
}

这些程序都是这么做的:

  • 执行的操作是从 int main 函数开始的
  • 通过 argc(参数数量)和 argv(参数值数组)获取调用的参数
  • 程序正常结束时,调用 exit(0) 退出表示调用成功,调用 exit(1) 退出表示调用失败,这个很重要!!!

我们可以猜测这么一个过程:在命令行输入某个命令时,会查找这个命令对应的可执行文件,并尝试去运行;程序通过 argc、argv 来获取命令参数

更进一步,通过查看 xv6 book

我们知道 argv 的第一个参数(即argv[0])存放着程序名称。

Most programs ignore the first element of the argument array, which is conventionally the name of the program

  • book-riscv-rev2.pdf#P12

我们还知道 shell 是通过 fork + exec 两个系统调用的配合来实现命令的执行。

The main loop reads a line of input from the user with getcmd. Then it calls fork, which creates a copy of the shell process…If exec succeeds then the child will execute instructions from echo instead of runcmd.

这个时候就大胆的看 xv6 中的 shell 源码,不过在这里就不多说了,后续有机会再好好分析。
user/sh.c

int
main(void)
{
  static char buf[100];
  int fd;

  // Ensure that three file descriptors are open.
  while((fd = open("console", O_RDWR)) >= 0){
    if(fd >= 3){
      close(fd);
      break;
    }
  }

  // Read and run input commands.
  while(getcmd(buf, sizeof(buf)) >= 0){
    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
      // Chdir must be called by the parent, not the child.
      buf[strlen(buf)-1] = 0;  // chop \n
      if(chdir(buf+3) < 0)
        fprintf(2, "cannot cd %s\n", buf+3);
      continue;
    }
    if(fork1() == 0)
      runcmd(parsecmd(buf));
    wait(0);
  }
  exit(0);
}

实验指导让我们往 MakefileUPROGS 段添加自己的程序名,看看这个地方放着什么:

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\

cat、echo、grep…都是可执行命令,可以想象的到,编译内核时会根据这个内容来添加对应的命令。

系统调用 sleep 学习

sleep 系统调用接受一个 int 类型的参数,即要睡眠的 ticks。

int sleep(int);

用户程序调用内核 sleep 的汇编代码

可见,调用 sleep 相当于调用 SYS_sleep

.global sleep
sleep:
 li a7, SYS_sleep
 ecall
 ret

sleep 的内核实现

uint64
sys_sleep(void)
{
   
  int n;
  uint ticks0;
	

  if(argint(0, &n) < 0)
    return -1;

  acquire(&tickslock);
  ticks0 = ticks;
  while(ticks - ticks0 < n){
   
    if(myproc()->killed){
   
      release(&tickslock);
      return -1;
    }
    sleep(&ticks, &tickslock);
  }
  release(&tickslock);
  return 0;
}
  1. 获取 trapframe 中调用进程传入的第一个参数,即 ticks
  2. 使用自旋锁 tickslock 确保只有一个进程在while 循环代码中执行
  3. 记录起始 ticks,当前 ticks 减去起始 ticks 小于给定的 n时,会先判断进程的状态;如果进程在睡眠中途被 kill 则会释放锁并退出,否则调用 sleep 睡眠

实现 sleep

我们要实现的命令类似于这样

$ sleep 10
(nothing happens for a little while)
$

由于命令行的参数都是字符串,而我们需要 int 参数来调用 sleep,因此需要转换函数 atoi

atoi 函数原型

int atoi(const char*);

user/sleep.c

//
// Created by 悠一木碧 on 2022/6/15.
//
#include "../kernel/types.h"
#include "../kernel/stat.h"
#include "../user/user.h"

int
main(int argc, char *argv[]) {
   

    if (argc != 2) {
   
	fprintf(2, "Usage: sleep ticks\n");
	exit(1);
    }

    int n = atoi(argv[1]);
    sleep(n);

    exit(0);
}

Makefile

...
...
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\
...
...

测试结果

在这里插入图片描述

pingpong (easy)

Write a program that uses UNIX system calls to ‘‘ping-pong’’ a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to the child; the child should print “: received ping”, where is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print “: received pong”, and exit. Your solution should be in the file user/pingpong.c.

Some hints:

  • Use pipe to create a pipe.
  • Use fork to create a child.
  • Use read to read from the pipe, and write to write to the pipe.
  • Use getpid to find the process ID of the calling process.
  • Add the program to UPROGS in Makefile.
  • User programs on xv6 have a limited set of library functions available to them. You can see the list in user/user.h; the source (other than for system calls) is in user/ulib.c, user/printf.c, and user/umalloc.c.

Run the program from the xv6 shell and it should produce the following output:

    $ make qemu
    ...
    init: starting sh
    $ pingpong
    4: received ping
    3: received pong
    $
  

Your solution is correct if your program exchanges a byte between two processes and produces output as shown above.

系统调用 pipe 学习

A pipe is a small kernel buffer exposed to processes as a pair of file descriptors, one for reading and one for writing. Writing data to one end of the pipe makes that data available for reading from the other end of the pipe.

  • book-riscv-rev2.pdf#P15

pipe 系统用创建两个文件描述符,并将其放置在给定的数组 arr 中;其中arr[0] 用于readarr[1]用于write。当向arr[1]写入数据时,可以从arr[0] 读取。

The program calls pipe… After fork, both parent and child have file descriptors referring to the pipe.

  • book-riscv-rev2.pdf#P16

Two file descriptors share an offset if they were derived from the same original file descriptor by a sequence of fork and dup calls.

  • book-riscv-rev2.pdf#P15

需要注意的是,当进程在调用 pipe 之后再调用 fork 创建子进程,父子进程都会有这两个文件描述符(file desciptor)的引用,且共享读写的 offset
这里提到的东西会很多,比如文件描述符的引用计数,涉及到了文件系统组织,读写的 offset 听上去很绕口;学习过操作系统和 IO 的同学可能会知道这里指什么。不过这里,我们也不细研究,只探讨书中所说的是否正确。

测试

为了测试书中所说的两个功能,我们使用 pipe 系统调用创建管道,并使用 fork 创建子进程,父子进程都通过 p[1] 写入,测试它们的写入是否共享 offset,并测试是否能从 p[0] 读取。

user/test.c

//
// Created by 悠一木碧 on 2022/6/15.
//
#include "../kernel/types.h"
#include "../kernel/stat.h"
#include "../user/user.h"

int
main(int argc, char *argv[])
{
   
    int p[2];
    pipe(p);

    int ret;
    if ((ret = fork()) == 0) {
   
        close(p[0]);
        const char *str = "hello ";
        int len = strlen(str);
        int n = write(p[1], str, len);
        int pid = getpid();
        if (n != len) {
   
            fprintf(2, "process %d: write %d bytes, but expect to write %d bytes!\n", pid, n, len);
            exit(1);
        }
        fprintf(2, "process %d: write %d bytes\n", pid, n);
        close(p[1]);
    } else if (ret > 0) {
   
        wait(0);
        const char *str = "world!";
        int len = strlen(str);
        int n = write(p[1], str, len);
        int pid = getpid();
        if (len != n) {
   
            fprintf(2, "process %d: write %d bytes, but expect to write %d bytes!\n", pid, n, len);
            exit(1);
        }
        fprintf(2, "process %d: write %d bytes\n", pid, n);
        close(p[1]);

        char buf[1024];
        while ((n = read(p[0], buf, 1023)) > 0) {
   
            buf[n] = '\0';
            fprintf(1, "process %d: read %d bytes, str is %s\n", pid, n, buf);
        }
        if (n < 0) {
   
            fprintf(2, "read error!\n");
            exit(1);
        }
        close(p[0])
### 回答1: :xv6是一个基于Unix的操作系统,它是一个教学用途的操作系统,旨在教授操作系统的基本概念和实现。它是在MIT的x86架构上开发的,包括了Unix的一些基本功能,如进程管理、文件系统、内存管理等。xv6的源代码是公开的,可以用于学习和研究。 Unix utilitiesUnix操作系统中的一些基本工具,如ls、cd、cp、mv、rm等。这些工具可以帮助用户管理文件和目录,执行各种操作。这些工具的实现是基于Unix的系统调用,可以通过编写C程序来调用这些系统调用实现相应的功能。这些工具是Unix操作系统的基础,也是其他操作系统的参考。 ### 回答2: lab: xv6 and unix utilities 实验是一项旨在帮助学生深入理解操作系统和 Unix 工具使用的实验。该实验分为两个部分,第一部分教授学生如何构建和运行 xv6 操作系统;第二部分则重点教授 Unix 工具的使用。 在 xv6 操作系统部分,学生将学习到操作系统内核的基本结构和实现原理。实验将引导学生理解内存管理、进程调度、系统调用等关键操作系统概念。此外,学生还将学习如何编写简单的 shell 以及如何通过修改 xv6 内核代码来实现新的系统调用和功能。 在 Unix 工具部分,学生将探索 Unix 系统中广泛使用的常见工具。这些工具包括 vi 编辑器、grep、awk、sed 等。实验将介绍这些工具的基本使用方法以及它们在处理文本和数据时的实际应用。这部分实验还将让学生深入了解 shell 和 shell 脚本的编写,帮助他们在 Unix 环境中轻松地编写脚本和自动化任务。 lab: xv6 and unix utilities 实验对计算机科学专业的学生具有重要意义。通过完成这个实验,学生将建立起对操作系统和 Unix 工具的深入理解,为他们成为一名优秀的软件工程师奠定坚实的基础。同时,这个实验还将为学生提供实践经验,让他们能够将所学知识应用到真实的软件开发和运维中。 ### 回答3: Lab: xv6 and Unix Utilities是一个计算机科学领域的实验,旨在让学生深入了解Unix操作系统以及操作系统本身的自我管理机制。在这个实验中,学生需要从零开始构建一个类似于Unix的操作系统,在这个操作系统中,学生需要设计一些基本命令,例如ls,cat,grep等等,并且将它们与系统的底层API结合起来,以实现各种功能。此外,学生还需要了解和探索xv6这个开发工具,它是一个轻量级基于Unix的操作系统实现,具有一定的可移植性和简洁性,因此,它可以作为一个基础框架来实现一个完整的Unix操作系统。 这个实验的目标是让学生了解Unix的基本命令结构和API,以及操作系统内部的一些基本机制,例如进程管理,文件系统交互以及进程通信等等。此外,通过实现这些命令,学生还可以学到一些基本的C语言编程技能,例如文件操作,字符串处理以及进程管理等等。还可以学习到如何使用Git等版本控制工具,以及如何进行调试和测试代码的技巧。 在整个实验过程中,学生需要有较强的自我管理能力和综合运用能力,因为在实现这些命令的同时,他们还需要和其他团队成员进行交流和合作,以及不断改进和完善他们的代码。总之,这个实验是一个非常有趣且富有挑战性的计算机科学课程,通过完成这个实验,学生可以更好地了解操作系统的构造和运作机制,以及如何设计和开发高效的系统级应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值