《x86汇编语言:从实模式到保护模式》(第9章---中断和动态时钟显示)

外部硬件中断

本部分主要以一个外部中断为例,详细说明一个外围部件向CPU请求中断的全过程。笔者阅读书中源码并根据个人理解适当改动注释。加载器代码仍然使用第八章的。

		;文件名:c09_01_user.asm
		;文件说明:用户程序
		;参考书:《x86汇编语言:从实模式到保护模式》李忠 著
		;代码功能:RTC更新周期结束中断(每秒更新1次)演示。RTC的中断号是0x70;		   RTC每秒更新1次时间,更新结束后发送中断请求,要求CPU执行中
		;		   断向量表0x70号对应的中断程序,事实上,是通知CPU的中断代理
		;		   (中断控制器,如8259芯片)请求中断的。
;===============================================================================
SECTION header vstart=0                     	;定义用户程序头部段 
		program_length  dd program_end          ;程序总长度[0x00]

		;用户程序入口点
		code_entry      dw start                ;偏移地址[0x04]
						dd section.code.start   ;段地址[0x06] 

		realloc_tbl_len dw (header_end-realloc_begin)/4
												;段重定位表项个数[0x0a]

	realloc_begin:
	;段重定位表           
		code_segment    dd section.code.start   ;[0x0c]
		data_segment    dd section.data.start   ;[0x14]
		stack_segment   dd section.stack.start  ;[0x1c]
    
header_end:                
    
;===============================================================================
SECTION code align=16 vstart=0           ;定义代码段(16字节对齐) 
	new_int_0x70:
		push ax
		push bx
		push cx
		push dx
		push es
      
	.w0:                                    
		mov al,0x0a                        ;阻断NMI。当然,通常是不必要的
		or al,0x80                          
		out 0x70,al
		in al,0x71                         ;读寄存器A
		test al,0x80                       ;测试第7位UIP 
		jnz .w0                            ;以上代码对于更新周期结束中断来说 
										   ;是不必要的 
		xor al,al
		or al,0x80
		out 0x70,al
		in al,0x71                         ;读RTC当前时间()
		push ax

		mov al,2
		or al,0x80
		out 0x70,al
		in al,0x71                         ;读RTC当前时间()
		push ax

		mov al,4
		or al,0x80
		out 0x70,al
		in al,0x71                         ;读RTC当前时间()
		push ax

		mov al,0x0c                        ;寄存器C的索引。且开放NMI 
		out 0x70,al
		in al,0x71                         ;读一下RTC的寄存器C,否则只发生一次中断
										   ;此处不考虑闹钟和周期性中断的情况 
		mov ax,0xb800
		mov es,ax

		pop ax
		call bcd_to_ascii
		mov bx,12*160 + 36*2               ;从屏幕上的1236列开始显示

		mov [es:bx],ah
		mov [es:bx+2],al                   ;显示两位小时数字

		mov al,':'
		mov [es:bx+4],al                   ;显示分隔符':'
		not byte [es:bx+5]                 ;反转显示属性 

		pop ax
		call bcd_to_ascii
		mov [es:bx+6],ah
		mov [es:bx+8],al                   ;显示两位分钟数字

		mov al,':'
		mov [es:bx+10],al                  ;显示分隔符':'
		not byte [es:bx+11]                ;反转显示属性

		pop ax
		call bcd_to_ascii
		mov [es:bx+12],ah
		mov [es:bx+14],al                  ;显示两位小时数字

		mov al,0x20                        ;中断结束命令EOI 
		out 0xa0,al                        ;8259从片发送 
		out 0x20,al                        ;8259主片发送 

		pop es
		pop dx
		pop cx
		pop bx
		pop ax

		iret

;-------------------------------------------------------------------------------
	bcd_to_ascii:                          ;BCD码转ASCII
										   ;输入:AL=bcd码
                                           ;输出:AX=ascii
		mov ah,al                          ;分拆成两个数字 
		and al,0x0f                        ;仅保留低4位 
		add al,0x30                        ;转换成ASCII 

		shr ah,4                           ;逻辑右移4位 
		and ah,0x0f                        
		add ah,0x30

		ret

