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_CREATED。stack是指向栈底的指针(栈溢出检查)。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,InitialArgState和WhenDonePCState, 分别代表 9, 3, 0 , 1和2。

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

线程入口函数地址被存储在以InitialPCState为下标(0号位置)的数组中。线程函数参数被存储在以InitialArg(1号位置)为下表的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的线程上下文切换是通过调用Scheduler的Run函数来进行的。

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时间时,所有存储在stackTop和MachineState的值会被加载到寄存器中,也包括存储返回地址的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();

3:currentThread->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于山西大同

Nachos线程管理相关推荐

  1. Nachos内存管理实现

    Nachos内存管理实现 存储器管理包括两个层次的内容:分别是内存管理和文件系统管理. 每个用户进程都是自己的地址空间.当进程运行时它们都会被加载到内存中. 接下来我们仍使用Nachos源代码来讲述地 ...

  2. java 线程的创建和执行_线程管理(一)线程的创建和运行

    声明:本文是< Java 7 Concurrency Cookbook>的第一章, 作者: Javier Fernández González 译者:郑玉婷 校对:欧振聪 线程的创建和运行 ...

  3. Unix进程和线程管理及其异同

    Unix进程和线程管理及其异同 一,进程 1,什么是进程 在最初的单处理器系统中,系统中的多道程序按照一定规则切换而实现多任务处理,后来发现多个程序并发导致系统资源被共享,为了描述和管理程序对共享资源 ...

  4. rtems线程管理与调度(一)

    rtemsahi一个以线程为基本调度单位的实施操作系统,调度算法是基于优先级的抢占式线程调度,支持256个线程优先级,0代表最高优先级,主要用于内部线程,255是最低线程,是空闲线程的优先级,用户线程 ...

  5. Android线程管理(一)

    线程通信.ActivityThread及Thread类是理解Android线程管理的关键. 线程,作为CPU调度资源的基本单位,在Android等针对嵌入式设备的操作系统中,有着非常重要和基础的作用. ...

  6. [转]C++ 11 多线程--线程管理

    转载地址:https://www.cnblogs.com/wangguchangqing/p/6134635.html 说到多线程编程,那么就不得不提并行和并发,多线程是实现并发(并行)的一种手段.并 ...

  7. 线程管理(七)守护线程的创建和运行

    声明:本文是< Java 7 Concurrency Cookbook >的第一章, 作者: Javier Fernández González 译者:郑玉婷 校对:方腾飞 守护线程的创建 ...

  8. C++ 11 多线程--线程管理

    说到多线程编程,那么就不得不提并行和并发,多线程是实现并发(并行)的一种手段.并行是指两个或多个独立的操作同时进行.注意这里是同时进行,区别于并发,在一个时间段内执行多个操作.在单核时代,多个线程是并 ...

  9. 聊一聊Flutter Engine线程管理与Dart Isolate机制

    阿里妹导读:Flutter是一款开源的移动跨平台UI开发套件,它不仅与现存的Native代码兼容,还能帮你用Dart语言快速开发高质量的跨平台App. 本文由闲鱼技术团队福居撰写,结合Flutter ...

最新文章

  1. Python 将数据框类型转为字典类型
  2. python工程师月薪-在三线城市,Python工程师也能拿到月薪20K?
  3. [android]-xml解析示便-SAX
  4. RabbitMq入门(七)消息处理(消息持久化autoDelete、消息确认ACK机制)
  5. 数据结构习题之线性表
  6. SlideSwitch仿iphone滑动开关组件,仿百度魔图滑动开关组件Android
  7. 3D视觉——基恩士LJ-X系列线激光
  8. 让别人查看云服务器的文件夹,让别人查看云服务器的文件夹
  9. arduino串口绘图_Arduino 串口数据可视化
  10. sqlmap注入语句整理
  11. 开启tomcat的gzip
  12. 下行文格式图片_谁能告诉我公文的下行文\上行文的模板
  13. ApacheCN C# 译文集 20211124 更新
  14. CF1071C. Triple Flips
  15. xctf攻防世界 MISC高手进阶区 2-1
  16. 关于页眉的奇偶页不同设置和奇数页页眉展示每一章名称的设置方法
  17. linux鼠标切换窗口的快捷键设置,Xshell中如何设置鼠标快捷键
  18. 神经网络预测结果分析,神经网络怎么预测数据
  19. rpm 安装 忽略依赖_使用 RPM 安装软件包的常见问题
  20. kubeflow--安装使用pipeline

热门文章

  1. mysql 1067_mysql启动错误1067进程意外终止的解决方法
  2. android图片资源加密解密,Android Glide实现图片加密解密 自定义格式
  3. 记录:d3实现刻度尺
  4. unity接入讯飞AIUI(Windows SDK)
  5. python输出图片到word_python将文本转换成图片输出的方法
  6. paddlespeech 语音识别 web流服务部署(Streaming Speech Recognition)
  7. SAP中采购订单关于收货和发票三种不同控制情况的分析
  8. 【数理统计】双因素方差分析
  9. 瀚高CEO苗健:用开源软件改变中国基础软件产业格局
  10. ros msg文件数组定义与使用