操作系统实验报告.docx
《操作系统实验报告.docx》由会员分享,可在线阅读,更多相关《操作系统实验报告.docx(17页珍藏版)》请在冰点文库上搜索。
课程设计(综合实验)报告
(2014--2015年度第一学期)
名 称:
操作系统综合实验题 目:
OSlab综合实验 院 系:
计算机系 班 级:
计科1202 学 号:
学生姓名:
指导教师:
赵文清 王新颖 设计周数:
第八、九周
成 绩:
日期:
2014年10月29日
实验3进程的创建
一、实验目的
练习使用EOSAPI函数CreateProcess创建一个进程,掌握创建进程的方法,理解进程和程序的区别。
调试跟踪CreateProcess函数的执行过程,了解进程的创建过程,理解进程是资源分配的单位。
二、实验内容
1准备实验
2练习使用控制台命令创建EOS应用程序的进程
3练习通过编程的方式让应用程序创建另一个应用程序的进程
4调试CreateProcess函数
5调试PsCreateProcess函数
6练习通过编程的方式创建应用程序的多个进程
三、问题答案及参考代码
1.在源代码文件NewTwoProc.c提供的源代码基础上进行修改,要求使用hello.exe同时创建10个进程。
提示:
可以使用PROCESS_INFORMATION类型定义一个有10个元素的数组,每一个元素对应一个进程。
使用一个循环创建10个子进程,然后再使用一个循环等待10个子进程结束,得到退出码后关闭句柄。
STARTUPINFOStartupInfo;PROCESS_INFORMATIONProcInfo[10];
ULONGulExitCode;
INTnResult=0;。
int i,j;//#ifdef_DEBUGasm("int $3\n nop");#endifprintf("Create10p//rocessesandwaitfortheprocessesexit...\n\n");StartupInfo.StdInput=GetStdHandle(STD_INPUT_HANDLE);StartupInfo.StdOutput=GetStdHandle(STD_OUTPUT_HANDLE);StartupInfo.StdError=GetStdHandle(STD_ERROR_HANDLE);
for(i=0;i<10;i++)if(CreateProcess("A:
\\Hello.exe",NULL,0,&StartupInfo,&ProcInfo[i]));
else{
for(j=0;j
GetExitCodeProcess(ProcInfo[j].ProcessHandle, &ulExitCode);
printf("\nTheprocess%dexitwith%d.\n",j,ulExitCode);CloseHandle(ProcInfo[j].ProcessHandle);
CloseHandle(ProcInfo[j].ThreadHandle);}printf("CreateProcessFailed,Errorcode:
0x%X.\n",GetLastError());
nResult=1;
returnnResult; }for(i=0;i<10;i++){
WaitForSingleObject(ProcInfo[i].ProcessHandle,INFINITE);GetExitCodeProcess(ProcInfo[i].ProcessHandle,&ulExitCode);}for(i=0i<10;i++){
printf("\nTheprocess%dexitwith%d.\n",i,ulExitCode);CloseHandle(ProcInfo[i].ProcessHandle);CloseHandle(ProcInfo[i].ThreadHandle);}
returnnResult;
}
3.在PsCreateProcess函数中调用了PspCreateProcessEnvironment函数后又先后调用了
PspLoadProcessImage和PspCreateThread函数,学习这些函数的主要功能。
能够交换这些函数被调用的顺序吗?
思考其中的原因。
PspCreateProcessEnvironment的主要功能是创建进程控制块,并且为进程创建了地址空间和分配了句柄表。
PspLoadProcessImage是将进程的可执行映像加载到了进程的地址空间中。
PspCreateThread创建了进程的主线程。
这三个函数被调用的顺序是不能够改变的。
就向上面描述的加载可执行映像之前必须已经为进程创建了地址空间,这样才能够确定可执行映像可以被加载到内存的什么位置。
在创建主线程之前必须已经加载了可执行映像,这样主线程才能够知道自己要从哪里开始执行,执行哪些指令。
因此不能交换他们的顺序。
实验4线程的状态和转换
一、实验目的
调试线程在各种状态间的转换过程,熟悉线程的状态和转换。
通过为线程增加挂起状态,加深对线程状态的理解。
二、实验内容
1准备实验
2调试线程状态的转换过程(阻塞—就绪、运行—就绪、就绪—运行、运行—阻塞)
3为线程增加挂起状态
三、问题答案及参考代码
PsResumThread(INHANDLEhThread){STATUSStatus;
BOOLIntState;
PTHREADThread;
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); }
returnStatus;
}resume命令执行的效果如图:
1.思考一下,在本实验中,当loop线程处于运行状态时,EOS中还有哪些线程,它们分别处于什么状态。
可以使用控制台命令pt查看线程的状态。
2.当loop线程在控制台1中执行,并且在控制台2中执行suspend命令时,为什么控制台
1中的loop线程处于就绪状态而不是运行状态?
答:
当在控制台2 中执行suspend命令时,实质上是优先级为24的控制台2线程抢占了处理器,也就是控制台2线程处于运行状态,所以此时loop线程处于就绪状态了。
4.总结一下在图5-3中显示的转换过程,哪些需要使用线程控制块中的上下文(将线程控制块中的上下文恢复到处理器中,或者将处理器的状态复制到线程控制块的上下文中),哪些不需要使用,并说明原因。
答:
一个进程在运行过程中或执行系统调用,或产生了一个中断事件,处理器都进行一次模式切换,操作系统接收控制权,有关系统例程完成必须的操作后,或恢复被中断进程或切换到新进程。
当系统调度新进程占有处理器时,新老进程随之发生上下文切换,因此,进程的运行被认为是在进程的上下文中执行,这时的控制权在操作系统手中,它在完成必要的操作后,可以恢复被中断的进程或切换到别的进程。
实验5进程的同步
一、实验目的
使用EOS的信号量,编程解决生产者—消费者问题,理解进程同步的意义。
调试跟踪EOS信号量的工作过程,理解进程同步的原理。
修改EOS的信号量算法,使之支持等待超时唤醒功能(有限等待),加深理解进程同步的原理。
二、实验内容
1、准备实验
2、使用EOS的信号量解决生产者-消费者问题
3、调试EOS信号量的工作过程
4、修改EOS的信号量算法if(Semaphore->Count>0){Semaphore->Count--;flag=STATUS_SUCCESS;
}//如果信号量大于零,说明尚有资源,可以为线程分配
else
flag=PspWait(&Semaphore->WaitListHead,Milliseconds);KeEnableInterrupts(IntState);//原子操作完成,恢复中断。
returnflag;
}//否则,说明资源数量不够,不能再为线程分配资源,因此要使线程等待
if(Semaphore->Count+ReleaseCount>Semaphore-
>MaximumCount){Status=STATUS_SEMAPHORE_LIMIT_EXCEEDED;}Else{
//记录当前的信号量的值//if(NULL!
=PreviousCount){*PreviousCount=Semaphore->Count;}intmm=Semaphore->Count;
//目前仅实现了标准记录型信号量,每执行一次信号量的释放操作只能使信号量的值增加1.//
while((!
ListIsEmpty(&Semaphore->WaitListHead))&&(ReleaseCount)){PspWakeThread(&Semaphore->WaitListHead,STATUS_SUCCESS);PspThreadSchedule();
ReleaseCount--;}
Semaphore->Count=mm+ReleaseCount;
//可能有线程被唤醒,执行线程调度。
//Status=STATUS_SUCCESS:
}
三、问题答案及参考代码
1.思考在ps/semaphore.c文件内的PsWaitForSemaphore和PsReleaseSemaphore函数中,为什么要使用原子操作?
答:
在执行释放信号量和等待信号量时,是不允许CPU响应外部中断的,否则,会产生不可预料的结果。
4.根据本实验3.3.2节中设置断点和调试的方法,自己设计一个类似的调试方案来验证消费者线程在消费24号产品时会被阻塞,直到生产者线程生产了24号产品后,消费者线程才被唤醒并继续执行的过程。
答:
调试方案如下:
①删除所有的断点。
②按F5启动调试。
OSLab会首先弹出
一个调试异常对话框。
③ 在调试异常对话框中选择“是”,调试会中断。
④ 在
Consumer函数中等待Full信号量的代码行(第173行)WaitForSingleObject(FullSemaphoreHandle,INFINITE);添加一个断点。
⑤在“断点”窗口(按Alt+F9打开)中此断点的名称上点击右键。
⑥在弹出的快捷菜单中选择“条件”
o⑦在“断点条件”对话框(按F1获得帮助)的表达式编辑框中,输入表达式“i==
24”。
⑧点击“断点条件”对话框中的“确定”按钮。
⑨按F5继续调试。
只有当消费者线程尝试消费24号产品时才会在该条件断点处中断。
实验6时间片轮转调度
一、实验目的
调试EOS的线程调度程序,熟悉基于优先级的抢先式调度。
为EOS添加时间片轮转调度,了解其它常用的调度算法。
二、实验内容
1、准备实验
2、阅读控制台命令“rr”相关的源代码
3、调试线程调度程序
4、为EOS添加时间片轮转调度
VOID
PspRoundRobin(VOID)
{
if(NULL!
=PspCurrentThread&&Running==PspCurrentThread->State)
{
PspCurrentThread->RemainderTicks--;if(0==PspCurrentThread->RemainderTicks){
PspCurrentThread->RemainderTicks=TICKS_OF_TIME_SLICE;if(BIT_TEST(PspReadyBitmap,PspCurrentThread->Priority))
{PspReadyThread(PspCurrentThread);}}
二、问题答案及参考代码
1.结合线程调度执行的时机,说明在ThreadFunction函数中,为什么可以使用“关中断”和“开中断”的方法来保护控制台这种临界资源。
一般情况下,应该使用互斥信号量
(MUTEX)来保护临界资源,但是在ThreadFunction函数中却不能使用互斥信号量,而只能使用“关中断”和“开中断”的方法,结合线程调度的对象说明这样做的原因。
答:
关中断后CPU就不会响应任何由外部设备发出的硬中断(包括定时计数器中断和键盘中断等)了,也就不会发生线程调度了,从而保证各个线程可以互斥的访问控制台。
这里绝对不能使用互斥信号量(mutex)保护临界资源的原因:
如果使用互斥信号量,则那些由于访问临界区而被阻塞的线程,就会被放入互斥信号量的等待队列,就不会在相应优先级的就绪列中了,而时间轮转调度算法是对就绪队列的线程进行轮转调度,而不是对这些被阻塞的线程进行调度,也就无法进行实验了。
使用“关中断”和“开中断”进行同步就不会改变线程的状态,可以保证那些没有获得处理器的线程都在处于就绪队列中。
3.使用低优先级线程也能获得执行机会的调度算法:
在ke/sysproc.c文件中的
ConsoleCmdRoundRobin函数调用Sleep函数语句的后面添加下面的语言,即可以演优先级线程抢占处理器后,低优先级线程无法运行的情况,待高优先级线程结束后,低优先级线程才能够继续运行。
HANDLEThreadHandle;THREAD_PARAMETERThreadParameter;
_asm(“cli”);ThreadParameter.Y=20;
ThreadParameter.StdHandle=StdHandle;ThreadParameter=(HANDLE)CreateThread{0,ThreadFunction,(PVOID)&ThreadParameter,0,NULL);PsSetThreadPriority(ThreeadHandle,9);
_asm(“sti”);
Sleep(10*1000);
TerminateThread(ThreadHandle,0);CloseHandle(ThreadHandle);Sleep(10*1000);
解决该问题的最简单的方法是实现动态优先级算法。
动态优先级是指在创建进程时所
赋予的优先级,可以随线程的推进而改变,以便获得良好的调度性能。
例如,可用规定,在就绪队列中的线程,随着其等待时间的增长,其优先级以速率X增加,并且正在执行的线程,其优先级以速率y下降。
这样,在各个线程具有不同优先级的情况下,对于优先级低的线程,在等待足够的时间后,其优先级便可能升为最高,从而获得被执行的机会。
此时,在基于优先级的抢占式调度算法、时间片轮转调度算法和动态优先级算法的共同作用下,可防止一个高优先级的长作业长期的垄断处理器。
4.EOS内核时间片大小取60ms(和Windows操作系统完全相同),在线程比较多时,就可以观察出线程轮流执行的情况(因为此时一次轮转需要60ms,20个线程轮流执行一次需要
60×20=1200ms,也就是需要1秒多的时间,所以EOS的控制台上可以清楚地观察到线程轮流执行的情况)。
但是在Windows、Linux等操作系统启动后,正常情况下都有上百个线程在并发执行,为什么觉察不到它们被轮流执行,
并且每个程序都运行的很顺利呢?
答:
在Windows、linux等操作系统中,虽然都提供了时间片轮转调度算法却很少真正
被派上用场,下面解释原因,在Windows任务管理器中,即使系统中已经运行了数百个线程,
但CPU的利用率仍然很低,甚至为0.因为这些线程在大部分时间都处于阻塞状态,阻塞的原因是各种各样的,最主要的原因是等待I/O完成或者等待命令消息的到达。
例如,在编辑
Word文档时,每敲击一次键盘,Word就会立即作出反应,并且文档中插入字符。
此时会感觉Word运行的非常流畅。
事实上,并非如此,Word主线程大部分时间都处于阻塞等待状态,等待用户敲击键盘。
在用户没有敲击键盘或没有使用鼠标点击时,Word主线程处于阻塞状态,它将让出处理器给其它需要的线程。
当用户敲击一个按键后,Word主线程将会立刻被操作系统唤醒,此时Word开始处理请求。
Word在处理输入请求时所用的CPU时间是非常短的
(因为CPU非常快),是微秒级的,远远低于时间片轮转调度的时间片大小(Windows下是60毫秒),处理完毕后Word又立刻进入阻塞状态,等待用户下一次敲击键盘。
或者拿音乐播放器来分析,表面上感觉播放器在不停地播放音乐,但是CPU的利用率仍然会很低。
这是由于播放器将一段声音编码交给声卡,由声卡来播放,在声卡播放完这段声音之前,播放器都是处于阻塞等待状态的。
当声卡播放完片段后,播放器将被唤醒,然后它将下一个声音片段交给声卡继续播放。
掌握了上面的知识后,就可以很容易解释为什么这么多线程同时在运行而一点都感觉不到轮替现象。
一、实验目的
实验7物理存储器与进程逻辑地址空间的管理
通过查看物理存储器的使用情况,并练习分配和回收物理内存,从而掌握物理存储器的管理方法。
通过查看进程逻辑地址空间的使用情况,并练习分配和回收虚拟内存,从而掌握进程逻辑地址空间的管理方法。
二、实验内容
1、准备实验
2、阅读控制台命令“pm”相关的源代码,并查看其执行的结果(pm命令的执行结果)
3、分配物理页和释放物理页(配物理页或者释放物理页后物理存储器的变化情况)
4、阅读控制台命令“vm”相关的源代码,并查看其执行的结果 使用“vm”命令查看系统进程虚拟地址描述符的结果
5、在系统进程中分配虚拟页和释放虚拟页
6、在应用程序进程中分配虚拟页和释放虚拟页
三、问题答案及参考代码
INT*d;
if(d=VirtualAlloc(0,sizeof(int),MEM_RESERVE|MEM_COMMIT)){//调用API函数VirtualAlloc,分配一个整型变量所需的空间,并使用一个整型变量的指针指向这个空间printf("Allocated%dbytesvirtualmemoryof0x%x\n\n",sizeof(int),d);
printf("virtualmemoryoriginalvalue:
0x%x\n\n",*d);
*d=0xFFFFFFFF;//修改整型变量的值为0xFFFFFFFFprintf("virtualmemorynewvalue:
0x%x\n\n",*d);printf("\nWaitfor10seconds\n");
Sleep(10000);//调用API函数Sleep,等待10秒钟。
if(VirtualFree(d,0,MEM_RELEASE))//调用API函数VirtualFree,释放之前分配的整型变量的空间
printf("\nRealeasevirtualmemorysuccess!
\n");else
printf("realeaseerror\n");printf("\nEndlessloop!
");
for(;;);
}
else
{
printf("error\n");
return-1;//若不能成功分配,打印出error,返回-1.
}
printf("Helloworld!
\n");return0;
1.在本实验3.3中,如果分配了物理页后,没有回收,会对EOS操作系统造成什么样的影响?
目前EOS操作系统内核函数MiAllocateAnyPages能处理所有物理页被分配完毕的情况吗?
例如在没有可分配的物理页的情况下调用该内核函数,是否会返回失败?
如果内核函数MiAllocateAnyPages还不能处理这种极端情况,尝试修改代码解决这个问题。
答:
如果分配了物理页后没有回收,EOS 操作系统将不能再使用未回收的物理页,如果分配的物理页都没有进行回收可能会造成EOS没有可用的物理页,从而导致EOS停止运
行。
目前EOS操作系统内核函数MiAllocateAnyPages还没有处理没有物理页可分配的情况。
2.在本实验3.3中,在分配物理页时是调用的内核函数MiAllocateAnyPages,该函数会优先分配空闲页,尝试修改代码,调用内核函数MiAllocateZeroedPages优先分配零页,并调试分配零页的情况。
尝试从性能的角度分析内核函数MiAllocateAnyPages和
MiAllocateZeroedPages。
尝试从安全性的角度分析分配零页的必要性。
答:
从性能的角度来分析,调用MIAllocateAnyPages函数分配物理页在某些情况下比
调用MIAllocateZeroedPages.函数要快速。
从安全行的角度来分析,分配零页更加安全。
例如,一个物理页被操作系统存储过重要的密码信息后被释放,如果没有清零就被分配给用户程序,则用户程序就可能从这个物理页中获得重要的密码信息。
3.观察本实验3.4中使用“vm”命令输出的系统进程的虚拟地址描述符(图15-3),可以看到在2号描述符和3号描述符之间有两个虚拟页的空隙,尝试结合虚拟页的分配和释放说明产生这个空隙的原因。
答:
产生的空隙是由于有虚拟页被释放造成的,在EOS启动时有一个初始化线程在初始化完毕后就退出了,线程的堆栈所占用的虚拟页就被释放了。
5.在本实验3.5中,调用MmAllocateVirtualMemory函数分配虚拟页时只使用了
MEM_RESERVE标志,没有使用MEM_COMMIT标志,尝试说明这两个标志的区别。
修改代码,在调用MmAllocateVirtualMemory函数时增加使用MEM_COMMIT标志,并调试为虚拟页映射物理页的过程。
答:
使用MEM_RESERVE标志分配虚拟页时,没有为其映射实际的物理页。
使用
MEM_COMMIT表示分配虚拟页时,会为其映射实际的物理页。
实验8分页存储器管理
一、实验目的
学习i386处理器的二级页表硬件机制,理解分页存储器管理原理。
查看EOS应用程序进程和系统进程的二级页表映射信息,理解页目录