;-------------------------------------------------------------------------------
	start:
		mov ax,[stack_segment]
		mov ss,ax
		mov sp,ss_pointer
		mov ax,[data_segment]
		mov ds,ax

		mov bx,init_msg                    ;显示初始信息 
		call put_string

		mov bx,inst_msg                    ;显示安装信息 
		call put_string

		mov al,0x70
		mov bl,4
		mul bl                             ;计算0x70号中断在IVT(中断向量表)中的偏移
		mov bx,ax                          

		cli                                ;防止改动期间发生新的0x70号中断
										   ;标志寄存器IF清零,CPU屏蔽所有中断
		push es
		mov ax,0x0000
		mov es,ax
		mov word [es:bx],new_int_0x70      ;中断处理程序的偏移地址写入0x70号中断的中断向量表处
										  
		mov word [es:bx+2],cs              ;中断处理程序的段地址写入0x70号中断的中断向量表处
		pop es

		mov al,0x0b                        ;RTC寄存器B
		or al,0x80                         ;阻断NMI 
		out 0x70,al
		mov al,0x12                        ;设置寄存器B,禁止周期性中断,开放更新周期结束中断 
		out 0x71,al                        ;采用BCD码,24小时制等,可对照书上0x0b寄存器表查看 

		mov al,0x0c
		out 0x70,al
		in al,0x71                         ;读RTC寄存器C,置零,允许新的中断发生

		in al,0xa1                         ;8259从片的IMR寄存器 
		and al,0xfe                        ;0位清零(此位连接RTC),这样RTC中断才能被8259芯片处理
		out 0xa1,al                        ;写回此寄存器 

		sti                                ;上面设置结束,重新开放中断 

		mov bx,done_msg                    ;显示安装完成信息 
		call put_string

		mov bx,tips_msg                    ;显示提示信息
		call put_string

		mov cx,0xb800
		mov ds,cx
		mov byte [12*160 + 33*2],'@'       ;屏幕第12行,35.idle:
		hlt                                ;使CPU进入低功耗状态,直到用中断唤醒
		not byte [12*160 + 33*2+1]         ;反转显示属性 
		jmp .idle

;-------------------------------------------------------------------------------
	put_string:                         ;显示串(0结尾)到屏幕。
										;输入:DS:BX=串地址
		mov cl,[bx]
		or cl,cl                        
		jz .exit                        ;最后一个字符是0,是0就退出
		call put_char
		inc bx                           
		jmp put_string

	.exit:
		ret

;-------------------------------------------------------------------------------
	put_char:                           	   ;显示一个字符
										   ;输入:cl=字符ascii
		push ax
		push bx
		push cx
		push dx
		push ds
		push es

		;以下取当前光标位置
		mov dx,0x3d4
		mov al,0x0e
		out dx,al						;向端口0x3d4表明要从寄存器0x0e得到光标高8位
		mov dx,0x3d5
		in al,dx                        ;从端口0x3d5读取高8位到al
		mov ah,al						;并存放到ah中

		mov dx,0x3d4
		mov al,0x0f
		out dx,al						;向端口0x3d4表明要从寄存器0x0f得到光标低8位
		mov dx,0x3d5
		in al,dx                        ;从端口0x3d5读取低8位到al 
		mov bx,ax						;BX=代表光标位置的16位数
		
		cmp cl,0x0d                     ;回车符?
		jnz .put_0a                     ;不是。看看是不是换行等字符 
		mov bl,80 						                     
		div bl							;是回车符,除以80得到光标所在行号放入al中
		mul bl							;行号乘以80得到当前行的行首位置
		mov bx,ax
		jmp .set_cursor

	.put_0a:
		cmp cl,0x0a                     ;换行符?
		jnz .put_other                  ;不是,那就正常显示字符 
		add bx,80
		jmp .roll_screen

	.put_other:                         ;正常显示字符
		mov ax,0xb800
		mov es,ax
		shl bx,1						;光标位置乘2代表字符的偏移地址(1个字符占2字节)
		mov [es:bx],cl

		;以下将光标位置推进一个字符
		shr bx,1
		add bx,1

	.roll_screen:
		cmp bx,2000                     ;光标超出屏幕?滚屏
		jl .set_cursor

		mov ax,0xb800
		mov ds,ax
		mov es,ax
		cld
		mov si,0xa0						;整体上移一行(80字符160字节)
		mov di,0x00
		mov cx,1920
		rep movsw
		mov bx,3840                     ;清除屏幕最底一行
		mov cx,80
	.cls:
		mov word[es:bx],0x0720
		add bx,2
		loop .cls

		mov bx,1920

	.set_cursor:						;设置光标在屏幕上的显示位置
										;输入:BX=光标位置
		mov dx,0x3d4
		mov al,0x0e
		out dx,al
		mov dx,0x3d5
		mov al,bh
		out dx,al
		mov dx,0x3d4
		mov al,0x0f
		out dx,al
		mov dx,0x3d5
		mov al,bl
		out dx,al

		pop es
		pop ds
		pop dx
		pop cx
		pop bx
		pop ax

		ret

