具有中断处理的内核

实验四:具有中断处理的内核

实验目的:

1、PC系统的中断机制和原理
2、理解操作系统内核对异步事件的处理方法
3、掌握中断处理编程的方法
4、掌握内核中断处理代码组织的设计方法
5、了解查询式I/O控制方式的编程方法

实验要求:

1、知道PC系统的中断硬件系统的原理
2、掌握x86汇编语言对时钟中断的响应处理编程方法
3、重写和扩展实验三的的内核程序,增加时钟中断的响应处理和键盘中断响应。
4、编写实验报告,描述实验工作的过程和必要的细节,如截屏或录屏,以证实实验工作的真实性

实验内容:

(1)编写x86汇编语言对时钟中断的响应处理程序:设计一个汇编程序,在一段时间内系统时钟中断发生时,屏幕变化显示信息。在屏幕24行79列位置轮流显示’|’、’/’和’\’(无敌风火轮),适当控制显示速度,以方便观察效果,也可以屏幕上画框、反弹字符等,方便观察时钟中断多次发生。将程序生成COM格式程序,在DOS或虚拟环境运行。
(2)重写和扩展实验三的的内核程序,增加时钟中断的响应处理和键盘中断响应。,在屏幕右下角显示一个转动的无敌风火轮,确保内核功能不比实验三的程序弱,展示原有功能或加强功能可以工作.
(4) 扩展实验三的的内核程序,但不修改原有的用户程序,实现在用户程序执行期间,若触碰键盘,屏幕某个位置会显示”OUCH!OUCH!”。
(5)编写实验报告,描述实验工作的过程和必要的细节,如截屏或录屏,以证实实验工作的真实性

自定义中断

x86中断调用允许我们用int来进行编程级别的中断调用, 这个调用可以是bios调用, 用于读字符和写扇区等, 也可以是用户自己定义的中断. 我们知道中断处理程序会在保留现场后去查中断向量表, 找到并跳转到中断服务程序. 我们要做的就是通过自定义中断服务程序并把它烧入中断向量表, 这样就能随时使用int执行我们想要的中断.
我们先尝试按照上面所说, 重写int 8h中断, 实现基于计数器的风火轮程序. 这个计数器会在每次中断时减一, 如果减到零就会让风火轮发生改变(写字符到显存). 这里我们直接在之前的弹跳程序里做改动, 让程序执行过程中还会感知时钟变化.

user.asm(部分)
BITS 16
org			0100h

%macro setIVT 2
	push es
	push si
	mov ax, 0000H
	mov es, ax
	mov ax, %1
	mov bx, 4
	mul bx
	mov si, ax
	mov ax, %2
	mov [es:si], ax
	add si, 2
	mov ax, cs
	mov [es:si], ax
	pop si
	pop es
%endmacro

_init:
	call Reset
	mov	ax,0B800h		; 文本窗口显存起始地址
	mov	gs,ax		; GS = B800h
	…
	setIVT 8, int8  ;自定义8号中断

Entrance:
	…
	
int8:
	cli	;关中断
	pusha	;保存寄存器
	dec dword[Timer]
	jnz int8end
	mov dword[Timer],timerdelay
	call spin ;打印风火轮
int8end:
	mov al, 20H
	out 20H, al
	out 0a0H, al
	popa	;恢复寄存器
	sti	;开中断
	iret

spin:
	inc word[wheel_idx]
	mov ax, 4
	cmp ax, word[wheel_idx]
	jne WriteWheel
	mov ax, 0
	mov word[wheel_idx], ax
WriteWheel:
	mov bx, wheel
	add bx, word[wheel_idx]
	mov al, byte[bx]
	mov ah, 0Fh
	mov [gs:((80*23+78)*2)],ax
	ret
wheel:				db		"-\|/"
wheel_idx			dw		0
timerdelay			equ 	20000
Timer				dd		timerdelay

