80386保护模式--中断机制,附pmtest9代码详解

2023-05-16

 一、80386,内存,8259A的连接如图1

            

                                                                            图 1               


二、编程8259A中断控制器(将ICW写入特定的寄存器)

       8259A是可编程中断控制器,对它的设置并不复杂,是通过向相对应的端口写入特定的ICW(Initialization Command Word)来实现的。主8259A对应的端口地址是20h和21h,从8259A对应的端口地址是A0h和A1h。ICW共有4个,每一个都是具有特定格式的字节,为了先对初始化8259A的过程有一个概括的了解,我们过一会再来关注每一个ICW的格式,现在,先来看一下初始化过程:

       1、往端口20h(主片)或A0h(从片)写入ICW1。

       2、往端口21h(主片)或A1h(从片)写入ICW2。

       3、往端口21h(主片)或A1h(从片)写入ICW3。

       4、往端口21h(主片)或A1h(从片)写入ICW4。


       ICW格式如图2:

       

       

         

                                                       图 2 ICW的格式                                  图 3 OCW1

       若想屏蔽或者打开外部中断,只需要往8259A写入OCW1就可以了,即写入中断屏蔽寄存器(IMR),OCW1的格式如图3。

       可屏蔽中断与NMI的区别在于是否受到IF位的影响,而8259A的中断屏蔽寄存器(IMR)也影响着中断是否会被响应。所以,外部可屏蔽中断的发生就会受到两个因素的影响,只有当IF位为1,并且IMR相应位为0时才会发生。除单步中断外,所有内部中断都无法禁止,即不能通过CLI指令使IF=0,使其不响应。


