您的位置:首页 > 大数据 > 人工智能

nachos 进程管理,跟踪main.cc 过程分析

2012-03-01 20:38 183 查看
1.从main.cc的注释中可以看出,main.cc该函数做的主要工作是:

Bootstrap code to initialize the operating system kernel.

Allows direct calls into internal operating system functions,

to simplify debugging and testing. In practice, the

bootstrap code would just initialize data structures,

and start a user program to print the login prompt.

Most of this file is not needed until later assignments.

一个用来初始化操作系统内核的引导程序,允许直接调用内部方法来简化调试和测试。

实际上,这个引导程序仅进行了数据的初始化和启动一个用户程序来显示登录提示。

许多内容只有在分配之后才是需要的。

(比如说在线程管理中,我们只定义了THREAD,这样文件系统等相关信息就不会被执行)

2.在我们执行 ./nachos 之后可以增加很多参数,每个参数都代表了不同的含义,下面是可以增加的参数和参数的具体含义

// Usage: nachos -d <debugflags> -rs <random seed #>

// -s -x <nachos file> -c <consoleIn> <consoleOut>

// -f -cp <unix file> <nachos file>

// -p <nachos file> -r <nachos file> -l -D -t

// -n <network reliability> -m <machine id>

// -o <other machine id>

// -z

以上是所有可以使用的参数值

// -d causes certain debugging messages to be printed (cf. utility.h)

// -rs causes Yield to occur at random (but repeatable) spots

// -z prints the copyright message

-d 与调试信息相关,如果执行时增加参数 -d -t 则是打印出和thread相关的调试信息,如果执行时增加参数 -d + 则打印出所有调试信息。否则不打印。

-d 所跟的参数代表了需要打印哪些相关参数(具体见utility.h)

-rs 在指定的范围内随机触发线程的Yield方法,让出cpu,这个范围就是-rs 之后跟的参数(个人理解,不一定对)

-z 打印版本信息

之后都是与 USER_PROGRAM,FILESYS,NETWORK相关的内容,现在先不考虑

3.

#ifdef THREADS

extern int testnum;

#endif

由于现在分析的是线程管理过程,我们在Makefile中只定义了THREADS

DEFINES = -DTHREADS

所以将会定义参数testnum,用来记录测试的线程数。

在额外函数中,我们只调用了如下函数,该函数在threadtest.cc 文件中,执行一个线程测试。

extern void ThreadTest(void)

4.进入主函数main

int argCount;

该变量用来获得执行参数中表示完整意义的参数的个数,比如-rs 之后必须跟随一个参数 <random seed #> ,否则没有实际意义,所以它的argCount为2.

比如-z 之后不需要跟随与之相关的参数,所以它的argCount为1.

5.

DEBUG('t', "Entering main");

调用utility.cc中的DEBUG方法,打印调试信息

如果执行时添加参数-d -t 则会在终端中打印出 DEBUG('t', "*");中的第二个参数的内容

否则不打印

6.

(void) Initialize(argc, argv);

调用system.cc中的Initialize(argc, argv)方法进行初始化,进入system.cc,查看具体实现,忽略于threads无关的过程:

void Initialize(int argc, char **argv)

{

int argCount;

char* debugArgs = ""; //用来记录-d 之后的参数值

bool randomYield = FALSE;

……

//判断命令参数,每循环一次,argc减1,argv往后挪一个

for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) {

argCount = 1;

if (!strcmp(*argv, "-d")) { //如果有参数等于-d

if (argc == 1) //并且该参数为最后一个,默认为-d +

debugArgs = "+"; // turn on all debug flags 打开所有的调试信息

else {

debugArgs = *(argv + 1); //debug的值为-d之后的参数值

argCount = 2;

}

} else if (!strcmp(*argv, "-rs")) { //如果参数等于-rs,在一个范围内随机调用yield ,线程让出cpu

ASSERT(argc > 1); //断言 argc 必定大于1,-rs之后必须跟随一个参数

RandomInit(atoi(*(argv + 1))); // initialize pseudo-random

// number generator

randomYield = TRUE;

argCount = 2;

}

……

}

DebugInit(debugArgs); // initialize DEBUG messages

//将用户输入的-d之后的值付给utility中的enableflags

stats = new Statistics(); // collect statistics

interrupt = new Interrupt; // start up interrupt handling

scheduler = new Scheduler(); // initialize the ready queue

if (randomYield) // start the timer (if needed) 如果有-rs命令则启动一个新的计时器

timer = new Timer(TimerInterruptHandler, 0, randomYield);

threadToBeDestroyed = NULL;

// We didn't explicitly allocate the current thread we are running in.

// But if it ever tries to give up the CPU, we better have a Thread

// object to save its state.

//这段话的意思是说,我们并不明确需要分配一个当前线程,但是如果所有线程都打算让出cpu,

//我们最好能有一个线程来保持这个状态。也就是说让cpu一直在运行中

currentThread = new Thread("main"); //当前的线程为main

currentThread->setStatus(RUNNING); //设置线程状态为running

interrupt->Enable(); //使中断可用