这样的一个程序实现每100k个时间单位就会让弹球移动一次, 同时, 每一个时间单位都会让中断计数器减一, 如果减到0就让风火轮旋转45度, 这里设定计数器初始值是20k. 在dos仿真环境测试程序, 可以看到
在这里插入图片描述
在这里插入图片描述
屏幕右下角的风火轮和上面弹球的更新拥有异步的时序, 风火轮的刷新频率是弹球的5倍.

在内核中嵌入时钟中断和键盘中断

我们除了上面的感知时钟信号的时钟中断以外还希望实现能够感知键盘的键盘中断, 这个技巧我们之前已经实现过了, 我们用重定义int 20h的方法, 让系统在运行用户程序时, 能够在键盘输入ctrl+z时返回内核. 这里我们也可以用类似的方法自定义感知任意键盘输入的中断.
此外, 为了更好地显示内核的控制权, 我们把用户程序设计成每次执行一个时间单位后返回, 这样就能在内核中用循环观察到同样的效果, 而且如果在循环中使用键盘中断和时钟中断就能起到监控的效果. 一旦出现键盘中断就在屏幕上显示一些信息, 时钟中断可以用来控制我们的信息显示时长. 综上, 我们可以设计这样的中断用于执行.

kernel.asm(部分)

_start:
setIVT 8h, int8h
	setIVT 21h, int21h
	setIVT 20h, int20h
	mov	ax,0B800h		; 文本窗口显存起始地址
	mov	gs,ax		; GS = B800h
	_CALL cs, main
	jmp $

;void CallProgram() 
CallProgram:
	dec dword[count]
	jz Ent
	int 8h
	int 20h
	int 21h
	cmp word[exitflag], 1
	je ExitProgram  ;如果识别到int 20h传来的返回信息, 则退出程序
	jmp CallProgram
Ent:
	mov dword[count],delay
	_CALL cs, OffSetOfUserPrg
	jmp CallProgram
ExitProgram:
	mov dword[count],delay
	dec word[exitflag]
	retf;


; 自定义的中断号
int20h:
	mov ah, 01h     ;缓冲区检测
	int 16h
	jz noclick      ;缓冲区无按键
	mov dword[ouchcount], ouchdelay
	print ouch, 10, 20, 60
	mov ah, 00h
	int 16h
	cmp ax, 2c1ah	; 检测Ctrl + Z
	jne noclick
	inc word[exitflag]   ;只是作为一个标志
	iret
noclick:
	iret
	
	
;------------------------------
int21h:
	cmp dword[ouchcount], 0
	je ret21h
	dec dword[ouchcount]
	jz clearouch
	jmp ret21h
clearouch:
	print space, 10, 20, 60
ret21h:
	iret
;------------------------------

其中int20h, 我们每次调用它都会读键盘缓冲区, 如果键盘缓冲区有字符就

  1. 在屏幕上显示一串”ouch!ouch!”的字符串, 并为显示它的延迟计数器赋值.
  2. 清掉缓冲区的这个字符, 如果该字符是ctrl+z就退出当前用户程序的执行.
    int21h是一个时钟中断, 它的计数器和int20h修改的计数器是同一个.
  3. 检测计数器的值是否为0, 如果不为0就减一.
  4. 如果当前刚好减到0, 那么屏幕上就不应该再显示”ouch!ouch!”了, 我们用一段空格覆盖掉它.
    然后我们把上面写过的int8h也放入内核, 并把用户程序改写成每call一次执行一步, 就能在运行程序的基础上, 看见屏幕右下角的风火轮旋转和在任意时刻按下键盘, 都能看见屏幕显示出ouch,ouch.
    在这里插入图片描述
    在这里插入图片描述

个性化用户程序的服务程序