三、保护模式下的中断

       1、0-19的中断和异常已经安排好了,如书上89页表3.8


       2、有特权级变化时,中断堆栈变化如图4。从中断或异常返回时必须使用指令iretd,它和ret很相似,只是它同时会改变eflags的值。需要注意的是,只有当CPL为0时,eflags中的IOPL域才会改变,而且只有当CPL小于等于IOPL时,IF才会被改变。另外,iretd执行时Error Code不会被自动从堆栈弹出,所以执行它之前要先将它从栈中清除掉。指令in、ins、out、outs、cli、sti只有在CPL小于等于IOPL时才能执行。这些指令被称为I/O敏感指令。如果低特权级的指令试图访问这些I/O敏感指令将会导致常规保护错误(#GP)。

       

                                                       图 4 中断或异常发生时的堆栈


       3、I/O许可位

       TSS偏移102字节处有一个被称做"I/O许可位图基址"的东西,它是一个以TSS的地址为首地址的偏移,指向的便是I/O许可位图。之所以叫做位图,是因为它的每一位表示一个字节的端口地址是否可用。如果某一位为0,则表示此位对应的端口号可用,为1则不可用。由于每一个任务都可以有单独的TSS,所以每个任务可以有它单独的I/O许可位图。


       4、保护模式下中断执行过程

      以下由硬件自动完成:

      ①从中断信息中取得中断类型码。

      ②标志寄存器eflags的值入栈,因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中。

      ③设置标志寄存器IF和TF位为0,为了防止在此中断中再来其他外部中断。

      ④cs,eip,error code入栈。

      ⑤根据中断类型码在IDT中找到中断门执行代码。

      IRETD 指令先弹出一个32位的EIP值,然后再弹出一个32位值并将最低的2个字节值传入CS寄存器,最后再弹出一个32位的标志寄存器值。


      pmtest9.asm代码如下:

%include	"pm.inc"	; 常量, 宏, 以及一些说明

org	07c00h
	jmp	LABEL_BEGIN

[SECTION .gdt]
; GDT
;                              段基址,       段界限     , 属性
LABEL_GDT:	   Descriptor       0,                0, 0           ; 空描述符
LABEL_DESC_NORMAL:	Descriptor	       0,            0ffffh, DA_DRW			; Normal 描述符
LABEL_DESC_CODE32: Descriptor       0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段
LABEL_DESC_CODE16:	Descriptor	       0,            0ffffh, DA_C			; 非一致代码段, 16
LABEL_DESC_VIDEO:  Descriptor 0B8000h,           0ffffh, DA_DRW	     ; 显存首地址
; GDT 结束

GdtLen		equ	$ - LABEL_GDT	; GDT长度
GdtPtr		dw	GdtLen - 1	; GDT界限
		dd	0		; GDT基地址

; GDT 选择子
SelectorNormal		equ	LABEL_DESC_NORMAL	- LABEL_GDT
SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
SelectorCode16		equ	LABEL_DESC_CODE16	- LABEL_GDT
SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT
; END of [SECTION .gdt]

[SECTION .data1]	 ; 数据段
ALIGN	32
[BITS	32]
LABEL_DATA:
; 实模式下使用这些符号
; 变量
_wSPValueInRealMode		dw	0
_SavedIDTR:			dd	0	; 用于保存 IDTR
				dd	0
_SavedIMREG:			db	0	; 中断屏蔽寄存器值


DataLen			equ	$ - LABEL_DATA
; END of [SECTION .data1]


; IDT
[SECTION .idt]
ALIGN	32
[BITS	32]
LABEL_IDT:;最多256个中断门,每个占8个字节,所以IDT最大占2K。前面20个已被占用。
; 门                目标选择子,            偏移, DCount, 属性
%rep 32               
		Gate	SelectorCode32, SpuriousHandler,      0, DA_386IGate
%endrep
.020h:		Gate	SelectorCode32,    ClockHandler,      0, DA_386IGate   ;转换成10进制是32
%rep 95
		Gate	SelectorCode32, SpuriousHandler,      0, DA_386IGate
%endrep
.080h:		Gate	SelectorCode32,  UserIntHandler,      0, DA_386IGate ;转换为10进制是128

IdtLen		equ	$ - LABEL_IDT
IdtPtr		dw	IdtLen - 1	; 段界限
		dd	0		; 基地址
; END of [SECTION .idt]

[SECTION .s16]
[BITS	16]
LABEL_BEGIN:
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, 0100h
	
	mov	[LABEL_GO_BACK_TO_REAL+3], ax
	mov	[_wSPValueInRealMode], sp
	
	; 初始化 16 位代码段描述符
	mov	ax, cs
	movzx	eax, ax
	shl	eax, 4
	add	eax, LABEL_SEG_CODE16
	mov	word [LABEL_DESC_CODE16 + 2], ax ;ds:LABEL_DESC_CODE16
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE16 + 4], al
	mov	byte [LABEL_DESC_CODE16 + 7], ah

	; 初始化 32 位代码段描述符
	xor	eax, eax
	mov	ax, cs
	shl	eax, 4
	add	eax, LABEL_SEG_CODE32
	mov	word [LABEL_DESC_CODE32 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE32 + 4], al
	mov	byte [LABEL_DESC_CODE32 + 7], ah

	; 为加载 GDTR 作准备
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_GDT		; eax <- gdt 基地址
	mov	dword [GdtPtr + 2], eax	; [GdtPtr + 2] <- gdt 基地址

	; 为加载 IDTR 作准备
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_IDT		; eax <- idt 基地址
	mov	dword [IdtPtr + 2], eax	; [IdtPtr + 2] <- idt 基地址

	; 保存 IDTR
	sidt	[_SavedIDTR] ;保存IDTR寄存器的值到ds:_SavedIDTR

	; 保存中断屏蔽寄存器(IMREG)值
	in	al, 21h
	mov	[_SavedIMREG], al  

	; 加载 GDTR
	lgdt	[GdtPtr]
	
	; 关中断
	cli ;int 80h可以起作用,但是可屏蔽中断(通过8255设置的)就不能响应了 IF=0
	
	; 加载 IDTR	
	lidt	[IdtPtr]
	

	; 打开地址线A20
	in	al, 92h
	or	al, 00000010b
	out	92h, al

	; 准备切换到保护模式
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax

	; 真正进入保护模式
	jmp	dword SelectorCode32:0	; 执行这一句会把 SelectorCode32 装入 cs,
					; 并跳转到 Code32Selector:0  处
					
LABEL_REAL_ENTRY:		; 从保护模式跳回到实模式就到了这里
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, [_wSPValueInRealMode]

	lidt	[_SavedIDTR]	; 恢复 IDTR 的原值

	mov	al, [_SavedIMREG]	; ┓恢复中断屏蔽寄存器(IMREG)的原值
	out	21h, al			; ┛

	in	al, 92h		; ┓
	and	al, 11111101b	; ┣ 关闭 A20 地址线
	out	92h, al		; ┛

	sti			; 开中断

	mov	ax, 4c00h	; ┓
	int	21h		; ┛回到 DOS
; END of [SECTION .s16]


[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS	32]

LABEL_SEG_CODE32:
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子(目的)

	call	Init8259A

	int	080h ;内中断,类似调用门
	sti ;开中断后,时钟中断开始生效 IF=1
	jmp	$ ;为了演示结果,去掉后跳回实模式
	
	call	SetRealmode8259A


	; 到此停止
	jmp	SelectorCode16:0
	
; Init8259A ---------------------------------------------------------------------------------------------
Init8259A:
	mov	al, 011hn ;00010001
	out	020h, al	; 主8259, ICW1.
	call	io_delay

	out	0A0h, al	; 从8259, ICW1.
	call	io_delay

	mov	al, 020h	; IRQ0 对应中断向量 0x20 00100000   因为前0-19号中断已经被占了
	out	021h, al	; 主8259, ICW2.
	call	io_delay

	mov	al, 028h	; IRQ8 对应中断向量 0x28 00101000
	out	0A1h, al	; 从8259, ICW2.
	call	io_delay

	mov	al, 004h	; IR2 对应从8259 00000100
	out	021h, al	; 主8259, ICW3.
	call	io_delay

	mov	al, 002h	; 对应主8259的 IR2 0000 0010
	out	0A1h, al	; 从8259, ICW3.
	call	io_delay

	mov	al, 001h 0000 0001
	out	021h, al	; 主8259, ICW4.
	call	io_delay

	out	0A1h, al	; 从8259, ICW4.
	call	io_delay

	;mov	al, 11111111b	; 屏蔽主8259所有中断
	mov	al, 11111110b	; 仅仅开启定时器中断,外部可屏蔽中断只有在IF=1和IMR对应位为0时才响应
	out	021h, al	; 主8259, OCW1.
	call	io_delay

	mov	al, 11111111b	; 屏蔽从8259所有中断
	out	0A1h, al	; 从8259, OCW1.
	call	io_delay

	ret
; Init8259A ---------------------------------------------------------------------------------------------


; SetRealmode8259A ---------------------------------------------------------------------------------------------
SetRealmode8259A:

	mov	al, 017h 0001 0111
	out	020h, al	; 主8259, ICW1.
	call	io_delay ;4个字节中断向量,单个8259

	mov	al, 008h	; IRQ0 对应中断向量 0x8
	out	021h, al	; 主8259, ICW2.
	call	io_delay ;ICW3已经不需要了

	mov	al, 001h
	out	021h, al	; 主8259, ICW4.
	call	io_delay



	ret
; SetRealmode8259A ---------------------------------------------------------------------------------------------

io_delay:
	nop
	nop
	nop
	nop
	ret

; int handler ---------------------------------------------------------------
_ClockHandler:
ClockHandler	equ	_ClockHandler - $$
	inc	byte [gs:((80 * 0 + 70) * 2)]	; 屏幕第 0 行, 第 70 列。
	mov	al, 20h
	out	20h, al				; 发送 EOI
	iretd  ;IRETD 指令先弹出一个32位的EIP值,然后再弹出一个32位值并将最低的2个字节值传入CS寄存器,
	       ;最后再弹出一个32位的标志寄存器值

_UserIntHandler:
UserIntHandler	equ	_UserIntHandler - $$
	mov	ah, 0Ch				; 0000: 黑底    1100: 红字
	mov	al, 'I'
	mov	[gs:((80 * 0 + 70) * 2)], ax	; 屏幕第 0 行, 第 70 列。
	iretd

_SpuriousHandler:
SpuriousHandler	equ	_SpuriousHandler - $$
	mov	ah, 0Ch				; 0000: 黑底    1100: 红字
	mov	al, '!'
	mov	[gs:((80 * 0 + 75) * 2)], ax	; 屏幕第 0 行, 第 75 列。
	jmp	$
	iretd
; ---------------------------------------------------------------------------

SegCode32Len	equ	$ - LABEL_SEG_CODE32
; END of [SECTION .s32]

; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN	32
[BITS	16]
LABEL_SEG_CODE16:
	; 跳回实模式:
	mov	ax, SelectorNormal
	mov	ds, ax
	mov	es, ax
	mov	fs, ax
	mov	gs, ax
	mov	ss, ax

	mov	eax, cr0
	and	al, 11111110b
	mov	cr0, eax

LABEL_GO_BACK_TO_REAL:
	jmp	0:LABEL_REAL_ENTRY	; 段地址会在程序开始处被设置成正确的值

Code16Len	equ	$ - LABEL_SEG_CODE16

; END of [SECTION .s16code]

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

80386保护模式--中断机制,附pmtest9代码详解 的相关文章

  • Docker镜像容器的迁移问题

    本文是本人项目踩坑经验 xff0c 如有错漏请见谅 xff01 背景需求 xff1a 一个已经配置好的容器 xff08 无build文件 xff09 需要部署到生产环境中 xff0c 容器内有程序绑定了宿主环境的硬件信息 需求部署后能让第三
  • 视觉里程计1 高翔

    小白 xff08 我 xff09 本着学习 xff0c 提高自我 xff0c 增加知识的想法 xff0c 决定认真分析高翔博士slam 在此立下一个flag xff1a 每周进行知识总结 xff08 CSDN xff09 xff1b 每周要
  • InvokeHelper函数的用法

    ActiveX控件的方法和属性操作与生成的C 43 43 类成员函数相关联都是通过InvokeHelper函数的调用来完成的 xff0c InvokeHelper函数的第一个参数是由Component Gallery xff08 控件提供者
  • 前向声明! struct netif; —— 只声明,无具体内部细节

    今天在看到 Linux阅码场 的 宋宝华 xff1a Linux内核编程广泛使用的前向声明 Forward Declaration xff0c 非常感谢 xff01 前向声明 编程定律 先强调一点 xff1a 在一切可能的场景 xff0c
  • MCU初始化流程——从上电到main()之间

    说明 xff1a 以下介绍示例的MCU地址空间如下 xff1a ROM空间为 xff1a 0x0000 0000 0x0000 8000 RAM空间为 xff1a 0x2000 0000 0x2000 2000 堆栈 SP 生长方向为 递减
  • FreeRTOS 启动第一个任务 prvStartFirstTask vPortSVCHandler

    asm void prvStartFirstTask void asm void prvStartFirstTask void PRESERVE8 Use the NVIC offset register to locate the sta
  • 组播知识 - IGMP

    https zhuanlan zhihu com p 258619129 组播初识 一 为什么要启用组播 xff1f 1 节省不必要的数据发送 2 需要发送相同的数据去往多个不同的接收者 3 减少带宽的占用 4 优化网络设备的处理进程 5
  • copy_from_user函数详细分析

    copy from user函数的目的是从用户空间拷贝数据到内核空间 xff0c 失败返回没有被拷贝的字节数 xff0c 成功返回0 这么简单的一个函数却含盖了许多关于内核方面的知识 比如内核关于异常出错的处理 从用户空间拷贝数据到内核中时

随机推荐