x86汇编语言-从实模式到保护模式笔记

中断和动态时钟显示

Posted by BINBIN Blog on October 24, 2019

硬中断

  • 硬中断的工作原理
  • 软中断的工作原理
  • 中断向量表

硬中断一般是外部硬件中断-就是从处理器外部来的中断信号。

当外部设备发生错误或者有数据要传送时,或者处理器交给它的任务处理完了,它都会向处理器发送信号,高速处理器。

如下图,外部硬件中断是通过两个信号线引入处理器内部的。这两根线的名字恩别叫做NMI和INTR!

当一个中断发生时,处理器将会通过中断引脚NMI和INTR得到通知。除此之外,处理器还需要知道发生了什么,以便采取适当的处理措施。

每种类型的中断都被统一编号,这称为中断类型号、中断向量或者中断号。

非屏蔽中断(不可屏蔽中断)

NMI接收的是不可屏蔽中断。

不可屏蔽中断的中断号统一为2(因为所有不可屏蔽中断都几乎是致命的无法解决,所以处理器干脆不处理的,统一将他们的中断号令为2,处理器接收到2号中断时直接放弃继续正常工作,也不会试图纠正已经发生的错误。)。

可屏蔽中断

和NMI不同,INTR一般接收的是可屏蔽中断。大多数的中断也是可屏蔽中断。

INTR接收可屏蔽中断。

Intel处理器允许有256个中断。中断号是0~255。如下图是一个中断控制器,它管理传送来的中断信号,决定是否将中断传送给CPU。

以上两个8259芯片负责15个中断号的管理。注意这里为什么是15个而不是256个呢?其实是每次它管理的中断号不一样的,这一次可能管理的是10-25号,下次可能管理的是其他的号。

那么在哪里屏蔽中断或者不屏蔽中断呢?实际上有两个位置,一个是8259内部,一个是处理器内部。

8259内部:在8259内部有一个中断屏蔽寄存器,这是个8位寄存器,对应着该芯片的8个输入引脚,对应的位是0还是1决定了该引脚输入的中断是否能通过8259芯片。0表示允许 1表示阻断

处理器内部:除了要看8259内部,最终还要看处理器。处理器内部的标志寄存器FLAGS有一个标志IF。这是中断标志。当IF为0时所有从INTR来的中断都被忽略。当IF为1时处理器可以接收和相应中断。

实模式下中断向量表

所谓中断处理,就是处理器要执行一段与该中断有关系的程序(指令)。处理器可以识别256个中断,那么理论上就需要256个中断程序。

这些程序的实际位置并不重要,重要的是,在实模式中,处理器要求将它们的入口点(还记得程序的入口点么?不记得话看上一篇文章)集中存放到内存中的从物理地址0x00000处(0x0000:0x0000)到0x003ff处(0x0000:0x03ff)。共1KB的内存空间。这就是所谓的中断向量表。

如下图是实模式下的中断向量表:

中断信号来自哪个引脚,8259芯片是最清楚的,所以他会把相应的中断号告诉处理器,处理器拿着这个中断号,要顺序做以下几件事:

保护断点的现场
    首先将FLAGS寄存器压栈,然后清楚它的IF位(防止在中断的时候被打断,如果想要有嵌套中断,可以在编写中断处理程序时适时使用sti指令开放中断)
    然后依次将CS于IP寄存器压栈

执行中断处理程序
    由于处理器已经拿到了中断号,它用中断号乘以4(见上图)就得到了该中断的入口点的偏移地址和段地址
    接着将得到的段地址与偏移地址传送给CS和IP

返回到断点接着执行
    所有中断的程序的最后是一条iret指令。这将导致处理器一次从栈中弹出IP CS FLAGS的原始内容,于是转到主程序继续执行
    由于中断处理程序返回时已经将FLAGS内容恢复,所以IF标志位也恢复。也就是说可以接收新的中断

软中断

和硬件中断不同,软中断是处理器内部产生的。是由执行指令引起的。

