本次Lab针对的内容了解线程的调度机制,每个时刻每个CPU上只能有一个线程运行,为了提高系统的吞吐量和工作效率,必须合理的安排每个线程上CPU的顺序和时间。在实用中,多数为几种调度策略结合使用的。包括是否抢占、动态优先级还是静态优先级、是否分时等等。
调度策略的实施通常需要在PCB中增加相应的修改,需要在其中增加相应的数据结构记录执行的状态和统计信息。
【用简洁的语言描述本次lab的主要内容;阐述本次lab中涉及到的重要的概念,技术,原理等,以及其他你认为的最重要的知识点。这一部分主要是看大家对lab的总体的理解。
要求:简洁,不需要面面俱到,把重要的知识点阐述清楚即可。】
|
Exercise1 |
Exercise2 |
Exercise3 |
完成情况 |
Y |
Y |
Y |
Exercise1
了解Linux或Windows中采用的进程/线程调度算法。
我调研的是Linux系统中的进程/线程调度算法。Linux的内核有三种调度策略:
1. SCHED_OTHER分时调度策略。
2. SCHED_FIFO实时调度策略,先到先服务。一旦占用CPU则一直运行直到有更高优先级任务达到或自己放弃。
3. SCHED_RR实时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列。放在队尾保证了所有具有相同优先级的RR任务的调度公平。
Linux内核将进程分成两个级别:普通进程和实时进程。实时进程的优先级都高于普通进,除此之外,它们的调度策略也有所不同。
如果一个进程有实时需求(它是一个实时进程),则只要它是可执行状态的,内核就一直让它执行,以尽可能地满足它对CPU的需求,直到它完成所需要做的事情,然后睡眠或退出(变成非可执行状态)。而如果有多个实时进程都处于可执行状态,则内核会先满足优先级最高的实时进程对CPU的需要,直到它变为非课执行状态。于是,只要有高优先级的实时进程一直处于可执行状态,低优先级的实时进程就一直不能得到CPU;只要一直有实时进程处于可执行状态,普通进程就一直不能得到CPU。后来内核进行了一些改动,限定了以sched_rt_period_us为周期的时间内,实时进程最多只能运行sched_rt_runtime_us这样给普通进程留下了一点点能够得到执行的机会。
在Linux 2.6版本中,内核调度曾有几度变迁,其基本思想是:1)提高实时进程调度相应比;2)普通进程调度体现“完全公平这个思想”。为实时进程(SCHED_FIFO/SCHED_RR)提供O(1)时间复杂度的调度算法,同时,为了兼顾“完全公平”这一设计思路,设计了CFS(Complete Fair Schedule)调度器,为普通进程提供满足公平性为原则的O(lgn)时间复杂度的调度算法.
Exercise2
阅读下列源代码,理解Nachos现有的线程调度算法。
code/threads/scheduler.h和code/threads/scheduler.cc
code/threads/switch.s
code/machine/timer.h和code/machine/timer.cc
scheduler.h和scheduler.cc
scheduler.h定义了线程调度用到的一些数据结构,主要包括了一个就绪队列的列表,还有一些关于操作就绪队列的方法,比如找到一个可以运行的线程,运行一个线程,将一个线程设置为Ready并加入就绪队列的末尾,打印就绪队列的内容等。
scheduler.cc中实现了这些函数,值得一提的是Run()函数。它的执行步骤如下:
1. 保存当前线程为旧线程;
2. 如果是用户程序,则保存当前CPU寄存器的状态;
3. 检查当前线程是否有存在栈溢出;
4. 将nextThread的状态设置为运行态,并作为currentThread运行线程;
5. 切换到nextThread线程运行;
6. 如果有threadToBeDestroyed线程,则销毁它,并释放它所占用的空间;
7. 如果是用户程序,恢复当前CPU寄存器的状态。
从scheduler可以看出来,现有的Nachos的调度只是简单的从readylist中取出第一个已经就绪的线程。
switch.s
这里定义了线程交换的有关内容,线程的上下文切换部分涉及到寄存器的分配,因此这部分内容直接依赖于机器。Nachos目前支持四种类型的机器架构。文件中定义了对这四种机器的两个操作,分别是ThreadRoot()和SWITCH()。
Nachos中,除了main线程外,所有其他的线程都是从ThreadRoot入口运行的,它的语法是ThreadRoot(int InitialPC,int InitialArg,int WhenDonePC,intStartupPC),其中InitialPC指明新生成线程的入口函数地址,InitialArg是该入口函数的参数,StartupPC是在运行该线程时需要作的一些初始化工作;而WhenDonePC是该线程运行结束时需要作的一些后续工作。在Nachos源代码中,没有任何一个函数和方法显式地调用ThreadRoot函数,ThreadRoot函数只有在被切换时才被调用到。这几个参数是在生成线程分配栈空间时被初始化。
Switch函数则相对简单,首先保存了原运行线程的状态,其次恢复新运行线程的状态,最后在新运行的线程的栈空间上运行新线程。
timer.h和timer.cc
这里定义了一个模拟硬件时间的数据结构。硬件计时器每隔一定的时间产生一个CPU中断,我们可以利用这个中断实现分时。
Timer(VoidFunctionPtr timerHandler, int callArg, bool doRandom)初始化这个模拟时钟。参数timerHandler表示时钟中断处理函数,callArg是timerHandler函数的参数,doRandom表示是否允许中断随机发生,而不是预先制定好。
变量初始化完成后,该函数把一个时钟中断插入等待处理的中断队列,当时钟中断时刻到来时,调用TimerHandler函数,调用它的TimerExpired方法,该方法将新的时钟中断插入等待处理中断队列中,然后再调用真正的时钟中断处理函数。
TimerExpired()该函数将新的时钟中断插入等待处理中断队列中,然后再调用真正的时钟中断处理函数,这样Nachos就可以定时收到时钟中断。
TimerOfNextInterrupt()该函数返回距离下一次中断发生所需要的时钟周期数。
Exercise3
扩展线程调度算法,实现基于优先级的抢占式调度算法。
设计思路:
要实现优先级,首先需要在线程的PCB结构中增加一个priority变量,以表示优先级,之后要定义优先级数和优先级大小的关系。因为看到list.cc中有一个SortedInsert(void*item,int sortKey)方法,该方法根据sortKey的从小到大向list插入元素。于是我定义优先数为0的线程优先级最高,优先数8的优先级最低。每次向readyList插入线程的时候,调用SortedInsert()函数,将优先级高的插入队首。
因为我设置的是静态优先级算法,当有新线程加入时,可能新加入的线程可能会比原来线程的优先级高,所以这里需要让当前线程退出CPU,重新选择一次优先级最高的线程。(而不在是在Threadtest测试代码中线程自己主动退出)。
还有一些修改在下面部分展示。
对代码进行的修改:
Thread.h和Thread.cc中增加int类型的priority变量,范围为0到8。0的优先级最高,8的最低。并定义getPriority()方法提供对该私有变量的访问。
并且对Yield()方法进行修改,使得现将当前运行的线程加入就绪队列,然后在选择新的线程,防止出现当前运行线程依旧是优先级最高的线程而没有调度到这个线程的情况。
在scheduler.cc的ReadyToRun()方法中,不再是每次都将线程加入就绪队列的最末尾,而是根据线程的优先级,插入就绪队列中合适的位置。
有两种情况会调用到ReadyToRun():
1. 新线程加入
2. currentThread线程退出时,调用了Yield()方法。
第一种情况下需要当前线程停止运行,重新在readyList中找优先级最高的线程。第二种情况下不能再调用Yield()方法,否则会出现两个方法相互循环调用的情况。所以增加以一些判断。
修改ThreadTest.cc
将SimpleThread()中的currentThread->Yield()注释掉,不再让当前线程主动让出CPU。并修改输出,使其输出线程的优先级。
编写测试函数,生成三个线程,分别调用SimpleThread方法,观察执行的先后顺序,因为线程的优先级从大到小如下:线程2、线程3、线程1.。所以运行的顺序应为2->3->1。观察结果。
为了进一步验证抢占的有效性,我又修改了ThreadTest.cc,使确保在线程1运行中线程2才生成,观察是否抢占了线程1。
在PriorityScheduler()中,只生成了一个线程,调用了p1()。在p1()中有五次循环输出,其中第一次的时候生成一个优先级更高的thread2。并调度执行p2()函数。
p2()同样是五次循环,第一次的时候又生成一个thread3线程,thread3线程的优先级在两个线程的中间。thread3执行SimpleThread()方法,简单的循环输出五次,其中SimpleThread()方法把currentThread->Yield()给注释掉,不再让线程自己主动让出CPU。
执行结果如图:
可见,Thread2、Thread3成功的抢占了Thread1得以执行。
再一次修改Thread3的线程优先级,使得Thread3>Thread2>Thread1.执行结果如下:
可见,根据优先级的抢占依然成功。
【对于阅读代码类的exercise,请对其中你认为重要的部分(比如某文件,或某个类、或某个变量、或某个函数……)做出说明。
对于要编程实现的exercise,请对你增加或修改的内容作出说明。如果增加或修改了某个类,请写出这个类写在那个文件中,它的功能是什么,你自己添加或修改的成员变量以及成员函数有哪些,它们的功能是什么;特别的,对于复杂的函数,请说明你的实现方法。不需要贴具体的实现代码。
要求:表述清楚即可, 可采用图表来辅助说明,不需要贴代码】
对操作系统中断处理过程的不熟悉,用中断完成该实验的话会更理想,但是由于对中断本身的及Nachos实现中断的代码不熟悉。没有在实验中体现出来中断。这个还需要继续看资料和代码。
【描述你在实习过程中遇到的困难,是与实习直接相关的技术方面的难题。突出说明你是如何攻克这些难关的。
要求:只需写一下有较深体会的困难。如果觉得整个过程都比较简单的话此部分可不用写。】
相信通过操作系统课的实习,我写C++代码的能力会有显著的提升,以前都是写Java的web,对C++忘的差不多了,通过这次的实验,可以捡回来不少。
另一个就是对线程的调度了解更加的透彻,相信可以通过以后的实验,对操作系统的各种机制有更深入的了解。
【自己的收获,任何关于实习的感想,可以是技术方面的或非技术方面的,可随意发挥。
要求:内容不限,体裁不限,字数不限,多多益善,有感而发。】
目前来讲,我觉得一切还都挺好的,老师讲的很不错,进度也可以,没有什么建议。
【请写下你认为课程需要改进的地方,任何方面,比如进度安排、难易程度、课堂讲解、考核方式、题目设置……甚至如果你认为源代码哪里写得不好也欢迎提出。
各位同学反馈的信息对课程建设会有极大帮助。】
[1] kouu’s home linux进程调度浅析
http://hi.baidu.com/_kouu/item/38c81042455c97d2c1a592d9
[2] 佚名 Nachos中文教程
http://wenku.baidu.com/link?url=1rGnypg8Hq6-43gAvuIYPWyVlPLZ0S_XNEXQJ-2ShqPPg3n2bqWvQgRYC8PdVXLmr66e9GpC2nCSbE1ofkgcT6aASWqVklMWUaBuZNSmXDy
【我们希望大家不要使用复制粘贴来拼凑你的报告。详细地列出你在完成lab的过程中引用的书籍,网站,讲义,包括你咨询过的大牛们。
要求:诚实,格式尽量按照论文的要求,请参考“论文参考文献格式.doc”】