实验目的

(1)、掌握计算机操作系统管理进程、处理机、存储器、文件系统的基本方法。

(2)、了解进程的创建、撤消和运行,进程并发执行;自行设计解决哲学家就餐问题的并发线程,了解线程(进程)调度方法;掌握内存空间的分配与回收的基本原理;通过模拟文件管理的工作过程,了解文件操作命令的实质。

(3)、了解现代计算机操作系统的工作原理,具有初步分析、设计操作系统的能力。

(4)、通过在计算机上编程实现操作系统中的各种管理功能,在系统程序设计能力方面得到提升。

实验要求

(1)题目1:

试解释一下yield函数、scheduler函数和sched函数的用途。

结合书本,确定xv6使用的是哪种调度算法。给出你的理由(通过分析代码证明你的观点)。

(proc.c L268) 有一个疑问,似乎每次xv6都是从进程表开头开始查找Runnable的进程。如果刚从CPU切换下来的进程恰好是进程表的第一个PCB,会不会调度器永远都选择它进行调度?

(2)题目2:

在xv6界面上按Ctrl+P,你会看到终端上会显示当前系统中的进程。

仿照相关代码,实现以下功能:在xv6界面上按Ctrl+R,打印出当前系统中sleeping的进程,并确定它们对应的等待队列是什么(chan/waiting channel)。

实验内容

题目1

试解释一下yield函数、scheduler函数和sched函数的用途。

结合书本,确定xv6使用的是哪种调度算法。给出你的理由(通过分析代码证明你的观点)。

(proc.c L268) 有一个疑问,似乎每次xv6都是从进程表开头开始查找Runnable的进程。如果刚从CPU切换下来的进程恰好是进程表的第一个PCB,会不会调度器永远都选择它进行调度?

yield函数、scheduler函数和sched函数都是xv6操作系统中proc.c文件下用于实现进程调度的函数。

yield函数

当一个进程使用完时间片后,会中断并调用yield函数来让出CPU给新的进程。

yield函数首先获取进程表锁,并将进程状态设为可运行,以便下次遍历时可以被唤醒。

之后执行sched函数,准备将CPU切换到scheduler context。最后释放进程表锁。

sched函数

sched()是切换至CPU context,并在切换context之前,进行一系列判断,以避免出现冲突的函数。

切换到scheduler必须:

1.持有p->lock并且已更改proc->state。

2.保存和恢复intena,因为intena是这个内核线程的属性,而不是这个CPU的属性。

检查完后调用scheduler()。

scheduler函数

scheduler函数是xv6中的核心调度器函数,用于根据选择的调度算法从就绪队列中选择下一个要运行的进程。

scheduler函数的实现方式取决于所选的调度算法。

当CPU初始化之后,即调用scheduler(),循环从进程队列中选择一个进程执行。

计划程序永远不会返回。它循环执行以下操作:

1.选择要运行的进程。

2.swtch开始运行该进程。

3.最终该进程会转移控制权,通过swtch返回到调度程序。

1:调度算法

为了确定采用了哪种调度算法,我们需要分析scheduler函数,代码如下:

首先,定义一个指向当前CPU结构的指针c,用于跟踪当前CPU上正在运行的进程。然后,将c->proc设置为0,以表示当前没有进程在运行。

在每次执行一个进程之前,开启CPU的中断。通过确保设备可以中断,来避免死锁。

循环遍历存放进程的proc数组,从中选择状态为RUNNABLE的进程来运行。

当找到一个RUNNABLE的进程时,首先获得该进程的锁,以确保它的状态不会在此期间被其他进程修改。

然后,将该进程的状态设置为RUNNING,表示它正在运行。

将该进程的指针赋值给c->proc,表示该进程正在当前CPU上运行。

通过调用swtch函数,将CPU的上下文切换到该进程的上下文,swtch函数负责保存当前CPU的上下文,并将控制权转移到指定进程的上下文。

当该进程运行结束并返回时, c->proc将被设置为0表示当前没有进程在运行,并且释放锁。

这是一个简单的时间轮转调度算法。它遍历所有的进程,并按顺序选择每个RUNNABLE进程运行一定时间,然后切换到下一个RUNNABLE进程。如果进程的运行时间达到了一定的限制,也会强制切换到下一个进程。

2:调度问题

(proc.c L268) 有一个疑问,似乎每次xv6都是从进程表开头开始查找Runnable的进程。如果刚从CPU切换下来的进程恰好是进程表的第一个PCB,会不会调度器永远都选择它进行调度?

不会,由前面for循环部分的代码可知,在执行完第一个进程后,并不会跳出for循环,而是接着遍历完整个进程表,寻找下一个RUNAABLE状态,故不会出现这种情况。

题目2

在xv6界面上按Ctrl+P,你会看到终端上会显示当前系统中的进程。

仿照相关代码,实现以下功能:在xv6界面上按Ctrl+R,打印出当前系统中sleeping的进程,并确定它们对应的等待队列是什么(chan/waiting channel)。

首先找到这个代码的出处,在 xv6 的 console.c 文件中,函数名为 consoleintr(),如下图:

为了实现这个功能,我们首先要识别我们输入了Ctrl+R,因此,增加一栏case,如下图所示:

其中print_sleeping_procs()是我们实现这个功能的函数

其中我们可以在proc.c文件中找到procdump()函数,故我们仿照其在proc.c文件中添加procdump_sleeping函数,遍历整个进程表,找到所有state为SLEEPING的进程,并将它们的信息打印出来。

在proc.h中找到进程块的代码

我们发现,如果为SLEEPING状态,那么等待队列的名字会保存在chan上,因此我们如果想知道对应的等待队列,只需输出他的chan即可。

print_sleeping_procs函数代码如下图所示

遍历每一个进程,若状态为SLEEPING,则输出他的chan。

在defs.h中添加函数声明

Ctrl+S保存后在xv6目录下输入make qemu编译运行,按下ctrl+p,结果如下:

那么按下Ctrl+R后显示的结果就应该包含这两个进程,即:

发现没有输出等待队列,上网查询资料得知:

在xv6中,如果一个进程处于睡眠状态,它的chan成员变量会被设置为一个标识睡眠原因的字符串。如果进程不是因为睡眠而阻塞,chan会被设置为空字符串。

因此,如果打印的进程的chan是空字符串,说明该进程当前不是因为睡眠而阻塞。

将结果强行转换:

这时编译运行后结果为:

实验小结

通过本次实验,对xv6的调度算法有了一定理解,结合最近的课程,更加熟悉了调度算法。在实验过程中遇到了一些问题,比如一开始ctrl+p无法打印,原因是与vscode快捷键冲突;又比如print_sleeping_procs函数编写后一直无法成功编译,查阅资料后得知需要在defs.h中添加声明;再还有就是实现函数后输出与预测不同的问题,通过强行转换结果后成功输出。

(by 归忆)

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