软中断是由int指令引起的。这类中断不需要中断识别总线周期,中断号在指令中给出。

实时时钟、 CMOS RAM 和 BCD 编码

为什么计算机能够准确地显示日期和时间?原因很简单,如图 9-2 所示, 在外围设备控制器芯片 ICH 内部,集成了实时时钟电路(Real Time Clock, RTC)和两小块由互补金属氧化物(CMOS)材料组成的静态存储器(CMOS RAM)。实时时钟电路负责计时,而日期和时间的数值则存储在这块存储器中!

RTC 芯片由一个振荡频率为 32.768kHz 的石英晶体振荡器(晶振)驱动,经分频后,用于对CMOS RAM 进行每秒一次的时间刷新!

CMOS RAM 的访问,需要通过两个端口来进行。 0x70 或者 0x74 是索引端口,用来指定 CMOS RAM 内的单元; 0x71 或者 0x75 是数据端口,用来读写相应单元里的内容。举个例子,以下代码用于读取今天是星期几:

mov al,0x06
out 0x70,al
in al,0x71

不得不说的是,从很早的时候开始,端口 0x70 的最高位(bit 7)是控制 NMI 中断的开关。当它为 0 时,允许 NMI 中断到达处理器,为 1 时,则阻断所有的 NMI 信号,其他 7 个比特,即 0~6 位,则实际上用于指定 CMOS RAM 单元的索引号,这种规定直到现在也没有改变。 如图 9-4 所示,尽管端口 0x70 的位 7 不是中断信号,但它能控制与非门的输出,决定真正的NMI 中断信号是否能到达处理器!

代码清单

代码如下:


1  			 ;代码清单9-1
2  			 ;文件名:c09_1.asm
3  			 ;文件说明:用户程序 
4  			 ;创建日期:2011-4-16 22:03
5  			 
6  	;===============================================================================
7  	SECTION header vstart=0                     ;定义用户程序头部段 
8  		program_length  dd program_end          ;程序总长度[0x00]
9  		
10 		;用户程序入口点
11 		code_entry      dw start                ;偏移地址[0x04]
12 						dd section.code.start   ;段地址[0x06] 
13 		
14 		realloc_tbl_len dw (header_end-realloc_begin)/4
15 												;段重定位表项个数[0x0a]
16 		
17 		realloc_begin:
18 		;段重定位表           
19 		code_segment    dd section.code.start   ;[0x0c]
20 		data_segment    dd section.data.start   ;[0x14]
21 		stack_segment   dd section.stack.start  ;[0x1c]
22 		
23 	header_end:                
24 		
25 	;===============================================================================
26 	SECTION code align=16 vstart=0           ;定义代码段(16字节对齐) 
27 	new_int_0x70:
28 		  push ax
29 		  push bx
30 		  push cx
31 		  push dx
32 		  push es
33 		  
34 	  .w0:                                    
35 		  mov al,0x0a                        ;阻断NMI。当然,通常是不必要的
36 		  or al,0x80                          
37 		  out 0x70,al
38 		  in al,0x71                         ;读寄存器A
39 		  test al,0x80                       ;测试第7位UIP 
40 		  jnz .w0                            ;以上代码对于更新周期结束中断来说 
41 											 ;是不必要的 
42 		  xor al,al
43 		  or al,0x80
44 		  out 0x70,al
45 		  in al,0x71                         ;读RTC当前时间(秒)
46 		  push ax
47 
48 		  mov al,2
49 		  or al,0x80
50 		  out 0x70,al
51 		  in al,0x71                         ;读RTC当前时间(分)
52 		  push ax
53 
54 		  mov al,4
55 		  or al,0x80
56 		  out 0x70,al
57 		  in al,0x71                         ;读RTC当前时间(时)
58 		  push ax
59 
60 		  mov al,0x0c                        ;寄存器C的索引。且开放NMI 
61 		  out 0x70,al
62 		  in al,0x71                         ;读一下RTC的寄存器C,否则只发生一次中断
63 											 ;此处不考虑闹钟和周期性中断的情况 
64 		  mov ax,0xb800
65 		  mov es,ax
66 
67 		  pop ax
68 		  call bcd_to_ascii
69 		  mov bx,12*160 + 36*2               ;从屏幕上的12行36列开始显示
70 
71 		  mov [es:bx],ah
72 		  mov [es:bx+2],al                   ;显示两位小时数字
73 
74 		  mov al,':'
75 		  mov [es:bx+4],al                   ;显示分隔符':'
76 		  not byte [es:bx+5]                 ;反转显示属性 
77 
78 		  pop ax
79 		  call bcd_to_ascii
80 		  mov [es:bx+6],ah
81 		  mov [es:bx+8],al                   ;显示两位分钟数字
82 
83 		  mov al,':'
84 		  mov [es:bx+10],al                  ;显示分隔符':'
85 		  not byte [es:bx+11]                ;反转显示属性
86 
87 		  pop ax
88 		  call bcd_to_ascii
89 		  mov [es:bx+12],ah
90 		  mov [es:bx+14],al                  ;显示两位小时数字
91 		  
92 		  mov al,0x20                        ;中断结束命令EOI 
93 		  out 0xa0,al                        ;向从片发送 
94 		  out 0x20,al                        ;向主片发送 
95 
96 		  pop es
97 		  pop dx
98 		  pop cx
99 		  pop bx
100		  pop ax
101
102		  iret
103
104	;-------------------------------------------------------------------------------
105	bcd_to_ascii:                            ;BCD码转ASCII
106											 ;输入:AL=bcd码
107											 ;输出:AX=ascii
108		  mov ah,al                          ;分拆成两个数字 
109		  and al,0x0f                        ;仅保留低4位 
110		  add al,0x30                        ;转换成ASCII 
111
112		  shr ah,4                           ;逻辑右移4位 
113		  and ah,0x0f                        
114		  add ah,0x30
115
116		  ret
117
118	;-------------------------------------------------------------------------------
119	start:
120		  mov ax,[stack_segment]
121		  mov ss,ax
122		  mov sp,ss_pointer
123		  mov ax,[data_segment]
124		  mov ds,ax
125		  
126		  mov bx,init_msg                    ;显示初始信息 
127		  call put_string
128
129		  mov bx,inst_msg                    ;显示安装信息 
130		  call put_string
131		  
132		  mov al,0x70
133		  mov bl,4
134		  mul bl                             ;计算0x70号中断在IVT中的偏移
135		  mov bx,ax                          
136
137		  cli                                ;防止改动期间发生新的0x70号中断
138
139		  push es
140		  mov ax,0x0000
141		  mov es,ax
142		  mov word [es:bx],new_int_0x70      ;偏移地址。
143											  
144		  mov word [es:bx+2],cs              ;段地址
145		  pop es
146
147		  mov al,0x0b                        ;RTC寄存器B
148		  or al,0x80                         ;阻断NMI 
149		  out 0x70,al
150		  mov al,0x12                        ;设置寄存器B,禁止周期性中断,开放更 
151		  out 0x71,al                        ;新结束后中断,BCD码,24小时制 
152
153		  mov al,0x0c
154		  out 0x70,al
155		  in al,0x71                         ;读RTC寄存器C,复位未决的中断状态
156
157		  in al,0xa1                         ;读8259从片的IMR寄存器 
158		  and al,0xfe                        ;清除bit 0(此位连接RTC)
159		  out 0xa1,al                        ;写回此寄存器 
160
161		  sti                                ;重新开放中断 
162
163		  mov bx,done_msg                    ;显示安装完成信息 
164		  call put_string
165
166		  mov bx,tips_msg                    ;显示提示信息
167		  call put_string
168		  
169		  mov cx,0xb800
170		  mov ds,cx
171		  mov byte [12*160 + 33*2],'@'       ;屏幕第12行,35列
172		   
173	 .idle:
174		  hlt                                ;使CPU进入低功耗状态,直到用中断唤醒
175		  not byte [12*160 + 33*2+1]         ;反转显示属性 
176		  jmp .idle
177
178	;-------------------------------------------------------------------------------
179	put_string:                              ;显示串(0结尾)。
180											 ;输入:DS:BX=串地址
181			 mov cl,[bx]
182			 or cl,cl                        ;cl=0 ?
183			 jz .exit                        ;是的,返回主程序 
184			 call put_char
185			 inc bx                          ;下一个字符 
186			 jmp put_string
187
188	   .exit:
189			 ret
190
191	;-------------------------------------------------------------------------------
192	put_char:                                ;显示一个字符
193											 ;输入:cl=字符ascii
194			 push ax
195			 push bx
196			 push cx
197			 push dx
198			 push ds
199			 push es
200
201			 ;以下取当前光标位置
202			 mov dx,0x3d4
203			 mov al,0x0e
204			 out dx,al
205			 mov dx,0x3d5
206			 in al,dx                        ;高8位 
207			 mov ah,al
208
209			 mov dx,0x3d4
210			 mov al,0x0f
211			 out dx,al
212			 mov dx,0x3d5
213			 in al,dx                        ;低8位 
214			 mov bx,ax                       ;BX=代表光标位置的16位数
215
216			 cmp cl,0x0d                     ;回车符?
217			 jnz .put_0a                     ;不是。看看是不是换行等字符 
218			 mov ax,bx                       ; 
219			 mov bl,80                       
220			 div bl
221			 mul bl
222			 mov bx,ax
223			 jmp .set_cursor
224
225	 .put_0a:
226			 cmp cl,0x0a                     ;换行符?
227			 jnz .put_other                  ;不是,那就正常显示字符 
228			 add bx,80
229			 jmp .roll_screen
230
231	 .put_other:                             ;正常显示字符
232			 mov ax,0xb800
233			 mov es,ax
234			 shl bx,1
235			 mov [es:bx],cl
236
237			 ;以下将光标位置推进一个字符
238			 shr bx,1
239			 add bx,1
240
241	 .roll_screen:
242			 cmp bx,2000                     ;光标超出屏幕?滚屏
243			 jl .set_cursor
244
245			 mov ax,0xb800
246			 mov ds,ax
247			 mov es,ax
248			 cld
249			 mov si,0xa0
250			 mov di,0x00
251			 mov cx,1920
252			 rep movsw
253			 mov bx,3840                     ;清除屏幕最底一行
254			 mov cx,80
255	 .cls:
256			 mov word[es:bx],0x0720
257			 add bx,2
258			 loop .cls
259
260			 mov bx,1920
261
262	 .set_cursor:
263			 mov dx,0x3d4
264			 mov al,0x0e
265			 out dx,al
266			 mov dx,0x3d5
267			 mov al,bh
268			 out dx,al
269			 mov dx,0x3d4
270			 mov al,0x0f
271			 out dx,al
272			 mov dx,0x3d5
273			 mov al,bl
274			 out dx,al
275
276			 pop es
277			 pop ds
278			 pop dx
279			 pop cx
280			 pop bx
281			 pop ax
282
283			 ret
284
285	;===============================================================================
286	SECTION data align=16 vstart=0
287
288		init_msg       db 'Starting...',0x0d,0x0a,0
289					   
290		inst_msg       db 'Installing a new interrupt 70H...',0
291		
292		done_msg       db 'Done.',0x0d,0x0a,0
293
294		tips_msg       db 'Clock is now working.',0
295					   
296	;===============================================================================
297	SECTION stack align=16 vstart=0
298			   
299					 resb 256
300	ss_pointer:
301	 
302	;===============================================================================
303	SECTION program_trail
304	program_end:

用户程序的入口点在代码清单 9-1 的第 119 行,从这一行开始,到第 124 行,用于初始化各个段寄存器的内容。下面开始在中断向量表中安装实时时钟中断的入口点。既然本章的主题是中断, 那么就很有必要强调一件事。当处理器执行任何一条改变堆栈段寄存器 SS 的指令时,它会在下一条指令执行完期间禁止中断!

想象一下,如果刚刚修改了段寄存器 SS,在还没来得及修改 SP 的情况下,就发生了中断,会出现什么后果,而且要知道,中断是需要依靠堆栈来工作的。 因此,处理器在设计的时候就规定,当遇到修改段寄存器 SS 的指令时,在这条指令和下一条指令执行完毕期间,禁止中断,以此来保护堆栈。换句话说,你应该在修改段寄存器 SS 的指令之后,紧跟着一条修改堆栈指针 SP 的指令!

就代码清单 9-1 来说,在第 121、 122 行执行期间,处理器禁止中断!

RTC 芯片的中断信号,通向中断控制器 8259 从片的第 1 个中断引脚 IR0。在计算机启动期间,BIOS 会初始化中断控制器,将主片的中断号设为从 0x08 开始,将从片的中断号设为从 0x70 开始。 所以,计算机启动后, RTC 芯片的中断号默认是 0x70。

