【X86汇编语言 从实模式到保护模式】05 中断和时钟显示

本文详细介绍了处理器如何处理外部硬件中断,包括非屏蔽中断和可屏蔽中断,以及中断向量表的作用。此外,还讲解了实时时钟的工作原理,包括CMOSRAM的存储结构和RTC芯片的功能。最后,展示了如何通过中断动态显示时钟的程序源码。
摘要由CSDN通过智能技术生成

1. 外部硬件中断

硬件中断一般是从处理器外面来的中断信号。当外部设备发生错误,或者有数据要传送(比如从网络接收到一个针对当前主机的数据包),或者处理器交给它的事情处理完了。

如下图,外部硬件中断是通过两个信号线引入处理器内部的。这两根线的名字叫做NMI和INTR。
在这里插入图片描述
中断信号的来源,或者说是产生中断的设备,称为中断源。当一个中断发生时,处理器将会通过中断引脚 NMI 和 INTR 得到通知。除此之外,处理器还需要知道发生了什么事,以便采取适当的处理措施。每种类型的中断都被统一编号,这称为中断类型号、中断向量或者中断号。

1.1 非屏蔽中断

NMI(Non Maskable Interrupt)接受的是非屏蔽中断。

实模式下,NMI 被赋予了统一的中断号 2,因为几乎所有触发非屏蔽中断的事件对处理器来说都是致命的,甚至是不可纠正的,所有处理器干脆不会处理,很可能只是由软件系统给出一个提示信息。

1.2 可屏蔽中断

可屏蔽中断一般是通过 INTR 引脚进入处理器的。大多数的中断也是可屏蔽中断,由一个中断代理来接收外部设备发起的中断信号。

Intel 处理器允许 256 个中断,中断号范围 0-255,如下图是一个单处理器的中断控制器 8259 芯片。
单处理器系统的中断机制
以上两个8259芯片负责15个中断号的管理。但是可以使用 in 和 out 指令来访问 0-255 个中断号。

如上图,8259 的主片引脚 0(IR0)接的是系统定时器/计数器芯片;从片引脚 0(IR0)接的是实时时钟芯片 RTC。

中断是否被处理?

在 8259 芯片内部:有中断屏蔽器(Interrupt Mask Register,IMR),这是一个8位寄存器,对应着该芯片的 8 个中断输入引脚,对应的位是0还是1决定了该引脚输入的中断是否能通过8259芯片。0表示允许 1表示阻断。8259 芯片是可编程的,主片的端口号是 0x20,0x21,从片的端口号是 0xa,0xa1。通过这些端口访问芯片。

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

IF 标志位可以通过指令 clisti 改变。cli(clear interrupt flag)用于清除 IF 标志位,sti(set interrupt flag)用于置位 IF 标志。

1.3 中断向量表

在实模式下处理器将 256 个中断程序对应的入口地址集中存放在物理地址 0x00000 - 0x003ff 共 1KB 的空间内,这就是中断向量表(Interrupt Vector Table)。

如下图所示,每个中断在中断向量表中占两个字,分别中断处理程序的偏移地址和段地址。
实模式下的中断向量表
在 8259 芯片那里,每个引脚都赋予了一个中断号,这些中断号以芯片为单位可以进行改变,比如直到主片的中断号从 0x08开始,那个它的每个引脚 IR0-IR7 所对应的中断号为 0x08 - 0x0e。

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

  • 保护断点的现场
    • 将标志寄存器 FLAGS 压栈,然后清除它的 IF 位和 TF 位。注意,由于 IF 标志被清除,处理器将不不再响应硬件中断,如果想要更高优先级的的中断嵌套,可以在编写中断处理程序时,适时用 sti 指令开发中断。
    • 将当前代码寄存器 CS 和指令指针寄存器 IP 压栈。
  • 执行中断程序
    • 由于处理器已经拿到中断号,它用中断号乘以4(见上图)就得到了该中断的入口点的偏移地址和段地址在中断向量表中的偏移地址。
    • 从表中依次取出中断程序的偏移地址和短地址,并分别传送到 IP 和 CS。
  • 返回到断点接着执行
    • 所有中断的程序的最后是一条iret指令。这将导致处理器一次从栈中弹出 IP、CS、FLAGS 的原始内容,于是转到主程序继续执行。由于恢复了 FLAGS 的原始内容,所有 IF 位也恢复了,即可以接收新的中断。
    • 和可屏蔽中断不同,NMI 发生时,处理器不会从外部获得中断号,它会自动生成中断号码 2。其他处理过程和可屏蔽中断一致。

