CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
实验题目
中断控制
实验目的
- 学习中断机制知识,掌握中断处理程序设计的要求
- 设计一个汇编程序,实现时钟中断处理程序
- 扩展 MyOS2,增加时钟中断服务,利用时钟中断实现与时间有关的操作
实验要求
- 操作系统工作期间,利用时钟中断,在屏幕 24 行 79 列位置轮流显示
|
、/
和\
,适当控制显示速度,以方便观察效果。 - 编写键盘中断响应程序,原有的你设计的用户程序运行时,键盘事件会做出有事反应:当键盘有按键时,屏幕适当位置显示
OUCH!OUCH!
。 - 在内核中,对 33 号、34 号、35 号和 36 号中断编写中断服务程序,分别在屏幕 1/4 区域内显示一些个性化信息。再编写一个汇编语言的程序,作为用户程序,利用
int 33
、int 34
、int 35
和int 36
产生中断调用你这 4 个服务程序。
实验方案
实验环境
软件
- Windows 10, 64-bit (Build 17763) 10.0.17763
- Windows Subsystem for Linux [Ubuntu 18.04.2 LTS]:WSL 是以软件的形式运行在 Windows 下的 Linux 子系统,是近些年微软推出来的新工具,可以在 Windows 系统上原生运行 Linux。
- gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04):C 语言程序编译器,Ubuntu 自带。
- NASM version 2.13.02:汇编程序编译器,通过
sudo apt install nasm
安装在 WSL 上。 - Oracle VM VirtualBox 6.0.4 r128413 (Qt5.6.2):轻量开源的虚拟机软件。
- VSCode - Insiders v1.33.0:好用的文本编辑器,有丰富的插件。
- hexdump for VSCode 1.7.2: VSCode 中一个好用的十六进制显示插件。
- GNU Make 4.1:安装在 Ubuntu 下,一键编译并连接代码,生成最终的文件。
大部分开发环境安装在 WSL 上,较之于双系统、虚拟机等其他开发方案,更加方便,也方便直接使用 Linux 下的一些指令。
硬件
开发环境配置
所用机器型号为 VAIO Z Flip 2016
- Intel(R) Core(TM) i7-6567U CPU @3.30GHZ 3.31GHz
- 8.00GB RAM
虚拟机配置
- 处理器内核总数:1
- RAM:4MB
实验原理
本次实验的关键在于写中断向量表。x86 计算机在启动时会自动进入实模式状态。系统的 BIOS 初始化8259A
的各中断线的类型(参见前图),在内存的低位区(地址为0~1023[3FFH]
,1KB)创建含 256 个中断向量的表 IVT (每个向量[地址]
占 4 个字节,格式为:16位段值:16位偏移值
)。
要实现「无敌风火轮」,可以利用时钟中断,对 8 号中断进行编程。在屏幕固定位置显示风火轮的字符,随后将字符修改为下一个。随后将 0x08 放入 0x20 的位置,处理时钟中断函数的入口放入 0x22。最后需要告诉硬件端口已经处理完中断,并正常返回。
在实验过程中,还遇到了进入操作系统时显示风火轮但是没有转起来的问题。经过思考,发现上一个实验中写的getch
是阻塞函数。于是,重新写了这个函数,重复扫描键盘缓冲区,有字符键入时再读进来。
实验过程
实验代码
bootloader.asm
和上一个实验中的代码完全相同,不再放出。
kernel.asm
操作系统内核的汇编部分代码,提供int 33
~int 36
中断在屏幕的四个象限上显示自定义信息,检测到Ctrl + C
时返回。
同时,提供了如下的全局函数供 C 语言部分调用。
_getch
从屏幕上无回显地读入一个字符。_getCursor
返回屏幕光标的位置。_setCursor
设置屏幕光标的位置。_putC
向光标位置写入一个字符。_pageUP
屏幕内容向上滚动。_loadProgram
加载用户程序。
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
%macro print 5 ; string, length, x, y, color
pusha
push ax
push bx
push cx
push dx
push bp
push ds
push es
mov ax, 0b800H
mov gs, ax
mov ax, cs
mov ds, ax
mov bp, %1
mov ax, ds
mov es, ax
mov cx, %2
mov ax, 1300H
mov dh, %3
mov dl, %4
mov bx, %5
int 10H
pop es
pop ds
pop bp
pop dx
pop cx
pop bx
pop ax
popa
%endmacro
%macro setIVT 2
push es
push ds
push si
pusha
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
popa
pop si
pop ds
pop es
%endmacro
bits 16
UserPrgOffset equ 0a100H
PrgSectorOffset equ 0
extern terminal
global _start
global _getch
global _getCursor
global _setCursor
global _putC
global _pageUP
global _loadProgram
_start:
setIVT 8, int8
setIVT 33, int33
setIVT 34, int34
setIVT 35, int35
setIVT 36, int36
call terminal
ret
_getCursor:
push ebp
mov ebp, esp
push ebx
sub esp, 4
mov eax, 768
mov edx, 0
mov ebx, edx
int 0x10
mov eax, edx
mov dword [ebp-8], eax
mov eax, dword [ebp-8]
add esp, 4
pop ebx
pop ebp
ret
_getch:
mov ah, 01H
int 16H
jz _getch
mov ah, 00H
int 16H
ret
_setCursor:
push ebp
mov ebp, esp
push ebx
mov eax, 512
mov ecx, 0
mov edx, dword [ebp+8]
mov ebx, ecx
int 0x10
pop ebx
pop ebp
ret
_putC:
push ebp
mov ebp, esp
push ebx
mov eax, dword [ebp+8]
or ah, 9
mov edx, dword [ebp+12]
mov ecx, 1
mov ebx, edx
int 0x10
pop ebx
pop ebp
ret
_pageUP:
push ebp
mov ebp, esp
mov eax, dword [ebp+8]
or ah, 6
mov ecx, 0
mov edx, 184fh
int 0x10
pop ebp
ret
_loadProgram:
push ebp
mov ebp, esp
push ax
push bx
push cx
push dx
push es
mov ax, cs
mov es, ax
mov bx, UserPrgOffset
mov ah, 2
mov al, 2
mov dl, 0
mov dh, 1
mov ch, 0
mov cl, byte [ebp+8]
add cl, PrgSectorOffset
int 13H
call UserPrgOffset
pop es
pop dx
pop cx
pop bx
pop ax
mov esp, ebp
pop ebp
ret
int8:
cli
pusha
push eax
call draw_slash
mov al, 20H
out 20H, al
out 0a0H, al
pop eax
popa
sti
iret
int33:
mov word[n], 12
mov word[m], 30
mov word[top], 2
mov word[left], 40
mov word[length], 8
mov word[msg], msg1
call show
iret
int34:
mov word[n], 12
mov word[m], 30
mov word[top], 2
mov word[left], 0
mov word[length], 10
mov word[msg], msg2
call show
iret
int35:
mov word[n], 12
mov word[m], 20
mov word[top], 13
mov word[left], 0
mov word[length], 20
mov word[msg], msg3
call show
iret
int36:
mov word[n], 12
mov word[m], 14
mov word[top], 13
mov word[left], 40
mov word[length], 26
mov word[msg], msg4
call show
iret
draw_slash:
print bar,1,24,78,7
cmp byte[bar],'|'
jne rslash
mov byte[bar],'/'
ret
rslash:
cmp byte[bar],'/'
jne hslash
mov byte[bar],'-'
ret
hslash:
cmp byte[bar],'-'
jne lslash
mov byte[bar],'\'
ret
lslash:
mov byte[bar],'|'
ret
show:
dec dword[cnt]
jnz show
mov dword[cnt],99999999
mov word ax, [t]
mov word bx, [n]
add bx, bx
sub bx, 2
xor dx, dx
div bx
cmp dx, [n]
jb xok
sub bx, dx
mov dx, bx
xok:
add dx, [top]
mov word[x], dx
mov word ax, [t]
mov word bx, [m]
add bx,bx
sub bx,2
xor dx, dx
div bx
cmp dx, [m]
jb yok
sub bx, dx
mov dx, bx
yok:
add dx,[left]
mov word [y],dx
inc word[t]
print [msg],[length],[x],[y],[x]
mov ah, 01H
int 16H
jz show
print msgouch,10,[x],[y],[x]
mov ah, 00H
int 16H
cmp ax, 2e03H
jne show
ret
datadef:
cnt dd 1
t dw 0
x dw 1
y dw 0
n dw 12
m dw 32
top dw 2
left dw 40
length dw 8
msg dw 1
msg1 db ' wu-kan '
msg2 db ' 17341163 '
msg3 db ' [email protected] '
msg4 db ' https://wu-kan.github.io '
msgouch db 'Ouch!Ouch!'
bar db '|'
kerner.c
操作系统内核 C 语言部分的代码。
和上一实验相比:
- 去掉了全部的内嵌汇编,改为调用外部汇编函数
- 修改了
clear
清屏的逻辑,原来是写若干个回车,现在是屏幕向上滚动一页并将光标移至首行首列 - 修改了显示回车的逻辑,原来是写若干个空格直至光标移动到下一行,现在直接移动光标
函数不能传字符变量的问题仍然没有解决。
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25
#define MAX_BUF_LEN (SCREEN_WIDTH * SCREEN_HEIGHT)
#define PROGRAM_NUM 4
extern int _getch();
extern int _getCursor();
extern void _pageUP(int);
extern void _loadProgram(int);
extern void _setCursor(int cur);
extern void _putC(int ch, int color);
void putchar(char c)
{
int cur = _getCursor(), curX = cur >> 8, curY = cur - (curX << 8);
if (c == '\r' || c == '\n')
{
if (++curX >= SCREEN_HEIGHT)
--curX, _pageUP(1);
return _setCursor(curX << 8);
}
_putC(c, 0x07);
if (++curY >= SCREEN_WIDTH)
putchar('\n');
_setCursor(curX << 8 | curY);
}
void gets(char *s)
{
for (;; ++s)
{
putchar(*s = _getch());
if (*s == '\r' || *s == '\n')
break;
}
*s = 0;
}
void printf(const char *s)
{
for (; *s; ++s)
putchar(*s);
}
int strcmp(const char *s1, const char *s2)
{
while (*s1 && (*s1 == *s2))
++s1, ++s2;
return (int)*s1 - (int)*s2;
}
void terminal()
{
const struct
{
const char *name, *size, *command;
int address;
} prg[PROGRAM_NUM] =
{{"prg1", " 3 bytes", "exec 1", 1},
{"prg2", " 3 bytes", "exec 2", 2},
{"prg3", " 3 bytes", "exec 3", 3},
{"prg4", " 3 bytes", "exec 4", 4}};
char str[MAX_BUF_LEN] = "$ ";
printf(str), gets(str);
const char
CLEAR_COM[] = "clear",
HELP_COM[] = "help",
EXEC_COM[] = "exec",
EXIT_COM[] = "exit",
LS_COM[] = "ls";
if (!strcmp(str, CLEAR_COM))
_pageUP(SCREEN_HEIGHT), _setCursor(0);
else if (!strcmp(str, HELP_COM))
{
const char
HELP_INFO[] =
"WuK-shell v0.0.2\n"
"These shell commands are defined internally.\n"
"Command Description\n"
"clear -- Clean the screen\n"
"help -- Show this list\n"
"exec -- Execute all the user programs\n"
"exec [num] -- Execute the num-th program\n"
"exit -- Exit OS\n"
"ls -- Show existing programs\n";
printf(HELP_INFO);
}
else if (!strcmp(str, EXEC_COM))
for (int i = 0; i < PROGRAM_NUM; ++i)
_loadProgram(prg[i].address);
else if (!strcmp(str, EXIT_COM))
return;
else if (!strcmp(str, LS_COM))
for (int i = 0; i < PROGRAM_NUM; ++i)
printf(prg[i].name), printf(prg[i].size), putchar('\n');
else
for (int i = 0;; ++i)
{
if (i == PROGRAM_NUM)
{
printf(str);
const char
COMM_NOT_FOUND[] =
" : command not found, type \'help\' for available commands list.\n";
printf(COMM_NOT_FOUND);
break;
}
if (!strcmp(str, prg[i].command))
{
_loadProgram(prg[i].address);
break;
}
}
terminal();
}
link.ld
将wukos.asm
和kernel.c
两个文件编译出来的内容连接起来。和上一个实验中的完全相同,不再放出。
prg1.asm~prg4.asm
直接调用 int 33、int 34、int 35 和 int 36 四个中断实现(编译出来的大小仅有 3bytes)。
1
2
3
org 0a100H
int 33
ret
上面是 prg1.asm 的内容,其余同理,不再放出。
Makefile
和上一个实验完全相同,不再放出。
运行结果
如上图,进入操作系统后开始了「无敌风火轮」(右下角)。
如上图,使用exec
指令轮流运行我的四个程序,分别调用软中断int 33
~int 36
。按下 Ctrl+C 可以退出程序。程序检测到键盘输入,因此显示Ouch!Ouch!
风火轮仍然在转。 输入若干指令。
风火轮仍然在转。 继续输入若干指令,此时超出屏幕显示范围,自动滚屏。
风火轮仍然在转。
实验总结
这次实验让我深入理解了中断服务程序的工作原理。中断响应后,先到内存指定位置找到中断向量表,然后跳转到中断服务程序。中断服务程序需先保存寄存器。中断服务程序完成后,需先还原寄存器,然后调用中断返回指令。所以在做实验的时候,就需要修改操作系统内核,使操作系统内核修改中断向量表,才能实现自定义中断服务程序。
在实验中,因为乱用宏以及没有恢复寄存器出了挺多问题,好在最终一一解决了。这也让我意识到之前的代码写的有多差、有多少隐患。因此,几乎将原先的代码完全重构了一遍。希望自己还是要多多注意一下这方面的问题。