12.创建IDT、中断处理程序,初始化8259A,中断测试

0. 中断那些事儿

中断分类

  1. 外部中断
    1.1 可屏蔽中断(INTR)
    1.2 不可屏蔽中断(NMI)
  2. 内部中断
    2.1 软中断
    2.2 异常

在这里插入图片描述
一共0~255,256个中断。
这个0~255就是中断向量号。处理器就是根据中断向量号来定位中断处理程序的。

操作系统是中断驱动的,在实模式下有中断向量表(IVT),中断发生后找到中断处理程序的入口;在实模式下有中断描述符表(IDT),中断发生后根据中断描述符来找到中断处理程序。

中断描述符表 IDT 中不只有中断描述符,还有任务门描述符、陷阱门描述符。

为什么称为“门”?
因为是通往某段程序的大门,中断描述符表中的描述符叫做——门。
在这里插入图片描述

中断处理过程
  1. 处理器根据中断向量号来定位到中断描述符。
  2. 进行特权级检查
    2.1若是软中断 int n、int3、into 引发的中断,先检查CPL权限大于等于门描述符DPL,即CPL <= 门描述符DPL。(门槛检查)
    2.2 CPL权限小于目标代码段的DPL,即CPL > 目标代码段的DPL(门框检查)。
    2.3 若是外部中断或者异常,则只检查2.2。
  3. 执行中断处理程序。
  4. 中断返回。返回时也要进行特权级检查。
中断发生时的压栈
  1. 比较CPL和目标代码的DPL,若CPL > DPL,则表示要向高特权级转移。
    1.1 保存旧栈SS_old、ESP_old
    1.2 找到目标级别的栈,加载到SS、ESP.
    1.3 将SS_old、ESP_old压入新栈
  2. 新栈中压入EFLAGS寄存器
  3. 将CS、EIP 保存到当前栈,(中断返回时用)
  4. 将ERROR_CODE记入栈
  5. 若在第 1 步判断未发生特权级转移,则用旧栈来保存EFLAGS、CS、EIP、ERROR_CODE。

下图为中断处理过程。
在这里插入图片描述
我们要处理中断,要做的就是:

  1. 建立中断处理函数
  2. 建立中断描述符表 IDT
  3. 打开中断
  4. 等待中断发生

问题来了,中断向量号是怎么给处理器的呢?
我们测试的是用 INTR 引脚给 处理器发送外部可屏蔽中断。

8259A 可编程中断控制器
它是可屏蔽中断的总代理,用它来统一管理可屏蔽中断,
在这里插入图片描述
各种可屏蔽中断得经过它才能达到CPU。
在这里插入图片描述
8259A 的编程就是两步,初始化、控制。
写 ICW1 ~ ICW4 寄存器和 OCW1 ~ OCW3 寄存器。
具体过程,各个寄存器代表的含义,不多说。

8259A 已经和CPU在硬件上连接好了,就像前面图所示。
我们只需对它进行编程就可以了。

1 总步骤

任务:我们要测试一个时钟中断,就是连接在 8259A 主片上的 IR0 的中断。
步骤:

  1. 写好中断处理程序。(kernel.S)
  2. 建立中断描述符表 IDT。(my_interrupt.c)
  3. 加载 IDT。(my_interrupt.c)
  4. 设置 8259A。 (8259A.S)
  5. 打开中断。(kernel.S)
  6. 观察是否执行中断处理程序的打印字符。

2 写中断处理函数

kernel.S
kernel.S先是实现了33个中断处理程序的入口地址,最后的一个函数是以后在my_interrupt.c中调用的,要加载 idt 到 IDTR寄存器的。

[bits 32]
%define ERROR_CODE nop		 ; 若在相关的异常中cpu已经自动压入了错误码,为保持栈中格式统一,这里不做操作.
%define ZERO push 0		 ; 若在相关的异常中cpu没有压入错误码,为了统一栈中格式,就手工压入一个0

extern idt_table		 ;idt_table是C中注册的中断处理程序数组


section .data
idt_ptr  dw  0 
	    dd  0			;为以后的加载 idt 存放48位立即数
global intr_entry_table			;在 C 中引用intr_entry_table数组,intr_entry_table里存的是0x21个中断处理程序的入口地址
intr_entry_table:

%macro VECTOR 2
section .text
intr%1entry:		 ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少

   %2				 ; 中断若有错误码会压在eip后面 
; 以下是保存上下文环境
   push ds
   push es
   push fs
   push gs
   pushad			 ; PUSHAD指令压入32位寄存器,其入栈顺序是: EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI

   ; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI 
   mov al,0x20                   ; 中断结束命令EOI
   out 0xa0,al                   ; 向从片发送
   out 0x20,al                   ; 向主片发送

   push %1			 ; 不管idt_table中的目标程序是否需要参数,都一律压入中断向量号,调试时很方便
   call [idt_table + %1*4]       ; 调用idt_table中的C版本中断处理函数
   jmp intr_exit