;===============================================================================
SECTION data align=16 vstart=0

    init_msg       db 'Starting...',0x0d,0x0a,0
                   
    inst_msg       db 'Installing a new interrupt 70H...',0
    
    done_msg       db 'Done.',0x0d,0x0a,0

    tips_msg       db 'Clock is now working.',0
                   
;===============================================================================
SECTION stack align=16 vstart=0
           
                 resb 256
ss_pointer:
 
;===============================================================================
SECTION program_trail
program_end:

在这里插入图片描述
hlt命令要比jmp死循环少耗内存很多。。。

内部中断

发生在CPU内部,比如除以0指令引发内部中断。

软中断

int指令引起的中断处理。如int 3导致3号中断。

最有名的软中断是 BIOS 中断,之所以称为 BIOS 中断,是因为这些中断功能是在计算机加电之后,BIOS 程序执行期间建立起来的。换句话说,这些中断功能在加载和执行主引导扇区之前,就已经可以使用了。
BIOS 中断,又称 BIOS 功能调用,主要是为了方便地使用最基本的硬件访问功能。不同的硬件使用不同的中断号,比如,使用键盘服务时,中断号是 0x16,即

int 0x16

通常,为了区分针对同一硬件的不同功能,使用寄存器 AH 来指定具体的功能编号。举例来说,以下指令用于从键盘读取一个按键:

mov ah,0x00 ;从键盘读字符
int 0x16 ;键盘服务。返回时,字符代码在寄存器 AL 中
		;文件名:c09_02_user.asm
		;文件说明:用户程序
		;参考书:《x86汇编语言:从实模式到保护模式》李忠 著
		;代码功能:用于演示BIOS中断的用户程序。调用BIOS的0x10的ah=0x0e中断
		;	向光标处写字符,并把光标向后推进一个位置。调用0x16(ah=0x00)中断,
		;	从键盘输入字符。(可尝试下键盘上的各个键,有意思。。。)
         
;===============================================================================
SECTION header vstart=0                     ;定义用户程序头部段 
    program_length  dd program_end          ;程序总长度[0x00]
    
    ;用户程序入口点
    code_entry      dw start                ;偏移地址[0x04]
                    dd section.code.start   ;段地址[0x06] 
    
    realloc_tbl_len dw (header_end-realloc_begin)/4
                                            ;段重定位表项个数[0x0a]
    
    realloc_begin:
    ;段重定位表           
    code_segment    dd section.code.start   ;[0x0c]
    data_segment    dd section.data.start   ;[0x14]
    stack_segment   dd section.stack.start  ;[0x1c]
    
header_end:                
    
;===============================================================================
SECTION code align=16 vstart=0           ;定义代码段(16字节对齐) 
start:
      mov ax,[stack_segment]
      mov ss,ax
      mov sp,ss_pointer
      mov ax,[data_segment]
      mov ds,ax
      
      mov cx,msg_end-message
      mov bx,message
      
 .putc:
      mov ah,0x0e		;在屏幕上写字符al
      mov al,[bx]
      int 0x10
      inc bx
      loop .putc

 .reps:
      mov ah,0x00		
      int 0x16			;键盘通信例程,从键盘读字符(ah=0x00;尝试回车键、退格键、Tab键及数字英文字母键试试!!
      
      mov ah,0x0e
      int 0x10

      jmp .reps

;===============================================================================
SECTION data align=16 vstart=0

    message       db 'Hello, friend!',0x0d,0x0a
                  db 'This simple procedure used to demonstrate '
                  db 'the BIOS interrupt.',0x0d,0x0a
                  db 'Please press the keys on the keyboard ->'
    msg_end:
                   
;===============================================================================
SECTION stack align=16 vstart=0
           
                 resb 256
ss_pointer:
 
;===============================================================================
SECTION program_trail
program_end:

在这里插入图片描述

参考资料

[1] 《x86汇编语言:从实模式到保护模式》李忠 著
[2] 《汇编语言》王爽 著

发布了323 篇原创文章 · 获赞 193 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/ccnuacmhdu/article/details/103653850
今日推荐