实验目的
(1)、掌握计算机操作系统管理进程、处理机、存储器、文件系统的基本方法。
(2)、了解进程的创建、撤消和运行,进程并发执行;自行设计解决哲学家就餐问题的并发线程,了解线程(进程)调度方法;掌握内存空间的分配与回收的基本原理;通过模拟文件管理的工作过程,了解文件操作命令的实质。
(3)、了解现代计算机操作系统的工作原理,具有初步分析、设计操作系统的能力。
(4)、通过在计算机上编程实现操作系统中的各种管理功能,在系统程序设计能力方面得到提升。
实验要求
(1)、题目1:(30%)
阅读xv6中文文档中的Chapter 4 Traps and system calls;
阅读这个很不错的网上课程翻译中的Lec06:
https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/
结合xv6-riscv代码,回答以下的代码理解问题:
在user/zombie.c中,sleep(5); (L12)。sleep是一个系统调用。请分析代码,阐述在代码中,这一系统调用如何一步步的转化为一个对核心函数sleep(kernel/proc.c / L536)的调用?
(2)、题目2:(70%)
一个在xv6-riscv上的游戏实例(xv6-riscv-2048.zip,a, d, w, s 分别代表左,右,上,下。):
这个小游戏移植于https://github.com/nyuichi/xv6。
可以读一下这个中文报道:
https://finance.sina.com.cn/tech/2020-10-08/doc-iivhuipp8489972.shtml
但我们只是完成了部分移植。原有版本上还有一些重要的重要功能我们并没有移植到risc-v上。比如原本包含的ioctl系统调用等。Nyuichi的xv6版本上还包含更多有趣的小程序,比如一个迷你的vi,一个扫雷游戏等。
请参考2048的的示例代码,实现一个xv6-riscv上的小游戏,或者小编辑器。
你可以:
- 自己写一个小游戏,或者类似nano的小编辑器;
移植一个开源的小应用程序到xv6-riscv上,比如移植Nyuichi的xv6版本上的mini-vi或扫雷程序到xv6-riscv上。
在提交大作业报告的同时,提供一个ZIP文件包,里面包含完整的源代码以及编译好的XV6虚拟机可执行文件。
你们对于这个题目不感兴趣,想在大作业中对于xv6-riscv作其他方面的改进也可以。但是要先征得杜智华老师的同意。
实验内容
(1)、题目1:回答以下的代码理解问题:
在user/zombie.c中,sleep(5); (L12)。sleep是一个系统调用。请分析代码,阐述在代码中,这一系统调用如何一步步的转化为一个对核心函数sleep(kernel/proc.c / L536)的调用?
在 user/zombie.c 文件中,有如下代码:
这段代码会创建一个子进程,如果 fork() 返回值是 0,则表示当前进程是子进程,子进程会执行 sleep(5) 和 exit(0)。
在user/zombie.c里调用sleep(5)时,会触发系统调用trap,进入内核态。
在riscv的trap处理代码kernel/trap.c里,会根据系统调用号找到正确的系统调用处理函数,在这个例子中是sys_sleep(kernel/syscall.c)。
sys_sleep函数会获取sleep(5)的参数nsec,转换为kernel内核中的时钟单位ticks。然后调用内核函数sleep(kernel/proc.c)。
被唤醒的进程会从睡眠点继续执行,所以会从sys_sleep函数返回,然后从trap返回到用户态zombie.c中继续执行。
在上述代码中,sys_sleep函数是sleep系统调用的处理函数。它首先从用户程序传递的参数中获取睡眠时间n。
接下来,函数会获取当前的系统时钟计数ticks,并在一个循环中检查当前时钟计数与起始时钟计数之间的差值是否小于睡眠时间n。
在循环内部,如果进程被杀死(killed(proc)为真),则会释放锁并返回错误值;否则,进程会调用sleep函数,将自己添加到睡眠队列中,并释放锁,使自己进入睡眠状态。
最后,当睡眠时间过去后,进程会被唤醒,继续执行。
因此,在代码执行过程中,当用户程序执行sleep(5);时,会触发一个中断,进入内核态。内核会调用sys_sleep函数来处理sleep系统调用,最终调用sleep函数实现进程的睡眠和唤醒操作。这样,sleep(5);就被转化为了对核心函数sleep的调用。
(2)、题目2:请参考2048的的示例代码,实现一个xv6-riscv上的小游戏,或者小编辑器。
移植以及相关移植准备,首先是对已经给出的小游戏2048进行移植
终端交互移植最关键的就是要把缺失的库函数都补完,首先就得增加和改进 xv6-riscv 原有的终端交互相关的库
在 xv6-riscv 的顶层目录下新建一个 include 文件夹,里面存放改进的头文件
Head What it does
assert.h 断言的相关宏定义和函数声明,用于断言检查
ctype.h 字符处理的函数和宏定义,对字符进行分类和转换
limits.h 定义了各种数据类型的取值范围以及其他与整数类型相关的常量
stdarg.h 提供了支持可变参数函数的宏和类型定义
stdbool.h 定义了布尔类型和相关的常量,没错,xv6-riscv 原本甚至不支持 bool 型变量
stddef.h 提供了一些与指针、大小和偏移量相关的常量和类型定义
stdio.h 用于处理标准输入输出
stdlib.h 用于内存管理、字符串转换、伪随机数生成、算术运算和程序终止等
termios.h 用于控制和配置终端的行为
unistd.h 定义了一些与系统调用、文件操作、进程控制、环境变量和程序终止相关的函数和常量
接下来在 xv6 的顶层目录新建 lib 目录,里面新建一个 libc.c 文件用于实现之前 include 文件夹下定义的改进函数
然后在 user 目录里面新建 2048.c ,里面存放 2048 这个小游戏的实现,整个游戏使用 wasd 进行方向控制,q 控制退出游戏,每次操作接收一个操作字母加回车
最后修改 Makefile ,将改进的库以及 2048.c 纳入编译的范围
注意的是,移植之后可能要细微修改 kernel 下面的 virtio.h 和 virtio_disk.c ,不然 xv6 可能无法正常启动,会抛出一个 panic 信息
然后是上网找到的小游戏扫雷的移植:
有了 2048 移植之后的功能库,扫雷的移植就非常简单了,修改 Makefile 和添加 minesweeper 的实现就行
在终端中输入minesweeper运行扫雷
输入help查看帮助
输入open 1 2后棋盘更新
输入mark 7 0标记
输入init重置棋盘
输入q退出游戏
然后是我们自己实现的一个小游戏,五子棋
init_board():初始化棋盘,将每个位置都设为EMPTY。
print_board():打印当前棋盘状态,包括棋盘上每个位置的状态,使用红色和黄色文本表示黑子和白子。
is_valid_move(int row, int col):检查给定位置是否为有效的落子位置,即该位置是否在棋盘内且尚未被占据。
is_win(int player):检查给定玩家是否获胜,即是否在水平、垂直、对角线或反对角线方向上连续出现五颗相同的棋子,遍历每个旗子,进行检查即可。
使用了fgets()函数从标准输入读取用户输入。因为xv6系统不支持scanf的读入。
最后还清空了缓冲区使其中内容输出,因为xv6的输出是将内容放在缓冲区,不会立刻输出,若不请空则有可能导致部分内容未输出。
在终端输入five进入游戏,弹出开始语。
打印初始棋盘,并提醒玩家1输入坐标
当输入B B后会提示玩家1输入内容并打印棋盘,接着提醒玩家2输入。
玩家2输入C C后,更新棋盘并打印。
当输入不合法时,会提醒重新输入。
当一方连成五子时,会弹出获胜玩家提示。
实验小结
通过此次实验,加深了对操作系统的理解,以及对xv6内部操作实现的理解,学会了如何在xv6中移植小游戏以及编写自己的小游戏。
(by 归忆)