Nachos线程管理

        Nachos线程管理

 

      Nachos中的线程是在内核中以一个thread类的对象的方式实现的。线程控制块是以类的数据成员的方式实现。

 

     //thread类源代码--定义

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
    int machineState[MachineStateSize];  // all registers except for stackTop

  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

    void Fork(VoidFunctionPtr func, int arg); 	// Make thread run (*func)(arg)
    void Yield();  				// Relinquish the CPU if any 
						// other thread is runnable
    void Sleep();  				// Put the thread to sleep and 
						// relinquish the processor
    void Finish();  				// The thread is done executing
    
    void CheckOverflow();   			// Check if thread has 
						// overflowed its stack
    void setStatus(ThreadStatus st) { status = st; }
    char* getName() { return (name); }
    void Print() { printf("%s, ", name); }

  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, int arg);
    					// Allocate a stack for thread.
					// Used internally by Fork()

#ifdef USER_PROGRAM
// 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.
#endif
};

 

 

      线程的状态存储在ThreadStatus 类型的status数据成员中。ThreadStatus定义如下:

 

enum ThreadStatus { JUST_CREATED, RUNNING, READY, BLOCKED };


 

     线程的状态必须是以上枚举类型之一。当线程的状态改变时,status值相应的改变。线程有自己的线程栈和寄存器。Nachos的线程栈在线程状态从JUST_CREATED变为RUNNING时被申请。thread类的构造函数会设置threadname,并且将线程状态设置为JUST_CREATEDstack是指向栈底的指针(栈溢出检查)。stackTop指向栈顶。包括PC在内的其他寄存器都被存储在MachineState

数组内。数组的大小有MachineStateSize确定。

 

     在Nachos中用户线程是从核心线程继承而来的。

      userRegisters数组是用户存储用户寄存器值的数组。其大小由NumTotalRegs确定。

      MachineState存储在内核状态下运行的线程的状态。而用userRegisters数组存储在用户模式下运行的线程状态。

 

      在Nachos中,用户线程都是以内核线程的方式开始的,当加载用户程序且创建地址空间之后,内核线程就转变成了用户线程。

 

      就绪队列用于存储所有处于就绪状态的线程或进程。与IO设备有关的队列存储处于阻塞态的线程或进程,它们都在等待IO请求完成。

 

     线程调度程序在这些队列间移动线程或进程。

     Fork方法让线程运行,将线程状态从JUST_CREATED变为RUNNING

     Yield方法会将线程从RUNNING变为READY状态。(放弃当前时间片)该方法就会当前线程添加到就绪队列尾部,并通过线程上下文转换从就绪队列中的一个线程变为运行状态。当就绪队列空时,       Yield方法不执行任何操作,当前线程继续运行。

      Sleep方法将线程状态从RUNNING切换到BLOCKED状态。并从就绪队列选择一个线程运行。当就绪队列空的时候,cpu保持空闲状态,直到有一个线程就绪为止。Sleep方法会在执行IO操作时或者是等待一个事件时经常被调用。在调用Sleep之前,线程经常把它自己放入IO设备等待队列。

      Finish方法用于终止当前线程。

 

      Nachos中,作业调度程序,是一个Scheduler类的对象实现的。它的方法提供了所有对线程或进程调度的功能。当系统启动时Scheduler对象会以一个全局变量scheduler的方式被创建。

//Scheduler类定义:

 

20 class Scheduler {

21 public:

22 Scheduler(); // Initialize list of ready threads

23 ˜Scheduler(); // De-allocate ready list

24

25 void ReadyToRun(Thread* thread); // Thread can be dispatched.

26 Thread* FindNextToRun(); // Dequeue first thread on the ready

27 // list, if any, and return thread.

28 void Run(Thread* nextThread); // Cause nextThread to start running

29 void Print(); // Print contents of ready list

30

31 private:

32 List *readyList; // queue of threads that are ready to run,

33 // but not running

34 };


 

      Scheduler的唯一的数据成员是就绪队列。它存储所有处于READY(就绪)状态的线程。

      ReadyToRun函数将一个线程添加到就绪队列的尾部。

      FindNextToRun返回队首线程指针。

      Scheduler类最有意思的方法是Run。该方法调用使用汇编写成的SWITCH函数来将当前线程上下文切换到另外一个线程的上下文。

当一个Thread类构造函数被调用时,它仅仅是初始化成员变量将线程状态变为JUST_CREATED状态。此时线程还不能运行,因为它的线程栈还没有被分配而且线程控制块还没有被初始化,更重要的PC寄存器没有赋值,程序不知道从哪里开始执行。

 

Fork(VoidFunctionPtr func, int arg)

      Fork方法负责线程栈的分配,func是线程函数入口地址,arg是线程函数。

Fork函数实现:

 

87 void

88 Thread::Fork(VoidFunctionPtr func, _int arg)

