操作系统课程设计指导书2015(4)

2019-04-16 22:41

开中断来达到。Swtch()的设计如下: void interrupt swtch(void) {

disable(); /*关中断*/

/* 保存现行堆栈的段址和栈顶指针供下次切换时用 */ ss1=_SS; /* ss1保存线程1的堆栈段址 */

sp1=_SP; /* sp1保存线程1的堆栈栈顶指针 */ /* 切换堆栈 */

_SS=ss2; /* ss2是线程2的堆栈段址 */

_SP=sp2; /* ss2是线程2的堆栈的栈顶指针 */ enable(); /*开中断*/ }

上面代码中用到的_SS,_SP是Turbo C提供的伪变量。 所谓的伪变量是一个和给定寄存器相一致的简单的标识符,通过它们,我们可以在C 语言程序中直接访问相应的寄存器。表2-1中给出了Turbo C可用的伪变量的完整列表、它们的类型、相应的寄存器以及那些寄存器通常的用处。

表2-1 Turbo C 伪变量表 伪变量 类型 寄存器 通常用处 _AX _AL _AH _BX _BL _BH _CX _CL _CH _DX _DH _CS _DS _SS _ES _SP _BP _DI _SI

由于swtch()是interrupt类型的函数,因此编译后的伪代码如图2-6b所示:

无符号整型 无符号字符型 无符号字符型 无符号整型 无符号字符型 无符号字符型 无符号整型 无符号字符型 无符号字符型 无符号整型 无符号字符型 无符号整型 无符号整型 无符号整型 无符号整型 无符号整型 无符号整型 无符号整型 无符号整型 AX AL AH BX BL BH CX CL CH DX DH CS DS SS ES SP BP DI SI 通用/累加器 AX的低字节 AX的高字节 通用/变址器 BX的低字节 BX的高字节 通用/计数和循环 CX的低字节 CX的高字节 通用/存放数据 DX的高字节 代码段地址 数据段地址 堆栈段地址 附加段地址 堆栈栈顶指针(对SS的偏移) 基址指针 用于寄存器变量 用于寄存器变量 - 16 -

swtch()push ax?push bp调用swtch()执行disable()ss1=_SSsp1=_SP_SS=ss2_SP=sp2enable()pop bp?pop axiret转向线程2执行?线程1对应的程序段线程2对应的程序段?地址2:线程2下次调度运行时将要执行的指令的地址?swtch()地址1?图a 线程1的程序段 图b swtch()内容图c 线程2的程序段

图2-6堆栈切换过程中CPU控制的转移情况

假设线程1对应的程序段如图2-6a所示,线程2对应的程序段如图2-6c所示。又假设现在是线程1正在CPU上运行,则当前堆栈是线程1的堆栈stack1,其内容如图2-7a所示。线程2目前处于就绪状态,其堆栈stack2的内容如图2-8a所示。

ss1:sp1 swtch()执行push操作后的栈顶ss1:sp1地址1的偏移调用swtch()地址1的段址时的栈顶Flags?...图b 线程1调用swtch() 时的stack1的栈顶BPDISIDSESDXCXBXAX地址1的偏移地址1的段址Flags?...?ss1:sp1调用swtch()前的栈顶图a 线程1调用swtch()前的 stack1的栈顶图c swtch()执行push 操作后stack1的内容 图2-7 CPU切换过程中线程1堆栈stack1的变化情况

- 17 -

ss2:sp2BPDISIDSESDXCXBXAX地址2的偏移地址2的段址Flags?...图a 线程2处于就绪状态时的stack2的内容原来保存在栈中的线程2的现场信息ss2:sp2 swtch()执行地址2的偏移pop操作后地址2的段址的栈顶Flags?...图b swtch()执行pop 操作后stack2的内容??swtch()返回后的栈顶图c swtch()返回后stack2的内容 图2-8 CPU切换过程中线程2堆栈stack2的变化情况

下面我们来分析CPU从线程1切换到线程2的过程中控制的转移情况以及堆栈的变化情况。

