Nachos實驗實現執行緒id、限制執行緒數和更改排程演算法(按優先順序排程)

_XJing_發表於2020-11-17

Nachos實驗1實現執行緒id、限制執行緒數和更改排程演算法(按優先順序排程)

一、理解Nachos執行緒的執行及排程原理

Nachos實驗主要是為了使我們深入的理解作業系統結構,Nachos也是一個作業系統,但是它很簡便,我們可以很容易的對它進行改動;通過對Nachos的實驗來增加我們對作業系統的理解,當前這個實驗主要是通過我們實現Nachos執行緒id和優先順序排程來對作業系統程式和執行緒的理解。Nachos裡面主要分為核心執行緒(例如main執行緒)和使用者執行緒,主要通過主執行緒來操作,例如新建和排程執行緒。其實對於增加執行緒id和限制最大執行緒數,只要瞭解程式的實現原理,看程式碼也會非常的容易理解,這樣就會非常簡單了。
對於理解這次實驗程式碼,我認為,需要看thread.h、thread.cc、scheduler.h、scheduler.cc、List.h、List.cc、main.h、main.cc,對於main.h和main.cc,只需要知道在執行Nachos的時候加-K命令(“./nachos -K”)就會進入執行緒測試就行了。

二、增加執行緒ID和最大執行緒數

在增加實現執行緒id和最大執行緒數時,thread類的建構函式不用改動,只需再重構一個構造方法,因為之前的建構函式可能在Nachos的其他類裡會呼叫它,所以我們不能再之前的建構函式裡構造,這樣才不會打亂原系統的正確性,所以需要增加一個建構函式Thread(char* debugName,int priority);至於int priority先不用管,這是之後優先順序排程需要用到的引數,不影響我們使用這個建構函式

1.增改class thread

#define MAX_SIZE  128  //最大執行緒數
//pk陣列主要記錄執行緒號被使用的狀態
//下標位執行緒號,陣列對應值位執行緒號狀態
//對應值為0時,表示該執行緒號沒有執行緒佔用
//對應值為1時,表示該執行緒號被佔用
static int pk[MAX_SIZE]={0};
static int threadMAX = 0;//記錄執行緒數

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

	int tid;
  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
	Thread(char* debugName,int priority);//新建構函式
	int getTid(){return this->tid;};//獲得執行緒id

    void Fork(VoidFunctionPtr func, void *arg); //將執行緒加入就緒佇列
    				// Make thread run (*func)(arg)
    void Yield();  //打斷當前執行緒,執行就緒佇列裡的執行緒
					// Relinquish the CPU if any 
				// other thread is runnable
    void Sleep(bool finishing);//將當前執行緒阻塞
								 // Put the thread to sleep and 
				// relinquish the processor
    void Begin();	// Startup code for the thread	
    void Finish();  //執行緒執行結束	
						// The thread is done executing
    
    void CheckOverflow();   	// Check if thread stack has overflowed
    void setStatus(ThreadStatus st) { status = st; }//設定執行緒狀態
    char* getName() { return (name); }//獲取執行緒名字
    void Print() { cout << name; }//列印執行緒名字
    void SelfTest();	//測試方法	// test whether thread impl is working

  private:
    // some of the private data for this class is listed above
    
    int *stack; 	 	// Bottom of the stack 
				// NULL if this is the main thread
				// (If NULL, don't deallocate stack)
    ThreadStatus status;	// ready, running or blocked
    char* name;

    void StackAllocate(VoidFunctionPtr func, void *arg);
    				// Allocate a stack for thread.
				// Used internally by Fork()

// A thread running a user program actually has *two* sets of CPU registers -- 
// one for its state while executing user code, one for its state 
// while executing kernel code.

    int userRegisters[NumTotalRegs];	// user-level CPU register state

  public:
    void SaveUserState();		// save user-level register state
    void RestoreUserState();		// restore user-level register state

    AddrSpace *space;			// User code this thread is running.
};

2.在thread.cc裡面增加我們需要的功能,切記,我們是新增建構函式,不是在原建構函式更改!!

Thread::Thread(char* threadName)//原建構函式,不要更改它
{
    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;
}
Thread::Thread(char* threadName,int priority)//新建構函式
{

	if(++threadMAX > MAX_SIZE){//限制執行緒數
		cout<<"最大執行緒數:"<<MAX_SIZE<<"!!!"<<endl;
		ASSERT(threadMAX<=MAX_SIZE);
	}
	int j;
	for(j=0;j<MAX_SIZE;j++){//設定id
		if(pk[j]==0){
			this->tid = j+1;
			pk[j] = 1;
			break;
		}
	}

    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;
}

3.增改解構函式

Thread::~Thread()
{
    DEBUG(dbgThread, "Deleting thread: " << name);
	pk[tid -1] = 0;//執行緒id撤銷
	threadMAX--;//執行緒數減一
    ASSERT(this != kernel->currentThread);
    if (stack != NULL)
	DeallocBoundedArray((char *) stack, StackSize * sizeof(int));
}

