操作系统 实验指导讲义全部Word文件下载.docx
《操作系统 实验指导讲义全部Word文件下载.docx》由会员分享,可在线阅读,更多相关《操作系统 实验指导讲义全部Word文件下载.docx(104页珍藏版)》请在冰豆网上搜索。
4.实验提示17
5.实验运行结果17
6.有关实验的改进意见:
21
实验八、银行家算法28
1.目的和要求28
2.实验内容28
3.实验环境28
4.实验提示28
实验九、存储管理51
1.目的和要求51
2.实验内容51
3.实验环境51
4.实验提示51
5.实验运行结果52
实验十、文件系统设计57
1.目的和要求57
2.实验内容57
3.实验环境57
4.实验提示57
实验一、认识常见的操作系统
计算机系统由硬件系统和软件系统两部分组成。
硬件系统是用户可以直接看见的部分,是完成用户各种任务的物质基础,而软件系统是对硬件功能的扩充,是人类编制出来,为方便人们使用计算机硬件或为完成某一特定任务和目标而编制的计算机程序。
软件因作用不同又可分为系统软件和应用软件。
操作系统是最重要的一种系统软件,是对硬件系统的第一次扩充。
自从第一代操作系统问世以来,随着计算机应用的发展,人们对计算机资源共享、通讯、和计算机效率等诸方面的要求越来越高,操作系统也因此发展得越来越快,市面上不同目的、各种特色的操作系统也应运而生。
1.目的和要求
通过Internet和/或图书馆馆藏资源,让学生从课堂以外,从感性上认识操作系统这种系统软件的功能,了解市场上常见的操作系统,为后面进一步的学习打下基础。
2.实验内容
搜索并归纳如下信息:
什么是操作系统?
你买回一台新计算机后,要装的第一个软件是什么?
为什么必须在计算机上安装操作系统?
目前市场上常见的操作系统有哪些类型?
请尽可能多地罗列出目前市场上已有的操作系统产品,分别给出这些操作系统所取得的成就,采用的新技术,以及这些操作系统的特点、特色和不足,指明它们的开发公司和开发年代。
实验二安装Linux操作系统
通过实验,使学生了解Linux安装的基本硬件资源要求;
学会安装Linux系统、启动Linux系统;
了解Linux多引导器的配置。
把Linux安装到本地硬盘或虚拟机上。
3.实验指导
1)如果BIOS支持光盘启动,则插入Linux安装光盘,重新启动计算机。
如果从DOS环境启动,则在DOS提示符下执行批处理命令,如autoboot。
或者,准备启动软盘,插入并重新启动计算机。
2)对硬盘分区,留出交换空间和文件系统的空间。
3)按提示分阶段装入系统。
4)配置系统。
实验三、操作系统的命令接口和图形接口
熟悉使用Linux字符界面、窗口系统的常用命令。
熟悉运用Linux常用的编程工具和在线求助系统。
掌握在Linux操作系统环境上编辑、编译、调试、运行一个C语言程序的全过程。
1)熟悉开机后登录Linux系统和退出系统的过程;
2)熟悉Linux字符界面——虚拟终端窗口和shell,以及图形界面——X-Window(如gnome或KDE):
练习并掌握常用的Linux操作命令,如ls、cat、ps、df、find、grep、cd、more、cp、rm、kill、at、vi、cc、man、help、control+d/c、等;
熟悉常用shell的提示符;
熟悉字符窗口与图形界面之间的切换。
3)学习使用Linux的在线求助系统,如man和help命令等。
4)掌握一种Linux的编辑器,特别是字符界面的vi工具的使用。
5)用vi编辑一个打印“Hello,IamaCprogram”字串的C语言程序,然后编译并运行它,记下整个过程。
熟悉gcc、gdb等编译器、调试器的使用。
实验四、观察Linux进程的异步并发执行
1.实验目的
通过本实验,加深学生理解进程与程序的区别;
掌握进程并发执行的原理,理解进程并发执行的特点,区分进程并发执行与串行执行;
了解fork()系统调用的返回值,掌握用fork()创建进程的方法;
熟悉wait、exit等系统调用。
1.编写一C语言程序,实现在程序运行时通过系统调用fork()创建两个子进程,使父、子三进程并发执行,父亲进程执行时屏幕显示“Iamfather”,儿子进程执行时屏幕显示“Iamson”,女儿进程执行时屏幕显示“Iamdaughter”。
2.多次连续反复运行这个程序,观察屏幕显示结果的顺序,直至出现不一样的情况为止。
记下这种情况,试简单分析其原因。
3.修改程序,利用nice()改变各进程的优先级,观察不同情况下进程调度效果的区别。
4.修改程序,在父、子进程中分别使用wait、exit等系统调用“实现”其同步推进,多次反复运行改进后的程序,观察并记录运行结果。
3.实验环境
Linux系统,C语言
4.实验提示
由于每个进程都至少包含下面三个元素:
一个可以执行的程序;
和该进程相关联的全部数据(包括变量,内存空间,缓冲区等等);
程序的执行上下文(executioncontext)。
进程表中的每一个表项,记录的是当前操作系统中一个进程的情况。
对于单CPU的情况而言,每一特定时刻只有一个进程占用CPU,但是系统中可能同时存在多个活动的(等待执行或继续执行的)进程。
一个称为“程序计数器(programcounter,pc)”的寄存器,指出当前占用CPU的进程要执行的下一条指令的位置。
当分给某个进程的CPU时间已经用完,操作系统将该进程相关的寄存器的值,保存到该进程在进程表中对应的表项里面;
把将要接替这个进程占用CPU的那个进程的上下文,从进程表中读出,并更新相应的寄存器(这个过程称为“上下文交换(processcontextswitch)”,其中包括了程序寄存器PC)。
4.1fork函数
观察下面程序的运行情况,理解fork()函数。
#include<
unistd.h>
sys/types.h>
main()
{
pid_tpid;
printf("
\n[%d]notforkpid=%d\n"
getpid(),pid);
pid=fork();
\n[%d]forkedpid=%d\n"
if(pid<
0)
{
errorinfork!
\n"
);
getchar();
exit
(1);
}
elseif(pid==0)
\n[%d]inchildprocess,p_id=%d\n"
getpid(),getpid());
else
\n[%d]inparentprocess,mypid=%d\n"
\n[%d]inparentprocess,mygetpid=%d\n"
}
函数fork()并不是进行进程切换,而是复制一个当前进程。
举例来说,假如父进程号为100,当进入fork()系统调用后,操作系统就把100号进程一模一样地复制出来一个新的子进程101号。
所谓一模一样,是指100号进程与101号进程所包含的任何信息,例如变量的值,空间分配,特别是正在执行的语句等等都相同。
这时你的一套代码便同时在这两个进程中执行,就象一个EXE文件被同时执行了两次一样。
由于两个进程下一步要执行的语句都是从fork()返回,这时操作系统就特意让100号进程中的fork()返回101,而101号进程中的fork()返回0,这样一套代码就能够分辨清楚到底是处在哪个进程中了。
pid=fork();
父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(一个正整数,如101),所以下面的if语句中pid<
0,pid==0的两个分支都不会执行。
所以父进程中输出iamtheparentprocess...
子进程在之后的某个时候得到调度,它的上下文被换入,占据CPU,操作系统对fork的实现,使得子进程中fork调用返回0。
所以在这个进程中pid=0。
这个进程继续执行的过程中,if语句中pid<
0不满足,但是pid==0是true。
所以输出iamthechildprocess...
4.2exec函数簇
在Linux中,系统调用execve()对当前进程进行替换,替换者为一个指定的程序,其参数包括文件名(filename)、参数列表(argv)以及环境变量(envp)。
exec函数族很多,但它们功能大致相同。
在Linux中,它们分别是:
execl,execlp,execle,execv,execve和execvp,下面以execlp为例,其它函数究竟与execlp有何区别,请通过manexec命令来了解它们的具体情况。
一个进程一旦调用exec类函数,它本身就"
死亡"
了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。
如果一个程序想启动另一程序的执行,自己仍想继续运行的话,怎么办呢?
那就是结合fork与exec的使用。
下面一段代码显示如何启动运行其它程序:
charcommand[256];
voidmain()
{
intrtn;
/*子进程的返回数值*/
while
(1)
{/*从终端读取要执行的命令*/
printf("
>
"
);
fgets(command,256,stdin);
command[strlen(command)-1]=0;
if(fork()==0)
{/*子进程执行此命令*/
execlp(command,command);
/*如果exec函数返回,表明没有正常执行命令,打印错误信息*/
perror(command);
exit(errorno);
}
{/*父进程,等待子进程结束,并打印子进程的返回值*/
wait(&
rtn);
childprocessreturn%d\n"
.rtn);
实验五、Windows系统的多线程同步
在掌握基于消息的Windows程序结构和多线程程序设计方法的基础上,设计一个多线程的程序。
使学生能够从程序设计的角度了解多线程程序设计的方法和在Windows系统下多线程同步互斥的机制。
利用Windows提供的MFC类实现读者/写者的同步互斥问题,保证:
1)一个人在写时,其他人不允许写;
2)一个人在写时,其他人不允许读;
3)一个人在读时,其他人不允许写;
4)一个人在读时,其他人允许读。
WindowsXP;
VisualC++
4.实验指导
4.1线程控制
目前,大部分操作系统提供了线程,一些编程工具如VC、DELPHI等语言也提供了线程实现的方法。
线程与进程的区别在于子线程与父线程序运行在同一进程空间内,而子进程和父进程则运行在不同的空间。
这样,同一进程内的不同线程间可以直接通过内存交换数据。
此外,在Win32的定义中一个进程至少拥有一个线程,所以进程也被叫做主线程。
MFC提供了对线程功能的封装类CWinThread,我们常使用的CWinApp类就是从这个类派生的。
在MFC中,可以通过
CWinThread*AfxBeginThread(
AFX_THREADPROCpfnThreadProc,//函数入口地址,函数的原形应该如同
UINTMyControllingFunction//(LPVOIDpParam);
LPVOIDpParam,//是传递给线程的参数
intnPriority=THREAD_PRIORITY_NORMAL,//表明线程的优先级。
他们为THREAD_PRIORITY_IDLE,THREAD_PRIORITY_NORMAL,THREAD_PRIORITY_TIME_CRITICAL和THREAD_PRIORITY_ABOVE_NORMAL。
UINTnStackSize=0,//为栈大小,如果为0表示使用系统默认值
DWORDdwCreateFlags=0,//为创建线程时的标记,为CREATE_SUSPENDED表示线程被创建后被挂起
LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL//为安全属性
函数的返回值是CWinThread类的指针,可以通过它实现对线程的控制。
在线程函数返回时线程将被结束,在线程内部可以利用voidAfxEndThread(UINTnExitCode);
结束线程,其中nExitCode为退出码。
在CWinThread类中通过DWORDCWinThread:
:
SuspendThread()和DWORDCWinThread:
ResumeThread()来挂起和恢复线程运行。
通过intCWinThread:
GetThreadPriority()和BOOLCWinThread:
SetThreadPriority(intnPriority)来获取和设置线程优先级。
此外CWinThread类中的成员变量:
m_hThread和m_nThreadID保存了线程句柄和线程ID号。
也可以通过其他API函数对线程进行操作。
API函数BOOLTerminateThread(HANDLEhThread,DWORDdwExitCode);
可以在线程外部强制结束一个线程,但这样做是有危险的,因为线程申请某些资源可能没法释放,而且也有可能引起进程的崩溃。
所以推荐的方法为设置一个标记当线程检测到后自己退出,而不是采用从外部强制结束线程的方法。
4.2进程/线程间同步方法
因为进程/线程间同步的方法比较多,每种方法都有不同的用途。
同步实现方法有:
临界区、互斥量、信号灯、事件。
1)临界区方法
操作系统提供了多种同步对象供我们使用,并且可以替我们管理同步对象的加锁和解锁。
我们需要做的就是对每个需要同步使用的资源产生一个同步对象,在使用该资源前申请加锁,在使用完成后解锁。
下面我们介绍临界区。
临界区:
临界区是一种最简单的同步对象,它只可以在同一进程内部使用。
它的作用是保证只有一个线程可以申请到该对象。
几个相关的API函数为:
∙VOIDInitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
//产生临界区
∙VOIDDeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
//删除临界区
∙VOIDEnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
//进入临界区,相当于申请加锁,如果该临界区正被其他线程使用则该函数会等待到其他线程释放
∙BOOLTryEnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
//进入临界区,相当于申请加锁,和EnterCriticalSection不同如果该临界区正被其他线程使用则该函数会立即返回FALSE,而不会等待
∙VOIDLeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
//退出临界区,相当于申请解锁
下面的示范代码演示了如何使用临界区来进行数据同步处理:
intiCounter=0;
//全局变量
CRITICAL_SECTIONcriCounter;
//CRITICAL_SECTION是MFC定义的类型
DWORDthreadA(void*pD)
{intiID=(int)pD;
for(inti=0;
i<
8;
i++)
{EnterCriticalSection(&
criCounter);
//申请进入临界区
intiCopy=iCounter;
Sleep(100);
iCounter=iCopy+1;
printf("
thread%d:
%d\n"
iID,iCounter);
LeaveCriticalSection(&
//退出临界区
}
return0;
//inmainfunction
{
InitializeCriticalSection(&
//创建临界区
//创建线程HANDLE
hThread[3];
CWinThread*pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
CWinThread*pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
CWinThread*pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
hThread[0]=pT1->
m_hThread;
hThread[1]=pT2->
hThread[2]=pT3->
//等待这三个线程结束
//至于WaitForMultipleObjects的用法后面会讲到。
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
DeleteCriticalSection(&
//删除临界区
\nover\n"
2)互斥量
互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。
所以创建互斥量需要的资源更多,如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。
因为互斥量是跨进程的,互斥量一旦被创建,就可以通过名字打开它。
下面介绍可以用在互斥量上的API函数:
创建互斥量:
HANDLECreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes,//安全信息
BOOLbInitialOwner,//最初状态,为真,则表示创建它的线程直接拥有了该互斥量,而不需要再申请
LPCTSTRlpName//名字,可以为NULL,但这样一来就不能被其他线程/进程打开
HANDLEOpenMutex(//打开一个存在的互斥量
DWORDdwDesiredAccess,//存取方式
BOOLbInheritHandle,//是否可以被继承
LPCTSTRlpName//名字
BOOLReleaseMutex(//释放互斥量的使用权,但要求调用该函数的线程拥有该互斥量的使用权
HANDLEhMutex//句柄
BOOLCloseHandle(//关闭互斥量
HANDLEhObject//句柄
DWORDWaitForSingleObject(//获取互斥量的使用权需要使用函数
HANDLEhHandle,//等待的对象的句柄
DWORDdwMilliseconds//等待的时间,以ms为单位,如果为INFINITE表示无限期的等待
其返回值的意义是:
WAIT_ABANDONED在等待的对象为互斥量时表明因为互斥量被关闭而变为有信号状态
WAIT_OBJECT_0得到使用权
WAIT_TIMEOUT超过(dwMilliseconds)规定时间
在线程调用WaitForSingleObject后,如果一直无法得到控制权,线程将被挂起,直到超过时间或是获得控制权。
WaitForSingleObject函数中的对象(Object)的含义,这里的对象是一个具有信号状态的对象,对象有两种状态:
有信号/无信号。
而等待的含义就在于等待对象变为有信号的状态,对于互斥量来讲如果正在被使用则为无信号状态,被释放后变为有信号状态。
当等待成功后WaitForSingleObject函数会将互斥量置为无信号状态,这样其他的线程就不能获得使用权而需要继续等待。
WaitForSingleObject函数还进行排队功能,保证先提出等待请求的线程先获得对象的使用权,下面的代码演示了如何使用互斥量来进行同步,代码的功能还是进行全局变量递增,通过输出结果可以看出,先提出请求的线程先获得了控制权:
intiCounter=0;
DWORDthreadA(void*pD)
{intiID=(int)pD;
//在内部重新打开
HANDLEhCounterIn=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"
samsp44"
for(inti=0;
{printf("
%dwaitforobject\n"
iID);
WaitForSingleObject(hCounterIn,INFINITE);
intiCopy=iCounter;
Sleep(100);
iCounter=iCopy+1;
printf("
\t\tthread%d:
ReleaseMutex(hCounterIn);
CloseHandle(hCounterIn);
return0;
{//创建互斥量
HANDLEhCounter=NULL;
if((hCounter=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"