(1)线程1执行swtch()函数调用指令,系统首先将Flags、地址1的段址、地址1的偏移压栈,此时stack1的内容如图2-7b所示。

(2)然后CPU转去执行swtch(),首先执行一组push操作,由于此时的当前堆栈仍然是线程1的stack1,因此push操作执行完毕后stack1的内容如图2-7c所示。

(3)接下来swtch()进行堆栈切换:首先保存线程1的堆栈stack1的段址和栈顶指针到变量ss1和sp1中;然后将线程2的堆栈stack2的段址和栈顶指针装配到CPU的SS好SP寄存器中,则从现在开始,系统的当前堆栈已经变成了stack2。

(4)接着swtch()执行一组pop操作,将stack2中从BP开始到AX结束的所有内容弹出并装入CPU的相应寄存器中,此时stack2的内容如图2-8b所示。

(4)最后swtch()执行中断返回指令“iret”,从stack2中弹出线程2的偏移、线程2的段址和Flags并送到CPU的IP、CS和FLAGS寄存器中,此时stack2的内容如图2-8c所示。

(5)CPU继续执行CS:IP寄存器所指向的指令,即线程2的地址2这个位置的指令。于是CPU的控制权从线程1切换到线程2。

2.4.2 DOS的不可重入性

在本课程设计题目中,要求采用基于优先权的时间片轮转调度算法,即当前线程时间片到时应重新进行CPU调度。那么是不是线程的时间片到了就立即要重新进行调度呢?要回答这个问题,必须先理解“DOS的不可重入性”的概念。

在执行DOS的系统功能调用INT 21H时,DOS内核先将AX、BX、CX、DX、SI、DI、BP、DS、ES九个寄存器的内容依次压入到用户堆栈中,并将用户堆栈当前双字指针(SS:SP)保存到DOS内核的一个双字单元里,就不再使用用户堆栈, 此时使用的是新建立的系统内部堆栈,系统功能调用完毕后再舍弃系统内部堆栈而恢复用户堆栈的使用。

DOS的系统内部堆栈有三个,它们位于DOS操作系统空间的特定位置。当系统功能调用号为01-0CH、50H、51H之一,而且目前已有严重错误(即INT 24H的执行标志为1)时,

- 18 -

使用第一个系统内部堆栈(也叫辅助堆栈);当功能调用号不属01-0CH、50H、51H之一,则使用第二个系统内部堆栈(也叫I/O堆栈);当功能调用号为01-0CH、50H、51H之一,但目前无严重错误发生时, 使用第三个系统内部堆栈(也叫磁盘堆栈)。

如果在程序1执行DOS系统功能调用的过程中,CPU被调度去执行程序2,而后者又要执行一系统功能调用,而且这两个系统功能调用使用的是同一个系统内部堆栈(如磁盘堆栈),则程序2的入栈数据将会把保存在该系统内部堆栈中的程序1的入栈数据覆盖掉,以后就无法再接着执行程序1。由此可见,以上的这种情况原则上仅允许对DOS的一次性重入,而且要求先后两次功能调用使用不同的系统内部堆栈,其它的对DOS 的重入都将导致错误。这就是DOS的不可重入性。

从另一方面考虑,不仅DOS在上述情况下会出现问题,当硬盘磁头响应INT 13H的调用,正处在读写磁盘的过程中时,进行CPU的调度, 而新的进程把磁头移到其它什么地方,这种重入问题虽对堆栈和可重复代码没什么影响,但将导致硬盘数据的混乱。 若按时间片进行CPU调度,时间片到时时,老线程很有可能是处于DOS的系统功能调用中,而调度后的新线程(或调度程序本身)又不可能完全不做系统功能调用。那么,如何来解决这一问题呢?有两种可考虑的办法:

(1) 如果发现INT 21H正在执行中,就推迟对处理机的切换。 (2) 在进行CPU调度时,设法为老线程保存DOS的所有内部信息(包括三个内部堆栈),并为新线程恢复DOS的所有内部信息(请参考有关“DOS的可对换数据区SDA”的书)。

