NASM(六)硬件中断处理


NASM(六)硬件中断处理

中断

  • 可屏蔽中断:

    对于那些不紧急,不用着急处理的中断信号,应该从INTR引脚输入。在处理器内部,根据需要,可以屏蔽掉从这个引脚来的中断信号,不对它们进行处理。因此,从INTR输入的中断信号叫作可屏蔽中断。

    可屏蔽中断通常与处理器的中断控制器相关联。中断控制器是一个硬件设备,负责接收来自外部设备的中断请求,并将其传递给处理器。通过配置中断控制器,可以选择性地屏蔽特定的中断信号。

    INTEL处理器允许256个中断,中断号的范围是0~255,8259中断控制器芯片负责提供其中的15个,但中断号并不固定,允许软件根据自己的需要灵活设置中断号,以防止发生冲突。该中断控制器芯片有自己的端口号,可以像访问其他外部设备一样用in和out指令来改变它的状态,包括各引脚的中断号。正是因为这样,它又叫可编程中断控制器(Programmable Interrupt Controller, PIC)。

  • 不可屏蔽中断:

    相反地,所有严重事件都必须无条件地加以处理,由这类事件引发的中断信号应当通过NMI引脚送入处理器,这些严重的事件包括不间断电源的后备电池即将耗尽、内存校验错误、I/O检验错误,等等。在处理器内部,对于从NMI引脚来的中断信号不会作屏蔽和过滤,而是必须进行处理。因为这个原因,从NMI引脚来的中断信号称为非屏蔽中断(Non Maskable Interrupt, NMI)。

中断向量表

在实模式下,处理器要求将它们的入口点集中存放到内存中从物理地址0x00000开始到0x003ff结束,共1KB的空间内,这就是所谓的中断向量表(Interrupt Vector Table, IVT)。每个中断在中断向量表中占2个字,分别是中断处理程序的偏移地址和逻辑段地址。注册中断程序时,需要将中断程序的段地址和偏移地址写入中断向量表。

RTC 实时时钟

实时时钟的读写需要访问CMOS RAM,这需要通过两个端口来进行。0x70或者0x74是索引端口,用来指定CMOS RAM内的单元;0x71或者0x75是数据端口,用来读写相应单元里的内容。

偏移地址内容
0x00
0x01闹钟秒
0x02
0x03闹钟分
0x04
0x05脑中时
0x06星期
0x07
0x08
0x09
0x0a寄存器A
0x0b寄存器B
0x0c寄存器C
0x0d寄存器D

实时时钟RTC电路可以产生三种中断信号,分别是:

  • 周期性中断(Periodic Interrupt, PF):每隔一段时间重复发生一次,速度在寄存器A中可调节,是否发生在寄存器B中控制。

    • 寄存器A的位6~位4用来选择外部时钟频率,而位3~位0则用来选择周期性中断信号发生的速率。
    • 寄存器B的位6控制周期性中断是否允许发生。这一位是周期性中断允许位(Periodic Interrupt Enable, PIE)。如果此位是0,表示不允许周期性中断;如果是1,表示允许发生周期性中断信号。
  • 更新周期结束中断(Update-ended Interrupt, UI):每隔一秒,实时时钟电路将更新CMOS RAM里面的时间和日期。在每个更新周期结束时,实时时钟电路可以发出一个中断信号,表示本次更新周期已经结束。更新周期是否会进行,是由寄存器B的位7来控制。一旦更新周期被禁止,RTC将不会自动更新时间和日期。
  • 闹钟中断(Alarm Interrupt, AI):当实时时钟到达指定的闹点时,如果允许的话,将产生闹钟中断信号。闹钟中断信号是否会产生,是由寄存器B的位5来控制的,这一位叫作闹钟中断允许(Alarm Interrupt Enable, AIE)位。如果此位是0,意味着不产生闹钟中断;如果此位是1,意味着允许产生闹钟中断信号。

实时时钟芯片的中断信号通过一根线连接到8259A从片的第一个引脚IR0。在计算机启动后,BIOS程序将它的中断号初始化为0x70。要想知道中断是否发生,以及发生的是什么中断,可以通过读寄存器C来做出判断

  • 寄存器C的位7是中断请求标志(Interrupt Request Flag, IRQF),如果有中断发生,则位7是1,否则是0。如果位7是1,有中断发生,则还需要判断位4、位5和位6来检查是哪种中断。对寄存器C的读操作将导致此位清零。
  • 寄存器C的位6是周期性中断标志(Periodic Interrupt Flag, PF),如果此位是1,意味着发生了周期性中断;0意味着不是周期性中断。对寄存器C的读操作将导致此位清零。
  • 寄存器C的位5是闹钟标志(Alarm Flag, AF),如果此位是1,意味着发生了闹钟中断;0意味着不是闹钟中断。对寄存器C的读操作将导致此位清零。
  • 寄存器C的位4是更新结束标志(Update-ended Flag, UF)。如果此位是1,意味着发生了更新周期结束中断;0意味着不是更新周期结束中断。对寄存器C的读操作将导致此位清零。
  • 寄存器C的低4位,即位0到位3是保留的,始终为0。注意,寄存器C是只读的,不能写入。寄存器C对读操作是敏感的,读操作将导致所有比特清零。

