操作系统课程设计指导书.docx
- 文档编号:27849333
- 上传时间:2023-07-05
- 格式:DOCX
- 页数:69
- 大小:312.72KB
操作系统课程设计指导书.docx
《操作系统课程设计指导书.docx》由会员分享,可在线阅读,更多相关《操作系统课程设计指导书.docx(69页珍藏版)》请在冰豆网上搜索。
操作系统课程设计指导书
操作系统课程设计指导书
梁红兵赵伟华
2013年2月
计算机学院
操作系统课程设计
第一章操作系统课程设计的内容与实施方法
1.1操作系统课程设计总体要求
1.遵守机房纪律,服从机房调度。
2.课程设计的设计和上机调试要求独立完成,不能拷贝。
3.上机前,努力准备上机内容,并预先作一些情况分析。
4.仔细观察上机时出现的各种现象,记录上机的结果。
5.认真书写课程设计报告。
报告中应包括:
课程设计的目的及要求、程序的设计思想及流程图、程序调试中遇到的问题及分析、程序代码清单和结果分析;程序的不足之处及修改方案等。
程序要带注释。
1.2操作系统课程设计的内容
本次课程设计共设置了以下两个题目:
1.基于DOS的多任务系统的实现
DOS系统是一个典型的单用户单任务操作系统。
“基于DOS的多任务系统的实现”的基本设计思想是设计一个运行在DOS系统中的应用程序,该应用程序能实现多线程机制,即能完成所有与线程管理有关的工作,包括线程创建与撤销、线程阻塞与唤醒、线程互斥与同步、线程调度、线程通信等。
我们利用这些功能创建多个线程,并调度这些线程在CPU上并发执行,每个线程执行一个函数完成指定的功能。
2.简单文件系统的实现
文件系统是操作系统内核中非常重要的组成部分之一。
一个相对完整的文件系统应该具备以下几个方面的功能:
磁盘存储空间管理、目录管理、文件读写管理、文件保护与共享。
由于对磁盘的存取操作必然涉及到磁盘驱动程序设计,为了降低设计难度,本实验的基本设计思想是在内存中申请一块存储空间作为虚拟磁盘,在其上建立一个类似于FAT的文件系统,所有对文件系统的操作都是在该虚拟磁盘空间中进行。
为了保存该文件系统中的内容,如我们创建的目录、文件等,在退出文件系统的使用之前必须将整个虚拟磁盘上的内容以一个文件的形式全部保存到系统真正的磁盘上;以后想再次使用该文件系统时又必须首先从磁盘上读入这个文件的内容到内存中的虚拟磁盘上,然后才能继续使用。
1.3操作系统课程设计实施方案
操作系统是计算机系统中最核心最重要的一组软件集合,用来控制系统中的所有硬件及其他软件的运行,各程序模块内部的控制流程及相互间的接口都很复杂。
本课程设计虽然只是实现其中的一部分功能,但对学生的综合要求依然较高,既要求对原理知识的综合掌握,又要求具有一定的C语言编程能力,特别是“基于DOS的多任务系统的实现”这个题目,由于要利用TurboC的interrupt类型的函数来实现线程切换过程中的线程运行现场及环境信息的自动保存及恢复,因此程序开发工具是采用字符型界面的TurboC。
而不同学生在编程能力上存在差异,且大多数学生对字符型界面的开发平台存在畏惧心理,为了达到因材施教的目的,保证每个学生都能根据自己的实际情况参与到课程设计过程中,我们开发设计了一个可视化的操作系统课程设计平台软件(该平台软件的使用方法见后面第三篇内容),该软件系统最大的特点是提供了模块式替换功能,即将每个课程设计题目的内容分解成若干个相对“较小”的功能模块(模块具体划分情况见后面课程设计的详细介绍),允许每个学生根据自身能力情况选择实现课程设计的全部或部分功能模块,学生完成一个或多个模块后可在软件系统中进行模块替换操作,替换后需要重新进行编译、链接工作,然后就可以运行程序,从而及时看到所编写模块的功能实现情况。
这样能够提高所有学生主动学习的兴趣,提高实际动手能力。
第二章基于DOS的多任务系统的实现
2.1设计目的和内容要求
1.设计目的
通过对线程(和进程)的创建和撤消、CPU的调度、同步机制、通信机制的实现,达到以下目的:
(1)加深对线程和进程概念的理解,明确进程和程序的区别。
(2)加深对CPU调度过程(现场保护、CPU的分派和现场恢复)的理解。
(3)进一步认识并发执行的概念,明确顺序执行和并发执行的区别。
(4)加深对临界资源、临界区、信号量以及同步机制的理解。
(5)加深对消息缓冲通信的理解。
2.内容要求
(1)用C语言完成线程的创建和撤消,并按先来先服务方式对多个线程进行调度。
(2)将线程调度算法修改为时间片轮转算法,实现时间片轮转调度。
(也可以结合优先权,实现优先权加时间片轮转算法的线程调度。
)
(3)改变时间片的大小,观察结果的变化。
思考:
为什么时间片不能太小或太大。
(4)假设两个线程共用同一软件资源(如某一变量,或某一数据结构),请用记录型信号量来实现对它的互斥访问。
(5)假设有两个线程共享一个可存放5个整数的缓冲,其中一个线程不停地计算1至50的平方,并将结果放入缓冲中,另一个线程不断地从缓冲中取出结果,并将它们打印出来,请用记录型信号量实现这一生产者和消费者的同步问题。
(6)实现消息缓冲通信,并与4、5中的简单通信进行比较。
(7)思考:
在线程间进行消息缓冲通信时,若对消息队列的访问没有满足互斥要求,情况将会怎样?
3.学时安排(共21学时)
(1)授课3学时,内容包括线程的创建、撤消、调度等内容。
(2)线程的创建、撤消、先来先服务调度,8学时上机。
(3)时间片轮转调度,3学时上机。
(4)信号量的实现,3学时上机。
(5)线程间的消息缓冲队列通信,4学时上机。
4.开发平台
TurboC2.0或3.0。
2.2线程描述
2.2.1线程基本概念
在一些多任务的环境下,用户可以同时运行多个完整的程序。
例如,在UNIX环境下,你可以用CC命令编译一个C程序,并把它作为一个后台进程运行(只需在命令行后加上字符‘&’);在前台,你又可以做其他的事情,比如,编辑另一个文件。
我们把这种系统称为基于进程的多任务系统。
另外有一种多任务系统,在其下,一个程序的多个部分可同时运行,我们把这种环境下的任务,即程序的每个部分叫做线程,称这种系统为基于线程的多任务系统。
在这种环境下,处理机的调度单位为线程,它们共享整个进程的资源,还拥有一些自己的私有资源。
我们将通过本课程设计实现多个线程的并发执行。
线程,有时也叫做轻进程(lightweightprocess),是CPU调度的基本单位,每个线程有自己的一个指令计数器、一组寄存器和一个私有堆栈。
而代码段、数据段以及操作系统的其它资源(如打开的文件)是由一组线程共享的,这一组线程组成一个Task(传统的进程,即heavyweightprocess相当于只有一个线程的Task)。
在许多方面,对线程的操作类似于进程:
线程可处于就绪、阻塞、执行三种状态之一;线程可共享CPU,在单机系统中,任何时刻最多只能有一个线程处于执行状态;一个Task中的多个线程可并发执行。
但与进程不同,一个Task中的多个线程并不互相独立,因为,所有线程均可访问所属Task的地址空间的任一单元,所以,一个线程读写其它线程的私有堆栈是十分容易的,即系统不提供线程间的保护。
注:
上面讲的Task是指一个完整的作业,其中可包括多个线程,与本课程设计中所讲的多任务中的任务(系统中可并发执行的部分,如线程或进程)含义不同,除此之外,本课程设计中所提到的Task或任务均代表后者。
线程的切换只需切换寄存器组的值,而不需做有关内存管理方面的工作,实现起来也就比较简单。
2.2.2线程控制块
与进程类似,基于线程的多任务系统中的任务,即线程,它不单是指静态的、可并发执行的程序段本身,其实也是一个动态的概念,是指可并发执行的程序段及其执行过程。
因此,我们要用一个类似于进程控制块PCB的数据结构——线程控制块TCB,来记录有关描述线程情况和控制线程运行所需的全部信息,具体来说,在一个TCB中主要应包括以下几方面的信息:
1.有关线程私有堆栈的信息
在线程调度的过程中,为了保护线程的现场信息,每个线程都必须有自己的私有堆栈。
我们把被切换线程的现场信息,包括目前各寄存器的值和下一条指令的地址都保存在它的堆栈中,再从新线程的私有堆栈中恢复出一组新值来布置系统的寄存器,并从私有堆栈中得到新线程的下一条指令地址。
另外,每个线程中用到的局部变量也是存放在它自己的私有堆栈中的。
因此,在TCB中必须有线程的私有堆栈的信息,包括它在内存的起始地址、堆栈的栈顶指针的段地址和偏移等信息。
DOS中内存的地址是20位的,而且DOS的内存管理采用分段的方式,每个段的基址的低4位必须为0,指令和数据的逻辑地址可用两个16位的整数来描述,即:
段地址seg和段内偏移off,其中段地址seg中有段基址的高16位,故逻辑地址seg:
off对应的物理地址为seg×24+off。
C语言经常用指针来描述一个地址,TurboC提供了三个宏函数用来实现指针方式到段地址、偏移地址方式的相互转换:
若P为一个指针,则可通过FP_SEG(p)得到该地址的段地址,FP_OFF(p)得到该地址的段内偏移;若seg为一个地址的段地址,off为其段内偏移,则可通过MK_FP(seg,off)得到对应的指针。
2.有关线程的状态的信息
在基于线程的多任务系统中,一个线程的状态在它的生命周期中是在不断地变化的,在此,我们把线程的主要状态划分为:
就绪、执行、阻塞和终止态。
如果,一个线程拥有CPU,我们就说它处于执行态;如果它现在虽不在执行,但一旦获得CPU就可执行,我们就说它处于就绪态;如果它在等待CPU以外的其他资源,则说它处于阻塞状态;如果线程所对应的程序段已运行完毕,则它处于终止状态。
因此,在TCB中要设置一状态字段,用来记录各线程的现行状态。
3.线程的标识符
线程标识符用于惟一地标识一个线程,与进程一样,通常一个线程有两个标识符:
(1)外部标识符:
它由创建者提供,通常是一个由字母、数字组成的字符串,记录在线程的TCB中。
(2)内部标识符,它通常是一个整数,由多任务系统在创建线程时设置。
在本课程设计中,我们在多任务系统的初始化过程中,设置了一个struct类型的TCB数组来统一为各新建线程提供空白TCB,为了简单起见,我们可以隐含地用各线程所分配到的TCB在整个TCB数组中的下标来表示该线程的内部标识符,所以不需要再专门记录在TCB中了。
4.其它信息
TCB中记录的信息量可随系统的复杂情况而变化,如当采用优先权算法进行调度时,在TCB中还必须设置优先权字段;当TCB要按某种方式排队时,在其中必须设置一链接指针字段;当必须唤醒因某种原因而阻塞的相关线程时,则必须设置阻塞原因字段;在使用消息缓冲队列机制实现线程通信时,则必须设置通信机制需要的字段,如接收线程的消息队列队首指针、消息队列的互斥信号量和资源信号量等。
用C语言来描述,一个最简单的TCB的数据结构可以表示如下:
/*状态码常量定义*/
/*null0notassigned*/
#defineFINISHED0/*表示线程处于终止态或TCB是空白状态*/
#defineRUNNING1/*表示线程处于运行态*/
#defineREADY2/*表示线程处于就绪态*/
#defineBLOCKED3/*表示线程处于阻塞态*/
structTCB{
unsignedchar*stack;/*线程堆栈的起始地址*/
unsignedss;/*堆栈段址*/
unsignedsp;/*堆栈指针*/
charstate;/*线程状态,取值可以是FINISHED、RUNNING、READY、BLOCKED*/
charname[10];/*线程的外部标识符*/
}tcb[NTCB];/*NTCB是系统允许的最多任务数*/
2.3线程的创建和撤消
2.3.1线程的创建
在创建一个新线程时,线程的创建者必需提供一些信息,如线程的外部标识符、线程所需的私有堆栈空间的大小、与线程所对应的程序段的入口地址的有关信息(这里假设一个线程执行程序里的一个函数,所以创建者只需提供线程要执行的函数的函数名即可)。
1.线程创建函数格式说明
(1)函数申明原型:
typedefint(far*codeptr)(void);/*定义了一个函数指针类型*/
Intcreate(char*name,codeptrcode,intstck);
(2)函数功能描述:
在main()函数中调用,创建一个新线程,让其执行code开始的代码。
(3)输入:
name:
新创建线程的外部标识符;
code:
新创建线程要执行的代码的入口地址,此处用函数名作为传入地址;
stck:
新创建线程的私有堆栈的长度。
(4)输出:
新创建线程的内部标识符,若创建失败,返回-1
2.函数实现的算法描述
在创建一个线程时主要应完成以下工作:
(1)为新线程分配一个空闲的线程控制块TCB,该TCB的数组下标即为新线程的内部标识符。
如果没有空闲的TCB,则返回-1,创建失败。
(2)为新线程的私有堆栈分配内存空间(因为同一进程的多个线程共享该进程的程序段和数据段空间,所以创建线程时不必象创建进程那样再为程序段和数据段分配内存空间)。
(3)初始化新线程的私有堆栈,即按CPU调度时现场信息的保存格式布置堆栈,这一点是非常重要的,因为当CPU首次调度该线程运行时,CPU中的SS寄存器和SP寄存器将指向该线程的私有堆栈,并从该堆栈中获得线程运行的正确的指令地址和其它现场信息。
新线程的首次执行是从对应函数的入口开始的;而且,执行时CPU的寄存器ES、DS应置上恰当的值;Flags寄存器的允许中断位也应置上1,这样,线程执行过程中才允许硬中断(如时钟中断)发生并及时响应中断;其它寄存器(AX、BX、CX、DX、SI、DI、BP)的值只在线程执行过程中才有意义,它们的初值可为任意值。
初始化工作完成后堆栈中各信息项的值及其相应位置如图2-1b所示。
为了方便堆栈的初始化工作,我们可以按照堆栈中的内容设计一个以下的数据结构:
structint_regs{
unsignedbp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flags,off,seg;
};
然后用一个指向该数据结构的指针给堆栈赋值。
(4)初始化线程控制块,即填入线程的外部标识符,设置好线程私有堆栈的始址、段址和栈顶指针,将线程的状态置成就绪态READY,如图2-1a所示。
另外,如果线程调度算法是按优先权方式进行CPU调度,则需在TCB中置上新线程的优先权信息(初始优先数可由用户提供);若TCB的组织方式是按某种方式拉链,系统设置了线程就绪队列,则还需将新线程的TCB插入就绪队列;如果要实现通信,还需要将线程的消息队列队首指针设置为Null、消息队列的互斥信号量和资源信号量分别设置为{1,Null}和{0,Null}
(5)最后,返回新线程的内部标识符。
在TurboC的small编译模式下,调用create("f1",(codeptr)f1,1024)创建一个对应于函数f1()的线程后新线程的内存映象如图2-1所示。
图2-1对应函数f1()的新线程的内存映像
2.3.2线程的撤消
引起线程撤销的原因主要有两个:
一是系统或用户要求撤销某个线程;二是当前线程所对应的函数已经运行完成。
对于第一种情况比较简单,只需调用线程撤销原语将指定线程撤销即可;对于第二钟情况,首先必须自动调用线程撤销原语撤销当前已经运行完成的线程,然后还需要自动地重新进行CPU调度。
1.线程撤销函数设计:
(1)函数申明原型:
voiddestroy(intid);
(2)功能:
撤销内部标识符为id的指定线程。
(3)输入:
id:
将要被撤销的线程的内部标识符。
(4)输出:
无
(5)函数实现的算法描述:
撤销线程所要完成的工作比较简单,主要是将线程所占据的资源归还给系统。
在操作系统原理中已经介绍了线程本身基本不占据资源,它与同进程的其他线程共享该进程的代码段和数据段空间;但是线程作为一个可以独立调度和运行的基本单元也拥有一些必不可少的资源,如线程控制块TCB和私有堆栈。
所以撤销线程所要做的事情主要就是两个:
(1)将线程的私有堆栈所占的内存空间归还给系统;
(2)将线程控制块TCB的各成员变量进行初始化操作。
2.撤销线程并重新进行调度
前面提到如果是因为当前线程运行完成而引起线程撤消,则系统应能自动撤消该线程,并重新进行CPU调度。
我们可以设置一个称为over()的函数来完成这个工作,该函数需要顺序做两件事情:
首先调用destroy()撤销当前线程,然后重新进行CPU调度。
所以现在关键的问题是在当前线程运行完成后CPU应能自动转去执行over(),这可通过在创建线程时进行一些相关的处理来实现:
在进行堆栈初始化时可预先将over()的入口地址压入线程的私有堆栈中,如前面图2-3b所示;这样,当线程所对应的函数正常结束时,over()函数的入口地址将作为函数的返回地址被弹出至CPU的CS、IP寄存器,从而使CPU的控制权自动转向over()去执行。
2.4线程调度设计
2.4.1CPU调度中的关键问题
CPU调度所要做的事情是保护旧线程的现场、找到新线程、恢复新线程的现场、并把处理机交给新线程让它执行。
其中,找一新线程是比较容易实现的,只需按某种线程调度算法从所有处于就绪状态的线程中选择一个即可;剩余的问题——旧线程的现场保护和新线程的现场恢复、CPU控制权的转移才是CPU调度的关键,它们是通过堆栈的切换来实现的。
在介绍堆栈切换的内容之前,我们先来看看函数调用和进行中断处理时控制转移的情况。
1.函数调用时的控制转移情况
在执行函数调用指令时,系统会自动地先将主调函数的下一条指令的地址(在CS:
IP中)压入堆栈,然后把被调函数的入口地址装入CS和IP寄存器(段内函数调用只需压入和装配IP),控制就从主调函数转向被调函数;当执行函数返回指令时,系统将当前堆栈的栈顶的两个字(主调函数下一条指令的地址)弹出并送到IP和CS中(段内函数返回只需弹出一个字送到IP中),控制就从被调函数返回到主调函数。
例如,我们编写了一个main()函数和一个f1()函数,在main()中调用f1()。
程序的设计及调用返回关系如图2-2所示:
图2-2函数调用及返回图
在执行f1()函数的调用指令前的当前堆栈的情况如图2-3a所示。
在执行函数调用指令时,系统首先将main()中函数调用语句的下一条指令即“i=1;”的地址(在CS:
IP中,这里用“返址1”表示)压入堆栈,此时堆栈内容如图2-3b所示。
然后将f1()函数的入口地址装入CS和IP寄存器,控制就从main()转向f1()。
当执行f1()的最后一条指令“return”(函数返回指令)时,系统将前面保存在堆栈中的返址1的偏移和返址1的段址弹出并送到IP和CS中,则控制就从f1()返回到main()了。
图2-3函数调用前后堆栈内容的变化
2.中断处理时的控制转移情况
除了函数调用以外,中断也能实现控制的转移。
当中断发生时,系统首先将标志寄存器Flags的值压入堆栈,然后将装有被中断程序下一条指令地址的CS和IP寄存器的内容也分别压入堆栈,再从中断向量中获取中断服务程序的入口地址并将它们装入CS和IP寄存器,这样,控制就从被中断的程序转向中断服务程序。
中断返回时,系统自动从栈顶弹出返址1的偏移、返址1的段址和Flags并送到IP、CS和Flags寄存器中,CPU就开始继续从断点处执行被中断程序。
中断处理前后堆栈内容的变化情况如图2-4所示:
图2-4中断处理前后堆栈内容的变化
3.Interrupt类型函数的特殊作用
在TurboC中提供了一个特殊的函数类型说明符interrupt,我们可利用它将一个函数申明为中断处理函数。
例如我们写了一个文件名为“aaa.c”的C程序,其内容如下:
voidinterruptfun(void);
inti;
main()
{
i=0;
fun();
i=1;
}
voidinterruptfun(void)
{
i=2;
}
用编译命令“tcc-Saaa”得到以上C程序的汇编码:
ifndef?
?
version
?
debugmacro
endm
endif
?
debugS"aaa.c"
_TEXTsegmentbytepublic'CODE'
DGROUPgroup_DATA,_BSS
assumecs:
_TEXT,ds:
DGROUP,ss:
DGROUP
_TEXTends
_DATAsegmentwordpublic'DATA'
d@labelbyte
d@wlabelword
_DATAends
_BSSsegmentwordpublic'BSS'
b@labelbyte
b@wlabelword
?
debugCE93B4F151F056161612E63
_BSSends
_TEXTsegmentbytepublic'CODE'
;?
debugL3
_mainprocnear
;?
debugL4
movwordptrDGROUP:
_i,0
;?
debugL5
pushf
callfarptr_fun
;?
debugL6
movwordptrDGROUP:
_i,1
@1:
;?
debugL7
ret
_mainendp
;?
debugL8
_funprocfar
pushax
pushbx
pushcx
pushdx
pushes
pushds
pushsi
pushdi
pushbp
movbp,DGROUP
movds,bp
;?
debugL10
movwordptrDGROUP:
_i,2
@2:
;?
debugL11
popbp
popdi
popsi
popds
popes
popdx
popcx
popbx
popax
iret
_funendp
_TEXTends
_BSSsegmentwordpublic'BSS'
_ilabelword
db2dup(?
)
_BSSends
?
debugCE9
_DATAsegmentwordpublic'DATA'
s@labelbyte
_DATAends
_TEXTsegmentbytepublic'CODE'
_TEXTends
public_main
public_fun
public_i
end
从编译后的代码中可以看出,对于fun()函数,由于定义的类型是interrupt类型的中断处理函数,所以在使用tcc命令进行编译时,编译器将自动在fun()的开始加入一组push操作(代码中第二组黑体字部分),以保存被中断程序的CPU现场环境信息;相对应地在fun()的代码最后自动加入一组pop操作(代码中第三组黑体字部分),以便中断返回时恢复被中断程序的现场环境信息。
从编译后的代码段中还可以看出,main()对fu
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 操作系统 课程设计 指导书