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

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

实验目的

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)编写实验报告,描述实验工作的过程和必要的细节,如截屏或录屏,以证实实验工作的真实性

1618293085038

实验环境

1.系统与虚拟机
  • Windows 10 - x64 18363.1139
  • VMware Workstation 16 Player:用于跑ubuntu虚拟机
  • Ubuntu 20.04.2 LTS
  • VirtualBox-6.1.18-142142-Win:用于运行.img文件
  • DOSBox DOS Emulator 0,74,0,0:用于tcc和tasm编译,并且运行.com文件
2.windows上的相关软件、编译器等
  • NASM version 2.10.07 compiled on Jan 2 2013
  • TCC.EXE:用于16位C语言编程
  • TLINK.EXE:用于C语言与汇编语言链接
  • TASM.EXE:用于.asm文件的汇编
3.Ubuntu上的相关软件、编译器等
  • NASM version 2.14.02
  • makefile:GNU Make 4.2.1

实验前置知识

x86计算机在启动时会自动进入实模式状态,在内存的低位区(地址为0~1023[3FFH],1KB)创建含256个中断向量的表IVT (每个向量[地址]占4个字节,格式为:16位段值:16位偏移值)。

image-20210413145103907

下图是中断向量表,我们可以看到这次实验用到的中断向量主要包括8号中断向量(时钟中断)和9号中断向量(键盘中断)

2

实验过程

1.例程分析

该代码实现的是,在窗口坐标为(12,39)的位置,显示字符。从'!'开始,按照ASCII的顺序,依次显示之后的所有字符。每隔一段时间字符就会变化一次。

  1. 将Timer在代码中的偏移量设置为时钟中断向量(08h)的偏移地址(中断号为8,4*8 = 32,转换为16进制即20h);再将该代码段的地址(CS)作为时钟中断向量的段地址。

  2. 程序在将初始字符'!'窗口中显示之后,就进入死循环。

  3. 而Timer中的代码会在很短的时间内进行调用:

    • 设置一个延迟计数,每四次中断才进行一次字符的变化;

    • 每次字符变化在相应的ASCII码基础上+1;

    • 中断结束后,需要发送EOI(End of Interrupt)到ISR相应的位置,将其清零,并中断返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
org 100h            ; 程序加载到100h,可用于生成COM
; 设置时钟中断向量(08h),初始化段寄存器
xor ax,ax ; AX = 0
mov es,ax ; ES = 0
mov word [es:20h],Timer ; 设置时钟中断向量的偏移地址
mov ax,cs
mov word [es:22h],ax ; 设置时钟中断向量的段地址=CS
mov ds,ax ; DS = CS
mov es,ax ; ES = CS
; 在屏幕右下角显示字符‘!’
mov ax,0B800h ; 文本窗口显存起始地址
mov gs,ax ; GS = B800h
mov ah,0Fh ; 0000:黑底、1111:亮白字(默认值为07h)
mov al,'!' ; AL = 显示字符值(默认值为20h=空格符)
mov [gs:((80*12+39)*2)],ax ; 屏幕第 24 行, 第 79 列
jmp $ ; 死循环
; 时钟中断处理程序
delay equ 4 ; 计时器延迟计数
count db delay ; 计时器计数变量,初值=delay
Timer:
dec byte [count] ; 递减计数变量
jnz end ; >0:跳转
inc byte [gs:((80*12+39)*2)] ; =0:递增显示字符的ASCII码值
mov byte[count],delay ; 重置计数变量=初值delay
end:
mov al,20h ; AL = EOI
out 20h,al ; 发送EOI到主8529A
out 0A0h,al ; 发送EOI到从8529A
iret ; 从中断返回

2.在右下角显示“无敌风火轮”

在老师的代码基础上进行修改。

我们只需要将风火轮的移动到右下角,并且将字符的变化修改为/|\-的依次变化即可(增加多一个横杠,会使风火轮更像是风火轮)。

主要修改的是Timer部分的代码。增加一个变量pos来对应字符的位置,每次中断发生,改变pos,即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
char db ' /|\-'            ;按顺序存储风火轮的四个状态
pos dw 4 ;字符的索引
delay equ 1 ; 计时器延迟计数
count db delay ; 计时器计数变量,初值=delay
Timer:
push ax
push bx
push cx
push dx
push bp
push es
push ds

mov ax,0B800h
mov es,ax
mov ah,0Fh

dec byte ptr [count]
jnz end1

mov bx, word ptr[pos]
mov al,byte ptr[char+bx] ;获取新的字符
mov [es:((80*24+79)*2)] , ax