在安装中断向量之前,应该先显示些什么。第 126~130 行,显示两行提示信息,表明正在安装中断向量。这两个字符串位于第 286 行的数据段中。对于过程 put_string 没有什么好说的,它的代码和上一章相同,工作过程更没有区别!

为了修改某中断在中断向量表中的登记项,需要先找到它。第 132~135 行,将中断号 0x70 乘以 4,就是它在中断向量表内的偏移!

第 137 行,修改中断向量表时,需要先用 cli 指令清中断。

第 139~141 行,将段寄存器 ES 压栈暂时保存,并使它指向中断向量表(所在的段)!

接着,第 142~145 行,访问中断向量表内 0x70 号中断的表项,分别写入新中断处理过程的偏移地址和段地址。新的中断处理过程是从标号 new_int_0x70 处开始的,而且位于当前代码段内。所以,该中断处理过程的偏移地址就是标号 new_int_0x70 的汇编地址(注意,段 code 的定义中带有vstart=0 子句), 段地址就是当前段寄存器 CS 的内容。表项修改完毕,从堆栈中恢复段寄存器 ES的原始内容。

接下来,我们要设置 RTC 的工作状态,使它能够产生中断信号给 8259 中断控制器。

为了设置该中断,代码清单 9-1 第 147 行,将 RTC 寄存器 B 的索引 0x0b 传送到寄存器 AL。在访问 RTC 期间,最好是阻断 NMI,因此,第 148、 149 行,先用 or 指令将 AL 的最高位置 1,再写端口 0x70。

第 150、 151 行,用于通过数据端口 0x71 写寄存器 B。写的内容是 0x12,其二进制形式为00010010,对照表 9-3,其意义不难理解:允许更新周期照常发生,禁止周期性中断,禁止闹钟功能,允许更新周期结束中断,使用 24 小时制,日期和时间采用 BCD 编码。

每次当中断实际发生时,可以在程序(中断处理过程)中读寄存器 C 的内容来检查中断的原因。比如,每当更新周期结束中断发生时, RTC 就将它的第 4 位置 1。该寄存器还有一个特点,就是每次读取它后,所有内容自动清零。而且,如果不读取它的话(换句话说,相应的位没有清零),同样的中断将不再产生。 为此,第 153~155 行,读一下寄存器 C 的内容,使之开始产生中断信号。注意,在向索引端口 0x70 写入的同时,也打开了 NMI。毕竟,这是最后一次在主程序中访问 RTC。

RTC 芯片设置完毕后,再来打通它到 8259 的最后一道屏障。正常情况下, 8259 是不会允许 RTC中断的,所以,需要修改它内部的中断屏蔽寄存器 IMR。 IMR 是一个 8 位寄存器,位 0 对应着中断输入引脚 IR0,位 7 对应着引脚 IR7,相应的位是 0 时,允许中断,为 1 时,关掉中断。 8259 芯片是我见过的芯片中,访问起来最麻烦,也是我最讨厌的一个。好在有关它的资料非常好找,这里就简单地进行讲解。代码清单 9-1 第 157~159 行,通过端口 0xa1 读取 8259 从片的 IMR寄存器,用 and 指令清除第 0 位,其他各位保持原状,然后再写回去。于是, RTC 的中断可以被8259 处理了。 第 161 行, sti 指令将标志寄存器的 IF 位置 1,开放设备中断。从这个时候开始,中断随时都会发生,也随时会被处理。