4.在測試函式增改測試程式碼

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[130];
	for(int i = 0;i < 130;i++){
		t[i] = new Thread("執行緒",1);
		cout<<t[i]->getName()<<t[i]->getTid()<<endl;
	}
}

測試

make
./Nachos -K

編譯
測試

結果


在這裡插入圖片描述
實現執行緒id和限制最大數的所有增改原始碼thread.h和thread.cc,下載連結:下載

三、實現更改Nachos執行緒排程,按優先順序排程

對於按優先順序排程,我的想法是,因為建立一個執行緒之後,需要加入就緒佇列就能執行,Fork方法就實現了將執行緒(readyList)加入就緒佇列裡的功能,還有Yield方法的功能是打斷當前正在執行的執行緒,執行就緒佇列裡的執行緒,再將被打斷的執行緒加入就緒佇列。而加入就緒佇列是呼叫的是Schedule類裡的ReadyToRun(Thread *thread)方法,ReadyToRun方法實現了將執行緒加入在就緒佇列裡的隊尾,就是一個先進先排程的排程機制。我經歷了無數次錯誤之後,最後把思想集中在了加入就緒佇列時按照執行緒優先順序加入佇列。實現如下(這是接著之前執行緒id做的):
1.增改thread.h檔案

#define MAX_SIZE  128  //最大執行緒數
//pk陣列主要記錄執行緒號被使用的狀態
//下標位執行緒號,陣列對應值位執行緒號狀態
//對應值為0時,表示該執行緒號沒有執行緒佔用
//對應值為1時,表示該執行緒號被佔用
static int pk[MAX_SIZE]={0};
static int threadMAX = 0;//記錄執行緒數

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

	int tid;//執行緒id
	int priority;//執行緒優先順序,值越大優先順序越高
  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
	Thread(char* debugName,int priority);//新建構函式
	int getTid(){return this->tid;};//獲得執行緒id
	int getPriority(){return priority;};//獲得執行緒優先順序


    void Fork(VoidFunctionPtr func, void *arg); //將執行緒加入就緒佇列
    				// Make thread run (*func)(arg)
    void Yield();  //打斷當前執行緒,執行就緒佇列裡的執行緒
					// Relinquish the CPU if any 
				// other thread is runnable
    void Sleep(bool finishing);//將當前執行緒阻塞
								 // Put the thread to sleep and 
				// relinquish the processor
    void Begin();	// Startup code for the thread	
    void Finish();  //執行緒執行結束	
						// The thread is done executing
    
    void CheckOverflow();   	// Check if thread stack has overflowed
    void setStatus(ThreadStatus st) { status = st; }//設定執行緒狀態
    char* getName() { return (name); }//獲取執行緒名字
    void Print() { cout << name; }//列印執行緒名字
    void SelfTest();	//測試方法	// test whether thread impl is working

  private:
    // some of the private data for this class is listed above
    
    int *stack; 	 	// Bottom of the stack 
				// NULL if this is the main thread
				// (If NULL, don't deallocate stack)
    ThreadStatus status;	// ready, running or blocked
    char* name;

    void StackAllocate(VoidFunctionPtr func, void *arg);
    				// Allocate a stack for thread.
				// Used internally by Fork()

// A thread running a user program actually has *two* sets of CPU registers -- 
// one for its state while executing user code, one for its state 
// while executing kernel code.

    int userRegisters[NumTotalRegs];	// user-level CPU register state

  public:
    void SaveUserState();		// save user-level register state
    void RestoreUserState();		// restore user-level register state

    AddrSpace *space;			// User code this thread is running.
};

2.增改thread.cc檔案

Thread::Thread(char* threadName,int priority)
{

	if(++threadMAX > MAX_SIZE){//限制執行緒數
		cout<<"最大執行緒數:"<<MAX_SIZE<<"!!!"<<endl;
		ASSERT(threadMAX<=MAX_SIZE);
	}
	int j;
	for(j=0;j<MAX_SIZE;j++){//設定id
		if(pk[j]==0){
			this->tid = j+1;
			pk[j] = 1;
			break;
		}
	}
    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;
}

3.增改List.h檔案


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

    T Front() { return first->item; }
    				// Return first item on list
				// without removing it
    T RemoveFront(); 		// Take item off the front of the list
    void Remove(T item); 	// Remove specific item from list

    bool IsInList(T item) const;// is the item in the list?

    unsigned int NumInList() { return numInList;};
    				// how many items in the list?
    bool IsEmpty() { return (numInList == 0); };
    				// is the list empty? 

    void Apply(void (*f)(T)) const; 
    				// apply function to all elements in list

    virtual void SanityCheck() const;	
				// has this list been corrupted?
    void SelfTest(T *p, int numEntries);
				// verify module is working

	void setNumInList(int numInList){this->numInList = numInList;}//設定佇列元素個數
	ListElement<T>* getfirst(){return first;};//get頭指標
	ListElement<T>* getlast(){return last;};//get尾指標
	void setfirst(ListElement<T> *first){this->first = first;};//設定頭結點
	void setlast(ListElement<T>* last){this->last = last;};//設定尾結點
  protected:
    ListElement<T> *first;  	// Head of the list, NULL if list is empty
    ListElement<T> *last;	// Last element of list
    int numInList;		// number of elements in list

    friend class ListIterator<T>;
};

