注:由于老师一直没发实验报告模板,所以直到今天才开始写,本次实验是延续上一次实验所做,并会对上一次实验中的某些过程进行修改(不理解的同学可以看一下上一篇博客)!!!
实验目的
(1)通过阅读相关源码,掌握NachOS调度的数据结构和实现过程;
(2)对NachOS线程描述进行完善,增加关于调度的内容;
(3)掌握NachOS线程调度的算法。
实验内容
(1)在NachOS线程描述中增加调度优先级的数据成员,并完善就绪队列管理的成员方法;
(2)实现基于优先级的FCFS调度。
实验过程
这次试验也是看了许多网上的方法,最后根据自己的能力写了一个自己能理解的方法,那就是:线程在进入后序队列时进行修改,让优先级高的线程排在优先级低的线程前面。
线程被调进就绪队列是在Yield函数(位置:thread.cc)中被调用:
kernel->scheduler->ReadyToRun(this); //将当前线程加入就绪队列
void
Thread::Yield ()
{
Thread *nextThread;
IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff); //关中断
ASSERT(this == kernel->currentThread);
DEBUG(dbgThread, "Yielding thread: " << name);
nextThread = kernel->scheduler->FindNextToRun();//找到下一个要运行的线程并使nextThrad指向它
if (nextThread != NULL) {
kernel->scheduler->ReadyToRun(this); //将当前线程加入就绪队列
kernel->scheduler->Run(nextThread, FALSE); //运行下一个线程
}
(void) kernel->interrupt->SetLevel(oldLevel);
}
所以需要转到scheduler.cc中查看:scheduler->ReadyToRun
void
Scheduler::ReadyToRun (Thread *thread)
{
ASSERT(kernel->interrupt->getLevel() == IntOff);
DEBUG(dbgThread, "Putting thread on ready list: " << thread->getName());
thread->setStatus(READY); //将线程状态置为就绪态
readyList->Append(thread); //将线程加入就绪队列的队尾
}
所我们就可以参照 Append() (位置:list.cc)方法的结构,自己写一个根据优先级插入的方法,线程在进入后序队列时,让优先级高的线程排在优先级低的线程前面。对于 Append() 函数的结构大家就自己去查看就好,下面就直接阐述实现的过程。
实现过程:
(1)因为Append() 函数是list.cc中的函数,所以我们自己写插入函数的时候,也应该是在list.cc中实现,在这之前需要在list.h中声明一下:
void Newsort(T item,int priority);
template <class T>
class List {
public:
List(); // initialize the list
virtual ~List(); // de-allocate the list
virtual void Prepend(T item);// Put item at the beginning of the list
virtual void Append(T item); // Put item at the end of the list
//----------------------------------------------------------------------------------------修改处--调度实验
void Newsort(T item,int priority); //创建一个方法 对要传入就绪队列的线程排序插入(根据优先级的高低)
//----------------------------------------------------------------------------------------
T Front() { return first->item; }
(2)然后就可以在list.cc中实现这个方法了(在这个函数中:priority值越大,则优先级越高):
//------------------------------------------------------------------修改处---实现list.h中的方法
template <class T>
void
List<T>::Newsort(T item, int priority) // item-->当前线程 priority-->当前线程的优先级
{
ListElement<T> *element = new ListElement<T>(item); //创建的一个以 item 为核心的节点
ASSERT(!IsInList(item)); //当要加入的线程已经在队列中时就打断运行,并输出报错信息
if(IsEmpty()) //当队列为空时,就直接将当前的 item 放在里面
{
Prepend(item); // 将其放在最后面 ,因为是空的,所以最后最前都是一样的
}
else
{
//当不为空时,则按照优先级值的大小进行插入 (类似于学数据结构时的链表的插入)
ListElement<T> *prev = first; //定义一个指针指向头节点first
ListElement<T> *ptr = NULL; //定义一个指针先放空, 用于后面插入的时候,记录前一个节点的位置
while(prev != NULL) //即当该节点不为空时,节点会不断向后移动,当移到最后一个节点的后面时就为空
{
if(priority > prev->item->priority) //当当前线程的优先级大于指针指向的节点的优先级时
{
element->next = prev; //当前线程的尾指针指向 prev
if(ptr == NULL)
first = element;//当当前队列中:当前线程的优先级是最高时,头节点直接指向它
else
ptr->next = element;//如果ptr不为空,则ptr记录了上一个节点的位置,
//则将上一个节点的尾指针指向当前线程
numInList++; //队列中的元素个数加1
break;
}
ptr = prev; //ptr记录当前指针的位置
prev = prev->next; // prev向下移动一个节点
}
if(ptr == last) //如果ptr记录的已经为最后一个节点了,则说明当前线程的优先级是队列中最低的
{ //直接将它插入到最后面
Append(item);
}
}
}
//-------------------------------------------------------------------------------------------
(3)方法已经实现了,那我们就可以使用这个方法了,方法应该要在哪里使用呢?之前的 Append() 函数是在scheduler->ReadyToRun(位置:scheduler.cc)中使用的,所以新创建的这个方法也在scheduler->ReadyToRun中使用就可以了,当然对其进行了一定的修改:
void
Scheduler::ReadyToRun (Thread *thread)
{
ASSERT(kernel->interrupt->getLevel() == IntOff);
DEBUG(dbgThread, "Putting thread on ready list: " << thread->getName());
thread->setStatus(READY); //将线程状态置为就绪态
//------------------------------------------------------------------------------------------ -修改处
// readyList->Append(thread); //将线程加入就绪队列的队尾-----------将原来的这句注释掉
if(readyList->IsEmpty()) //如果就绪队列是空时
{ // 直接将当前线程加队尾(这里队尾和队头都是一样的,因为里面是空的)
readyList->Append(thread);
return;
}
readyList->Newsort(thread, thread->priority); //不为空时调用自己写的函数,参数为:当前线程、优先级值
//------------------------------------------------------------------------------------------------
}
注:当我们这些条件都准备好后,就可以对thread.cc进行修改了。因为这次实验是在上一次实验基础做的,所以对于上一次实验的部分不在阐述,重新修改的地方将会用 //*************** 指明。
(4)首先对原构造函数Thread::Thread(char* threadName) (位置:thread.cc)
进行修改,将main线程和postal worker线程的优先级进行置为最高10000;将ping线程的优先级置为-1;将里面的输出语句注释掉,输出语句我们放到测试函数中去实现。
Thread::Thread(char* threadName)
{
//--------------------------------------------------------------------------------------修改处
if(++threadNUM>=MAX_SIZE){ //当线程数大于规定的最大线程数时,输出最大线程数,并打断程序执行
cout<<"最大线程数:"<<MAX_SIZE<<endl;
ASSERT(threadNUM<=MAX_SIZE);
}
int i=0;
for(i=0;i<MAX_SIZE;i++){ //每次创建一个新的线程,这里便要从0开始检查,直到到达未被使用的线程号为止
if(Tstatus[i]==0){ //如果线程号未被使用,便执行以下操作
this->tid=i+1; // 给线程赋上一个id号, 因为是从0开始计算,所id号需要加1
srand(time(0)+i); //设置一个随机种子
this->priority=rand()%(100); //给每个线程附加一个随机的优先级值
Tstatus[i]=1; //将该线程号状态置为1,表示已经被使用了
break;
}
}
//************************************************************************************************修改处
if(strcmp(threadName,"main")==0||strcmp(threadName,"postal worker")==0)
this->priority =10000;
if(strcmp(threadName,"ping")==0)
this->priority=-1;
//***********************************************************************************************
name = threadName;
stackTop = NULL;
stack = NULL;
status = JUST_CREATED;
for (int i = 0; i < MachineStateSize; i++) {
machineState[i] = NULL; // not strictly necessary, since
// new thread ignores contents
// of machine registers
}
space = NULL;
//*****************************************************************************************************修改处---将上一次实验的修改处注释掉
//--------------------------------------------------------------------------修改处
// cout<<"线程名:"<<this->getName()<<" 线程id:"<<this->getid()<<" 线程优先级:"<<this->priority<<endl;
//输出线程一系列参数:名字、id、优先级。
//-------------------------------------------------------------------------------
}
(5)对析构函数Thread::~Thread()(位置:thread.cc)进行修改:
Thread::~Thread()
{
DEBUG(dbgThread, "Deleting thread: " << name);
//---------------------------------------------------------------------------修改处
Tstatus[tid-1]=0; //将该线程号的状态置0,即被释放了之后,该线程号可以被其他线程使用
threadNUM--; // 将现在线程的总数减1
//-----------------------------------------------------------------------------
ASSERT(this != kernel->currentThread);
if (stack != NULL)
DeallocBoundedArray((char *) stack, StackSize * sizeof(int));
}
(6)对测试函数Thread::SelfTest()(位置:thread.cc)进行修改:
void
Thread::SelfTest()
{
DEBUG(dbgThread, "Entering Thread::SelfTest");
//-----------------------------------------------------------------------修改处--以下部分为原代码被注释掉
/* Thread *t = new Thread("forked thread");
t->Fork((VoidFunctionPtr) SimpleThread, (void *) 1);
kernel->currentThread->Yield();
SimpleThread(0);
*/
//************************************************************修改处---将第一次修改的部分注释掉
//------------------------------------------------------------------修改处-
/* Thread *t[124]; //定义一个大小为124的线程数组,因为在程序运行中会产生另外4个线程,1个main线程
// 1个postal worker 线程,2个ping线程,所以这里创建124个线程正好满足128个线程
for(int i = 0;i < 124;i++){
t[i] = new Thread("线程");
} */
//------------------------------------------------------------------------
//******************************************************************修改处---添加本次的修改处
Thread *t[5];
for(int i=0;i<5;i++){
t[i]= new Thread("New Thread");
cout<<"线程名: "<< t[i]->getName()<<" 线程ID: "<< t[i]->getid()<<" 优先级: "<<t[i]->getPriority()<<endl;
if(i==4) cout<<endl;
t[i]->Fork((VoidFunctionPtr)SimpleThread,(void *)t[i]);
}
}
(7)注:我们在修改的测试函数中SimpleThread,(void *)t[i]), 参数变为了 t[i] ,所以下面我们修改SimpleThread (int which) (位置:thread.cc)函数时需要对其参数一并修改:
static void
SimpleThread(Thread *t) //将这里的参数类型变为 Thread类型
{
int num;
for (num = 0; num < 2; num++) { //这里就只运行2次便于观察
cout<<"线程名: "<<t->getName()<<" 线程ID:"<<t->getid()<<" 优先级: "<<t->getPriority()<<" lopped: "<<num<<" times"<<endl;
kernel->currentThread->Yield();
}
}
(8)改到这里就以基本完成了,但你这个时候去运行就会发现会报错,这是因为priority是一个私有变量,无法传递到某些类中去使用,所以就必须将它变为公开的变量,这就需要到 thread.h 中进行修改(这里将 tid 一并转移到了公开区域),同时创建了一个getPriority() 函数:
class Thread {
private:
// NOTE: DO NOT CHANGE the order of these first two members.
// THEY MUST be in this position for SWITCH to work.
int *stackTop; // the current stack pointer
void *machineState[MachineStateSize]; // all registers except for stackTop
//*******************************************************************将私有变量 priority和 tid 注释掉
//------------------------------------------------------------------------------修改处
// int tid; //定义每一个线程的id号
// int priority; //定义每个线程的优先级
//因为每个线程的id和由优先级都是独有的,所以定义在私有变量
//--------------------------------------------------------------------------------
public:
Thread(char* debugName); // initialize a Thread
~Thread(); // deallocate a Thread
// NOTE -- thread being deleted
// must not be running when delete
// is called
// basic thread operations
//*************************************************修改处---将上面两个变量定义到公开区域,同再创建一个getPriority() 函数
//---------------------------------------------------------------------------修改处
int tid; //定义每一个线程的id号
int priority; //定义每个线程的优先级
int getid()
{return this->tid;} //返回该线程的id号
int getPriority()
{ return this->priority;} //返回新构造函数的优先级值
//--------------------------------------------------------------------------
这样就完成了所有的修改,下面是实验运行的结果:
注1:由于使用的是原构造函数,所以就会有main线程和postal worker线程的出现,但是我们将原构造函数中的输出语句放到了测试函数中,所以就不会输出这两个线程的一系列参数,但是它们是存在的,所以最后输出的ID号是从3开始的,但不影响结果,写好注释说明即可。
注2:实验环境:Ubuntu14.04。
写的不是很好,大家就将就看一下,有错误的地方大家多多包涵。
最后希望大家早日完成实验报告,少熬夜。