Nasm Code

引导程序见 NASM(五)加载用户程序及用户程序

section header align=16 vstart=0
    program_length dd program_end
    code_entry dw start
               dd section.code.start
    realloc_tb_len dw (realloc_tb_end - realloc_tb_begin) / 4
realloc_tb_begin:
    code_segment dd section.code.start
    data_segment dd section.data.start
    stack_segment dd section.stack.start
realloc_tb_end:


section code align=16 vstart=0
; ---------flush_screen----------
; 屏幕置空
flush_screen:
    push ax
    push cx
    push si
    push es

    mov ax, 0xb800
    mov es, ax
    mov si, 0
    mov cx, 4000
    mov al, ' '
    mov ah, 0x07
loop_flush:
    mov [es:si], ax
    add si, 2
    loop loop_flush

    pop es
    pop si
    pop cx
    pop ax
    ret

; -------to_screen----------
to_screen:
put:
    mov cl, [ds:si]  ; 拿字符
    cmp cl, 0
    je exit
    add si, 1        ; 偏移+1
    push si          ; 入栈保存
    push ax          ; 入栈保存
    call put_char
    pop ax
    pop si
    dec ax
    cmp ax, 0        ; 判断数据是否传输完
    jne put
exit:
    ret


get_cursor:
    mov dx, 0x3d4       ; 索引寄存器
    mov al, 0x0e        ; 0x0e光标寄存器
    out dx, al          ; 选择 0x0e光标寄存器, 高8位
    mov dx, 0x3d5       ; 读数据
    in al, dx           ; 读
    mov ah, al          ; 放到高8位

    mov dx, 0x3d4       ; 索引寄存器
    mov al, 0x0f        ; 0x0f光标寄存器
    out dx, al          ; 选择 0x0e光标寄存器, 高8位
    mov dx, 0x3d5       ; 读数据
    in al, dx           ; 读
    ; mov bx, ax          ; 把光标信息存储在bx中
    ret     

put_char:               ; 接受参数cl, 用于提供要显示的ASCII码。
    call get_cursor     
    cmp cl, 0x0d        ; 判断是否是0x0d
    jne check_0x0a      ; 判断是否是0x0a
    mov bl, 80          ; 判断行列
    div bl              ; 被除数存储在"AX"寄存器中 "AL"中得到的是当前行的行号。
                        ; 商(结果的整数部分)将存储在"AL"寄存器中
                        ; 余数(结果的小数部分)将存储在"AH"寄存器中
    mul bl              ; 乘数应存储在AL寄存器中
                        ; 结果的低8位存储在"AL"寄存器中
                        ; 结果的高8位存储在"AH"寄存器中
    mov bx, ax          ; 保存到bx中

    jmp set_cursor

check_0x0a:
    cmp cl, 0x0a
    jne put_c
    add bx, 80      ; 换行
    cmp bx, 2000
    jl set_cursor   ; 屏幕只能显示2000个字符, 多余的滚行
    mov ax,0xb800
    mov ds,ax
    mov es,ax
    cld             ; 清除标志位,使movsw向正方向前进
    mov si,0xa0
    mov di,0x00
    mov cx,1920     ; 位置前挪,空出最后一行
    rep movsw       ; 该指令需要配合源地址寄存器(SI),目标地址寄存器(DI)和计数寄存器(CX)一起使用
                    ; 它会根据CX寄存器中的计数值重复执行movsw指令
clean:              ; 最后一行全部置为空格
    mov bx, 3840    ; 屏幕上第25行第1列在显存中的偏移地址是3840
    mov cx, 80
    mov word[es:bx], 0x0720  ; 清除第25行的内容
    add bx, 2
    loop clean
    mov bx,1920     ; 设置光标

set_cursor:
    mov dx,0x3d4    ; 索引寄存器
    mov al,0x0e     ; 0x0e光标寄存器
    out dx,al       ; 选择0x0e光标寄存器, 高8位
    mov dx,0x3d5    ; 选择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
    ret

put_c:
    mov ax,0xb800
    mov es,ax
    shl bx,1          ; bx 乘2
    mov byte [es:bx], cl
    inc bx
    mov byte [es:bx], 0x07  ; 白字黑底
    dec bx
    shr bx,1          ; bx 除2
    add bx,1
    jmp set_cursor