相比较而言,第一种办法更加简单。该方法的关键问题是如何确定DOS是否处于忙状态(所谓DOS处于忙状态是指此时INT 21H正在执行中)。 在DOS的内部有一字节,当系统没进入INT 21H时,该字节的值为0; 当系统进入INT 21H时,DOS将该字节的值置成非0,而在退出INT 21H时,DOS又将它的值清0。这一字节被叫做DOS的安全标志,我们称它为INDOS标志,即当该标志的值为0 时,DOS不处于忙状态,可进行处理机调度;否则,DOS处于忙状态,需将处理机调度工作推迟。 使用未公开的34h号系统功能调用,可得到INDOS标志的地址(由ES:BX返回)。由于此地址是在特定操作环境下的常量,所以,可将得到的INDOS 标志的地址保存起来供判断DOS是否处于忙状态时使用。

另外,当DOS出现关键性错误时(如用21H号系统功能调用随机读一个记录时发生的读盘错误),DOS的严重错误处理程序将置上严重错误标志(DOS空间特定位置的一字节),并清除INDOS标志,再调用严重错误的中断处理程序INT 24H;而在INT 24H执行完后,又将严重错误标志清0,再将INDOS标志置上, 然后根据用户输入的Retry、Ignore和Abort决定是重新读盘、还是忽略错误结束系统功能调用、还是结束整个应用程序。即在严重错误标志被置上时,虽然INDOS标志为0,但INT 21H可能仍没结束,所以,此时也应推迟对处理机的调度。在MS-DOS 2.x 版本中,严重错误标志的地址在INDOS标志的后一个字节处,在MS-DOS 3.x中, 此标志的地址位于INDOS标志的前一个字节处,而在MS-DOS 3.10及更高版本中,可通过5D06H 号系统功能调用获得此标志的地址。

下面的C程序说明如何取得INDOS标志和严重错误标志的地址,并根据这两个标志判断DOS是否处于忙状态。

/* INDOS.C -- Functions to manage DOS flags */ #include #include

#define GET_INDOS 0x34

- 19 -

#define GET_CRIT_ERR 0x5d06

char far *indos_ptr=0; /*该指针变量存放INDOS标志的地址*/ char far *crit_err_ptr=0; /*该指针变量存放严重错误标志的地址*/

void InitDos(void); int DosBusy(void);

/* InitDos()函数:功能是获得INDOS标志的地址和严重错误标志的地址 */ void InitDos(void) {

union REGS regs; struct SREGS segregs;

/* 获得 INDOS 标志的地址 */ regs.h.ah=GET_INDOS;

/* intdosx() :Turbo C的库函数,其功能是调用DOS的INT21H中断*/ intdosx(®s,®s,&segregs);

/* MK_FP():不是一个函数,只是一个宏。*/

/*其功能是做段基址加上偏移地址的运算,也就是取实际地址。 */

indos_ptr=MK_FP(segregs.es,regs.x.bx);

/*获得严重错误标志的地址 */

/*代码中用到的_osmajor、_osminor是Turbo C的全程变量,其中前者为*/

/*DOS版本号的主要部分,后者为版本号的次要部分。*/

if (_osmajor<3)

crit_err_ptr=indos_ptr+1;

else if (_osmajor==3 && _osminor==0) crit_err_ptr=indos_ptr-1; else {

regs.x.ax=GET_CRIT_ERR; intdosx(®s,®s,&segregs);

crit_err_ptr=MK_FP(segregs.ds,regs.x.si); }

}

/* DosBusy():函数功能是获得Indos标志及严重错误标志的值,判断是否dos忙:*/

/* 如果返回值是1,表示dos忙;*/

/* 如果返回值是0,表示dos不忙;*/

/* 如果返回值是-1,表示还没有调用InitDos() */ int DosBusy(void) {

if (indos_ptr && crit_err_ptr)

return(*indos_ptr || *crit_err_ptr);

- 20 -


操作系统课程设计指导书2015(4).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:计算机网络课后习题答案(仅供参考)

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: