文档库 最新最全的文档下载
当前位置:文档库 › 计算机操作系统实验报告记录

计算机操作系统实验报告记录

计算机操作系统实验报告记录
计算机操作系统实验报告记录

计算机操作系统实验报告记录

————————————————————————————————作者:————————————————————————————————日期:

2

操作系统实验报告

学院:计算机与通信工程学院

专业:计算机科学与技术

班级:

学号:

姓名:

指导教师:

成绩:

2014年 1 月 1 日

实验一线程的状态和转换(5分)

1 实验目的和要求

目的:熟悉线程的状态及其转换,理解线程状态转换与线程调度的关系。

要求:

(1)跟踪调试EOS线程在各种状态间的转换过程,分析EOS中线程状态及其转换的相关源代码;

(2)修改EOS的源代码,为线程增加挂起状态。

2 完成的实验内容

2.1 EOS线程状态转换过程的跟踪与源代码分析

(分析EOS中线程状态及其转换的核心源代码,说明EOS定义的线程状态以及状态转换的实现方法;给出在本部分实验过程中完成的主要工作,包括调试、跟踪与思考等)

1.EOS 准备了一个控制台命令“loop ”,这个命令的命令函数是ke/sysproc.c 文件中的ConsoleCmdLoop 函数(第797行,在此函数中使用LoopThreadFunction 函数(第755 行)创建了一个优先级为8 的线程(后面简称为“loop 线程”),该线程会在控制台中不停的(死循环)输出该线程的ID和执行计数,执行计数会不停的增长以表示该线程在不停的运行。loop命令执行的效果可以参见下图:

2. 线程由阻塞状态进入就绪状态

(1)在虚拟机窗口中按下一次空格键。

(2)此时EOS会在PspUnwaitThread函数中的断点处中断。在“调试”菜单中选择“快速监视”,在快速监视对话框的表达式编辑框中输入表达式“*Thread”,然后点击“重新计算”按钮,即可查看线程控制块(TCB)中的信息。其中State域的值为3(Waiting),双向链表项StateListEntry的Next和Prev指针的值都不为0,说明这个线程还处于阻塞状态,并在某个同步对象的等待队列中;StartAddr域的值为IopConsoleDispatchThread,说明这个线程就是控制台派遣线程。

(3)关闭快速监视对话框,激活“调用堆栈”窗口。根据当前的调用堆栈,可以看到是由键盘中断服务程序(KdbIsr)进入的。当按下空格键后,就会发生键盘中断,从而触发键盘中断服务程序。在该服务程序的最后中会唤醒控制台派遣线程,将键盘事件派遣到活动的控制台。

(4)在“调用堆栈”窗口中双击PspWakeThread函数对应的堆栈项。可以看到在此函数中连续调用了PspUnwaitThread函数和PspReadyThread函数,从而使处于阻塞状态的控制台派遣线程进入就绪状态。

(5)在“调用堆栈”窗口中双击PspUnwaitThread函数对应的堆栈项,先来看看此函数是如何改变线程状态的。按F10单步调试直到此函数的最后,然后再从快速监视对话框

中观察“*Thread”表达式的值。此时State域的值为0(Zero),双向链表项StateListEntry 的Next和Prev指针的值都为0,说明这个线程已经处于游离状态,并已不在任何线程状态的队列中。仔细阅读PspUnwaitThread函数中的源代码,理解这些源代码是如何改变线程状态的。

(6)按F5继续执行,在PspReadyThread函数中的断点处中断。按F10单步调试直到此函数的最后,然后再从快速监视对话框中观察“*Thread”表达式的值。此时State域的值为1(Ready),双向链表项StateListEntry的Next和Prev指针的值都不为0,说明这个线程已经处于就绪状态,并已经被放入优先级为24的就绪队列中

3.线程由运行状态进入就绪状态

(1)按F5继续执行,在PspSelectNextThread函数中的断点处中断。在快速监视对话框中查看“*PspCurrentThread”表达式的值,观察当前占用处理器的线程的情况。其中State域的值为2(Running),双向链表项StateListEntry的Next和Prev指针的值都为0,说明这个线程仍然处于运行状态,由于只能有一个处于运行状态的线程,所以这个线程不在任何线程状态的队列中;StartAddr域的值为LoopThreadFunction,说明这个线程就是loop线程。注意,在本次断点被命中之前,loop线程就已经被中断执行了,并且其上下文已经保存在线程控制块中。

(2)按F10单步调试,直到对当前线程的操作完成(也就是花括号中的操作完成)。再从快速监视对话框中查看“*PspCurrentThread”表达式的值。其中State域的值为1(Ready),双向链表项StateListEntry的Next和Prev指针的值都不为0,说明loop 线程已经进入了就绪状态,并已经被放入优先级为8的就绪队列中。仔细阅读PspSelectNextThread函数这个花括号中的源代码,理解这些源代码是如何改变线程状态的,并与PspReadyThread函数中的源代码进行比较,说明这两段源代码的异同,体会为什么在这里不能直接调用PspReadyThread函数。

4.线程由就绪状态进入运行状态

(1)按F5继续执行,在PspUnreadyThread函数中的断点处中断。在快速监视对话框中查看“*Thread”表达式的值。其中State域的值为1(Ready),双向链表项StateListEntry的Next和Prev指针的值都不为0,说明这个线程处于就绪状态,并在优先级为24的就绪队列中;StartAddr域的值为IopConsoleDispatchThread,说明这个线程就是控制台派遣线程。

(2)关闭快速监视对话框后,在“调用堆栈”窗口中激活PspSelectNextThread 函数对应的堆栈项,可以看到在PspSelectNextThread函数中已经将PspCurrentThread 全局指针指向了控制台派遣线程,并在调用PspUnreadyThread函数后,将当前线程的状态改成了Running。

(3)在“调用堆栈”窗口中激活PspUnreadyThread函数对应的堆栈项,然后按

F10单步调试,直到返回PspSelectNextThread函数并将线程状态修改为Running。再从快速监视对话框中查看“*PspCurrentThread”表达式的值,观察当前占用处理器的线程的情况。其中State域的值为2(Running),双向链表项StateListEntry的Next 和Prev指针的值都为0,说明控制台派遣线程已经处于运行状态了。接下来,会将该

线程的上下文从线程控制块(TCB)复制到处理器的各个寄存器中,处理器就可以从该

线程上次停止运行的位置继续运行

5.线程由运行状态进入阻塞状态.

(1)按F5继续执行,在PspWait函数中的断点处中断。在快速监视对话框中查

看“*PspCurrentThread”表达式的值,观察当前占用处理器的线程的情况。其中State 域的值为2(Running),双向链表项StateListEntry的Next和Prev指针的值都为0,说明这个线程仍然处于运行状态;StartAddr域的值为IopConsoleDispatchThread,说明这个线程就是控制台派遣线程。

(2)按F10单步调试,直到左侧的黄色箭头指向代码第248行。再从快速监视对

话框中查看“*PspCurrentThread”表达式的值。其中State域的值为3(Waiting),双向链表项StateListEntry的Next和Prev指针的值都不为0,说明控制台派遣线程

已经处于阻塞状态了,并在某个同步对象的等待队列中。第248行代码可以触发线程调度功能,会中断执行当前已经处于阻塞状态的控制台派遣线程,并将处理器上下文保存

到该线程的线程控制块。

2.2为线程增加挂起状态的实现

(给出实现方法的简要描述、源代码、测试和结果等)

1.为线程增加挂起状态

(1)删除之前添加的所有断点。

(2)按F5启动调试。

(3)待EOS启动完毕,在EOS控制台中输入命令“loop”后按回车。此时可以看到loop 线程的执行计数在不停增长,说明loop线程正在执行。记录下loop线程的ID。

(4)按Ctrl+F2切换到控制台2,输入命令“suspend 31”(如果loop线程的ID是31)后按回车。命令执行成功的结果如下图所示。

(5)按Ctrl+1切换回控制台1,可以看到由于loop线程已经成功被挂起,其执行计数已经停止增长了。此时占用处理器的是EOS中的空闲线程。

2.完成Resume原语后,可以先使用suspend命令挂起loop线程,然后在控制台2中输入命令“Resume 31”(如果loop线程的ID是31)后按回车。命令执行成功的结果如下图所示。如果切换回控制台1后,发现loop线程的执行计数恢复增长就说明Resume 原语可以正常工作。

设计代码

STATUS

PsResumThread(

IN HANDLE hThread

)

{

STATUS Status;

BOOL IntState;

PTHREAD Thread;

Status = ObRefObjectByHandle(hThread, PspThreadType, (PVOID*)&Thread);

if (EOS_SUCCESS(Status)) {

IntState = KeEnableInterrupts(FALSE); // 关中断

if (Zero == Thread->State) {

ListRemoveEntry(&Thread->StateListEntry);

PspReadyThread(Thread);

PspThreadSchedule();

Status = STATUS_SUCCESS;

} else {

Status = STATUS_NOT_SUPPORTED;

}

KeEnableInterrupts(IntState); // 开中断

ObDerefObject(Thread);

}

return Status;

}

1. 首先调用ListRemoveEntry 函数将线程从挂起线程队列中移除。

2. 然后调用PspReadyThread 函数将线程恢复为就绪状态。

3. 最后调用PspThreadSchedule 宏函数执行线程调度,让刚刚恢复的线程有机会执行。

3 其他需要说明的问题

实验二进程的同步(7分)

1 实验目的和要求

目的:理解进程同步的原理和意义,掌握信号量的实现方法和应用。

要求:

(1)使用EOS的信号量,实现生产者-消费者问题;

(2)跟踪调试EOS信号量的工作过程,分析EOS信号量实现的源代码;

(3)修改EOS信号量的实现代码,使之支持等待超时唤醒和批量释放功能。

2 完成的实验内容

2.1 使用EOS的信号量实现生产者-消费者问题

(简要说明使用EOS的信号量解决生产者-消费者问题的实现方法;给出在本部分实验过程中完成的主要工作,包括调试、跟踪、测试与思考等)

EOS使用CreateThread函数创建线程,使用CreateMutex、CreateSemaphore

创建信号量。WaitForSingleObject与ReleaseMutex、ReleaseSemaphore函数相当于P、V原语。

设计思路和流程图:

按照下面的步骤查看生产者-消费者同步执行的过程:

1. 使用pc.c 文件中的源代码,替换之前创建的 EOS 应用程序项目中 EOSApp.c 文件内的源代码。

2. 按F7生成修改后的 EOS 应用程序项目。

3. 按F5启动调试。OS Lab 会首先弹出一个调试异常对话框。

4. 在调试异常对话框中选择“否”,继续执行。

5. 立即激活虚拟机窗口查看生产者-消费者同步执行的过程,如图 13-2。

6. 待应用程序执行完毕后,结束此次调试。

main 函数开

创建Mutex

创建Empty 信号量对象

创建Full 信 号量对象

创建生产者

创建消费者

等待生产者线程和消费者线

关闭句柄

main 函数结

Producer

生产完

等待Empty 信

等待Mutex 对

生产一个产品,占用一个

循环向后移动缓冲区指针

释放Mutex 对

释放Full 信

等待500

Pr od uc er

Consumer 函数

消费

等待Full 信号

等待Mutex

消费一个产品,清空一个缓冲循环向后移

释放Mutex

释放Empty 信号量

前10

等待2000毫

等待

100毫秒

Consume r 函数

1.Mutex、Empty、Full三个信号量的初始值分别为1、10、0,当存在一个生产者线程访问缓冲池时,首先对Empty减1,如果大于0,则说明还有剩余缓冲区可以让生产者放入产品,否则生产者线程进入等待队列;再对Mutex减1,如果大于等于0,则说明没有线程占用缓冲池,否则生产者线程进入等待队列。生产完产品后,对Mutex加1,解除封锁;再对Full加1,说明生产了一个产品占用了一个缓冲区。消费者线程同理,对信号量的操作顺序与生产者线程相反。不能对这三个同步对象的操作改变顺序,否则可能造成死锁。

2.因为临界资源的访问限制,程序中限定了缓冲池的大小为10,只有缓冲池有空余时生产者才能向里边放产品,同时只有缓冲池有产品时消费者才能向外取东西。当生产者生产了13号产品后,共生产了从0到13的14个产品,但是只消费了从0到3的4个产品,所以缓冲池中的10个缓冲区就都被占用了,所以不能继续生产14号产品,而要等到消费者消费掉一个产品后,缓冲池有空余位置,才能继续生产14号产品。当生产者线程生产了13号产品后,此时Full信号量的值为10,而Empty信号量的值为0,此时若生产者线程要再生产一个产品,先对Empty减1,此时Empty值小于零,生产者线程进入等待队列;而此时若有一个消费者线程要消费一个产品,先对Full减1,此时Full值为9,大于0,如果没有线程占用缓冲池,消费者可以消费一个产品。这样,生产者和消费者就能实现同步过程了。

2.2 EOS信号量工作过程的跟踪与源代码分析

(分析EOS信号量实现的核心源代码,简要阐述其实现方法;给出在本部分实验过程中完成的主要工作,包括调试、跟踪与思考等)

EOS的P、V原语实现是PsWaitForSemaphore、PsReleaseSemaphore,这两个函数使用KeEnableInterrupts开关中断来实现原语操作。

PsWaitForSemaphore流程图:

PsReleaseSemaphore函数的流程图:

2.3支持等待超时唤醒和批量释放功能的EOS信号量实现

(给出实现方法的简要描述、源代码、测试和结果等)

修改PsWaitForSemaphore函数,先用计数值和0 比较,当计数值大于0时,将计数值减1后直接返回成功;当计数值等于0 时,调用PspWait 函数阻塞线程的执行(将参数Milliseconds 做为PspWait 函数的第二个参数,并使用PspWait函数的返回值做为返回值)。

修改PsWaitForSemaphore函数如下添加支持超时。

STATUS

PsWaitForSemaphore(

IN PSEMAPHORE Semaphore,

IN ULONG Milliseconds

)

{

BOOL IntState;

ASSERT(KeGetIntNesting() == 0);

IntState = KeEnableInterrupts(FALSE);

STATUS ret;

if (Semaphore->Count > 0) {

Semaphore->Count--;

ret = STATUS_SUCCESS;

} else {

ret = PspWait(&Semaphore->WaitListHead, Milliseconds);

}

KeEnableInterrupts(IntState); /

return ret;

}

修改PsReleaseSemaphore函数如下添加批量释放支持。

STATUS

PsReleaseSemaphore(

IN PSEMAPHORE Semaphore,

IN LONG ReleaseCount,

OUT PLONG PreviousCount

)

{

STATUS Status;

BOOL IntState;

IntState = KeEnableInterrupts(FALSE);

if (Semaphore->Count + ReleaseCount > Semaphore->MaximumCount) { Status = STATUS_SEMAPHORE_LIMIT_EXCEEDED;

} else {

if (NULL != PreviousCount) {

*PreviousCount = Semaphore->Count;

}

int i;

for(i = ReleaseCount; i--;) {

Semaphore->Count++;

if (Semaphore->Count <= 0) {

PspWakeThread(&Semaphore->WaitListHead, STATUS_SUCCESS);

}

}

PspThreadSchedule();

Status = STATUS_SUCCESS;

}

KeEnableInterrupts(IntState);

return Status;

}

3 其他需要说明的问题

实验三时间片轮转调度(5分)

1 实验目的和要求

目的:理解进程(线程)调度的执行时机和过程,掌握调度程序实现的基本方法。

要求:

(1)跟踪调试EOS的线程调度程序,分析EOS基于优先级的抢占式调度的源代码;(2)修改EOS的调度程序,添加时间片轮转调度。

2 完成的实验内容

2.1 EOS基于优先级的抢占式调度工作过程的跟踪与源代码分析

(分析EOS基于优先级的抢占式调度的核心源代码,简要阐述其实现方法;给出在本部

分实验过程中完成的主要工作,包括调试、跟踪与思考等)

实验使用EOS提供的rr命令观察时间片的轮转。

1.准备实验

(1)启动OS Lab。

(2)新建一个EOS Kernel项目。

2.阅读控制台命令“rr”相关的源代码

(1)按F7生成在本实验3.1中创建的EOS Kernel项目。

(2)按F5启动调试。

(3)待EOS启动完毕,在EOS控制台中输入命令“rr”后按回车。

3.调试线程调度程序

a)调试当前线程不被抢先的情况