; --------int70 code ----------
int70:
    push ax
    push bx
    push cx
    push dx
    push es

w:
    mov al, 0x0a  ; 选择寄存器A
    or al, 0x80   ; 阻断NMI
    out 0x70, al  ; 选择寄存器a  
    in al, 0x71   ; 读寄存器a
    test al, 0x80 ; 测试最高位是否为1,是否处更新周期在更新周期中
    jnz w         ; 处在更新周期中,则等待

    mov cx, 3
    mov bl, 0
read_rtc:
    mov al, bl
    add bl, 2
    or al, 0x80
    out 0x70, al  ; 读秒,分,时
    in al, 0x71
    push ax
    loop read_rtc
    
    mov al, 0x0c
    out 0x70, al  ; 告诉RTC,中断已经得到处理,可以继续下一次中断。
    in al, 0x71   ; 读一下寄存器c,把标志位清0

    mov ax, 0xb800    ; 显示时间
    mov es, ax
    mov bx, 12 * 160 + 36 * 2  ; 从屏幕上的12行36列开始显示
    mov cx, 3
show_rtc:
    pop ax
    call BCD2ASCII
    mov [es:bx], ah
    mov [es:bx + 2], al
    mov al, ':'
    mov [es:bx + 4], al


    add bx, 6
    loop show_rtc
    mov al, ' '             ; 消去最后一个':'
    mov [es:bx - 2], al
                            ; 在8259芯片内部,有一个中断服务寄存器(Interrupt Service Register, ISR)
                            ; 这是一个8位寄存器,每一位都对应着一个中断输入引脚。
                            ; 当中断处理过程开始时,8259芯片会将相应的位置1,表明正在服务从该引脚来的中断。
                            ; 一旦响应了中断,8259中断控制器无法知道该中断什么时候才能处理结束。
                            ; 同时,如果不清除相应的位,下次从同一个引脚出现的中断将得不到处理。
                            ; 在这种情况下,需要程序在中断处理过程的结尾,显式地对8259芯片编程来清除该标志。
                            ; 方法是向8259芯片发送中断结束命令(End Of Interrupt, EOI)。
    mov al,0x20             ;中断结束命令EOI 
    out 0xa0,al             ;向从片发送 
    out 0x20,al             ;向主片发送 

    pop es
    pop dx
    pop cx
    pop bx
    pop ax
    iret



BCD2ASCII:
    mov ah, al
    and al, 0x0f
    add al, 0x30
    shr ah, 4
    and ah, 0x0f
    add ah, 0x30
    ret


; --------int70 set-----------
int70_set:
    mov al, 0x70
    mov bl, 4
    mul bl
    mov bx, ax               ; 计算在实模式下0x70号中断在中断向量表中的偏移
    cli                      ; 关闭中断
    mov ax, 0
    mov es, ax
    mov word [es:bx], int70  ; 写入偏移地址
    mov ax, cs               ; 注意不要用[code_segment],因为ds已经指向该程序数据段,而不是程序开头
    mov word [es:bx + 2], ax ; 写入段地址

    mov al, 0x0b
    or al, 0x80
    out 0x70, al
    mov al, 0x12             ; 禁止周期性中断,开发更新结束后中断,BCD码,24h制
    out 0x71, al              

    mov al, 0x0c             ; 选择c寄存器
    out 0x70, al
    in al, 0x71              ; 复位未决的中断状态

    in al, 0xa1              ; 读8259从片的IMR寄存器 
    and al, 0xfe             ; 清除bit 0(此位连接RTC)
    out 0xa1, al
    sti
    ret


; --------------------------
start:
    mov ax, [stack_segment]  ; 引导程序现在使ds指向程序开头,先更新ss
    mov ss, ax
    mov sp, stack_pointer
    mov ax, [data_segment]
    mov ds, ax

    call flush_screen
    mov bx, 0         ; 光标存储在bx中,先初始化到0处
    call set_cursor

    mov si, Init_msg
    call to_screen
    mov si, inst_msg
    call to_screen
    push bx
    call int70_set
    pop bx
    mov si, Done_msg
    call to_screen
    mov si, Tips_msg
    call to_screen

    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




section data align=16 vstart=0
    Init_msg       db 'Starting...',0x0d,0x0a,0
    inst_msg       db 'Installing a new interrupt 70H...',0x0d,0x0a,0
    Done_msg       db 'Done.',0x0d,0x0a,0
    Tips_msg       db 'Clock is now working.',0

section stack align=16 vstart=0
    resb 256
stack_pointer:

section trail align=16

program_end:

声明:Hello World|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - NASM(六)硬件中断处理


我的朋友,理论是灰色的,而生活之树是常青的!