实验报告——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

相关文章

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

Linux中的进程间通信之匿名管道解读

《Linux中的进程间通信之匿名管道解读》:本文主要介绍Linux中的进程间通信之匿名管道解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、基本概念二、管道1、温故知新2、实现方式3、匿名管道(一)管道中的四种情况(二)管道的特性总结一、基本概念我们知道多

nvm如何切换与管理node版本

《nvm如何切换与管理node版本》:本文主要介绍nvm如何切换与管理node版本问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录nvm切换与管理node版本nvm安装nvm常用命令总结nvm切换与管理node版本nvm适用于多项目同时开发,然后项目适配no

Linux进程终止的N种方式详解

《Linux进程终止的N种方式详解》进程终止是操作系统中,进程的一个重要阶段,他标志着进程生命周期的结束,下面小编为大家整理了一些常见的Linux进程终止方式,大家可以根据需求选择... 目录前言一、进程终止的概念二、进程终止的场景三、进程终止的实现3.1 程序退出码3.2 运行完毕结果正常3.3 运行完毕

Windows命令之tasklist命令用法详解(Windows查看进程)

《Windows命令之tasklist命令用法详解(Windows查看进程)》tasklist命令显示本地计算机或远程计算机上当前正在运行的进程列表,命令结合筛选器一起使用,可以按照我们的需求进行过滤... 目录命令帮助1、基本使用2、执行原理2.1、tasklist命令无法使用3、筛选器3.1、根据PID

Redis实现RBAC权限管理

《Redis实现RBAC权限管理》本文主要介绍了Redis实现RBAC权限管理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1. 什么是 RBAC?2. 为什么使用 Redis 实现 RBAC?3. 设计 RBAC 数据结构

linux本机进程间通信之UDS详解

《linux本机进程间通信之UDS详解》文章介绍了Unix域套接字(UDS)的使用方法,这是一种在同一台主机上不同进程间通信的方式,UDS支持三种套接字类型:SOCK_STREAM、SOCK_DGRA... 目录基础概念本机进程间通信socket实现AF_INET数据收发示意图AF_Unix数据收发流程图A

mac安装nvm(node.js)多版本管理实践步骤

《mac安装nvm(node.js)多版本管理实践步骤》:本文主要介绍mac安装nvm(node.js)多版本管理的相关资料,NVM是一个用于管理多个Node.js版本的命令行工具,它允许开发者在... 目录NVM功能简介MAC安装实践一、下载nvm二、安装nvm三、安装node.js总结NVM功能简介N

Python中多线程和多进程的基本用法详解

《Python中多线程和多进程的基本用法详解》这篇文章介绍了Python中多线程和多进程的相关知识,包括并发编程的优势,多线程和多进程的概念、适用场景、示例代码,线程池和进程池的使用,以及如何选择合适... 目录引言一、并发编程的主要优势二、python的多线程(Threading)1. 什么是多线程?2.

springboot的调度服务与异步服务使用详解

《springboot的调度服务与异步服务使用详解》本文主要介绍了Java的ScheduledExecutorService接口和SpringBoot中如何使用调度线程池,包括核心参数、创建方式、自定... 目录1.调度服务1.1.JDK之ScheduledExecutorService1.2.spring