我们可以编写特殊的用户服务程序, 来让单个用户程序实现我们之前四个用户程序所实现的功能. 我们之前的四个程序是在四个子屏幕执行弹球逻辑, 并显示不同的字符串.
那么怎样用中断的形式, 改变用户程序的内部值呢? 一般我们的内核是无法直接访问用户程序的变量的, 所以我们需要在用户程序内使用中断, 参考int 10h的字符串输出调用, 我们传递字符串的地址, 打印位置, 字符串长度和颜色等等, 都是通过寄存器来传参的. 既然如此我们也可以用寄存器来传递要修改的变量地址, 为了一个程序顶四个程序用, 我的用户程序需要修改的变量有四个, 字符串, 字符串长度, 还有弹球要反弹的上下左右四个边界, 那么我们就把该修改的变量直接压栈, 并存下ds. 存储完毕后我们调用中断, 中断服务程序把这些地址对应的变量数值修改成我们需要的数值. 修改完毕后, 用户程序就能表现出多样性.

user.asm(部分)
INITDATA:
	cmp word[intflag], 0
	je _intdata1
	jmp _intdata2
INITXY:
	cmp word[initflag], 0
	je	_init
_intdata1:
	mov ax, Message
	push ax
	mov bx, Strlen
	push bx
	mov ax, ds
	int 33h
	pop bx
	pop ax
	inc word[intflag]
	jmp INITXY
	
_intdata2:
	mov ax, Message
	push ax
	mov bx, Strlen
	push bx
	mov ax, ds
	int 34h
	pop bx
	pop ax
	dec word[intflag]
	jmp INITXY
kernel.asm(部分)
int33h:
	push es
	push si
	mov es, ax
	mov ax, word[esp+12]
	mov si, ax
	mov byte[es:si], 'a'
	mov ax, word[esp+10]
	mov si, ax
	mov byte[es:si], 1
	pop si
	pop es
	iret
	
int34h:
	push es
	push si
	mov es, ax
	mov ax, word[esp+12]
	mov si, ax
	mov byte[es:si], 'b'
	mov ax, word[esp+10]
	mov si, ax
	mov byte[es:si], 1
	pop si
	pop es
	iret

这样的用户程序交替执行int 33h和34h中断, 中断会直接修改字符串内容和长度, 从而我们可以在执行用户程序时看到下面的输出.
在这里插入图片描述
当然我们也可以每调用一次中断就让程序切换到新的逻辑.

user.asm
BITS 16
org			0100h

%macro print 4	; string, length, x, y
	push ds
	push es
	push bp
	mov ax, cs
	mov ds, ax
	mov es, ax
	mov ss, ax
	mov bp, %1
	mov cx, %2
	mov ax, 1301h
	mov bx, 000fh
	mov edx,0
	mov dh, %3
	mov dl, %4
	int 10h
	pop bp
	pop es
	pop ds
%endmacro

INITDATA:
	dec word[count]
	jz _intdata
INITXY:
	cmp word[initflag], 0
	je	_init
Entrance:
	jmp	BoundaryCheckx	;边界检测与速度更新
DispStr:
	print Message,word[Strlen],byte[x],byte[y]

Updatexy:
	mov al, byte[x]
	add	al, byte[vx]
	mov	byte[x], al
	mov al, byte[y]
	add	al, byte[vy]
	mov	byte[y], al
	jmp Exit ;退出
	
	
BoundaryCheckx:
	mov al, byte[x]
	add	al, byte[vx]	;预测下一刻的x
	cmp	al, byte[upper]	;如果x小于上边界
	jl	Changevx		;更新vx
	cmp	al, byte[lower]	;如果x大于下边界
	jg	Changevx		;更新vx
	
BoundaryChecky:
	mov al, byte[y]
	add	al, byte[vy]
	cmp	al, byte[left]	;如果y小于左边界
	jl	Changevy		;更新vy
	add	al, byte[Strlen];预测下一刻的yr=y+字符串长
	cmp	al, byte[right]	;如果yr大于下边界
	jg	Changevy		;更新vy
	jmp	DispStr			;如果不需要更新vx vy就继续打印流程			


Changevx:
	neg	byte[vx]
	jmp	BoundaryChecky
	
Changevy:
	neg	byte[vy]
	jmp	DispStr

_init:
	mov	al, byte[upper]
	add	al, 5
	mov	byte[x],al
	mov	al, byte[left]
	add	al, 5
	mov	byte[y],al
	inc word[initflag]
	
	jmp Entrance