89 {

90 #ifdef HOST_ALPHA

91 DEBUG(’t’, "Forking thread \"%s\" with func = 0x%lx, arg = %ld\n",

92 name, (long) func, arg);

93 #else

94 DEBUG(’t’, "Forking thread \"%s\" with func = 0x%x, arg = %d\n",

95 name, (int) func, arg);

96 #endif

97

98 StackAllocate(func, arg);

99

100 IntStatus oldLevel = interrupt->SetLevel(IntOff);

101 scheduler->ReadyToRun(this); // ReadyToRun assumes that interrupts

102 // are disabled!

103 (void) interrupt->SetLevel(oldLevel);

104 }

AllocateStack函数首先被调用。它用于分配线程栈并且初始化MachineState数组。

AllocateStack实现:

257 void

258 Thread::StackAllocate (VoidFunctionPtr func, _int arg)

259 {

260 stack = (int *) AllocBoundedArray(StackSize * sizeof(_int));

...

20

272 stackTop = stack + StackSize - 4; // -4 to be on the safe side!

...

284 machineState[PCState] = (_int) ThreadRoot;

285 machineState[StartupPCState] = (_int) InterruptEnable;

286 machineState[InitialPCState] = (_int) func;

287 machineState[InitialArgState] = arg;

288 machineState[WhenDonePCState] = (_int) ThreadFinish;

289 }


 

     AllocBoundedArray分配线程栈并将stack指向线程栈底部。

stack指向线程栈顶部。

 

      宏PCState, StartupPCState, InitialPCState,InitialArgStateWhenDonePCState, 分别代表 9, 3, 0 , 12

      ThreadRoot是一个函数名,它是由汇编实现。InterruptEnableThreadFinish是两个静态函数名称。它们都被存储在MachineState数组中。代表各个寄存器的值。

      线程入口函数地址被存储在以InitialPCState为下标(0号位置)的数组中。线程函数参数被存储在以InitialArg1号位置)为下表的MachineState数组中。

      当线程开始运行时MachineState[InitialPCState]会被加载到ra寄存器。ra被称为返回地址寄存器,存储线程函数的第一条指令开始的位置。

      ThreadRoot是以汇编形式写成的,它是在线程运行前第一个被运行的函数。

 

 

69 .globl ThreadRoot

70 .ent ThreadRoot,0

71 ThreadRoot:

72 or fp,z,z # Clearing the frame pointer here

73 # makes gdb backtraces of thread stacks

74 # end here (I hope!)

75

76 jal StartupPC # call startup procedure

77 move a0, InitialArg

78 jal InitialPC # call main procedure

79 jal WhenDonePC # when we are done, call clean up procedure

80

81 # NEVER REACHED

82 .end ThreadRoo


 

      除了main线程外,所有其它线程都是从ThreadRoot开始运行的。它的语法是:

ThreadRoot(intInitialPC, int InitialArg, int WhenDonePC,int StartupPC)

其中,InitialPC指明新生成线程的入口函数地址,InitialArg是该入口函数的参数;StartupPC是在运行该线程是需要作的一些初始化工作指向InterruptEnable函数。,比如开中断;而WhenDonePC是当该线程运行结束时需要作的一些后续工作,指向ThreadFinish函数。在Nachos的源代码中,没有任何一个函数和方法显式地调用ThreadRoot函数,ThreadRoot函数只有在线程切换时才被调用到。

      一个线程在其初始化的最后准备工作中调用StackAllocate方法,该方法设置了几个寄存器的值,(InterruptEnable函数指针,ThreadFinish函数指针以及该线程需要运行函数的函数指针和运行函数的参数),该线程第一次被运行时调用的就是ThreadRoot函数。其工作过程是:

     1. 调用StartupPC函数,它指向InterruptEnable函数。执行开中断操作;

     2. 调用InitialPC 函数,指向线程入口函数;

     3. 调用WhenDonePC函数,该函数调用CurrentThread->Finish()结束线程的运行;

     Nachos的线程上下文切换是通过调用SchedulerRun函数来进行的。

 

 

90 void

91 Scheduler::Run (Thread *nextThread)

