https://www.cnblogs.com/Philip-Tell-Truth/p/5317983.html   这里有详细的过程说明。文字很多,为了方便阅读和理解,提炼了一些要点后归纳、整理了如下导图:

  这次主要介绍实模式下的中断原理和demo示例;按照中断来源,分外部硬件中断、CPU内部中断和软中断。

  1、外部硬件中断:可简单理解为和CPU关联的外部设备产生的中断;

  • 非可屏蔽中断

    (1)non maskable interupt,简称NMI。从字面上看,既然不可屏蔽,说明优先级最高,CPU必须立即处理,不能有丝毫怠慢,一般情况下遇到了严重错误,比如电源掉电、总线奇偶位错误、内存读写错误等;

    (2)有专门的NMI引脚接受外部信号,一旦触发(需要持续4个时钟周期),产生2号中断,随后跳转到IVT查找相应的处理例程;

      (3)CMOS的0x70端口最高位置1时仍然能屏蔽NMI信号,所以严格讲还是能被屏蔽的;

    

  •  可屏蔽中断

    (1)通过INTR引脚触发。那么问题来了:只有一个INTR引脚,但外设有很多(键盘、鼠标、显示器、打印机、蓝牙、wifi、串口、网卡等),cpu该怎么响应不同外设的中断请求了?——通过8259A代理;8259A有8个输入,可以接受8个外部中断源的输入。超过8个后还能互相级联,将中断源扩展至16个,如下:

    

    (2)EFLAGS.IF=0时CPU不再响应通过8259输入的中断请求(EFLAGS在CPU内部,影响所有引脚);cli置IF=0,sti置IF=1;

     (3)8259内置IMR寄存器,可选择性屏蔽某个引脚的中断信号,其他引脚不影响;

     (4)从片的IR0连接系统定时器(8号口输入的中断,俗称8号中断),主要由os根据RTC维护,严格讲仅仅是个计时器

  •  中断向量表IVT

    (1)整个地址的起始空间:0x00000~0x003ff;中断号*4处,取高2byte作为cs,低2byte作为ip寻找具体的中断处理例程

    (2)发生中断时,EGLAGS、cs、ip分别入栈,执行完后再调用iret依次弹出;入栈EFLAGS的同时IF和TF置0,但也可以人为设置sti打开中断请求

    (3)IVT可由os修改;中断例程函数执行完后需要显示执行end of interupt命令告诉主从芯片,以便重新打开中断请求

  2、内部中断

        CPU执行指令时产生的中断,比如除0;不受EFLAGS.IF位和时钟周期影响;

  3、软中断

      (1)BIOS自带,从磁盘加载引导扇区之前就有了,所以加载过程中是可以直接调用的,比如 int 0x10再屏幕显示字符、int 0x13从磁盘读写数据

    (2)部分外设自带ROM存储了初始化代码和中断例程

  4、时钟中断,如下:

    

  (1)南桥内部有两个核心芯片:RTC独立于CPU,负责实时计时,为整个计算机提供最基础的时间服务;CMOS负责存储时间;

   (2)8259主片: IR0连接系统定时器, 从片IR0连接RTC计时器

   (3) 系统定时器每55ms向CPU发送中断请求,然后进入8号中断例程执行,这里无需用户显示执行int指令

(4)PC定时器有个输入频率是1193180Hz,每个时钟周期减1,减到0就输出时钟中断信号;计数器是16位的,最大65536,那么时钟中断发生频率=1193180Hz/65536=18Hz,也就是每秒输出18次;每次输出间隔=1000ms/18hz=55毫秒

(5)8号中断例程又会执行int 0x70,该例程默认只有iret,用户可根据需求添加所需功能,比如线程切换、定时等

  

                                  中断号一览表:

         

  6、核心代码解析

  (1)用户代码加载到内核后重定位:实模式下把段基址(物理地址)写回头部事现预留的特定位置;保护模式把选择子写回;

  (2)计算0x70号中断在IVT的偏移地址,这里bx=0x1c0

    mov al,0x70
mov bl,
mul bl ;计算0x70号中断在IVT中的偏移=0x70*0x4=0x1c0
mov bx,ax ;bx=0x1c0

  (3)自定义的0x70例程的入口写回IVT

    cli                                ;防止改动期间发生新的0x70号中断

      push es
mov ax,0x0000 ;IVT地址范围:0x00000~0x003ff
mov es,ax
mov word [es:bx],new_int_0x70 ;自定义中断处理例程的偏移地址 [0x1c0]=0x0000
mov word [es:bx+],cs ;段地址[0x1c2]=0x1002
pop es

  (4)RTC、CMOS RAM、8259各种参数设置

     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 ;重新开放中断

  (5)自定义0x70函数:主要是从RTC读取当前时间,然后在显示器打印出来

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

  (6)显示效果如下:

(7)完整代码:

  •   加载扇区
app_lba_start equ            ;声明常数(用户程序起始逻辑扇区号)
;常数的声明不会占用汇编地址 SECTION mbr align= vstart=0x7c00 ;设置堆栈段和栈指针
mov ax,
mov ss,ax
mov sp,ax mov ax,[cs:phy_base] ;计算用于加载用户程序的逻辑段地址
mov dx,[cs:phy_base+0x02]
mov bx,
div bx
mov ds,ax ;令DS和ES指向该段以进行操作
mov es,ax ;以下读取程序的起始部分
xor di,di
mov si,app_lba_start ;程序在硬盘上的起始逻辑扇区号
xor bx,bx ;加载到DS:0x0000处
call read_hard_disk_0 ;以下判断整个程序有多大
mov dx,[] ;曾经把dx写成了ds,花了二十分钟排错
mov ax,[]
mov bx, ;512字节每扇区
div bx
cmp dx,
jnz @1 ;未除尽,因此结果比实际扇区数少1
dec ax ;已经读了一个扇区,扇区总数减1
@1:
cmp ax, ;考虑实际长度小于等于512个字节的情况
jz direct ;读取剩余的扇区
push ds ;以下要用到并改变DS寄存器 mov cx,ax ;循环次数(剩余扇区数)
@2:
mov ax,ds
add ax,0x20 ;得到下一个以512字节为边界的段地址
mov ds,ax xor bx,bx ;每次读时,偏移地址始终为0x0000
inc si ;下一个逻辑扇区
call read_hard_disk_0
loop @2 ;循环读,直到读完整个功能程序 pop ds ;恢复数据段基址到用户程序头部段 ;计算入口点代码段基址
direct:
mov dx,[0x08]
mov ax,[0x06]
call calc_segment_base
mov [0x06],ax ;回填修正后的入口点代码段基址 ;开始处理段重定位表
mov cx,[0x0a] ;需要重定位的项目数量
mov bx,0x0c ;重定位表首地址 realloc:
mov dx,[bx+0x02] ;32位地址的高16位
mov ax,[bx]
call calc_segment_base
mov [bx],ax ;回填段的基址
add bx, ;下一个重定位项(每项占4个字节)
loop realloc jmp far [0x04] ;转移到用户程序 ;-------------------------------------------------------------------------------
read_hard_disk_0: ;从硬盘读取一个逻辑扇区
;输入:DI:SI=起始逻辑扇区号
; DS:BX=目标缓冲区地址
push ax
push bx
push cx
push dx mov dx,0x1f2
mov al,
out dx,al ;读取的扇区数 inc dx ;0x1f3
mov ax,si
out dx,al ;LBA地址7~0 inc dx ;0x1f4
mov al,ah
out dx,al ;LBA地址15~8 inc dx ;0x1f5
mov ax,di
out dx,al ;LBA地址23~16 inc dx ;0x1f6
mov al,0xe0 ;LBA28模式,主盘
or al,ah ;LBA地址27~24
out dx,al inc dx ;0x1f7
mov al,0x20 ;读命令
out dx,al .waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits ;不忙,且硬盘已准备好数据传输 mov cx, ;总共要读取的字数
mov dx,0x1f0
.readw:
in ax,dx
mov [bx],ax
add bx,
loop .readw pop dx
pop cx
pop bx
pop ax ret ;-------------------------------------------------------------------------------
calc_segment_base: ;计算16位段地址
;输入:DX:AX=32位物理地址
;返回:AX=16位段基地址
push dx add ax,[cs:phy_base]
adc dx,[cs:phy_base+0x02]
shr ax,
ror dx,
and dx,0xf000
or ax,dx pop dx ret ;-------------------------------------------------------------------------------
phy_base dd 0x10000 ;用户程序被加载的物理起始地址 times -($-$$) db
db 0x55,0xaa
  •  中断处理代码