(1)结束之前的调试。

(2)在ke/sysproc.c文件的ThreadFunction函数中,调用fprintf函数的代码行(第680行)添加一个断点。

(3)按F5启动调试。

(4)待EOS启动完毕,在EOS控制台中输入命令“rr”后按回车。“rr”命令开始执行后,会在断点处中断。

(5)查看ThreadFunction函数中变量pThreadParameter->Y的值应该为0,说明正在调试的是第0个新建的线程。

(6)激活虚拟机窗口,可以看到第0个新建的线程还没有在控制台中输出任何内容,原因是fprintf函数还没有执行。

(7)激活OS Lab窗口后按F5使第0个新建的线程继续执行,又会在断点处中断。再次激活虚拟机窗口,可以看到第0个新建的线程已经在控制台中输出了第一轮循环的内容。可以多按几次F5查看每轮循环输出的内容。

b)调试当前线程被抢先的情况

(1)选择“调试”菜单中的“删除所有断点”,删除之前添加的所有断点。

(2)在ps/sched.c文件的PspSelectNextThread函数的第395行添加一个断点。(3)按F5继续执行,激活虚拟机窗口,可看到第0个新建的线程正在执行。

(4)在虚拟机窗口中按下一次空格键,EOS会在之前添加的断点处中断。

