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

相关文章

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的

深入理解Apache Airflow 调度器(最新推荐)

《深入理解ApacheAirflow调度器(最新推荐)》ApacheAirflow调度器是数据管道管理系统的关键组件,负责编排dag中任务的执行,通过理解调度器的角色和工作方式,正确配置调度器,并... 目录什么是Airflow 调度器?Airflow 调度器工作机制配置Airflow调度器调优及优化建议最

Linux内存泄露的原因排查和解决方案(内存管理方法)

《Linux内存泄露的原因排查和解决方案(内存管理方法)》文章主要介绍了运维团队在Linux处理LB服务内存暴涨、内存报警问题的过程,从发现问题、排查原因到制定解决方案,并从中学习了Linux内存管理... 目录一、问题二、排查过程三、解决方案四、内存管理方法1)linux内存寻址2)Linux分页机制3)

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

python多进程实现数据共享的示例代码

《python多进程实现数据共享的示例代码》本文介绍了Python中多进程实现数据共享的方法,包括使用multiprocessing模块和manager模块这两种方法,具有一定的参考价值,感兴趣的可以... 目录背景进程、进程创建进程间通信 进程间共享数据共享list实践背景 安卓ui自动化框架,使用的是

SpringBoot使用minio进行文件管理的流程步骤

《SpringBoot使用minio进行文件管理的流程步骤》MinIO是一个高性能的对象存储系统,兼容AmazonS3API,该软件设计用于处理非结构化数据,如图片、视频、日志文件以及备份数据等,本文... 目录一、拉取minio镜像二、创建配置文件和上传文件的目录三、启动容器四、浏览器登录 minio五、

IDEA中的Kafka管理神器详解

《IDEA中的Kafka管理神器详解》这款基于IDEA插件实现的Kafka管理工具,能够在本地IDE环境中直接运行,简化了设置流程,为开发者提供了更加紧密集成、高效且直观的Kafka操作体验... 目录免安装:IDEA中的Kafka管理神器!简介安装必要的插件创建 Kafka 连接第一步:创建连接第二步:选