92 {

93 Thread *oldThread = currentThread;

94

22

95 #ifdef USER_PROGRAM // ignore until running user programs

96 if (currentThread->space != NULL) { // if this thread is a user program,

97 currentThread->SaveUserState(); // save the user’s CPU registers

98 currentThread->space->SaveState();

99 }

100 #endif

101

102 oldThread->CheckOverflow(); // check if the old thread

103 // had an undetected stack overflow

104

105 currentThread = nextThread; // switch to the next thread

106 currentThread->setStatus(RUNNING); // nextThread is now running

107

108 DEBUG(’t’, "Switching from thread \"%s\" to thread \"%s\"\n",

109 oldThread->getName(), nextThread->getName());

110

111 // This is a machine-dependent assembly language routine defined

112 // in switch.s. You may have to think

113 // a bit to figure out what happens after this, both from the point

114 // of view of the thread and from the perspective of the "outside world".

115

116 SWITCH(oldThread, nextThread);

117

118 DEBUG(’t’, "Now in thread \"%s\"\n", currentThread->getName());

119

120 // If the old thread gave up the processor because it was finishing,

121 // we need to delete its carcass. Note we cannot delete the thread

122 // before now (for example, in Thread::Finish()), because up to this

123 // point, we were still running on the old thread’s stack!

124 if (threadToBeDestroyed != NULL) {

125 delete threadToBeDestroyed;

126 threadToBeDestroyed = NULL;

127 }

128

129 #ifdef USER_PROGRAM

130 if (currentThread->space != NULL) { // if there is an address space

131 currentThread->RestoreUserState(); // to restore, do it.

132 currentThread->space->RestoreState();

133 }

134 #endif

135 }


 

      该函数首先设置oldThread,将参数设置为currentThread。然后调用SWITCH进行线程上下文切换。SWITCH是用汇编实现。该函数首先将所有重要的寄存器的内容存储在当前线程的控制块中。回想下Thread类的第一个私有数据成员是stackTop,其后是MachineState数组。换句话说指向Thread对象的指针,也是在指向stackTop。它们的位置非常重要不能该变或颠倒。

     当一个线程再次获得cpu时间时,所有存储在stackTopMachineState的值会被加载到寄存器中,也包括存储返回地址的ra

     Run函数属于内核,它会在将线程上下文切换,且新线程运行后返回。

      下面我们先来介绍下Nachos内核定义的几个全局变量。在threads/system.cc目录下至少定义了6个全局变量。

它们是:

 

 

Thread *currentThread; // 当前执行线程

Thread *threadToBeDestroyed; // 刚结束的线程

 Scheduler *scheduler; //就绪队列

Interrupt *interrupt; //中断状态。

 Statistics *stats; //性能标准。

 Timer *timer; // 计时器设备,用于导致线程上下文切换。

#ifdef FILESYS_NEEDED

23 FileSystem *fileSystem;

24 #endif

25

26 #ifdef FILESYS

27 SynchDisk *synchDisk;

28 #endif

29

30 #ifdef USER_PROGRAM // requires either FILESYS or FILESYS_STUB

31 Machine *machine; // user program memory and registers

32 #endif

33

34 #ifdef NETWORK

35 PostOffice *postOffice;

36 #endif


 

     currentThread指向当前运行线程。scheduler指向内核负责调度线程和管理就绪队列的Scheduler类对象。

     这些全局变量会Nachos系统启动时被创建。创建这些变量的函数为:

Initialize(int argc, char **argv)

     ThreadFinish会在线程从线程入口函数返回时被调用,它仅仅调用thread类的Finish函数。在Finish函数中它将threadToBeDestroyed设置为currentThread。此后,原来线程的清理工作就交给了新线程。     在SWITCH执行完线程上下文切换后,我们看到有这样一句代码:

 

 

24 if (threadToBeDestroyed != NULL) {

125 delete threadToBeDestroyed;

126 threadToBeDestroyed = NULL;

127 }


 

     这些代码就是新线程执行清理操作。

     每次执行线程上下文切换后,新线程都会检查threadToBeDestroyed,将老线程清理掉。

     接下来我们来讨论下Nachos内核。Nachos内核是Nachos操作系统的一部分。内核本身也是一个程序,它必须也有个main()函数。在threads/main.cc文件内可以找到main函数。main函数主要执行一下操作:

     1:调用Initialize();

     2:调用ThreadTest();

     3currentThread->Finish();

Initialize执行创建并初始化全局变量的工作。

 

ThreadTest是一个测试函数。定义如下:

 

 

41 void

42 ThreadTest()

43 {

44 DEBUG(’t’, "Entering SimpleTest");

45

46 Thread *t = new Thread("forked thread");

47

48 t->Fork(SimpleThread, 1);

49 SimpleThread(0);

50 }


 

这个函数创建了一个名为forked thread的线程去执行SimpleThread函数。

SimpleThread定义如下:

 

24 void

25 SimpleThread(int which)

26 {

27    int num;

28

29    for (num = 0; num < 5; num++) {

30    printf("*** thread %d looped %d times\n", which, num);

31    currentThread->Yield();

32   }

33 }


 

      主线程和新建线程都执行SimpleThread函数。它们不断进行线程上下文切换直到结束。

      main函数的最后调用的函数是currentThread->Finish()。这个函数决不会返回。因为在线程上下文切换后,这个线程会被新线程释放。

                                                以上翻译自《Nachos study book》如有纰漏,请不吝赐教!!
                                                                           2013.1.1于山西大同
  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值