SECTION header vstart=                     ;定义用户程序头部段
program_length dd program_end ;程序总长度[0x00] ;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code.start ;段地址[0x06] realloc_tbl_len dw (header_end-realloc_begin)/
;段重定位表项个数[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= vstart= ;定义代码段(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,
or al,0x80
out 0x70,al
in al,0x71 ;读RTC当前时间(分)
push ax mov al,
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 ;es指向显存段,从这里开始的字符串会被打印 pop ax ;RTC当前时间(时)
call bcd_to_ascii
mov bx,* + * ;从屏幕上的12行36列开始显示 mov [es:bx],ah
mov [es:bx+],al ;显示两位小时数字 mov al,':'
mov [es:bx+],al ;显示分隔符':'
not byte [es:bx+] ;反转显示属性 pop ax
call bcd_to_ascii
mov [es:bx+],ah
mov [es:bx+],al ;显示两位分钟数字 mov al,':'
mov [es:bx+],al ;显示分隔符':'
not byte [es:bx+] ;反转显示属性 pop ax
call bcd_to_ascii
mov [es:bx+],ah
mov [es:bx+],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位
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,
mul bl ;计算0x70号中断在IVT中的偏移=0x70*0x4=0x1c0
mov bx,ax ;bx=0x1c0 cli ;防止改动期间发生新的0x70号中断 push es
mov ax,0x0000 ;IVT地址范围:0x00000~0x003ff
mov es,ax
mov word [es:bx],new_int_0x70 ;自定义中断处理例程的偏移地址 [0x1c0]=0x0000
mov word [es:bx+],cs ;段地址[0x1c2]=0x1002
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行,35列 .idle:
hlt ;使CPU进入低功耗状态,直到用中断唤醒
not byte [* + *+] ;反转显示属性
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 ;以下用于获取光标的位置,光标的位置用一个16位的数表示,屏幕的0行0列开始从0开始编号,光标寄存器在显卡寄存器中的索引为0x000e(光标位置的高8位)和0x000f(光标位置的低8位)
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,
div bl
mul bl
mov bx,ax
jmp .set_cursor .put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other ;不是,那就正常显示字符
add bx,
jmp .roll_screen .put_other: ;正常显示字符
mov ax,0xb800
mov es,ax
shl bx,
mov [es:bx],cl ;以下将光标位置推进一个字符
shr bx,
add bx, .roll_screen:
cmp bx, ;光标超出屏幕?滚屏
jl .set_cursor mov ax,0xb800
mov ds,ax
mov es,ax
cld
mov si,0xa0
mov di,0x00
mov cx,
rep movsw
mov bx, ;清除屏幕最底一行
mov cx,
.cls:
mov word[es:bx],0x0720
add bx,
loop .cls mov bx, .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= vstart= init_msg db 'Starting...',0x0d,0x0a, inst_msg db 'Installing a new interrupt 70H...', done_msg db 'Done.',0x0d,0x0a, tips_msg db 'Clock is now working.', ;===============================================================================
SECTION stack align= vstart= resb
ss_pointer: ;===============================================================================
SECTION program_trail
program_end:

--------------------------------------------------------------------------------------分割线---------------------------------------------------------------------------------------------------------

以上是正常的demo,做实验的过程中突然想到两个问题:

  (1)进入0x70例程中断后,如果关闭中断,同时死循环,在单核下是不是会卡死?多核下会不会一直占着这个核不放手了?如果是,那病毒、木马岂不是都杀不死了?想想都有点小激动了,继续查看中断例程的物理地址,如下:

  静态分析也很容易找到中断例程起始位置:header最后一个字段从0x1c开始,是dd类型,占用4字节,下一个就应该从0x20开始了,刚好也能16字节对齐;整个程序被加载到0x10000处,所以中断例程从0x10020处开始:

 

    0x10020确实是自定义的中断例程开始地址:

  随即在末尾几个pop前添加如下代码:

    cli                                  ;这里关闭中断
.DeadLoop:
jmp .DeadLoop ;这里死循环,单核下是不是就卡死?多核下是不是该进程杀不死?

   然后下断点,运行后发现并未断下来,而是和之前一样打印时间,并一直刷新,效果没区别...............

  原因不详,猜测可能和bochs断点原理有关,也可能和IVT的原理有关,欢迎各位的大佬们留言指导~~~~

  (2)非屏蔽中断NMI,虽说叫非屏蔽,但仍可通过设置0X70端口的最高位来屏蔽。一旦NMI被屏蔽,是不是没法通过硬件的关机键关机了?PC机只能拔电源关机了?笔记本只能等电池电量耗尽才能关机了?

  

 

x86架构:实模式下的中断的更多相关文章

  1. ASM:《X86汇编语言-从实模式到保护模式》第8章:实模式下硬盘的访问,程序重定位和加载

        第八章是一个非常重要的章节,讲述的是实模式下对硬件的访问(这一节主要讲的是硬盘),还有用户程序重定位的问题.现在整理出来刚好能和保护模式下的用户程序定位作一个对比. ★PART1:用户程序的重 ...

  2. ASM:《X86汇编语言-从实模式到保护模式》第9章:实模式下中断机制和实时时钟

    中断是处理器一个非常重要的工作机制.第9章是讲中断在实模式下如何工作,第17章是讲中断在保护模式下如何工作. ★PART1:外部硬件中断 外部硬件中断是通过两个信号线引入处理器内部的,这两条线分别叫N ...

  3. x86架构:保护模式下利用中断实现抢占式多任务运行

         站在用户角度考虑,一个合格的操作系统即使在单核下也能 "同时" 执行多个任务,这就要求CPU以非常快的频率在不同任务之间切换,让普通人根本感觉不到任务的切换.windwo ...

  4. ASM:《X86汇编语言-从实模式到保护模式》越计卷:实模式下对DMA和Sound Blaster声卡的控制

    说实话越计卷作者用了16页(我还是删过的),来讲怎么控制声卡,其实真正归纳起来就那么几点. ★PART1:直接存储访问 1. 总线控制设备(bus master) 在硬件技术不发达的早期,处理器是最重 ...

  5. 保护模式下pmtest1.asm的理解

    整个代码对应内存线性地址分为四段,[gdt] [code32] [video32] [code16] 代码先在实模式[code16]下运行,code16中的cs就是系统分配的该程序物理地址的基址. 编 ...

  6. ASM:《X86汇编语言-从实模式到保护模式》第10章:32位x86处理器的编程架构

    ★PART1:32位的x86处理器执行方式和架构 1. 寄存器的拓展(IA-32) 从80386开始,处理器内的寄存器从16位拓展到32位,命名其实就是在前面加上e(Extend)就好了,8个通用寄存 ...

  7. x86架构:从实模式进入保护模式

    详细的过程说明参考:(1)  https://www.cnblogs.com/Philip-Tell-Truth/p/5211248.html    (2)x86汇编:从实模式到保护模式 这里简化一下 ...

  8. 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16

    一.Intel 32 位处理器的工作模式 如上图所示,Intel 32 位处理器有3种工作模式. (1)实模式:工作方式相当于一个8086 (2)保护模式:提供支持多任务环境的工作方式,建立保护机制 ...

  9. 8086中断系统——《x86汇编语言:从实模式到保护模式》读书笔记04

    80X86中断系统 能够处理256个中断 用中断向量号0-255区别 可屏蔽中断还需要借助专用中断控制器Intel 8259A实现优先权管理 1.中断的分类 中断可以分为内部中断和外部中断. (1)内 ...

随机推荐

  1. 最近用unity写三消游戏,mark一个准备用的unity插件,用来控制运动。

    http://www.pixelplacement.com/itween/index.php itween 听说还不错!

  2. DLL 函数导出的规则和方法

    参考博客:https://blog.csdn.net/xiaominggunchuqu/article/details/72837760

  3. 一题搞定static关键字

    基础不牢,地动山摇 开篇一道题,考察代码执行顺序: public class Parent { static { System.out.println("Parent static init ...

  4. poi excel单元格的校验

    switch (cell.getCellType()) { case HSSFCell.CELL_TYPE_NUMERIC://数值类型 if (0 == cell.getCellType()) { ...

  5. Java中的堆和栈以及堆栈的区别

    在正式内容开始之前要说明一点,我们经常所说的堆栈堆栈是堆和栈统称,堆是堆,栈是栈,合在一起统称堆栈: 1.栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方.与C++不同,Jav ...

  6. python 爬虫:HTTP ERROR 406

    解决方法: 设置了Accept头后解决了,但是还是不知道原因 headers:{ Accept:"text/html, application/xhtml+xml, */*" }原 ...

  7. python 面向对象专题(六):元类type、反射、函数与类的区别、特殊的双下方法

    目录 Python面向对象06 /元类type.反射.函数与类的区别.特殊的双下方法 1. 元类type 2. 反射 3. 函数与类的区别 4. 特殊的双下方法 1. 元类type type:获取对象 ...

  8. 微博大数据即席查询(OLAP)引擎实践

    前言 适用于 即席查询 场景的开源查询引擎有很多,如:Elasticsearch.Druid.Presto.ClickHouse等:每种系统各有利弊,有的擅长检索,有的擅长统计:实践证明,All In ...

  9. 计算机网络学习socket--day2

    1.TCP客户/服务器模型(C/S) 2.回射客户/服务器模型 3.socket.bind.listen.accept.connect ||------------------------------ ...

  10. 怎样才能做好软件测试——Python自动化测试工程师七年感悟

    即使不想在文章的开头过分的正经严肃,但这是一个十分正经技术类规划类的分享.不讲笑话也不讲故事,直接进入主题. 如何学好软件测试?反推一下作为一名优秀的软件测试工程师需要什么能力.   学习测试讲究实践 ...