操作系统实验--进程的同步heu
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
操作系统
实验报告
哈尔滨工程大学
计算机科学与技术学院
第六讲进程的同步
一、实验概述
1. 实验名称
进程的同步
2. 实验目的
(1)使用EOS 的信号量编程解决生产者—消费者问题,理解进程同步的意义。
(2)调试跟踪EOS 的信号量的工作过程,理解进程同步的原理。
(3)修改EOS 的信号量算法,使之支持等待超时唤醒功能(有限等待),加深理解进程同步的原理。
3. 实验类型
验证型实验,设计性实验
4. 实验内容
(1)准备实验
(2)使用 EOS 的信号量解决生产者-消费者问题
(3)调试 EOS 信号量的工作过程
1)创建信号量2)等待释放信号量3)等待信号量(不阻塞)4)释放信号量(不唤醒)5) 等待信号量(阻塞)6) 释放信号量(唤醒)
(4)修改 EOS 的信号量算法
二、实验环境
操作系统集成实验环境OS Lab
三、实验过程
1. 设计思路和流程图
2. 算法实现
3. 需要解决的问题及解答
(1). P143生产者在生产了13号产品后本来要继续生产14号产品,可此时生产者为什么必须等待消费者消费了4号产品后,才能生产14号产品呢?生产者和消费者是怎样使用同步对象来实现该同步过程的呢?
答:此时生产了0-13号14个产品,消费了0-3号4个产品,缓冲区都占满了。
只有缓冲区有空闲生产者才能生产东西,有权向里面放东西。
所以它必须等到消费者,取走产品,有空闲缓冲区时,才继续生产14号产品。
(2). P145-3.4 修改EOS的信号量算法(只看一次消费1个产品的,一次消费2个产品的可以写到实验报告中)
答:见三,四部分
(3). 思考在ps/semaphore.c文件内的PsWaitForSemaphore和PsReleaseSemaphore函数中,为什么要使用原子操作?
答:原子操作要求一旦开始就要运行到结束,不能有中断。
在执行等待信号量和释放信号量的时候,不允许cpu响应外部中断,所以使用原子操作。
(4). 绘制ps/semaphore.c文件内PsWaitForSemaphore和PsReleaseSemaphore函数的流程图。
PsWaitForSemaphore
Ps Release Semaphore
4. 主要数据结构、实现代码及其说明
1)修改PsWaitForSemaphore函数
if (Semaphore->Count>0){
Semaphore->Count--;
flag=STATUS_SUCCESS;
}//如果信号量大于零,说明尚有资源,可以为线程分配
else
flag=PspWait(&Semaphore->WaitListHead, Milliseconds);
KeEnableInterrupts(IntState); // 原子操作完成,恢复中断。
return flag;
}//否则,说明资源数量不够,不能再为线程分配资源,因此要使线程等待
2)修改PsReleaseSemaphore函数
if (Semaphore->Count + ReleaseCount > Semaphore->MaximumCount) {
Status = STATUS_SEMAPHORE_LIMIT_EXCEEDED;
} else {
//
// 记录当前的信号量的值。
//
if (NULL != PreviousCount) {
*PreviousCount = Semaphore->Count;
}
int mm=Semaphore->Count;
//
// 目前仅实现了标准记录型信号量,每执行一次信号量的释放操作
// 只能使信号量的值增加 1。
//
while ((!ListIsEmpty(&Semaphore->WaitListHead))&&(ReleaseCount)){ PspWakeThread(&Semaphore->WaitListHead, STATUS_SUCCESS);
PspThreadSchedule();
ReleaseCount--;
}
Semaphore->Count=mm+ReleaseCount;
//
// 可能有线程被唤醒,执行线程调度。
//
Status = STATUS_SUCCESS;
}
5. 源程序并附上注释
#include "psp.h"
VOID
PsInitializeSemaphore(
IN PSEMAPHORE Semaphore,
IN LONG InitialCount,
IN LONG MaximumCount
)
/*++
功能描述:
初始化信号量结构体。
参数:
Semaphore -- 要初始化的信号量结构体指针。
InitialCount -- 信号量的初始值,不能小于0 且不能大于MaximumCount。
MaximumCount -- 信号量的最大值,必须大于0。
返回值:
无。
--*/
{
ASSERT(InitialCount >= 0 && InitialCount <= MaximumCount && MaximumCount > 0);
Semaphore->Count = InitialCount;
Semaphore->MaximumCount = MaximumCount;
ListInitializeHead(&Semaphore->WaitListHead);
}
STATUS
PsWaitForSemaphore(
IN PSEMAPHORE Semaphore,
IN INT Milliseconds,
IN STATUS i
)
/*++
功能描述:
信号量的Wait 操作(P 操作)。
参数:
Semaphore -- Wait 操作的信号量对象。
Milliseconds -- 等待超时上限,单位毫秒。
返回值:
STATUS_SUCCESS。
当你修改信号量使之支持超时唤醒功能后,如果等待超时,应该返回STA TUS_TIMEOUT。
--*/
{
BOOL IntState;
ASSERT(KeGetIntNesting() == 0); // 中断环境下不能调用此函数。
IntState = KeEnableInterrupts(FALSE); // 开始原子操作,禁止中断。
//
// 目前仅实现了标准记录型信号量,不支持超时唤醒功能,所以PspWait 函数
// 的第二个参数的值只能是INFINITE。
//
if(Semaphore->Count > 0)
{ Semaphore->Count--;
i=STATUS_SUCCESS;
}
else
{
i=PspWait(&Semaphore->WaitListHead,Milliseconds );
}
KeEnableInterrupts(IntState); // 原子操作完成,恢复中断。
return i;
}
STATUS
PsReleaseSemaphore(
IN PSEMAPHORE Semaphore,
IN LONG ReleaseCount,
OUT PLONG PreviousCount
)
/*++
功能描述:
信号量的Signal 操作(V 操作)。
参数:
Semaphore -- Wait 操作的信号量对象。
ReleaseCount -- 信号量计数增加的数量。
当前只能为1。
当你修改信号量使之支持
超时唤醒功能后,此参数的值能够大于等于1。
PreviousCount -- 返回信号量计数在增加之前的值。
返回值:
如果成功释放信号量,返回STATUS_SUCCESS。
--*/
{
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 j=Semaphore->Count;
//
// 目前仅实现了标准记录型信号量,每执行一次信号量的释放操作
// 只能使信号量的值增加1。
//
while((!ListIsEmpty(&Semaphore->WaitListHead))&&(ReleaseCount)){ PspWakeThread(&Semaphore->WaitListHead, STATUS_SUCCESS);
PspThreadSchedule();
ReleaseCount--;
}
Semaphore->Count=j+ReleaseCount;
Status = STATUS_SUCCESS;
}
KeEnableInterrupts(IntState); // 原子操作完成,恢复中断。
return Status;
}
POBJECT_TYPE PspSemaphoreType = NULL;
//
// 用于初始化semaphore 结构体的参数结构体。
//
typedef struct _SEM_CREATE_PARAM{
LONG InitialCount;
LONG MaximumCount;
}SEM_CREATE_PARAM, *PSEM_CREATE_PARAM;
//
// semaphore 对象的构造函数,在创建新semaphore 对象时被调用。
//
VOID
PspOnCreateSemaphoreObject(
IN PVOID SemaphoreObject,
IN ULONG_PTR CreateParam
)
{
PsInitializeSemaphore( (PSEMAPHORE)SemaphoreObject,
((PSEM_CREATE_PARAM)CreateParam)->InitialCount,
((PSEM_CREATE_PARAM)CreateParam)->MaximumCount ); }
//
// semaphore 对象类型的初始化函数。
//
VOID
PspCreateSemaphoreObjectType(
VOID
)
{
STATUS Status;
OBJECT_TYPE_INITIALIZER Initializer;
Initializer.Create = PspOnCreateSemaphoreObject;
Initializer.Delete = NULL;
Initializer.Wait = (OB_WAIT_METHOD)PsWaitForSemaphore;
Initializer.Read = NULL;
Initializer.Write = NULL;
Status = ObCreateObjectType("SEMAPHORE", &Initializer, &PspSemaphoreType);
if (!EOS_SUCCESS(Status)) {
KeBugCheck("Failed to create semaphore object type!");
}
}
//
// semaphore 对象的构造函数。
//
STATUS
PsCreateSemaphoreObject(
IN LONG InitialCount,
IN LONG MaximumCount,
IN PSTR Name,
OUT PHANDLE SemaphoreHandle
)
{
STATUS Status;
PVOID SemaphoreObject;
SEM_CREATE_PARAM CreateParam;
if(InitialCount < 0 || MaximumCount <= 0 || InitialCount > MaximumCount){ return STATUS_INV ALID_PARAMETER;
}
//
// 创建信号量对象。
//
CreateParam.InitialCount = InitialCount;
CreateParam.MaximumCount = MaximumCount;
Status = ObCreateObject( PspSemaphoreType,
Name,
sizeof(SEMAPHORE),
(ULONG_PTR)&CreateParam,
&SemaphoreObject);
if (!EOS_SUCCESS(Status)) {
return Status;
}
Status = ObCreateHandle(SemaphoreObject, SemaphoreHandle);
if (!EOS_SUCCESS(Status)) {
ObDerefObject(SemaphoreObject);
}
return Status;
}
//
// semaphore 对象的signal 操作函数。
//
STATUS
PsReleaseSemaphoreObject(
IN HANDLE Handle,
IN LONG ReleaseCount,
IN PLONG PreviousCount
)
{
STATUS Status;
PSEMAPHORE Semaphore;
if (ReleaseCount < 1) {
return STATUS_INV ALID_PARAMETER;
}
// 由semaphore 句柄得到semaphore 对象的指针。
Status = ObRefObjectByHandle(Handle, PspSemaphoreType, (PVOID*)&Semaphore);
if (EOS_SUCCESS(Status)) {
Status = PsReleaseSemaphore(Semaphore, ReleaseCount, PreviousCount);
ObDerefObject(Semaphore);
}
return Status;
}
6. 程序运行时的初值和运行结果
(1)准备实验
1)启动 OS Lab。
2)新建一个 EOS Kernel 项目。
3)生成 EOS Kernel 项目,从而在该项目文件夹中生成 SDK 文件夹。
4)新建一个 EOS 应用程序项目。
5)使用在第 3 步生成的 SDK 文件夹覆盖 EOS 应用程序项目文件夹中的 SDK 文件夹。
(2)使用EOS的信号量解决生产者-消费者问题
1)使用 pc.c 文件中的源代码,替换之前创建的 EOS 应用程序项目中 EOSApp.c 文件内的源代码。
2)按 F7 生成修改后的 EOS 应用程序项目。
3)按 F5 启动调试。
OS Lab 会首先弹出一个调试异常对话框。
4)在调试异常对话框中选择“否”,继续执行。
5)立即激活虚拟机窗口查看生产者-消费者同步执行的过程。
6)待应用程序执行完毕后,结束此次调试。
(3)调试EOS信号量的工作过程
1)创建信号量
○1按 F5 启动调试 EOS 应用项目。
OS Lab 会首先弹出一个调试异常对话框。
○2在调试异常对话框中选择“是”,调试会中断。
○3在 main 函数中创建 Empty 信号量的代码行(第 77 行)
EmptySemaphoreHandle = CreateSemaphore(BUFFER_SIZE, BUFFER_SIZE, NULL); 添加一个断点。
○4按 F5 继续调试,到此断点处中断。
○5按 F11 调试进入 CreateSemaphore 函数。
可以看到此 API 函数只是调用了 EOS 内核中的PsCreateSemaphoreObject 函数来创建信号量对象。
○6按 F11 调试进入 semaphore.c 文件中的 PsCreateSemaphoreObject 函数。
在此函数中,会在 EOS 内核管理的内存中创建一个信号量对象(分配一块内存),而初始化信号量对象中各个成员的操作是在 PsInitializeSemaphore 函数中完成的。
○7在 semaphore.c 文件的顶部查找到 PsInitializeSemaphore 函数的定义(第 19 行),在此函数的第一行(第 39 行)代码处添加一个断点。
○8按 F5 继续调试,到断点处中断。
观察 PsInitializeSemaphore 函数中用来初始化信号量结构体成员的值,应该和传入 CreateSemaphore 函数的参数值是一致的。
○9按 F10 单步调试 PsInitializeSemaphore 函数执行的过程,查看信号量结构体被初始化的过程。
打开“调用堆栈”窗口,查看函数的调用层次。
2)等待信号量(不阻塞)
○1删除所有的断点(防止有些断点影响后面的调试)。
○2在 eosapp.c 文件的 Producer 函数中,等待 Empty 信号量的代码行(第 144 行)
WaitForSingleObject(EmptySemaphoreHandle, INFINITE); 添加一个断点。
○3按 F5 继续调试,到断点处中断。
○4WaitForSingleObject 函数最终会调用内核中的 PsWaitForSemaphore 函数完成等待操作。
所以,在 semaphore.c 文件中 PsWaitForSemaphore 函数的第一行(第 68 行)添加一个断点。
○5按 F5 继续调试,到断点处中断。
○6按 F10 单步调试,直到完成 PsWaitForSemaphore 函数中的所有操作。
可以看到此次执行并没有进行等待,只是将 Empty 信号量的计数减少了 1(由 10 变为了 9)就返回了。
3)释放信号量(不唤醒)
○1删除所有的断点(防止有些断点影响后面的调试)。
○2在 eosapp.c 文件的 Producer 函数中,释放 Full 信号量的代码行(第 152 行)
ReleaseSemaphore(FullSemaphoreHandle, 1, NULL); 添加一个断点。
○3按 F5 继续调试,到断点处中断。
○4按 F11 调试进入 ReleaseSemaphore 函数。
○5继续按 F11 调试进入 PsReleaseSemaphoreObject 函数。
○6先使用 F10 单步调试,当黄色箭头指向第 269 行时使用 F11 单步调试,进入PsReleaseSemaphore函数。
○7按 F10 单步调试,直到完成 PsReleaseSemaphore 函数中的所有操作。
可以看到此次执行没有唤醒其它线程(因为此时没有线程在 Full 信号量上被阻塞),只是将 Full 信号量的计数增加了 1(由 0 变为了 1)。
4)等待信号量(阻塞)
○1结束之前的调试。
○2删除所有的断点。
○3按 F5 重新启动调试。
OS Lab 会首先弹出一个调试异常对话框。
○4在调试异常对话框中选择“是”,调试会中断。
○5在 semaphore.c 文件中的 PsWaitForSemaphore 函数的
PspWait(&Semaphore->WaitListHead, INFINITE); 代码行(第 78 行)添加一个断点。
○6按 F5 继续调试,并立即激活虚拟机窗口查看输出。
开始时生产者、消费者都不会被信号量阻塞,同步执行一段时间后才在断点处中断。
○7中断后,查看“调用堆栈”窗口,有 Producer 函数对应的堆栈帧,说明此次调用是从生产者线程函数进入的。
○8在“调用堆栈”窗口中双击 Producer 函数所在的堆栈帧,绿色箭头指向等待 Empty 信号量的代码行,查看 Producer 函数中变量 i 的值为 14,表示生产者线程正在尝试生产 14 号产品。
○9在“调用堆栈”窗口中双击 PsWaitForSemaphore 函数的堆栈帧,查看 Empty 信号量计数(Semaphore->Count)的值为-1,所以会调用 PspWait 函数将生产者线程放入 Empty 信号量的等待队列中进行等待(让出 CPU)。
○10激活虚拟机窗口查看输出的结果。
生产了从 0 到 13 的 14 个产品,但是只消费了从 0 到 3 的 4 个产品,所以缓冲池中的 10 个缓冲区就都被占用了,这与之前调试的结果是一致的。
5)释放信号量(唤醒)
○1删除所有断点。
○2在 eosapp.c 文件的 Consumer 函数中,释放 Empty 信号量的代码行(第 180 行)
ReleaseSemaphore(EmptySemaphoreHandle, 1, NULL); 添加一个断点。
○3按 F5 继续调试,到断点处中断。
○4查看 Consumer 函数中变量 i 的值为 4,说明已经消费了 4 号产品。
○5按照 3.3.2.2 中的方法使用 F10 和 F11 调试进入 PsReleaseSemaphore 函数。
○6查看 PsReleaseSemaphore 函数中 Empty 信号量计数(Semaphore->Count)的值为-1,和生产者
线程被阻塞时的值是一致的。
○7按 F10 单步调试 PsReleaseSemaphore 函数,直到在代码行(第 132 行)
PspWakeThread(&Semaphore->WaitListHead, STATUS_SUCCESS);
处中断。
此时Empty 信号量计数的值已经由-1 增加为了0,需要调用PspWakeThread 函数唤醒阻
塞在Empty 信号量等待队列中的生产者线程(放入就绪队列中),然后调用PspSchedule 函数执
行调度,这样生产者线程就得以继续执行。
6)验证生产者线程被唤醒后,是从之前被阻塞时的状态继续执行的:
○1在semaphore.c 文件中PsWaitForSemaphore 函数的最后一行(第83 行)代码处添加一个断点。
○2按F5 继续调试,在断点处中断。
○3查看PsWaitForSemaphore 函数中Empty 信号量计数(Semaphore->Count)的值为0,和生产者线程被唤醒时的值是一致的。
○4在“调用堆栈”窗口中可以看到是由Producer 函数进入的。
激活Producer 函数的堆栈帧,查看Producer 函数中变量i 的值为14,表明之前被阻塞的、正在尝试生产14 号产品的生产者线程已
经从PspWait 函数返回并继续执行了。
○5结束此次调试。
(4)修改 EOS 的信号量算法
1)修改 PsWaitForSemaphore 函数
2)修改 PsReleaseSemaphore 函数
3)使用修改完毕的 EOS Kernel 项目生成完全版本的 SDK 文件夹,并覆盖之前的生产者-消费者应用程序项目的 SDK 文件夹。
4)按 F5 调试执行原有的生产者-消费者应用程序项目,结果必须仍然与图 13-2 一致。
如果有错误,可以调试内核代码来查找错误,然后在内核项目中修改,并重复步骤 1。
5)将 Producer 函数中等待 Empty 信号量的代码行
WaitForSingleObject(EmptySemaphoreHandle, INFINITE); 替换为
while(WAIT_TIMEOUT == WaitForSingleObject(EmptySemaphoreHandle, 300)){
printf("Producer wait for empty semaphore timeout\n");
}
6)将 Consumer 函数中等待 Full 信号量的代码行
WaitForSingleObject(FullSemaphoreHandle, INFINITE);
替换为
while(WAIT_TIMEOUT == WaitForSingleObject(FullSemaphoreHandle, 300)){
printf("Consumer wait for full semaphore timeout\n");
}
7)启动调试新的生产者-消费者项目,查看在虚拟机中输出的结果,验证信号量超时等待功能是否
能够正常执行。
如果有错误,可以调试内核代码来查找错误,然后在内核项目中修改,并重复步骤 1。
8)如果超时等待功能已经能够正常执行,可以考虑将消费者线程修改为一次消费两个产品,来测试
ReleaseCount 参数是否能够正常使用。
使用实验文件夹中 NewConsumer.c 文件中的 Consumer 函数替换原有的Consumer函数
9)一次性消费一个产品
10)一次性消费两个产品
四、实验体会
这次实验里我增强了对于进程同步算法的理解,对书上的消费者生产者模型有了更深的认识,并且留下了新的印象。
这次实验提高了我的编程能力和各方面
动手能力,让我感受到进程相关内容编程的风格和方法。