section .data
   dd    intr%1entry	 ; 存储各个中断入口程序的地址,形成intr_entry_table数组
%endmacro

section .text
global intr_exit
intr_exit:	     
; 以下是恢复上下文环境
   add esp, 4			   ; 跳过中断号
   popad
   pop gs
   pop fs
   pop es
   pop ds
   add esp, 4			   ; 跳过error_code
   iretd

VECTOR 0x00,ZERO
VECTOR 0x01,ZERO
VECTOR 0x02,ZERO
VECTOR 0x03,ZERO 
VECTOR 0x04,ZERO
VECTOR 0x05,ZERO
VECTOR 0x06,ZERO
VECTOR 0x07,ZERO 
VECTOR 0x08,ERROR_CODE
VECTOR 0x09,ZERO
VECTOR 0x0a,ERROR_CODE
VECTOR 0x0b,ERROR_CODE 
VECTOR 0x0c,ZERO
VECTOR 0x0d,ERROR_CODE
VECTOR 0x0e,ERROR_CODE
VECTOR 0x0f,ZERO 
VECTOR 0x10,ZERO
VECTOR 0x11,ERROR_CODE
VECTOR 0x12,ZERO
VECTOR 0x13,ZERO 
VECTOR 0x14,ZERO
VECTOR 0x15,ZERO
VECTOR 0x16,ZERO
VECTOR 0x17,ZERO 
VECTOR 0x18,ERROR_CODE
VECTOR 0x19,ZERO
VECTOR 0x1a,ERROR_CODE
VECTOR 0x1b,ERROR_CODE 
VECTOR 0x1c,ZERO
VECTOR 0x1d,ERROR_CODE
VECTOR 0x1e,ERROR_CODE
VECTOR 0x1f,ZERO 
VECTOR 0x20,ZERO

section .text
global lodid
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
由于我只能上传不大于20M的内容,所以分割为2部分,抱歉了~ 第1章 预备知识 1. 1 linux内核简介 1. 2 intel x86 cpu系列的寻址方式 1. 3 i386的页式内存管理机制 1. 4 linux内核源代码中的c语言代码 1.5 linux内核源代码中的汇编语言代码 第2章 存储管理 2.1 linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户堆栈的扩展 2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2. 9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() . 第3章 中断、异常和系统调用 3.1 x86 cpu对中断的硬件支持 3. 2 中断向量idt初始化 3. 3 中断求队列的初始化 3. 4 中断的响应和服务 3. 5 软中断与bottom half 3.6 页面异常的进入和返回 3. 7 时钟中断 3. 8 系统调用 3. 9 系统调用号与跳转 第4章 进程与进程调度 4.1 进程四要素 4.2 进程三部曲:创建、执行与消亡 4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核中的互斥操作 第5章 文件系统 5.1 概述 5. 2 从路径名到目标节点 5. 3 访问权限与文件安全性 5. 4 文件系统的安装和拆卸 5.5 文件的打开与关闭 5. 6 文件的写与读 5.7 其他文件操作 5. 8 特殊文件系统/proc 第6章 传统的unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6. 5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量
GDT(全局描述符)和IDT中断描述符)是操作系统中用于管理内存和中断的重要数据结构。 GDT是一个,用于存储所有内存段的描述符描述符包含了段的基地址、段的长度、访问权限等信息。在x86体系结构的保护模式下,所有的内存访问都必须通过段寄存器来实现。当CPU执行一条访存指令时,它会把段寄存器的值当做段描述符的索引,在GDT中找到对应的描述符,从而确定要访问的内存地址的范围和访问权限。因此,初始化GDT是操作系统启动时的必要步骤IDT是另一个,用于存储所有中断和异常处理程序描述符。当CPU收到一个中断求或异常时,它会从IDT中找到对应的描述符,从而确定要执行的中断或异常处理程序的地址。因此,初始化IDT也是操作系统启动时的必要步骤。 GDT和IDT初始化大致可以分为以下几个步骤: 1.创建并填充GDT和IDT项,每个项对应一个内存段或中断处理程序。 2.创建并填充GDTR(GDT寄存器)和IDTR(IDT寄存器),这两个寄存器分别存储GDT和IDT的地址和大小信息。 3.使用LGDT和LIDT指令将GDTR和IDTR的值加载到CPU中,从而告诉CPU如何寻找GDT和IDT。 需要注意的是,为了保证安全性,GDT和IDT通常被放置在内核态的固定位置,并且只有内核态的代码才能够修改它们。此外,为了简化实现,现代操作系统通常会使用一些预定义的GDT和IDT项,而不是每次都手动填充项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值