在为中断过程做了初始化工作之后,主程序还是要继续执行的。代码清单 9-1 第 163~167行,用于显示中断处理程序已安装成功的消息。

接着,第 169~171 行,使段寄存器 DS 指向显示缓冲区,并在屏幕上的第 12 行 33 列显示一个字符“@”,该位置差不多是整个屏幕的中心。表达式 12160 + 332 是在指令编译阶段计算的,是该字符在显存中的位置。每个字符在显存中占 2 字节的位置,每行 80 个字符。

第 174 行, hlt 指令使处理器停止执行指令,并处于停机状态,这将降低处理器的功耗。

第 174~176 行用于形成一个循环,先是停机,接着某个外部中断使处理器恢复执行。 一旦处理器的执行点来到 hlt 指令之后,则立即使它继续处于停机状态。

第 42~52 行,分别访问 CMOS RAM 的 0、 2、 4 号单元,从中读取当前的秒、 分、时数据,按顺序压栈等待后续操作。 第 60~62 行,读一下 RTC 的寄存器 C,使得所有中断标志复位。这等于是告诉 RTC,中断已经得到处理,可以继续下一次中断。否则的话, RTC 看到中断未被处理,将不再产生中断信号。 RTC产生中断的原因有多种,可以在程序中通过读寄存器 C 来判断具体的原因。不过这里不需要,因为除了更新周期结束中断外,其他中断都被关闭了。

现在,终于可以在屏幕上显示时间信息了。

第 64、 65 行,临时将段寄存器 ES 指向显示缓冲区。 第 67、 68 行,首先从堆栈中弹出小时数,调用过程 bcd_to_ascii 来将用 BCD 码表示的“小时”转换成 ASCII。该过程是在第 105 行定义的,调用该过程时,寄存器 AL 中的高 4 位和低 4 位分别是“小时”的十位数字和个位数字。 第 108 行,将寄存器 AL 中的内容复制一份给 AH,以方便下一步操作。 第 109、 110 行,将寄存器 AL 中的高 4 位清零,只留下“小时”的个位数字。接着,将它加上0x30,就得到该数字对应的 ASCII 码。 十位上的数字在寄存器 AH 的高 4 位。第 112 行,用右移 4 位的方法,将“拉”到低 4 位,高 4 位在移动的过程中自动清零。 接着,第 113、 114 行,用同样的办法来得到十位数字的 ASCII 码。此时,寄存器 AH 中是十位数字的 ASCII 码, AL 中是个位数字的 ASCII 码,它们将作为结果返回给调用者。 最后,第 116 行用于返回调用者。

接着回到第 69 行,为了连续在屏幕上显示内容,最好是采用基址寻址来访问显存。这一行用于指定显示的内容位于显存的什么位置。实际上,这里指定的是第 12 行 36 列。同以前一样,每个字符在显存中占两个字节,每行 80 个字符,所以这里使用了表达式 12160 + 362,该表达式的值是在编译阶段计算的。 第 71、 72 行,分别将“小时”的两个数位写到显存中, 段地址在 ES 中,偏移地址分别是由寄存器 BX 和 BX+2 提供的。这里没有写入显示属性,这是因为我们希望采用默认的显示属性(屏幕是黑的,默认的显示属性是 0x07,即黑底白字)。 第 74、 75 行,用于在下一个屏幕位置显示冒号“:”,这是在显示时间时都会采用的分隔符。

最后,第 96~102 行,从堆栈中恢复被中断程序的现场,并用中断返回指令 iret 回到中断之前的地方继续执行。 iret 的意思是 Interrupt Return。

