一、3.外部硬件中断与进入保护模式

外部硬件中断:处理器接两根线NMI传输非屏蔽中断(即无法屏蔽的中断)和INTR传输可屏蔽中断

image-20230731215800052

IR0到IR7优先级依次降低

image-20230731220027450

处理器中中断标志位起决定作用,IF为0时屏蔽所有INTR引脚来的信号都被屏蔽

image-20230731220202828

image-20230731220619331

BIOS创建中断向量表

image-20230731221247135

实时时钟和CMOS RAM

CMOS RAM中的日期和时间通常使用二进制形式的十进制编码(BCD)来表示

image-20230731222012037

实时时钟RTC电路可以发出的中断信号类型:

  • 周期性中断信号
  • 更新周期结束中断
  • 闹钟中断

周期性中断信号

以固定周期进行中断

image-20230731223058346

寄存器A指定时基和速率

image-20230801073234092

寄存器B控制周期性中断是否运行

image-20230731223445947

更新周期结束中断

更新周期:每隔一秒更新时间、检查数据有效性、检查闹钟、写回更新的数据

image-20230801073732792

image-20230801074008211

image-20230801074308231

闹钟中断

image-20230801074555454

实时时钟发出的中断信号的类型判断:

寄存器C只读,读取后自动清零

image-20230801074757680

程序中修改SS寄存器和修改SP寄存器的指令必须相邻,否则可能会在中间发生中断导致栈错误

安装中断处理过程

image-20230801081345297

SECTION code align=16 vstart=0           ;定义代码段(16字节对齐) 
new_int_0x70:
	...
	iret

...
start:
    mov ax,[stack_segment]
    mov ss,ax
    mov sp,ss_pointer
    mov ax,[data_segment]
    mov ds,ax
    mov al,0x70 ; 中断号0x70
    mov bl,4
    mul bl ; 中断号乘4得到中断向量表IVT中对应中断的入口地址所在的地址
    mov bx,ax

    cli ; 关中断,防止中断发生

    push es
    mov ax,0x0000 ; IVT所在段的地址
    mov es,ax
    mov word [es:bx],new_int_0x70 ;写入自定义的中断处理过程的偏移地址

    mov word [es:bx+2],cs ;写入自定义的中断处理过程的段地址
    pop es

启用更新周期结束中断

image-20230801084517320

    mov al,0x0b ; RTC寄存器B
    or al,0x80 ;最高位置1,NMI引脚外接一个与门的输出,输入一个是端口0x70最高位的反相,另一个是NMI中断信号,置1可阻断NMI 
    out 0x70,al ; 0x70为索引端口,用于指定CMOS RAM内的单元
    mov al,0x12 ;设置寄存器B,禁止周期性中断,开放更新结束后中断,BCD码,24小时制 0001_0010
    out 0x71,al ; 0x71为数据端口,将寄存器B的设置信息输出给寄存器B

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

    in al,0xa1 ;读8259从片的IMR寄存器(中断屏蔽寄存器,0为允许1为屏蔽)
    and al,0xfe ;清除最低位,此位连接RTC,开放RTC中断
    out 0xa1,al ;写回此寄存器 

    sti ;重新开放中断 

image-20230801085043354

中断处理过程:

等待更新周期结束

SECTION code align=16 vstart=0 ;定义代码段(16字节对齐) 
new_int_0x70:
      push ax
      push bx
      push cx
      push dx
      push es
      
  .w0:                                    
      mov al,0x0a ; 寄存器A
      or al,0x80                          
      out 0x70,al ;阻断NMI。当然,通常是不必要的
      in al,0x71 ;读寄存器A
      test al,0x80 ;本质是与操作,但是结果不保留,ZF为0表示测试位不是0,测试第7位UIP 
      jnz .w0

更新周期时间段:

image-20230801091932411

读取BCD码的时间:

xor al,al ; 清零al,秒的内容在CMOS RAM中存放的地址为0x00
or al,0x80 ; 阻断NMI
out 0x70,al ; 索引端口
in al,0x71 ;读RTC当前时间(秒,8位)
push ax

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

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

mov al,0x0c ;寄存器C的索引。最高位为0,开放NMI 
out 0x70,al
in al,0x71 ;读一下RTC的寄存器C,清零寄存器C,否则只发生一次中断,因为已经把闹钟和周期性中断关闭了,所以不考虑 

BCD码转ASCII:

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

    shr ah,4 ;逻辑右移4位,保留高4位                 
    add ah,0x30

    ret

显示并退出中断:

pop ax ; 取出小时的BDC码
call bcd_to_ascii
mov bx,12*160 + 36*2 ;从屏幕上的12行36列开始显示,行号*80*2 + 列号*2 = 显存中的偏移地址

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

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

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

mov [es:bx+10], ':' ;显示分隔符':'
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

image-20230801094509408

image-20230801100011390

处理器进入低功耗状态:hlt指令,在该指令行停机,但能被外部中断唤醒并继续执行之后的代码

.idle:
    hlt ;使CPU进入低功耗状态,直到用中断唤醒
    not byte [12*160 + 33*2+1] ;反转显示属性 
    jmp .idle

内部中断:处理器内部中断,处理器直接到中断对应处理过程处执行代码

软中断:代码产生中断

  • int 中断号
  • int3,在调试时将原指令机器码改为0XCC(即int3)从而中断去处理调试相关程序,调试完改行后将0XCC改回原机器码
  • into溢出中断,标志寄存器OF为1时才进入中断处理过程

BIOS中断:

image-20230801114236090

ROM BIOS搜索C0000~E0000这个区域,当有55AA这字则执行对应外设的功能调用例程代码,这个代码初始化设备,并把设备的外部中断号放入中断向量表中

mov cx,msg_end-message ; 获取message的字符长度
mov bx,message

.putc:
    mov ah,0x0e ; 0x0e命令
    mov al,[bx]
    int 0x10 ; 0x10中断,ah为0x0e,查bios中断表可知是显示字符(光标前移)功能,AL=字符,BL=前景色(图形模式)
    inc bx ; 选中下一个字符
    loop .putc
    
.reps:
    mov ah,0x00
    int 0x16 ; 0x16中断,ah为0x00,从键盘读字符功能

    mov ah,0x0e
    mov bl,0x07
    int 0x10 ; 显示字符中断

	jmp .reps

32位处理器的寄存器扩展和扩充

image-20230801121036460

段寄存器:CS, DS ES, FS, GS, SS,长度还是16位

  • 实模式:(段地址<<4)+偏移地址 最大10FFEF,8086只有20根地址线,只能访问FFFF,80386有32根地址线,可以访问到10FFEF,FFFF~10FFEF这个区域称为高位内存区(HMA)。虽然32位处理器有32根地址线,但是实模式下超出10FFEF的内存是无法访问的
  • 保护模式:在描述符表中登记每个段的各种信息。段寄存器选中一个描述符,处理器取出描述符段基地址,和程序中给出的偏移地址组合,得到完整的内存地址

image-20230801133018823

image-20230801133115598

在保护模式下,一个段想被访问就必须去登记

image-20230801140700998

image-20230801143839753

gdt_size         dw 0 ; 一个字
gdt_base         dd 0x00007e00     ;GDT的物理地址 一个双字

;计算GDT所在的逻辑段地址 
mov ax,[cs:gdt_base+0x7c00]        ;低16位,主引导扇区开始是0x7c00,需要加上 
mov dx,[cs:gdt_base+0x7c00+0x02]   ;高16位 
mov bx,16        
div bx ; 右移4位
mov ds,ax ;逻辑段地址,令DS指向该段以进行操作
mov bx,dx ;逻辑偏移地址,段内起始偏移地址 

image-20230801144559603

image-20230801144740774

S=1时

image-20230801145148592

image-20230801145557487

段界限:低32位0 ~ 15,高32位16 ~ 19

image-20230801151805033

image-20230801152208667

G位:高双字的第23位,粒度,用于解释段界限的单位,G=0段界限以字节为单位,G=1以4k字节为单位

实际使用的段界限 = 描述符中的段界限 * 0x1000G + 0xFFF * G

P位:高双字的第15位,段存在位,P=10段不存在,访问报错

L位:高双字的第21位,64位代码段标志

D/B位:高双字的第22位,操作尺寸(是代码段时)/栈上部边界(是数据段时),D/B=0该段按16位进行操作,D/B=1该段按32位进行操作

AVL位:高双字的第20位,保留位

安装存储器的段描述符

;DS:BX指向GDT起始地址
;创建0#描述符,它是空描述符,这是处理器的要求
mov dword [bx+0x00],0x00
mov dword [bx+0x04],0x00  

;创建#1描述符,保护模式下的代码段描述符
mov dword [bx+0x08],0x7c0001ff     
mov dword [bx+0x0c],0x00409800 

image-20230801155048130

image-20230801160625233

;初始化描述符表寄存器GDTR
mov word [cs:gdt_size+0x7c00], 15 ;主引导程序从7c00开始,加上偏移量gdt_size,定位到描述符表的界限,两个描述符,8字节*2-1(总字节数减一)   

lgdt [cs:gdt_size+0x7c00] ; 指定gdt_size的地址,取出6字节(2字节描述符表界限,4字节描述符表起始地址)赋给GDTR寄存器

打开A20地址线:为了使用到地址为0x100000以上的内存,开启第21条地址线(A20)

image-20230801171637640

in al, 0x92 ;南桥芯片内的0x92端口 
or al, 0000_0010B ; 第1位置1
out 0x92, al ;打开A20

进入保护模式:设置CR0寄存器的PE位(第0位)为1

cli ;保护模式下中断机制尚未建立,应禁止中断 
mov eax,cr0
or eax,1 ; 最低位PE置1
mov cr0,eax

保护模式下的内存访问:

image-20230801202936673

段选择器内容不再是逻辑段地址,而是称为段选择子

image-20230801203135251

image-20230801203507513

mov cx,00000000000_10_000B ;加载数据段选择子(0x10)
mov ds,cx ; 此时ds的描述符高速缓存器中存放目标段基地址

mov byte [0x00],'P'  ; 使用ds的描述符高速缓存器存放的段地址进行内存访问(0xb800)

image-20230801203728349

实模式下:

image-20230801204741190

指令操作尺寸:一段指令的操作数的长度

image-20230802073408760

image-20230802073438301

image-20230802074842949

32位特有内存寻址:

image-20230802080025125

image-20230802080708673

image-20230802080920307

image-20230802080939610

image-20230802082706672

可以看到操作尺寸为16位数据和32位数据时,相同格式的汇编指令的机器指令相同

  • 16位操作尺寸允许8位或16位操作数,以及16位有效地址
  • 32位操作尺寸允许8位或32位操作数,以及32位有效地址
  • 32位处理器支持16位和32位操作尺寸
  • 同一时刻处理器只能按一种操作尺寸工作,这种操作尺寸称为默认操作尺寸

image-20230802083245476

指定默认操作尺寸:bits 16/32

image-20230802090756133

;创建#2描述符,保护模式下的代码段描述符(主引导程序)
mov dword [bx+0x08],0x7c0001ff ; D位是1,即用32位操作尺寸
mov dword [bx+0x0c],0x00409800 
...
 ;以下进入保护模式... ...
jmp dword 0000_0000_0000_0010_0_00B:flush ;16位的描述符选择子:32位偏移,选择第3个描述符表项(主引导程序)
bits 32 ; 因为在描述符中选择32位操作尺寸,所以之后的代码需要默认尺寸32位来编译,在此之前的代码是按16位编译
flush:
	...

image-20230802085719860

;在创建描述符表或进行段选择时,都需要对描述符表进行操作,这时对DS赋值有两种方式,传统编译器编译结果不同
mov ds, ax ;16位操作尺寸,编译8E D8
		   ;32位操作尺寸,编译66 8E D8
mov ds, eax ;16位操作尺寸,编译66 8E D8
	  	   ;32位操作尺寸,编译8E D8
;在32位操作尺寸下优先选后者
;NASM编译两者结果无论什么操作尺寸,都是8E D8

修改段寄存器时的保护:

  1. 检查选择子的合法性image-20230802112158076
  2. 确认描述符类型——描述符类型是否与段寄存器匹配,P位是否为1image-20230802112405142对于CS和SS的选择器,不允许向其传送为0的选择子

代码段执行时的保护:

  1. 执行的代码未超过段界限image-20230802113326549

数据段:

;创建1#描述符,这是一个数据段,对应0~4GB的线性地址空间
mov dword [ebx+0x08],0x0000ffff    ;基地址为0,段界限为0xfffff
mov dword [ebx+0x0c],0x00cf9200    ;粒度为4KB,存储器段描述符 
;这个数据段囊括了整个4G的地址空间,包括代码段,所以虽然代码段无法直接通过代码段的描述符写入,但是可以通过这个数据段写入

;创建数据段描述符,实际用做第一个栈段
mov dword [ebx+0x18],0x6c0007ff    ; 基地址0x00006c00,向上扩展,D/B=1
mov dword [ebx+0x1c],0x00409200    ; 界限0x7ff,粒度字节,数据段描述符
;段的扩展方向决定了处理器检查段界限时的检查方向,与栈的推进方向无关
;描述符中的段界限决定了偏移量的变化范围,即栈的容量
;描述符的D/B=1时,使用ESP,反之使用SP
;初始化时将SP/ESP设为段大小,即段界限+1(0x800)
;压栈,高位向低位推进
push dword 0x072e074d ;压入一个双字
; | 07 |  0x07ff
; | 2e |
; | 07 |
; | 4d | <- esp = 0x07fc

向上扩展的数据段,在写入数据之前进行检查:操作数的有效地址(SP/ESP - 压栈值的大小,算的是存入数据的空间) + 操作数的大小(字节)- 1 <= 实际使用的段界限

检查通过后即可写入,写入后SP/ESP指向写入的最后一个栈元素

;出栈
pop dword [0x0b800]
; | 07 |  0x07ff
; | 2e |
; | 07 |
; | 4d | <- esp = 0x07fc

向上扩展的数据段,在弹出数据之前进行检查:操作数的有效地址(SP/ESP) + 操作数的大小(字节)- 1 <= 实际使用的段界限

同样,弹出的数据在存入数据段时也要进行检查:000b8000 + 4 - 1 <= ffff_ffff

内存线性地址回绕特性

image-20230802132448073

向下扩展的段作为栈段:

;创建数据段描述符,实际用作第二个栈段
mov dword [ebx+0x20],0x7c00fffe    ;基地址为0c00007c00,向下扩展,D/B=1
mov dword [ebx+0x24],0x00cf9600    ;段界限为0xffffe,粒度为4KB,数据段描述符 
;初始化使SP/ESP=0

image-20230802132938858

image-20230802133736580

向下扩展的数据段,在写入数据之前进行检查:操作数的有效地址(SP/ESP - 压栈值的大小,算的是存入数据的空间)> 实际使用的段界限

;压栈
;                                |-ffffffff
;                                |--fffffffc <- esp=0-4=fffffffc 负数
;                                |--fffff000 实际使用段界限+1
; 00007c00    | 07 |-------------|-00000000 段基地址
;             | 68 | <- esp

向下扩展的数据段,在弹出数据之前进行检查:操作数的有效地址(SP/ESP)> 实际使用的段界限

xchg al,ah ,交换两操作数内容,可以同时是寄存器,不能同时是内存地址

冒泡排序:

;开始冒泡排序 
mov ecx,pgdt-string-1              ;遍历次数=串长度-1 
@@1:
    push ecx                           ;32位模式下的loop使用ecx 
    xor bx,bx                          ;32位模式下,偏移量可以是16位,也可以 
    @@2:                                      ;是后面的32位 
        mov ax,[string+bx] 
        cmp ah,al                          ;ah中存放的是源字的高字节 
        jge @@3 
        xchg al,ah 
        mov [string+bx],ax 
	@@3:
        inc bx 
        loop @@2 
        pop ecx 
        loop @@1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值