4.增改schedule.h檔案


class Scheduler {
  public:
    Scheduler();		// Initialize list of ready threads 
    ~Scheduler();		// De-allocate ready list

    void ReadyToRun(Thread* thread);	
    				// Thread can be dispatched.
    Thread* FindNextToRun();	// Dequeue first thread on the ready 
				// list, if any, and return thread.
    void Run(Thread* nextThread, bool finishing);
    				// Cause nextThread to start running
    void CheckToBeDestroyed();// Check if thread that had been
    				// running needs to be deleted
    void Print();		// Print contents of ready list
    
    // SelfTest for scheduler is implemented in class Thread
	
	void ReadyToRunPriority(Thread* thread); //將執行緒按優先順序加入就緒佇列    
  private:
    List<Thread *> *readyList;  // queue of threads that are ready to run,
				// but not running
    Thread *toBeDestroyed;	// finishing thread to be destroyed
    				// by the next thread that runs
};

5.增改schedule.cc檔案(實現ReadyToRunPriority方法)

void Scheduler::ReadyToRunPriority(Thread *thread){
	//將執行緒thread按優先順序加入就緒佇列
	ASSERT(kernel->interrupt->getLevel() == IntOff);
	DEBUG(dbgThread, "Putting thread on ready list: " << thread->getName());
	thread->setStatus(READY);

	ASSERT(!readyList->IsInList(thread));//斷言
	ListElement<Thread *> *element = new ListElement<Thread *>(thread);//建立新結點
	if(readyList->IsEmpty()){
		//當就緒佇列為空時,直接加入佇列
		readyList->setfirst(element);
		readyList->setlast(element);
		readyList->setNumInList(readyList->NumInList()+1);
	}else{
		//遍歷就緒佇列
		//當執行緒優先順序大於就緒佇列結點所對應執行緒優先順序時
		//將element插入在所對應結點之前
		if(thread->getPriority() > readyList->getfirst()->item->getPriority()){
			element->next = readyList->getfirst();
			readyList->setfirst(element);
			readyList->setNumInList(readyList->NumInList()+1); 
		}else{
			ListElement<Thread *> *pre = readyList->getfirst(); 
			ListElement<Thread *> *ptr = pre->next;
			while(ptr != NULL){
				if(thread->getPriority() > ptr->item->getPriority()){
					element->next = ptr;
					pre->next = element;
					readyList->setNumInList(readyList->NumInList()+1); 
					break;
				}else{
					pre = ptr;
					ptr = ptr->next;
				}
			}
			if(ptr == NULL){
				element->next = NULL;
				pre->next = element;
				readyList->setlast(element);
				readyList->setNumInList(readyList->NumInList()+1); 
			}
		}
	}
}

6.在thread.cc裡更改Fork方法

void
Thread::Fork(VoidFunctionPtr func, void *arg)
{
    Interrupt *interrupt = kernel->interrupt;
    Scheduler *scheduler = kernel->scheduler;
    IntStatus oldLevel;

    DEBUG(dbgThread, "Forking thread: " << name << " f(a): " << (int) func << " " << arg);

    StackAllocate(func, arg);

    oldLevel = interrupt->SetLevel(IntOff);
    scheduler->ReadyToRunPriority(this);//將執行緒按優先順序加入就緒佇列
                                    // ReadyToRun assumes that interrupts 
                    // are disabled!
    (void) interrupt->SetLevel(oldLevel);
}

7.在thread.cc裡增改Yield方法

void
Thread::Yield ()
{
    Thread *nextThread;
    IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff);

    ASSERT(this == kernel->currentThread);

    DEBUG(dbgThread, "Yielding thread: " << name);

    nextThread = kernel->scheduler->FindNextToRun();
    if (nextThread != NULL) {
    kernel->scheduler->ReadyToRunPriority(this);//將執行緒按優先順序加入就緒佇列
    kernel->scheduler->Run(nextThread, FALSE);
    }
    (void) kernel->interrupt->SetLevel(oldLevel);
}


7.在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[130];
    for(int i = 0;i < 130;i++){
        t[i] = new Thread("執行緒",1);
        cout<<t[i]->getName()<<t[i]->getTid()<<endl;
    }*/

    Thread *t = new Thread("執行緒1",1);
    cout<<t->getName()<<"優先順序:"<<t->getPriority()<<endl;
    t->Fork((VoidFunctionPtr) SimpleThread, (void *)t->getTid());
    Thread *t1 = new Thread("執行緒2",4);
    cout<<t1->getName()<<"優先順序:"<<t1->getPriority()<<endl;
    t1->Fork((VoidFunctionPtr) SimpleThread, (void *)t1->getTid());
    Thread *t2 = new Thread("執行緒3",3);
    cout<<t2->getName()<<"優先順序:"<<t2->getPriority()<<endl;
    t2->Fork((VoidFunctionPtr) SimpleThread, (void *)t2->getTid());


}

測試
結果
本次Nachos實驗實現的所有增改原始碼的所有原始碼,下載連結:

相關文章