mov byte ptr[count],delay
dec word ptr[pos]
jz loop1 ;若pos已为0,
end1:
mov al,20h ; AL = EOI
out 20h,al ; 发送EOI到主8529A
out 0A0h,al ; 发送EOI到从8529A

pop ds
pop es
pop bp
pop dx
pop cx
pop bx
pop ax
iret

loop1:
mov word ptr[pos], 4
jmp end1

而在监控程序monitor.asm,也需要进行修改。在程序开始之前,需要将Timer设置到时钟中断的位置。注意!!!在这之后需要对寄存器重新设置初始值。

1
2
3
4
5
6
7
8
9
10
xor ax,ax            ; AX = 0
mov es,ax ; ES = 0
mov word ptr es:[20h],offset Timer
mov ax,cs
mov word ptr es:[22h],ax

mov ax,cs
mov ds,ax; DS = CS
mov es,ax; ES = CS
mov ss,ax; SS = cs

3.先尝试使用键盘中断实现风火轮

我们先来尝试一下,把无敌风火轮改写为使用键盘来控制它转动与否。只需要改写一下9号中断向量的段地址和偏移地址,并添加下述代码到Timer这一段代码的最开头。

60h为CPU对应键盘的输入缓冲端口 in al,60h就从键盘缓冲中读取1个byte,到al中。只有这样,才能够判断是否需要发生键盘中断。

1
in al,60h

很快我们就能实现一个由键盘输入控制的风火轮了。

4.OUCH!

首先,根据上述的方法,我们可以写出一个打印"OUCH! OUCH!"键盘中断程序。基本过程为:

1
寄存器入栈——>输出字符串——>延迟——>清空输出的字符串——>发送EOI——>寄存器出栈——>中断返回

这里输出字符串,一开始使用的是BIOS中的10h中断,但是出现了一些bug,我改用了循环直接显示字符的方法,具体原因会在后文说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
    ouch_delay equ 50000                
ouch_ddelay equ 2000
ouch_count dw ouch_delay
ouch_dcount dw ouch_ddelay
ouch_str db "OUCH! OUCH!"
index dw 0
Keyboard:
push ax
push bx
push cx
push dx
push bp
push es
push ds
mov word ptr[index], 0
showagain:
mov bx,word ptr[index]
add bx,80*3+55
mov ax,2
mul bx
mov bp,ax
mov ax,0B800h
mov es,ax
mov ah,07h ; 0000:黑底、1111:亮白字(默认值为07h)
mov bx,word ptr[index]
mov al,byte ptr[ouch_str+bx] ; AL = 显示字符值(默认值为20h=空格符)
mov es:[bp],ax ; 显示字符的ASCII码值
inc word ptr[index]
cmp word ptr[index], 11
jnz showagain
continue:

;延迟
dec word ptr[ouch_count] ; 递减计数变量
jnz continue ; >0:跳转;
mov word ptr[ouch_count],ouch_delay ;延时

dec word ptr[ouch_dcount] ; 递减计数变量
jnz continue
mov word ptr[ouch_count],ouch_delay
mov word ptr[ouch_dcount],ouch_ddelay ;延时

;清除
mov ah,6
mov al,0
mov ch,3
mov cl,55
mov dh,3
mov dl,65
mov bh,7
int 10H

in al,60h
mov al,20h ; AL = EOI
out 20h,al ; 发送EOI到主8529A
out 0A0h,al ; 发送EOI到从8529A

;中断返回
pop ds
pop es
pop bp
pop dx
pop cx
pop bx
pop ax

iret

那要如何实现在用户程序运行的过程中,能成功产生键盘中断,同时在用户程序结束后,还能够正常地通过键盘来进行输入与输出呢?

当运行用户程序时,要先把原有的中断向量口保存起来,然后将自己写的键盘中断Keyboard存放到9号中断位置。

1
2
3
4
5
6
7
8
9
10
xor ax, ax
mov es, ax
mov bp,offset interrupt_saved
mov word ptr ax,es:[36]
mov word ptr [bp],ax
mov word ptr ax,es:[38]
mov word ptr [bp+2],ax

mov word ptr es:[36], offset Keyboard
mov word ptr es:[38], cs

在用户程序返回内核之前,把原有键盘中断向量还原回去。

1
2
3
4
5
6
7
xor ax, ax
mov es, ax
mov bp,offset interrupt_saved
mov word ptr ax,[bp]
mov word ptr es:[36],ax
mov word ptr ax,[bp+2]
mov word ptr es:[38],ax

5.将所有文件进行编译,并写入软盘