1.4 实时时钟显示

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

日期和时钟信息保存在 CMOS RAM 中,通常有 128 字节,除了日期和时间信息,其它空间用于保存整机配置信息,如各种硬件的类型和工作参数、开机密码和辅助存储设备的启动顺序等。这些参数的修改通常在 BIOS SETUP 开机程序中进行。

如下表所示,常规的日期和时间信息占据了 CMOS RAM 开始部分的 10 字节。

偏移地址内容偏移地址内容
0x000x07
0x01闹钟秒0x08
0x020x09
0x03闹钟分0x0A寄存器A
0x040x0B寄存器B
0x05闹钟时0x0C寄存器C
0x06星期0x0D寄存器D

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

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

不得不说的是,在早期,端口 0x07 的 bit 7 是控制 NMI 中断的开关 。为 0 时允许 NMI 中断到达处理器,为 1 时则阻断所有 NMI 信号,其它 7 位用于指定 CMOS RAM 单元的索引号。在访问 RTC 时,我们直接关闭 NMI,访问结束后,再打开 NMI。如下图所示。
端口 0x70 的位 7 用于禁止会允许 NMI
单元 0x0A - 0x0D 被定义成 4 个寄存器的索引号,这 4 个寄存器用于设置实时时钟电路的参数和工作状态。

寄存器A功能如下图。
在这里插入图片描述
在这里插入图片描述

寄存器B功能如下图。
在这里插入图片描述

寄存器C功能如下图。
在这里插入图片描述

寄存器D功能如下图。
在这里插入图片描述

使用中断动态显示时钟的程序源码见代码清单 9-1。

         ;代码清单9-1
         ;文件名:c09_1.asm
         ;文件说明:用户程序 
         ;创建日期:2011-4-16 22:03
         
;===============================================================================
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                        ;向从片发送 
      out 0x20,al                        ;向主片发送 

      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号中断

      push es
      mov ax,0x0000
      mov es,ax
      mov word [es:bx],new_int_0x70      ;偏移地址。
                                          
      mov word [es:bx+2],cs              ;段地址
      pop es

      mov al,0x0b                        ;RTC寄存器B
      or al,0x80                         ;阻断NMI 
      out 0x70,al
      mov al,0x12                        ;设置寄存器B,禁止周期性中断,开放更 
      out 0x71,al                        ;新结束后中断,BCD码,24小时制 

      mov al,0x0c
      out 0x70,al
      in al,0x71                         ;读RTC寄存器C,复位未决的中断状态

      in al,0xa1                         ;8259从片的IMR寄存器 
      and al,0xfe                        ;清除bit 0(此位连接RTC)
      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                        ;cl=0 ?
         jz .exit                        ;是的,返回主程序 
         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
         mov dx,0x3d5
         in al,dx                        ;8位 
         mov ah,al

         mov dx,0x3d4
         mov al,0x0f
         out dx,al
         mov dx,0x3d5
         in al,dx                        ;8位 
         mov bx,ax                       ;BX=代表光标位置的16位数

         cmp cl,0x0d                     ;回车符?
         jnz .put_0a                     ;不是。看看是不是换行等字符 
         mov ax,bx                       ; 
         mov bl,80                       
         div bl
         mul bl
         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
         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
         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:
         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:

2. 内部中断

和硬件中断不同,软中断是处理器内部产生的,是由执行指令引起的。内部中断不受标志寄存器 IF 位的影响,也不需要中断识别总线周期,它们的中断类型是固定的,可以立即转入相应的处理过程。

3. 软中断

软中断是由 int 指令引起的。这类中断不需要中断识别总线周期,中断号在指令中给出。最有名的软中断是 BIOS 中断,这些中断功能是计算机加电之后,BIOS 程序执行期间建立起来的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值