CallOnUserAbort(Cleanup); //用户点击ctrl+c手动中断 if user hits ctl-C

}

7.继续查看main.cc中的内容

#ifdef THREADS

//循环判断传入的参数

for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) {

argCount = 1;

switch (argv[0][1]) { //判断参数中的第二个字符,比如执行./nachos cq 4 即设定测试线程数为4个

case 'q': //如果等于q

testnum = atoi(argv[1]); //将q后的参数为测试线程数

argCount++;

break;

default: //否则,线程数默认为1

testnum = 1;

break;

}

}

ThreadTest(); //在操作系统上运行测试线程

#endif

执行./nachos cq 4,将会打印如下内容:

No test specified.

这是因为系统只为我们写好了测试数为1的测试内容,如果指定数目,则需要我们自定义测试线程类。

8.ThreadTest();

最后调用线程测试方法,该方法在threadtest.cc中:

void

ThreadTest()

{

switch (testnum) { //判断传入的测试线程数

case 1:

ThreadTest1();

break;

default: //不为1的话就打印

printf("No test specified.\n");

break;

}

}

如上所示,只定义了testnum为1时的情况,我们可以在switch中添加几个case,就可以定义我们自己的测试方法。

当测试线程数为1时调用ThreadTest1()方法:

void

ThreadTest1()

{

DEBUG('t', "Entering ThreadTest1");

Thread *t = new Thread("forked thread"); //声明一个新线程

t->Fork(SimpleThread, 1); //线程创建执行,标识为1

SimpleThread(0); //当前线程标识为0

}

首先我们创建一个新的线程,将会调用thread.cc中的构造方法,在该方法中初始化线程名等相关信息,此时线程的状态为JUST_CREATED

(nachos线程一共包含4个状态JUST_CREATED, RUNNING, READY, BLOCKED)

Thread::Thread(char* threadName)

{

name = threadName; //线程名

stackTop = NULL;

stack = NULL;

status = JUST_CREATED; //初始化状态为刚刚创建

#ifdef USER_PROGRAM

space = NULL;

#endif

}

然后调用该线程的Fork方法,运行线程。两个参数分别为新进成需要执行的方法名以及传入该方法的参数。

void Thread::Fork(VoidFunctionPtr func, int arg)

{

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

name, (int) func, arg);

//分配堆栈,执行方法

StackAllocate(func, arg);

IntStatus oldLevel = interrupt->SetLevel(IntOff); //关闭中断状态

scheduler->ReadyToRun(this); // ReadyToRun assumes that interrupts 假设中断不可用

// are disabled!

(void) interrupt->SetLevel(oldLevel); //执行完之后,中断恢复为原来的状态

}

在Fork方法中又调用了StackAllocate(func, arg)方法,该方法的主要工作是为不同型号的机器分配不同的栈空间,和栈顶位置

machineState是除了栈顶以外的其他寄存器,我们将相关信息写到寄存器中,包括func 和 arg,线程即会运行该func

该方法的注释中说到,该方法时用来分配和初始化线程堆栈,这个栈中的一帧保存了线程根,使得线程可以中断,执行,调用方法和结束finish

//----------------------------------------------------------------------

// Thread::StackAllocate

// Allocate and initialize an execution stack. The stack is

// initialized with an initial stack frame for ThreadRoot, which:

// enables interrupts

// calls (*func)(arg)

// calls Thread::Finish

//

// "func" is the procedure to be forked

// "arg" is the parameter to be passed to the procedure

//----------------------------------------------------------------------

最后再让我们回到ThreadTest1()方法

void

ThreadTest1()

{

DEBUG('t', "Entering ThreadTest1");

Thread *t = new Thread("forked thread"); //声明一个新线程

t->Fork(SimpleThread, 1); //线程创建执行,标识为1

SimpleThread(0); //当前线程标识为0

}

主线程首先创建了一个新线程,并让新线程调用SimpleThread方法。

然后主线程自己调用SimpleThread方法,传入的参数为当前执行该方法的线程标识。当前线程为0,新创建的线程为1

void

SimpleThread(int which)

{

int num;

for (num = 0; num < 5; num++) { //每运行一次当前线程就让出cpu,让另外一个线程继续执行

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

currentThread->Yield();

}

}

在该方法中,每个线程需要循环执行5此打印操作,每执行一次打印,当前线程就执行Yield()方法,让另一个线程执行。

来回切换直到循环结束。

void

Thread::Yield ()

{

Thread *nextThread;

IntStatus oldLevel = interrupt->SetLevel(IntOff); //中断关闭

ASSERT(this == currentThread); //断言执行Yield的线程一定是当前线程

DEBUG('t', "Yielding thread \"%s\"\n", getName());

nextThread = scheduler->FindNextToRun(); //利用调度器找到下一个需要执行的线程

if (nextThread != NULL) { //如果下一个线程不为空

scheduler->ReadyToRun(this); //将该线程的状态设置为就绪状态

scheduler->Run(nextThread); //调度器运行下一个线程

}

(void) interrupt->SetLevel(oldLevel); //中断状态恢复

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