这一次,我将用户程序和开机程序都存放到了磁头号为1的地方,以给内核更大的空间。(扇区号0~17,当满18的时候,就要在磁头号+1;而makefile文件中的seek值只需要继续往下数就可以了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BIN = loader.bin startOS.bin 19335209_A.bin 19335209_B.bin 19335209_C.bin 19335209_D.bin
IMG = wuhlan.img
all: clear $(BIN) $(IMG)
clear:
rm -f $(BIN) $(IMG)
%.bin: %.asm
nasm -fbin $< -o $@
%.img:
/sbin/mkfs.msdos -C $@ 1440
dd if=loader.bin of=$@ conv=notrunc
dd if=MONITOR.COM of=$@ seek=1 conv=notrunc
dd if=19335209_A.bin of=$@ seek=18 conv=notrunc
dd if=19335209_B.bin of=$@ seek=19 conv=notrunc
dd if=19335209_C.bin of=$@ seek=20 conv=notrunc
dd if=19335209_D.bin of=$@ seek=21 conv=notrunc
dd if=startOS.bin of=$@ seek=22 conv=notrunc

实验结果

通过观察,我们可以发现,无论是开机程序还是用户程序都可以成功显示"OUCH! OUCH!",同时可以看到,无敌风火轮一直在右下角旋转。

image-20210416111142520
image-20210416111029567
image-20210416111057382

问题与解决方式

  1. 延续之前实验出现的错误,当C程序写的过多的时候,字符串的输出就会出现各种莫名错误。解决方法就是,将没有必要的函数如upper()lower()等函数删除。

  2. restart.bin开机动画程序可以显示"OUCH! OUCH!",但是用户程序无法显示。

    问题原因不太清楚,很可能与BIOS的int 10h调用有关。但是明明我已经将所有寄存器放入栈中。

    我将其修改为直接将字符循环一个个地在窗口中显示,最终用户程序和开机程序都可以成功显示"OUCH!"

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        mov word ptr[index], 0
    showagain:
    mov bx,word ptr[index]
    add bx,80*3+55
    mov ax,2
    mul bx
    mov bp,ax
    mov ax,0B800h
    mov es,ax
    mov ah,07h ; 0000:黑底、1111:亮白字(默认值为07h)
    mov bx,word ptr[index]
    mov al,byte ptr[ouch_str+bx] ; AL = 显示字符值(默认值为20h=空格符)
    mov es:[bp],ax ; 显示字符的ASCII码值
    inc word ptr[index]
    cmp word ptr[index], 11
    jnz showagain

创新性工作

  1. 在风火轮三种形态的基础上,再多添加一个横杠,使其更像是在转动
  2. 在"OUCH! OUCH!"出现之后,过一小段时间,会自动清空。给用户以警告——“不要再碰键盘啦!!!”。同时消失之后,不会影响用户程序的观看体验(捂脸,逃~)

实验总结

本次实验,是在上一个实验基础上完成的,仅仅加入了两个中断内容。本以为在上次搭好了基本框架后,这一次实验会相对轻松。但是。。。现实并不如我所愿。如果说之前搭的地基就歪了,接下来建造的楼只会越来越歪,最后甚至可能倒塌。

这次实验除了加入中断向量外,还需要对上次的实验进行完善,工作量并没有减少。首先是对所有的用户程序的扇区号进行后移,以提供给内核更大的空间,在这个过程中,对于磁盘的划分,磁头号扇区等有了更深的理解;其次是删除了C语言中冗余的函数;还有就是重写用户程序,曾经自以为是地使用“Esc”来返回用户程序,如今在用户程序运行的过程中,因为键盘中断被改写了,只好使用户程序在运行到某个特定地时刻,就自动返回监控程序。

本次实验重点学习了,中断向量表相关知识、用户程序执行过程中是如何发生中断的。在写中断程序相关的函数时,我特别地小心,在刚开始的时候就尽量将用到的所有寄存器都入栈,防止寄存器混乱出现莫名的错误。时间中断是相对容易实现的,只需要加入 入栈出栈的操作,并且在监控程序启动的时候就将该代码段存入中断向量表就可以了。但是键盘中断要复杂很多,涉及到用户程序启动时改变键盘中断,用户程序结束后恢复键盘中断的操作。而且很容易与现有的用户程序产生冲突,所以花的时间多很多。另外,我会尽可能地在一些完成一个小步骤或是小修改之后,就立刻进行编译debug,防止错误过多,到后期难以处理。

最后,希望接下来的操作系统实验能顺顺利利吧。


实验4:具有中断处理的内核
https://wuhlan3.gitee.io/2021/04/16/实验4:具有中断处理的内核/
Author
Wuhlan3
Posted on
April 16, 2021
Licensed under