9-2 代码清单如下:

1 			 ;代码清单9-2
2 			 ;文件名:c09_2.asm
3 			 ;文件说明:用于演示BIOS中断的用户程序 
4 			 ;创建日期:2012-3-28 20:35
5 			 
6 	;===============================================================================
7 	SECTION header vstart=0                     ;定义用户程序头部段 
8 		program_length  dd program_end          ;程序总长度[0x00]
9 		
10		;用户程序入口点
11		code_entry      dw start                ;偏移地址[0x04]
12						dd section.code.start   ;段地址[0x06] 
13		
14		realloc_tbl_len dw (header_end-realloc_begin)/4
15												;段重定位表项个数[0x0a]
16		
17		realloc_begin:
18		;段重定位表           
19		code_segment    dd section.code.start   ;[0x0c]
20		data_segment    dd section.data.start   ;[0x14]
21		stack_segment   dd section.stack.start  ;[0x1c]
22		
23	header_end:                
24		
25	;===============================================================================
26	SECTION code align=16 vstart=0           ;定义代码段(16字节对齐) 
27	start:
28		  mov ax,[stack_segment]
29		  mov ss,ax
30		  mov sp,ss_pointer
31		  mov ax,[data_segment]
32		  mov ds,ax
33		  
34		  mov cx,msg_end-message
35		  mov bx,message
36		  
37	 .putc:
38		  mov ah,0x0e
39		  mov al,[bx]
40		  int 0x10
41		  inc bx
42		  loop .putc
43
44	 .reps:
45		  mov ah,0x00
46		  int 0x16
47		  
48		  mov ah,0x0e
49		  mov bl,0x07
50		  int 0x10
51
52		  jmp .reps
53
54	;===============================================================================
55	SECTION data align=16 vstart=0
56
57		message       db 'Hello, friend!',0x0d,0x0a
58					  db 'This simple procedure used to demonstrate '
59					  db 'the BIOS interrupt.',0x0d,0x0a
60					  db 'Please press the keys on the keyboard ->'
61		msg_end:
62					   
63	;===============================================================================
64	SECTION stack align=16 vstart=0
65			   
66					 resb 256
67	ss_pointer:
68	 
69	;===============================================================================
70	SECTION program_trail
71	program_end:

从键盘读字符并显示

代码清单 9-2 在框架上和前面的用户程序是一致的,差别在于代码段的功能上。 代码清单 9-2 第 28~32 行用于初始化各个段寄存器,这和以前的做法是相同的。 第 34~42 行用于在屏幕上显示字符串,采用的是循环的方法。循环用的是 loop 指令,为此,第 34 行用于计算字符串的长度,并传送到寄存器 CX 中,以控制循环的次数。第 35 行用于取得字符串的首地址。 向屏幕上写字符使用的是 BIOS 中断,具体地说就是中断 0x10 的 0x0e 号功能,该功能用于在屏幕上的光标位置处写一个字符,并推进光标位置。第 38~40 行分别按规范的要求准备各个参数,执行软中断。 第 41、 42 行将递增寄存器 BX 中的偏移地址,以指向下一个字符在数据段中的位置。然后, loop指令将寄存器 CX 的内容减 1,并在其不为零的情况下返回到循环体开始处,继续显示下一个字符。 剩下的工作内容既复杂,又简单。复杂是指,从键盘读取你按下的那个键,并把它显示在屏幕上,很复杂,需要访问硬件,写一大堆指令。简单是指,因为有了 BIOS 功能调用,这只需几条语句就能完成。 第 45、 46 行使用软中断 0x16 从键盘读字符,需要在寄存器 AH 中指定 0x00 号功能。该中断返回后,寄存器 AL 中为字符的 ASCII 码。 第 48~50 行又一次使用了 int 0x10 的 0x0e 号功能,把从键盘取得的字符显示在屏幕上。 第 52 行,执行一个无条件转移指令,重新从键盘读取新的字符并予以显示。