_intdata:
	mov ax, left
	push ax
	mov ax, Strlen
	push ax
	mov ax, Message
	push ax
	
	mov word[count],20
	mov word[initflag], 0
	cmp word[intflag], 3
	jle _intdata2
	mov word[intflag], 0
_intdata2:
	mov ax, ds
	cmp word[intflag], 0
	je call33h
	cmp word[intflag], 1
	je call34h
	cmp word[intflag], 2
	je call35h
	cmp word[intflag], 3
	je call36h
_intdata3:
	pop ax
	pop ax
	pop ax
	inc word[intflag]
	jmp INITXY
	

call33h:
	int 33h
	jmp _intdata3
call34h:
	int 34h
	jmp _intdata3
call35h:
	int 35h
	jmp _intdata3
call36h:
	int 36h
	jmp _intdata3


Exit:
	retf


Message:			db		"17310031"
Strlen              dw      $-Message
initflag			dw		0
intflag				dw		0
count				dw		1

vx    				db 		1
vy    				db 		1
left				db		0
upper				db		1
right				db		39
lower				db		12
x    				db 		0
y    				db		0


times	510-($-$$) 	db		0
dw		0xaa55

kernel.asm(部分)
int33h:
	push es
	push si
	
	mov es, ax
	mov ax, word[esp+10]
	mov si, ax
	mov byte[es:si], 'a'
	
	mov ax, word[esp+12]
	mov si, ax
	mov byte[es:si], 1
	
	mov ax, word[esp+14]
	mov si, ax
	mov byte[es:si], 0
	inc si
	mov byte[es:si], 1
	inc si
	mov byte[es:si], 39
	inc si
	mov byte[es:si], 12
	
	pop si
	pop es
	iret
	
int34h:
	push es
	push si
	
	mov es, ax
	mov ax, word[esp+10]
	mov si, ax
	mov byte[es:si], 'b'
	inc si
	mov byte[es:si], 'c'
	
	mov ax, word[esp+12]
	mov si, ax
	mov byte[es:si], 2
	
	mov ax, word[esp+14]
	mov si, ax
	mov byte[es:si], 40
	inc si
	mov byte[es:si], 1
	inc si
	mov byte[es:si], 79
	inc si
	mov byte[es:si], 12
	
	pop si
	pop es
	iret
	
int35h:
	push es
	push si
	
	mov es, ax
	mov ax, word[esp+10]
	mov si, ax
	mov byte[es:si], 'B'
	inc si
	mov byte[es:si], 'C'
	
	mov ax, word[esp+12]
	mov si, ax
	mov byte[es:si], 2
	
	mov ax, word[esp+14]
	mov si, ax
	mov byte[es:si], 0
	inc si
	mov byte[es:si], 13
	inc si
	mov byte[es:si], 39
	inc si
	mov byte[es:si], 24
	
	pop si
	pop es
	iret
	
int36h:
	push es
	push si
	
	mov es, ax
	mov ax, word[esp+10]
	mov si, ax
	mov byte[es:si], 'A'
	
	mov ax, word[esp+12]
	mov si, ax
	mov byte[es:si], 1
	
	mov ax, word[esp+14]
	mov si, ax
	mov byte[es:si], 40
	inc si
	mov byte[es:si], 13
	inc si
	mov byte[es:si], 79
	inc si
	mov byte[es:si], 24
	
	pop si
	pop es
	iret

在这里插入图片描述
可以看到同一个程序执行四种逻辑的屏幕输出, 而且两个程序之间有速度的继承关系.

小结

实验比起上个实验的难度减少了不少, 不需要太多踩坑的过程. 主要要掌握的技术是通过编程直接修改IVT表来自定义中断, 又因为中断的全局性, 我们可以在任意子程序内使用中断.
中断比起一般的函数call做了一些其他的工作, 它会进行标志寄存器的压栈, 进行查表来跳转到目标中断服务程序. 如果要把服务程序作为一般的函数用栈传参, 则需要一些额外的处理.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章