总体概述
本次lab的主要内容是拓展nachos的线程机制,在nachos的线程模型中添加tid和uid,并添加相应的管理机制。
本次lab的重要知识点在于对nachos中线程具体实现方式的理解,
任务完成情况
- Exercise 1 –> Y
- Exercise 2 –> Y
- Exercise 3 –> Y
- Exercise 4 –> Y
具体完成情况如下所示:
Exercise 1 调研
调研Linux或Windows中进程控制块(PCB)的基本实现方式,理解与Nachos的异同。
Linux中的PCB包含以下的内容
- PID
- 特征信息
- 进程状态
- 优先级
- 通信状态
- 现场保护区
- 资源需求、分配控制信息
- 进程实体信息
- 其他(工作单位、工作区、文件信息)
Nachos中的PCB实现
PCB的实现
thread.h
中,其中仅包含以下信息:
- stackTop and stack, 表示当前进程所占的栈顶和栈底
- machineState, 保留未在CPU上运行的进程的寄存器状态
- status, 表示当前进程的状态
Exercise 2 源代码阅读
仔细阅读下列源代码,理解Nachos现有的线程机制
1. code/threads/main.cc和code/threads/threadtest.cc
2. code/threads/thread.h和code/threads/thread.cc
thread.cc
中实现了
Thread
类的一系列成员函数,包含如下几种:
Thread();//Thread的构造函数
~Thread();//Thread的析构函数
Fork(VoidFunctionPtr func, int arg);//在线程中运行func方法,参数为arg
ChechOverflow();//检查线程的栈是否溢出
Finsh();//当Fork运行结束后由ThreadRoot调用,调用sleep,并准备将当前进程销毁
Yield();//如果有其他进程在等待,则让当前进程下CPU
Sleep();//当前进程由于被阻塞而下CPU
......
threadtest.cc
是一个简单的线程调试程序,在main中调用
ThreadTest()
函数,并完成一些可自定义的测试方法。
Exercise 3 拓展线程的数据结构
增加“用户ID、线程ID”两个数据成员,并在Nachos现有的线程管理机制中增加对这两个数据成员的维护机制。
思路
维护一个标识数组tid_flag,用以表明当前flag是否被分配,每次创建进程时去表中找到第一个未被分配的tid,将其分配给这个数组。当删除一个进程时,则将进程对应的tid_flag置为0
添加数据
在
thread.h
中,对
Thread
类添加以下内容:
private:
int tid;
int uid;
public:
int getTid(){
return this->tid;
}
int getUid(){
return this->uid;
}
数据成员的维护机制
在
system.h
中,声明外部变量:
extern int tid_flag[128];
同时在
system.cc
中声明变量, 并在
Initialize()
中对变量进行初始化:
//变量声明
int tid_flag[128];
//in Initialize()
for(int i = 0;i < 128; ++i){
tid_flag[i] = 0;
}
更改
Thread::Thread(char* threadName)
:
Thread::Thread(char* threadName)
{
name = threadName;
stackTop = NULL;
stack = NULL;
status = JUST_CREATED;
// allocate thread id for the current thread, wyx
for(int i = 0; i < 128; ++i){
if(tid_flag[i] == 0){
this->tid = i;
tid_flag[i] = 1;
ThreadNum++;
tid_pointer[i] = this;
break;
}
}
#ifdef USER_PROGRAM
space = NULL;
#endif
printf("Thread %d created!
", this->tid);
}
更改
Thread::~Thread()
:
Thread::~Thread()
{
DEBUG('t', "Deleting thread "%s"
", name);
ASSERT(this != currentThread);
//Deallocate the thread id
tid_flag[this->tid] = 0;
if (stack != NULL)
DeallocBoundedArray((char *) stack, StackSize * sizeof(int));
printf("Thread %d deleted
", this->tid);
}
Exercise 4 增加全局线程管理机制
- 在Nachos中增加对线程数量的限制,使得Nachos中最多能够同时存在128个线程;
- 仿照Linux中PS命令,增加一个功能TS(Threads Status),能够显示当前系统中所有线程的信息和状态。
思路
在Thread中添加
static Thread* getInstance(char* threadName)
方法,每次创建新的Thread时首先调用该方法,若当前进程数未超过限制,则正常创建,并返回新创建指针的指针,否则,提示出错,返回NULL
对于
TS()
,维护
tid_flag
数组时维护一个
Thread* tid_pointer[128]
的数组,该数组初始化为NULL,保存tid对应的Thread的指针,在TS中,通过指针得到Thread的对应信息
添加变量ThreadNum
记录当前的线程数,定义类似于tid_flag,故不赘述
更改Thread::Thread()
属相
将
Thread::Thread()
由public改为private
添加Thread::getInstance()
方法
Thread* Thread::getInstance(char* threadName){
if(ThreadNum < 128){
return new Thread(threadName);
}else{
printf("You cannot create more threads!
");
return NULL;
}
}
TS函数
//TS, print the thread status
void TS(){
printf("---------------Call TS---------------
");
for(int i = 0;i < 128;++i){
if(tid_flag[i] != 0){
printf("tid: %d, thread name: %s.
", tid_pointer[i]->getTid(), tid_pointer[i]->getName());
}
}
printf("-------------------------------------
");
}
测试结果:
最大线程数
testnum = 130
ThreadTest()
中循环调用testnum次
ThreadTest1()
TS函数
在
ThreadTest()
最后调用
遇到的困难以及解决方式
我遇到了以下两个问题:
1. UID的管理机制
在向
Thread
类中添加了uid后,我并不清楚应该怎么获得userid,所以并不知道uid的初始化应该如何完成
解决方法:
通过和同学讨论,我们一致认为当前的lab并不涉及用户进程,所有进程都是跑在内核态,故并不需要对userid进程分析,可以在初始化时将uid设置一个缺省值
2. 最大进程数的实现
我一开始并不明确应该用什么方法实现这个功能,所以做了一些尝试。
我最初的实现方法是在
Thread::Thread(char* threadName)
中判定当前是否有可分配的tid,若有则正常分配,否则将其tid设为缺省值128,在对线程进程回收时根据tid判定,若为128则将其回收
但是后来思考发现,可以通过getInstance的方式,首先判断条件,然后再根据情况返回指向Thread的指针或者NULL。
其实我最终的实现方法有个问题,因为并不能调用
Thread::Thread()
新建一个进程,给人感觉并不直观,甚至有点奇怪。而且我不确定这种方式对后续的lab是否有影响。
收获及感想
通过这次lab,我觉得我最大的收获在于对nachos的线程机制以及对操作系统内部进程的管理机制的理解更加深刻。
对课程的意见和建议
暂无
参考文献
[1] Andrew S. Tanenbaum著.陈向群 马洪兵 译 .现代操作系统[M].北京:机械工业出版社,2011:47-95.
[2] Nachos Doc:
http://homes.cs.washington.edu/~tom/nachos/