(5)在“监视”窗口中查看就绪位图的值为1000000000000000100000001,说明此时在优先级为24的就绪队列中存在就绪线程。在“监视”窗口中添加表达式“ListGetCount(&PspReadyListHeads[24])”,其值为1,说明优先级为24的就绪队列中只有一个就绪线程。扫描就绪位图后获得的最高优先级的值HighestPriority也就应该是24。

(6)按F10单步调试一次,执行的语句会将当前正在执行的第0个新建的线程,放入优先级为8的就绪队列的队首。“监视”窗口中显示的优先级为8的就绪队列中的线程数量就会增加1,变为20。

(7)继续按F10单步调试,直到在第444行中断执行,注意观察线程调度执行的每一个步骤。此时,正在执行的第0个新建的线程已经进入了“就绪”状态,让出了CPU。线程调度程序接下来的工作就是选择优先级最高的非空就绪队列的队首线程作为当前运行线程,也就是让优先级为24的线程在CPU上执行。

(8)按F10单步调试一次,当前线程PspCurrentThread指向了优先级为24的线程。可以在“快速监视”窗口中查看表达式“*PspCurrentThread”的值,注意线程控制块中StartAddr域的值为IopConsoleDispatchThread函数(在文件io/console.c中定义),说明这个优先级为24的线程是控制台派遣线程。

(9)继续按F10单步调试,直到在PspSelectNextThread函数返回前(第465行)中断执行,注意观察线程调度执行的每一个步骤。此时,优先级为24的线程已经进入了“运行”状态,在中断返回后,就可以开始执行了。在“监视”窗口中,就绪位图的值变为100000001,优先级为24的就绪队列中线程的数量变为0,就绪位图和就绪队列都是在刚刚被调用过的PspUnreadyThread函数内更新的。

(10)删除所有断点后结束调试。

2.2为EOS添加时间片轮转调度的实现

(给出实现方法的简要描述、源代码、测试和结果等)

修改PspRoundRobin函数如下:

VOID

PspRoundRobin(

VOID

)

{

if (NULL == PspCurrentThread || Running != PspCurrentThread->State) { return;

}

PspCurrentThread->RemainderTicks--;

if(PspCurrentThread->RemainderTicks > 0) {

return;

}

PspCurrentThread->RemainderTicks = TICKS_OF_TIME_SLICE;

if(0 == BIT_TEST(PspReadyBitmap, PspCurrentThread->Priority)) { return;

}

PspReadyThread(PspCurrentThread);

return;

}

在EOS控制台中输入命令“rr”后按回车,执行效果如下图:

3 其他需要说明的问题

相关文档