实验目的与要求:
实验目的:
(1)、掌握计算机操作系统管理进程、处理机、存储器、文件系统的基本方法。
(2)、了解进程的创建、撤消和运行,进程并发执行;自行设计解决哲学家就餐问题的并发线程,了解线程(进程)调度方法;掌握内存空间的分配与回收的基本原理;通过模拟文件管理的工作过程,了解文件操作命令的实质。
(3)、了解现代计算机操作系统的工作原理,具有初步分析、设计操作系统的能力。
(4)、通过在计算机上编程实现操作系统中的各种管理功能,在系统程序设计能力方面得到提升。
实验要求:
(1) 在xv6环境下开发应用程序
题目:仿照echo,写一个命令echo_reversal,实现以下功能:把输入的每个参数中的字符次序颠倒输出。
例如:$ echo_reversal Hello World!
则输出: olleH !dlroW
(2) 回答以下问题:
Xv6中并发进程有几种状态,在源码中分别以什么常量代表,试解释每种状态的意义。
Xv6中PCB是以什么方式存放的,链表还是数组?系统最多允许同时运行多少个进程?
Xv6是否支持多核cpu? 如果支持的话,是通过哪个数据结构支持的?
系统启动的第一个进程,其入口函数在哪个文件第几行?它主要实现什么功能?(提示:阅读《xv6中文文档》第1章“第一个进程”)
(1). 在xv6环境下开发应用程序
题目:仿照echo,写一个命令echo_reversal,实现以下功能:把输入的每个参数中的字符次序颠倒输出。
例如:$ echo_reversal Hello World!
则输出: olleH !dlroW
- 首先,在user文件夹下找到echo.c文件,查看xv6的源码如图1所示。
图 1 xv6 echo.c代码
可以看到它实现的功能是将echo后面的每一个参数直接输出,如果不是最后一个参数则输出后带一个空格,如果是最后一个参数则带一个换行符。
2. 在终端执行make qemu(-gdb),测试其功能,如图2所示。
图 2 测试xv6 echo命令
可以看到确实只有最简单的输出功能,当参数之间有多个空格时只输出一个空格,运行的是xv6中的echo。
3.要实现将每个参数反转输出,可以编写一个reverse_string(char *str)函数,如图3所示,实现将每一个字符串参数进行反转。
图 3 reverse_string函数
4. 在调用write将字符串输出到终端前调用此函数,其余部分与echo.c保持一致即可。如图4所示。
图 4 echo_reversal.c主函数
5.退出xv6环境,重新运行make qemu编译运行环境,发现无法使用echo_reversal命令,如图5所示。
图 5 执行echo_reversal失败
检查user文件夹发现编译后没有生成echo_reversal.d echo_reversal.o echo_reversal.asm echo_reversal.sym等文件。猜测是echo_reversal.c没有参与编译,于是检查Makefile文件。
6.查看Makefile文件,发现可使用make clean命令清除上一次编译产生的结果,如图6所示。
图 6 make clean命令
查看qumu和qemu-gdb命令,发现编译时会调用fs.img(图7),而fs.img会访问UPROGS变量(图8),UPROGS中以“user/_command_names”的形式包含各种常见命令,包括echo在内,于是在UPROGS加上“user/_echo_reversal”(图8)。
图 7 qemu和qemu-gdb命令
图 8 修改UPROGS
7.执行make clean,可以看到编译输出的文件都被删除了,如图9所示。
图 9 执行make clean
执行make qemu编译进入环境,测试echo_reversal命令功能,如图10所示。
图 10 测试echo_reversal命令
可以看到单个和多个参数都能正确颠倒输出,符合期望。
拓展:由于一般的操作系统可以使用“-xx”的参数对命令的功能进行选择,所以想编写一个echo2.c文件,使得echo2命令可以在第二个参数为以“-”开头时,通过参数控制其功能,例如“echo2 -r xxxx”时执行的为本题中echo_reversal的功能。
创建echo2.c文件,编写代码如图11所示。
图 11 echo2.c代码
使用switch语句实现不同功能的选择(仅实现-r),若第二个参数不是“-?”形式则执行原echo的功能。编译运行后测试其功能,如图12所示。
图 12 测试echo2功能
可以看到效果与预期一致。
(2). 回答以下问题:
a. Xv6中并发进程有几种状态,在源码中分别以什么常量代表,试解释每种状态的意义。
b. Xv6中PCB是以什么方式存放的,链表还是数组?系统最多允许同时运行多少个进程?
c. Xv6是否支持多核cpu? 如果支持的话,是通过哪个数据结构支持的?
d. 系统启动的第一个进程,其入口函数在哪个文件第几行?它主要实现什么功能?(提示:阅读《xv6中文文档》第1章“第一个进程”)
a.在 xv6 操作系统的源码中,进程的并发状态在 proc.h 头文件中以以下常量表示:
UNUSED:表示未使用的进程。这些进程在系统中没有被分配或被创建,处于空闲状态,可以用于新的进程分配。
EMBRYO:表示萌芽态的进程。这些进程已经被分配并正在初始化,但尚未完全准备好运行。通常,在分配了进程表项和内核堆栈后,进程会进入这个状态。
SLEEPING:表示阻塞态的进程。这些进程因为某种原因(如等待 I/O 完成、等待某个事件发生等)主动挂起,并进入等待队列。等待的条件满足后,进程将被唤醒并转换到就绪态。
RUNNABLE:表示就绪态的进程。这些进程已经准备好运行,但由于 CPU 资源有限,它们还没有被调度执行。当调度器决定将 CPU 分配给该进程时,它会从就绪队列中选择一个进程进行执行。
ZOMBIE:表示僵死状态的进程。这些进程已经执行完成,但其父进程尚未回收其资源(如进程表项等)。僵死进程等待父进程调用 wait 或 waitpid 系统调用来回收其资源。一旦资源被回收,进程将被从系统中移除。
图 13 proc.h中存储的并发状态常量
b.在 xv6 操作系统中,进程控制块(PCB)以数组的形式存放在系统内核中。每个进程都有一个对应的 PCB 条目,这些 PCB 条目在内核中按照数组的形式进行存储和管理。
在源代码中,PCB 数组的定义和管理主要位于 proc.c 文件中,如图14所示。该文件包含了与进程管理相关的函数和数据结构,其中包括了进程表(即 PCB 数组)的定义和操作。数组元素结构体proc在proc.h中定义,如图15所示。
图 14 PCB数组定义
图 15 进程状态结构体定义
在 xv6 中,最大并发进程数量由系统在编译时通过宏定义NPROC进行设置,在 param.h 文件中定义如图16所示。
图 16 param.h中宏定义NPROC
如上表示系统的最大并发数为64。
c.xv6 操作系统支持多核 CPU。它通过 struct cpu 数据结构来支持多核处理器。
在 proc.h 文件中可以找到 struct cpu 的定义,它表示一个 CPU 核心,并通过cpus以数组形式进行管理,如图17所示。
图 17 xv6中多核CPU管理数据结构定义
同时,可以在param.h中找到宏定义NCPU,定义了最大CPU数量,如图18所示。
图 18 NCPU宏定义
d.查看kernel/main.c文件,通过注释可以看到,main函数在进行一系列设备和子系统的初始化后通过调用userinit()来建立第一个进程,这就是启动第一个进程的入口函数,如图19所示。在vscode中,按下ctrl单击userinit()可跳转至函数定义。
图 19 main.c中调用userinit函数建立第一个进程
Userinit函数定义图20所示。
图 20 userinit函数定义
可以看到,userinit中首先调用了allocproc为新进程分配一个槽位,并初始化进程的状态。进一步查看allocproc的定义,它通过设置返回程序计数器的值,使新进程的内核线程首先运行在 forkret 的代码中,然后返回到 trapret 中运行。
userinit 准备好新进程的内核栈和上下文,并将其设置为 RUNNABLE 状态,使其可以被调度执行。接着调用 setupkvm 函数为进程创建一个只映射内核区的页表,调用 inituvm 函数分配一页物理内存,将虚拟地址0映射到该页,并拷贝初始化代码到该页中,然后设置进程的初始用户模式状态,包括设置段选择器和栈指针。进程初始化完毕后,userinit 将进程的状态设置为 RUNNABLE,使其可以被调度执行。
在main函数调用userinit后,mpmain函数调用scheduler开始运行进程。scheduler找到一个可运行的进程initproc,将proc设置为该进程,并调用switchuvm切换页表。然后,scheduler将进程状态设置为RUNNING,调用swtch切换上下文到目标进程的内核线程。最后,处理器运行在进程的内核栈上。
allocproc函数设置了initproc的返回地址为forkret,然后执行ret指令。trapret函数恢复寄存器状态,使用iret指令将控制权转移到目标进程的用户代码。进程的页表由allocuvm函数建立。
接下来,initcode.S触发了exec系统调用,用新的程序替换当前进程的内存和寄存器。然后,init程序创建控制台设备文件并打开它。最后,进程进入一个循环,运行控制台shell,处理僵尸进程,直到shell退出。
这样,第一个进程的创建、初始化和执行就完成了。
其他(例如感想、建议等等)。
本次实验在xv6环境下深入阅读了源码,完成编写指定功能命令的任务,并在此基础上进行了拓展探究。通过阅读xv6中文文档和源码,与课堂知识互相印证,加深了操作系统的理解,收获颇丰。