实验报告——Nachos 进程管理与调度

2023-10-24 18:58

本文主要是介绍实验报告——Nachos 进程管理与调度,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


1.      实验目的

(1) 掌握进程管理与同步:实现fork、exec、join 系统调用。

(2) 掌握进程调度:实现优先级调度。

 

2.      实验内容   运用理论课上学习的 fork、exec、waitpid / join 等系统调用的工作原理,在 Nachos 上实现进程的管理、同步与调度。主要包含以下几点: 1. 实现 fork、exec、join系统调用. 2. 实现进程优先级调度. 3. 编写一个简单的 Nachos shell. 

 

3.      具体实现

(1) part1——三种系统调用的实现

i.     fork 的实现 function:创建一个子进程

fork 在 exception.cc 的中断中的操作实际上就是调用了 SysFork(),然后写回返回值,更新 pc,这里略去代码,直接通过注释来讲解 SysFork()的关键代码。



这里调用了 t->fork(),这个函数定义如下:

void Thread::Fork(VoidFunctionPtr func, void *arg)

看其注释:


 所以我们这里分配栈空间,直接调用了 forked(),如下

void forked(int arg)

 {

    {

     kernel->currentThread->RestoreUserState(); //寄存器状态恢复

     kernel->currentThread->space->RestoreState(); //进程空间恢复

     kernel->machine->WriteRegister(2,0); //写返回值

     kernel->machine->PCplusPlus(); //PC 指向下一条指令

     kernel->machine->Run(); //执行用户空间的代码

     }

 }

          此外,为了使每个被创建的进程有不同的进程 ID,我在 thread.cc 增加了静态变量 idinit        在构造函数里添加代码:

idinit++;

    tid = idinit;

 

ii. Exec的实现function:载入并执行可执行程序 exec 的流程实际比较简单,通过注释讲解:


助教提示了执行的部分到 main.cc里学习,我实际上是看到了一个例子,部分代码如下:

if (space->Load(userProgName)) {

 // load the program into the space

 space->Execute(); // run the program

ASSERTNOTREACHED(); // Execute never returns }


 可以看到是执行了 space->Execute();然后我将此借用到了自己代码当中。

 

 iii.      Join 的实现

function:令父进程等待子进程执行结束

join 和上面二者不同,基本功能实在 thread.cc 中实现的:

voidThread::join(int tid)

    Thread *t;

    t=kernel->getThreadByID(tid);                          //获取子进程     if(t)

    {

        Semaphore *sem=newSemaphore("debug",0);             //新信号量         kernel->currentThread->joinSemMap_insert(tid,sem);      //插入joinSemMap

        sem->P()                                            //等待信号量       

kernel->currentThread->joinSemMap_remove(tid);      //移除信号量     }

}

这里有两个操作比较关键:一是对joinSemMap 的操作,这个是通过数据类型map<int,Semaphore>存放线程 ID 和与之对应的信号量信号量,Join 必须通过管理这个 map,才能实现对子进程的信号量的管理。二是 P(),这个函数,实际上主要功能是阻塞当前进程,直到获得信号量。

 


2 part2——进程优先级调度和 shell 的实现

 

 i.与调度相关的代码理解

 本实验中通过对 Scheduler类里的函数 FindNextToRun()进行修改来实现优先级调度的方

法,这个函数的基本功能就是调整优先级,找到下一要运行的进程,并修改相关的进程状态。其具体实现后文结合代码详细介绍。

 我们可以发现在 thread.cc中有这样一个函数 Yield(),私以为这个函数相当重要,所以着重讲一下,可以看到,在这个函数中调用了上面说的 FindNextToRun()去找下一个要运行的进程,然后寻找成功,就会对当前进程执行 ReadyToRun(),即把当前进程添加到准备队列中,并且对

nextThread 执行 run(),这个函数里保留了当前进程的上下文,然后 nextThread 的状态被置为 running,读取其上下文开始执行,此不做具体分析。


至于调度的时机,显现要找对 Yield()的调用,这个我们在 interrupt.cc中可以找到。

Interrupt::OneTick()

然后看相关注释可以得知,OneTick()执行的时机有两种情况:1.中断被 re-enabled 时  2.每当一条用户指令被执行,所以这也就是调度的时机。

 

ii.优先级调度的实现

 

 优先级调度是一种允许抢占的进程调度方式,每个进程有一个优先级,通过优先级判断谁先执行,为了避免死锁和饿死,这个优先级是动态变化的,其基本逻辑是,当前进程会随着执行时间变低,而等待队列的进程优先级别会不断增加,这样就确保每个进程都会被执行到。

 据此,本实验采用的优先级调整公式为:

         #调整当前进程优先级计算公式——priority= priority - (当前系统时间- lastSwitchTick ) /100      #调整所有就绪进程的优先级计算公式—— priority =priority + AdaptPace

             下面通过注释的方式去分析优先级调度的代码,理解优先级调度算法的流程:

Thread *Scheduler::FindNextToRun()

{

    。。。。。。

    if (readyList->IsEmpty())//如果没有就绪进程,显然返回NULL

    {

        returnNULL;

    }     else {

        //更新当前线程的优先级

        Thread * t;

        t=kernel->currentThread;

        t->priority-=(kernel->stats->totalTicks-lastSwitchTick)/100;         t->setPriority(t->priority);//依照计算好的优先级进行设置

        //刷新所有就绪线程的优先级

        flushPriority();//下面会贴出         Print(); //打印当前进程状态

        // 计算最近调度与现在的间隔

        inttime=kernel->stats->totalTicks-lastSwitchTick;

        // 间隔太小,返回 NULL (避免过分频繁地调度)

        if(time<MinSwitchPace)              returnNULL;

        // 就绪队列为空,返回 NULL (没有就绪进程可以调度)

        if(readyList->IsEmpty())              returnNULL;

        // 找到优先级最高的就绪态进程t

        Thread *t1=readyList->Front();

        if(threadCompareByPriority(t,t1)==1)//把当前进程和就绪队列的第//一个进程进行比较,决定是否切 //

        {

            lastSwitchTick = kernel->stats->totalTicks;

        }//因为将要进行一次进程切换,所以上一次切换时间要用本次切换时间代替

        else

            returnNULL;

 

        return readyList->RemoveFront();//把就绪就列里的第一个进程移除

                                        //并且返回这个进程,即此为下一个

                                        //要调度的进程

       }}


  这里是刷新就绪队列进程优先级的代码,原理和公式上面已经讲过,没太多可分析的,作为关键代码贴出:

iii.shell 的简单实现

shell 的逻辑比较简单,首先是从控制台接受指令,通过字符串处理分解命令,得到一个序列,对于这个命令序列,依次创建进行子进程,装载相应的程序代码,这里父进程要等待所有创建的子进程完成之后再继续执行。很显然,只要对子进程调用Exec(),对父进程调用 Join()即可实现相应功能,下面是关键代码,可以看到,十分简单。

iv.测试运行结果

 

  按照上面代码,这里 2进程最先创建,但是最后完成输出,而不是第一个完成,从这里其实就可以看到子进程不是依次执行的,而是有一个调度在里面,至于2 进程最后完成,这是由进程本身的耗时较长导致的(与add 等相比)。  

                  

可以看到,刚开始只有 main 进程,然后创建子进程,执行 2,后来又变成 3 个进程,这里没有显示,第三个进程装载了 add 并执行,对应 for 循环里子进程被依次创建。然后我们观察进程的优先级pri,就绪队列里的进程,其优先级以2 的幅度增加,而执行中的进行优先级在不断降低,直到就绪队列优先级最高的进程比当前进程高,进程切换就发生了。

 

 v.part2 中遇到的问题

 1.刚开始没有仔细思考完成了 shell 的编写,大致如下:

 这样的 shell 是有问题的,可以看到,父进程 main创建了第一个子进程后,就执行 Join(),这样实际上要等第一个进程完成以后才能创建第二个子进程,如此优先级调度完全没有意义了。所以就修改成了上述的代码。

 

2.关于检查的时候一个问题

问题大致:查看进程切换,为什么执行到中间,main进程消失不见了?

这个问题我之前没有注意到,所以一时间就没有回答上来,而且我注意力在“中间”这个词,理解为还在第一个 for 循环中,没有意识到这个时候进程创建已经完成了,所以回答得很不好。

现在想来实际上比较简单,是因为子进程的创建已经完成了,父进程就执行 Join(),这和时候父进程 main 会阻塞自己,把自己从就绪队列中移出去。

回去之后我又仔细的看了代码,可以发现在join()里调用了 p(),转到 synch.cc 中 p()的定义里,发现

currentThread->Sleep(FALSE);

然后在 sleep()里有:

status = BLOCKED;

这样就很清楚了。只怪自己实验时不够细致,没有意识到考虑到这个问题,然后一问就蒙圈了。

 

 

 


这篇关于实验报告——Nachos 进程管理与调度的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/277132

相关文章

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

搭建Kafka+zookeeper集群调度

前言 硬件环境 172.18.0.5        kafkazk1        Kafka+zookeeper                Kafka Broker集群 172.18.0.6        kafkazk2        Kafka+zookeeper                Kafka Broker集群 172.18.0.7        kafkazk3

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

安全管理体系化的智慧油站开源了。

AI视频监控平台简介 AI视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上进行简单的操作,就可以实现全视频的接入及布控。摄像头管理模块用于多种终端设备、智能设备的接入及管理。平台支持包括摄像头等终端感知设备接入,为整个平台提

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Sentinel 高可用流量管理框架

Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。 Sentinel 具有以下特性: 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应

java 进程 返回值

实现 Callable 接口 与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。 public class MyCallable implements Callable<Integer> {public Integer call() {return 123;}} public static void main(String[] args

C#关闭指定时间段的Excel进程的方法

private DateTime beforeTime;            //Excel启动之前时间          private DateTime afterTime;               //Excel启动之后时间          //举例          beforeTime = DateTime.Now;          Excel.Applicat

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(