vi设计案例网站,网站编译成dll,火狐浏览器下载,网站建设 步骤由于 CPU 获知了计算机中发生的某些事#xff0c;CPU 暂停正在执行的程序#xff0c;转而去执行处理该事件的程序#xff0c;当这段程序执行完毕后#xff0c;CPU 继续执行刚才的程序。整个过程称为中断处理#xff0c;也称为中断。
把中断按事件来源分类#xff0c;来自…由于 CPU 获知了计算机中发生的某些事CPU 暂停正在执行的程序转而去执行处理该事件的程序当这段程序执行完毕后CPU 继续执行刚才的程序。整个过程称为中断处理也称为中断。
把中断按事件来源分类来自CPU外部的中断就称为外部中断来自CPU内部的中断就称为内部中断。
外部中断按是否导致宕机来划分可分为可屏蔽中断和不可屏蔽中断。内部中断按中断是否正常来划分可分为软中断和异常。
外部中断
外部中断可分为可屏蔽中断和不可屏蔽中断
外部中断是指来自CPU外部的中断而外部的中断源必须是某个硬件所以外部中断又称为硬件中断。CPU提供了两条信号线外部硬件的中断是通过两根信号线通知CPU的这两根信号线就是INTRINTeRrupt和 NMINon Maskable Interrupt
可屏蔽中断可屏蔽中断是通过INTR引脚进入CPU的外部设备如硬盘、网卡等发出的中断都是可屏蔽中断。可屏蔽的意思是此外部设备发出的中断CPU可以不理会因为它不会让系统宕机所以可以通过eflags寄存器的IF位将所有这些外部设备的中断屏蔽。另外这些设备都是接在某个中断代理设备的通过该中断代理也可以单独屏蔽某个设备的中断。对于这类可屏蔽中断CPU 可以选择不用理会甚至即使在理会后也可以像 Linux 那样把中断分为上半部和下半部分开处理。不可屏蔽中断不可屏蔽中断是通过 NMI 引脚进入 CPU 的它表示系统中发生了致命的错误它等同于宣布计算机的运行到此结束了。 eflags 寄存器中的 IF 位对其无效
内部中断
内部中断可分为软中断和异常
软中断软中断是由软件主动发起的中断。由于该中断是软件运行中主动发起的所以它是主观上的并不是客观上的内部错误
以下是可以发起中断的指令 “int 8 位立即数”。这是我们以后常用的指令我们要通过它进行系统调用8 位立即数可表示 256种中断这与处理器所支持的中断数是相吻合的。 “int3”。这可不是 int 空格 3它们之间无间隙。int3 是调试断点指令其所触发的中断向量号是 3int3的机器码是0xcc。 into。这是中断溢出指令它所触发的中断向量号是 4。不过能否引发 4 号中断是要看 eflags 标志寄存器中的 OF 位是否为 1如果是 1 才会引发中断否则该指令悄悄地什么都不做低调得很。 bound。这是检查数组索引越界指令它可以触发 5 号中断用于检查数组的索引下标是否在上下边界之内。该指令格式是bound 16/32 位寄存器 16/32 位内存。目的操作数是用寄存器来存储的其内容是待检测的数组下标值。。源操作数是内存其内容是数组下标的下边界和上边界。当执行 bound 指令时若下标处于数组索引的范围之外则会触发 5 号中断。 ud2。未定义指令这会触发第 6 号中断。该指令表示指令无效CPU 无法识别。主动使用它发起中断常用于软件测试中无实际用途。
以上几种软中断指令除第一种的“int 8 位立即数”之外其他的几种又可以称为异常。 异常异常是指令执行期间CPU内部产生的错误引起的由于是运行时错误所以它不受标志寄存器 eflags 中的 IF 位影响无法向用户隐瞒因为运行不下去了错误兜不住了。
并不是所有的异常都很致命按照轻重程度可以分为以下三种。: (1) Fault也称为故障。这种错误是可以被修复的一种类型属于最轻的一种异常它给软件一次“改过自新”的机会。当发生此类异常时 CPU 将机器状态恢复到异常之前的状态之后调用中断处理程序时CPU 将返回地址依然指向导致 fault 异常的那条指令。通常中断处理程序中会将此问题修复待中断处理程序返回后便能重试。最典型的例子就是操作系统课程中所说的缺页异常 page fault话说 Linux 的虚拟内存就是基于 page fault 的这充分说明这种异常是极易被修复的甚至是有益的。 (2) Trap也称为陷阱这一名称很形象地说明软件掉进了 CPU 设下的陷阱导致停了下来。此异常通常用在调试中比如 int3 指令便引发此类异常为了让中断处理程序返回后能够继续向下执行CPU将中断处理程序的返回地址指向导致异常指令的下一个指令地址。 (3) Abort也称为终止从名字上看这是最严重的异常类型一旦出现由于错误无法修复程序将无法继续运行操作系统为了自保只能将此程序从进程表中去掉。导致此异常的错误通常是硬件错误或者某些系统数据结构出错。
向量号助记符说明类型错误号产生源0#DE除 出错故障无DIV或IDIV指令1#DB调试故障/陷阱无任何代码或数据引用或是INT 1指令2- -NMI中断中断无非屏蔽外部中断3#BP断点陷阱无INT 3指令4#OF溢出陷阱无INTO指令5#BR边界范围超出故障无BOUND指令6#UD无效操作码未定义操作码故障无UD2指令或保留的操作码(奔腾Pro中加入的新指令)7#NM设备不存在无数学协处理器故障无浮点或WAIT/FWAIT指令8#DF双重错误异常终止有0任何可产生异常、NMI或INTR的指令9- -协处理器段超越保留故障无浮点指令386以后的CPU不产生该异常10#TS无效的任务状态段TSS故障有任务交换或访问TSS11#NP段不存在故障有加载段寄存器或访问系统段12#SS堆栈错误故障有堆栈操作和SS寄存器加载13#GP一般保护错误故障有任何内存引用和其他保护检查14#PF页面保护故障有任何内存引用15Intel保留请勿使用无16#MFx87FPU浮点错误数学错误故障无x87FPU浮点或WAIT/FWAIT指令17#AC对齐检查故障有0对内存中任何数据的引用18#MC机器检查异常终止无错误码若有和产生源与CPU类型有关奔腾处理器引进19#XMSIMD浮点异常故障无SSE和SSE2浮点指令PIII处理器引进20~31- -Intel保留请勿使用32~255- -用户定义非保留中断中断外部中断或者INT n指令 中断描述符表
中断描述符表Interrupt Descriptor TableIDT是保护模式下用于存储中断处理程序入口的表当CPU接收一个中断时需要用中断向量在此表中检索对应的描述符在该描述符中找到中断处理程序的起始地址然后执行中断程序中断描述符表中不仅有中断描述符还有任务门描述符和陷阱门描述符。由于表中所有描述符都是记录一段程序的起始地址相当于某段程序的“大门”所以中断描述符表中的描述符有自己的名称-------门 任务门
任务门和任务状态段Task Status SegmentTSS是Intel处理器在硬件一级提供的任务切换机制所以任务门需要和TSS配合在一起使用在任务门中记录的是TSS选择子偏移量未使用。任务门可以存在于全局描述符表GDT、局部描述符表LDT、中断描述符表IDT中。描述符中任务门的type值为二进制0101。大多数操作系统包括 Linux都未用 TSS 实现任务切换。
中断门
中断门包括了中断处理程序所在段的段选择子和段内偏移地址。当通过此方式进入中断后标志位寄存器eflags中的IF位自动置0也就是在进入中断后自动把中断关闭避免中断嵌套Linux就是利用中断门实现的系统调用就是那个著名的int 0x80。中断门只允许存在于IDT中。描述符中中断门的type值为二进制1110.
陷阱门
陷阱门和中断门非常相似区别是由陷阱门进入中断后标志寄存器eflags中的IF位不会自动置0。陷阱门只允许存在于IDT中。描述符中陷阱门的type值为二进制1111.
调用门
调用门是提供给用户进程进入特权0级的方式。其DPL为3。调用门中记录例程的地址它不能用int指令调用只能用call和jmp指令。调用门可以安装在GDT和LDT中。描述符中调用门的type值为二进制1100。 CPU 内部有个中断描述符表寄存器Interrupt Descriptor Table RegisterIDTR该寄存器分为两部分第0~15位是表界限 IDT 大小减 1第 1647 位是 IDT 的基地址。16 位的表界限表示最大范围是 0xffff即 64KB。可容纳的描述符个数是 64KB/88K8192 个。特别注意的是 GDT 中的第 0个段描述符是不可用的但 IDT 却无此限制第 0 个门描述符也是可用的中断向量号为 0 的中断是除法错。但处理器只支持 256个中断即 0254中断描述符中其余的描述符不可用。在门描述符中有个 P 位所以咱们将来在构建 IDT 时记得把 P 位置 0这样就表示门描述符中的中断处理程序不在内存中。
同加载 GDTR 一样加载 IDTR 也有个专门的指令—lidt其用法是lidt 48 位内存数据 中断处理过程及保护
完整的中断过程分为 CPU 外和 CPU 内两部分:
CPU 外外部设备的中断由中断代理芯片接收处理后将该中断的中断向量号发送到 CPU。CPU 内CPU 执行该中断向量号对应的中断处理程序。
CPU 内的过程
处理器根据中断向量号定位中断门描述符。 中断向量号是中断描述符的索引当处理器收到一个外部中断向量号后它用此向量号在中断描述符表中查询对应的中断描述符然后再去执行该中断描述符中的中断处理程序。由于中断描述符是8个字节所以处理器用中断向量号乘以8后再与IDTR中的中断描述符表地址相加所求的地址之和便是该中断向量号对应的中断描述符处理器进行特权级检查 由于中断是通过中断向量号通知处理器的中断向量号只是个整数并没有RPL所以在对由中断引起的特权级转移做特权级检查中并不涉及RPL。中断门的特权检查同调用门类似对于软件主动发起的软中断当前特权级CPL必须在门描述符DPL和门中目标代码段DPL之间。这是为了防止位于3特权级下的用户程序主动调用某些只为内核服务的例程。 (a) 如果是由软中断int n、int3和into引发的中断这些是用户进程中主动发起的中断由于用户代码控制处理器要检查当前特权级CPL和门描述符DPL这是检查进门的特权下限如果CPL权限大于等于DPL特权级“门槛”检查通过进入下一步的“门框”检查否则处理器抛出异常 (b) 这一步检查特权级的上限门框处理器要检查当前特权级CPL和门描述符中所记录的选择子对应的目标代码段 DPL如果 CPL 权限小于目标代码段 DPL检查通过否则 CPL 若大于等于目标代码段 DPL处理器将引发异常也就是说除了用返回指令从高特权级返回特权转移只能发生在由低向高。 若中断是由外部设备和异常引起的只直接检查 CPL 和目标代码段的 DPL和上面的步骤 b是一样的要求 CPL 权限小于目标代码段 DPL否则处理器引发异常。执行中断处理程序。 特权级检查通过后将门描述符目标代码段选择子加载到代码段寄存器 CS 中把门描述符中中断处理程序的偏移地址加载到 EIP开始执行中断处理程序。 中断发生后eflags中的NT位和TF位会被置0.如果中断对应的门描述符是中断门标志寄存器eflags的IF位会被自动置0避免中断嵌套即中断处理过程中又来了个新的中断这是为防止处理某个中断的过程中又来了个相同的中断。这会导致一般保护性GP异常。这表示默认情况下处理器会在无人打扰的方式下执行中断门描述符中的中断处理例程。 若中断发生时对应的是任务门或陷阱门CPU 是不会将 IF 位清 0 的。因为陷阱门主要用于调试它允许 CPU 响应更高级别的中断所以允许中断嵌套。而对任务门来说这是执行一个新任务任务都应该在开中断的情况下进行否则就独占 CPU 资源操作系统也会由多任务退化成单任务了。 从中断返回的指令是 iret它从栈中弹出数据到寄存器 cs、eip、eflags 等根据特权级是否改变判断是否要恢复旧栈也就是说是否将栈中位于 SS_old 和 ESP_old 位置的值弹出到寄存 ss 和 esp。当中断处理程序执行完成返回后通过 iret 指令从栈中恢复 eflags 的内容。
中断发生时的压栈
中断发生后由于CS加载了新的目标代码段选择子处理器不管新的选择子和任何段寄存器中当前的选择子是否相同也不管这两个选择子是否指向相同的段只要段寄存器被加载段描述符缓冲寄存器就会被刷新处理器都会认为换了一个段属于段间转移也就是远转移。所以当前进程被中断打断后为了从中断返回后能继续运行该进程处理器自动把CS和EIP的当前值保存到中断处理程序使用的栈中。不同特权级别下处理器使用不同的栈至于中断处理程序使用的是哪个栈要视它当时所在的特权级别因为中断是可以在任何特权级别下发生的。除了要保存CSEIP外还需要保存标志寄存器EFLAGS如果涉及到特权级变换还要压入SS和ESP寄存器。
下面看看以上寄存器入栈情况及顺序这里不再讨论有关特权检查的内容 处理器根据中断向量号找到对应的中断描述符后拿CPL和中断门描述符中选择子对应的目标代码段的DPL比对若CPL权限比DPL低这表示要向高特权级转移需要切换到高特权级的栈。这意味着当执行完中断处理程序后若要正确返回到当前被中断的进程同样需要将栈恢复为此时的旧栈。于是处理器先临时保存旧栈SS和ESP的值记作SS_old 和 ESP_old然后在TSS中找到同目标代码段DPL级别相同的栈加载到寄存器SS和ESP中记作SS_new和ESP_new再将之前临时保存的SS_old和ESP_old压入新栈备份以备返回时重新加载到栈段寄存器SS和栈指针ESP。由于SS_old是16位数据32位模式下的栈操作数是32位所以将SS_old用0扩展其16位成为32位数据后入栈。此时新栈内容如图A所示。 在新栈中压入EFLAGS寄存器新栈内容如图B所示 由于要切换到目标代码段对于这种段间转移要将CS和EIP保存到当前栈中备份记作CS_old和EIP_old,以便中断程序执行结束后能恢复到被中断的进程。同样 CS_old 是 16 位数据需要用 0 填充其高 16 位扩展为 32 位数据后入栈。此时新栈内容如图C所示。 某些异常会有错误码此错误码用于报告异常是在哪个段上发生的也就是异常发生的位置所以错误码中包含选择子等信息错误码会紧跟在 EIP 之后入栈记作 ERROR_CODE。此时新栈内容如图D所示。
如果在第 1 步中判断未涉及到特权级转移便不会到 TSS 中寻找新栈而是继续使用当前旧栈因此也谈不上恢复旧栈此时中断发生时栈中数据不包括 SS_old 和 ESP_old。比如中断发生时当前正在运行的是内核程序这是 0 特权级到 0 特权级无特权级变化 处理器进入中断执行完中断处理程序后还要返回到被中断的进程这是进入中断的逆过程。中断返回是用iret指令实现的。Iret即 interrupt ret此指令专用于从中断处理程序返回假设在32位模式下它从当前栈顶处依次弹出32 位数据分别到寄存器 EIP、CS、EFLAGS。iret 指令并不清楚栈中数据的正确性它只负责把栈顶处往上的数据每次 4 字节对号入座弹出到相关寄存器所以在使用 iret 之前一定要保证栈顶往上的数据是正确的且从栈顶往上的顺序是 EIP、CS、EFLAGS根据特权级是否有变化还有 ESP、SS。由于段寄存器 CS 是 16 位故从栈中返回的 32 位数据其高16 位被丢弃只将低 16 位载入到 CS。若处理器发现返回后特权级会变化还会继续将两个双字数据返回到 ESP、SS其中 SS也是 16 位寄存器所以同样也是弹出 32 位数据后只将其中的低 16 位加载到 SS。iret 指令意味着从中断返回所以它是中断处理程序中最后一个指令。同类的指令还有 iretw 和 iretd16 位模式下用 iretw32 位模式下用 iretd。iret 是 iretw 和 iretd的简写无论是在 16 位模式还是在 32 位模式下编码都可以只用 iret 指令它是被编译成 iretw还是iretd取决于伪指令 BITS 所指明的字长。
下面咱们聊聊这个从中断处理程序返回的过程 (1当处理器执行到 iret 指令时它知道要执行远返回首先需要从栈中返回被中断进程的代码段选择子CS_old 及指令指针 EIP_old。这时候它要进行特权级检查。先检查栈中 CS 选择子 CS_old根据其RPL 位即未来的 CPL判断在返回过程中是否要改变特权级。 (2) 栈中 CS 选择子是 CS_old根据 CS_old 对应的代码段的 DPL 及 CS_old 中的 RPL 做特权级检查如果检查通过随即需要更新寄存器 CS 和 EIP。由于 CS_old 在入栈时已经将高 16 位扩充为 0现在是 32 位数据段寄存器 CS 是 16 位故处理器丢弃 CS_old 高 16 位将低 16 位加载到 CS将 EIP_old 加载到 EIP 寄存器之后栈指针指向 EFLAGS。如果进入中断时未涉及特权级转移此时栈指针是 ESP_old说明在之前进入中断后是继续使用旧栈。否则栈指针是 ESP_new说明在之前进入中断后用的是 TSS 中记录的新栈。 (3) 将栈中保存的 EFLAGS 弹出到标志寄存器 EFLAGS。如果在第 1 步中判断返回后要改变特权级此时栈指针是 ESP_new它指向栈中的 ESP_old。否则进入中断时属于平级转移用的是旧栈此时栈指针是 ESP_old栈中已无因此次中断发生而入栈的数据栈指针指向中断发生前的栈顶。 (4) 如果在第 1 步中判断出返回时需要改变特权级也就是说需要恢复旧栈此时便需要将 ESP_old和 SS_old 分别加载到寄存器 ESP 及 SS丢弃寄存器 SS 和 ESP 中原有的 SS_new 和 ESP_new同时进行特权级检查。补充由于 SS_old 在入栈时已经由处理器将高 16 位填充为 0现在是 32 位数据所以在重新加载到栈段寄存器 SS 之前需要将 SS_old 高 16 位剥离丢弃只用其低 16 位加载 SS。
中断错误码
有些中断会在栈中压入错误码有点“临终遗言提供线索”的意味用来指明中断发生在哪个段上。所以错误码最主要的部分就是选择子只不过此选择子可以在多种表中检索描述符。错码码由几部分组成格式如图 所示
EXT 表示 EXTernal event即外部事件用来指明中断源是否来自处理器外部如果中断源是不可屏蔽中断 NMI 或外部设备EXT 为 1否则为 0。
可编程中断控制器 8259A 8259A 用于管理和控制可屏蔽中断它表现在屏蔽外设中断对它们实行优先级判决向 CPU 提供中断向量号等功能。而它称为可编程的原因就是可以通过编程的方式来设置以上的功能。 Intel 处理器共支持 256 个中断但 8259A 只可以管理 8 个中断所以为了多支持一些中断设备提供了另一个解决方案将多个 8259A 组合官方术语就是级联。有了级联这种组合后每一个 8259A 就被称为 1 片。若采用级联方式即多片 8259A 芯片串连在一起最多可级 9 个也就是最多支持 64 个中断。n 片 8259A 通过级联可支持 7n1 个中断源级联时只能有一片 8259A为主片 master其余的均为从片 slave。来自从片的中断只能传递给主片再由主片向上传递给 CPU也就是说只有主片才会向 CPU 发送 INT 中断信号。 每个独立运行的外部设备都是一个中断源它们所发出的中断只有接在中断请求IRQ:InterruptReQuest信号线上才能被 CPU 大神知晓这也就是大家在开机时电脑屏幕上会看到的 IRQ1…IRQn这些都是为外部设备所分配的中断号。 8259A 内部结构逻辑示意图
INT8259A 选出优先级最高的中断请求后发信号通知 CPU。INTAINT Acknowledge中断响应信号。位于 8259A 中的 INTA 接收来自 CPU 的 INTA 接口的中断响应信号。IMRInterrupt Mask Register中断屏蔽寄存器宽度是 8 位用来屏蔽某个外设的中断。IRRInterrupt Request Register中断请求寄存器宽度是 8 位。它的作用是接受经过 IMR 寄存器过滤后的中断信号并锁存此寄存器中全是等待处理的中断“相当于”5259A 维护的未处理中断信号队列。PRPriority Resolver优先级仲裁器。当有多个中断同时发生或当有新的中断请求进来时将它与当前正在处理的中断进行比较找出优先级更高的中断。ISRIn-Service Register中断服务寄存器宽度是 8 位。当某个中断正在被处理时保存在此寄存器中。
8259A工作流程当某个外设发送一个中断信号时由于主板上已经将信号通路指向了8259A芯片的某个IRQ接口所以该中断信号最终被送入了8259A。
8259A首先检查IMR寄存器中是否已经屏蔽了来自该IRQ接口的中断信号。IMR寄存器中的位为1则表示中断屏蔽为0则表示中断放行。如果该IRQ对应的的位已经被置1即表示来自该IRQ接口上的中断已经被屏蔽了。则将该中断信号丢弃否则将其送入IRR寄存器送入IRR寄存器后将该IRQ接口所在IRR寄存器中对应的BIT置1。IRR寄存器的作用“相当于”待处理中断队列。在某个恰当时机优先级仲裁器PR会从IRR寄存器中挑选一个优先级最大的中断此处的优先级决判很简单就是IRQ接口号越低优先级越大所以IRQ0优先级最大。之后8259A会在控制电路中通过INT接口向CPU发送INTR信号。信号被送入了CPU的INTR接口后这样CPU便知道有信的中断来了又有活干了于是CPU将手里的指令执行完后马上通过自己的INTA接口向8259A的INTA接口回复一个中断响应信号表示现在CPU我已经准备好啦8259A你可以继续后面的工作。8259A在收到这个信号后立即将刚才选出的优先级最大的中断在ISR寄存器中对应的BIT置1此寄存器表示当前正在处理的中断同时要将该中断从“待处理中断队列”IRR中去掉也就是IRR中奖该中断对应1的BIT置0。之后CPU将再次发送INTA信号给8259A这一次是想获取中断对应的中断向量号。由于大部分情况下8259A的起始中断向量号并不是 0起始中断向量号被修改原因后面会说所以用起始中断向量号IRQ 接口号便是该设备的中断向量号由此可见外部设备虽然会发中断信号但它并不知道还有中断向量号这回事不知道自己会被中断代理如 8259A分配一个这样的整数。随后8259A 将此中断向量号通过系统数据总线发送给 CPU。CPU 从数据总线上拿到该中断向量号后用它做中断向量表或中断描述符表中的索引找到相应的中断处理程序并去执行。
如果8259A的“EOI通知End Of Interrupt”若被设置为非自动模式手工模式中断处理程序结束处必须有向 8259A 发送 EOI 的代码8259A 在收到 EOI 后将当前正处理的中断在 ISR 寄存器中对应的 BIT 置 0。如果“EOI 通知”被设置为自动模式在刚才 8259A 接收到第二个 INTA 信号后也就是 CPU 向 8259A 要中断向量号的那个 INTA8259A 会自动将此中断在 ISR 中对应的 BIT 置 0。 8259A 的编程
8259A 的编程就是对它进行初始化设置主片与从片的级联方式指定起始中断向量号以及设置各种工作。
中断向量号是逻辑上的东西它在物理上是 8259A 上的 IRQ 接口号。8259A 上 IRQ 号的排列顺序是固定的但其对应的中断向量号是不固定的这其实是一种由硬件到软件的映射通过设置 8259A可以 IRQ 接口映射到不同的中断向量号。
在 8259A 内部有两组寄存器一组是初始化命令寄存器组用来保存初始化命令字InitializationCommand WordsICWICW 共 4 个ICW1ICW4。另一组寄存器是操作命令寄存器组用来保存操作命令字Operation Command WordOCWOCW 共 3 个OCW1OCW3。所以我们对 8259A 的编程也分为初始化和操作两部分。
一部分是用 ICW 做初始化用来确定是否需要级联设置起始中断向量号设置中断结束模式。其编程就是往 8259A 的端口发送一系列 ICW。由于从一开始就要决定 8259A 的工作状态所以要一次性写很多设置某些设置之间是具有关联、依赖性的也许后面的某个设置会依赖前面某个 ICW 写入的设置所以这部分要求严格的顺序必须依次写入 ICW1、ICW2、ICW3、ICW4。另一部分是用 OCW 来操作控制 8259A前面所说的中断屏蔽和中断结束就是通过往 8259A 端口发送 OCW 实现的。OCW 的发送顺序不固定3 个之中先发送哪个都可以。 ICW1 用来初始化 8259A 的连接方式和中断信号的触发方式。连接方式是指用单片工作还是用多片级联工作触发方式是指中断请求信号是电平触发还是边沿触发。注意ICW1 需要写入到主片的 0x20 端口和从片的 0xA0 端口IC4 表示是否要写入 ICW4这表示并不是所有的 ICW 初始化控制字都需要用到。IC4 为 1 时表示需要在后面写入 ICW4为 0则不需要。注意x86 系统 IC4 必须为 1。SNGL 表示 single若 SNGL 为 1表示单片若 SNGL 为 0表示级联cascade。这里说一下若在级联模式下这要涉及到主片1个和从片多个用哪个 IRQ 接口互相连接的问题所以当 SNGL 为 0 时主片和从片也是需要 ICW3 的。ADI 表示 call address interval用来设置 8085 的调用时间间隔x86 不需要设置。LTIM 表示 level/edge triggered mode用来设置中断检测方式LTIM 为 0 表示边沿触发LTIM 为 1表示电平触发。第 4 位的 1 是固定的这是 ICW1 的标记。第 57 位专用于 8085 处理器x86 不需要直接置为 0 即可。 ICW2 用来设置起始中断向量号就是前面所说的硬件 IRQ 接口到逻辑中断向量号的映射。由于每个8259A 芯片上的 IRQ 接口是顺序排列的所以咱们这里的设置就是指定 IRQ0 映射到的中断向量号其他IRQ 接口对应的中断向量号会顺着自动排下去。
注意ICW2 需要写入到主片的 0x21 端口和从片的 0xA1 端口由于咱们只需要设置 IRQ0 的中断向量号IRQ1IRQ7 的中断向量号是 IRQ0 的顺延所以咱们只负责填写高 5 位 T3T7ID0ID2 这低 3 位不用咱们负责。由于咱们只填写高 5 位所以任意数字都是8 的倍数这个数字表示的便是设定的起始中断向量号。这是有意设计的低 3 位能表示 8 个中断向量号这由 8259A 根据 8 个 IRQ 接口的排列位次自行导入IRQ0 的值是 000IRQ1 的值是 001IRQ2 的值便 010……以此类推这样高 5 位加低 3 位便表示了任意一个 IRQ 接口实际分配的中断向量号。 ICW3 仅在级联的方式下才需要如果 ICW1 中的 SNGL 为 0用来设置主片和从片用哪个 IRQ 接口互连。 由于主片和从片的级联方式不一样对于这个 ICW3主片和从片都有自己不同的结构
ICW3 需要写入主片的 0x21 端口及从片的 0xA1 端口。对于主片ICW3 中置 1 的那一位对应的 IRQ 接口用于连接从片若为 0 则表示接外部设备。比如若主片 IRQ2 和 IRQ5 接有从片则主片的 ICW3 为 00100100对于从片要设置与主片 8259A 的连接方式“不需要”指定用自己的哪个 IRQ 接口与主片连接从片上专门用于级联主片的接口并不是 IRQ。设置从片连接主片的方法是只需要在从片上指定主片用于连接自己的那个 IRQ 接口就行了在中断响应时主片会发送与从片做级联的 IRQ 接口号所有从片用自己的 ICW3 的低 3 位和它对比若一致则认为是发给自己的。比如主片用 IRQ2 接口连接从片 A用 IRQ5 接口连接从片 B从片 A 的 ICW3 的值就应该设为 00000010从片 B 的 ICW3 的值应该设为 00000101。所以从片 ICW3中的低 3 位 ID0ID2 就够了高 5 位不需要为 0 即可 第 75 位未定义直接置为 0 即可。SFNM 表示特殊全嵌套模式Special Fully Nested Mode若 SFNM 为 0则表示全嵌套模式若 SFNM 为1则表示特殊全嵌套模式。BUF 表示本 8259A 芯片是否工作在缓冲模式。BUF 为 0则工作非缓冲模式BUF 为 1则工作在缓冲模式。当多个 8259A 级联时如果工作在缓冲模式下M/S 用来规定本 8259A 是主片还是从片。若 M/S 为1则表示则表示是主片若 M/S 为 0则表示是从片。若工作在非缓冲模式BUF 为 0下M/S 无效。AEOI 表示自动结束中断Auto End Of Interrupt8259A 在收到中断结束信号时才能继续处理下一个中断此项用来设置是否要让 8259A 自动把中断结束。若 AEOI 为 0则表示非自动即手动结束中断咱们可以在中断处理程序中或主函数中手动向 8259A 的主、从片发送 EOI 信号。这种“操作”类命令通过下面要介绍的 OCW 进行。若 AEOI 为 1则表示自动结束中断。μPM 表示微处理器类型microprocessor此项是为了兼容老处理器。若 μPM 为 0则表示 8080 或8085 处理器若 μPM 为 1则表示 x86 处理器。 OCW1 用来屏蔽连接在 8259A 上的外部设备的中断信号实际上就是把 OCW1 写入了 IMR 寄存器。这里的屏蔽是说是否把来自外部设备的中断信号转发给 CPU。由于外部设备的中断都是可屏蔽中断所以最终还是要受标志寄存器 eflags 中的 IF 位的管束若 IF 为 0可屏蔽中断全部被屏蔽也就是说在IF 为 0 的情况下即使 8259A 把外部设备的中断向量号发过来CPU 也置之不理。
注意OCW1 要写入主片的 0x21 或从片的 0xA1 端口M0M7 对应 8259A 的 IRQ0IRQ7某位为 1对应的 IRQ 上的中断信号就被屏蔽了。否则某位为0 的话对应的 IRQ 中断信号则被放行。 OCW2 用来设置中断结束方式和优先级模式。注意OCW2 要写入到主片的 0x20 及从片的 0xA0 端口。OCW2 的配置比较复杂各种属性位要配合在一起组合出 8259A 的各种工作模式。由高 3 位 R、SL、EOI 可以定义多种中断结束方式和优先级循环方式。R,Rotation表示是否按照循环方式设置中断优先级。R 为 1 表示优先级自动循环R 为 0 表示不自动循环采用固定优先级方式。SL,Specific Level表示是否指定优先等级。等级是用低 3 位来指定的。此处的 SL 只是开启低 3 位的开关所以 SL 也表示低 3 位的 L2L0 是否有效。SL 为 1 表示有效SL 为 0 表示无效。EOIEnd Of Interrupt为中断结束命令位。令 EOI 为 1则会令 ISR 寄存器中的相应位清 0也就是将当前处理的中断清掉表示处理结束。向 8259A 主动发送 EOI 是手工结束中断的做法所以使用此命令有个前提就是 ICW4 中的 AEOI 位为 0非自动结束中断时才用。第 43 位的 00 是 OCW2 的标识。L2L0 用来确定优先级的编码这里分两种一种用于 EOI 时表示被中断的优先级别另一种用于优先级循环时指定起始最低的优先级别。 OCW3 用来设定特殊屏蔽方式及查询方式注意OCW3要写入主片的0x20端口或从片的0xA0端口。第 7 位未用到。6 位的 ESMMEnable Special Mask Mode和第 5 位的 SMMSpecial Mask Mode是组合在一起用的用来启用或禁用特殊屏蔽模式。ESMM 是特殊屏蔽模式允许位是个开关。SMM 是特殊屏蔽模式位。只有在启用特殊屏蔽模式时特殊屏蔽模式才有效。也就是若 ESMM 为 0则 SMM 无效。若 ESMM 为 1SMM 为 0表示未工作在特殊屏蔽模式。若 ESMM 和 SMM 都为 1这才正式工作在特殊屏蔽模式下。第 43 位的 01 是 OCW3 的标识8259A 通过这两位判断是哪个控制字。P,Poll command查询命令当 P 为 1 时设置 8259A 为中断查询方式这样就可以通过读取寄存器RR,Read Register读取寄存器命令。它和 RIS 位是配合在一起使用的。当 RR 为 1 时才可以读取寄存器。RIS,Read Interrupt register Select读取中断寄存器选择位顾名思义就是用此位选择待读取的寄存器。有点类似显卡寄存器中的索引的意思。若 RIS 为 1表示选择 ISR 寄存器若 RIS 为 0表示选择 IRR 寄存器。这两个寄存器能否读取前提是 RR 的值为 1。
对于 8259A 的初始化必须最先完成步骤是
无论 8259A 是否级联ICW1 和 ICW2 是必须要有的并且要顺序写入。只有当 ICW1 中的 SNGL 位为 0 时这表示级联级联就需要设置主片和从片这才需要在主片和从片中各写入 ICW3。注意ICW3 的格式在主片和从片中是不同的。只能当 ICW1 中的 IC4 为 1 时才需要写入 ICW4。不过x86 系统 IC4 必须为 1。x86 系统中对于初始化级联 8259A4 个 ICW 都需要初始化单片 8259AICW3不要其余全要。
在以上初始化 8259A 之后才可以用 OCW 对它操作。 //interrupt.c
#include interrupt.h
#include print.h
#include io.h/*中断门描述符结构体*/
struct gate_desc {unsigned short func_offset_low_word;unsigned short selector;unsigned char dcount; //此项为双字计数字段是门描述符中的第4字节。此项固定值不用考虑unsigned char attribute;unsigned short func_offset_high_word;
};static struct gate_desc idt[33]; // idt是中断描述符表,本质上就是个中断门描述符数组
extern int intr_entry_table[33];static void idt_desc_init(void)
{int i;for (i 0; i 33; i) {idt[i].func_offset_low_word ((unsigned int)intr_entry_table[i]) 0x0000FFFF;idt[i].selector0b1000; //代码段选择子idt[i].dcount 0; //未使用idt[i].attribute0b10001110;idt[i].func_offset_high_word ((unsigned int)intr_entry_table[i] 0xFFFF0000) 16;}put_str( idt_desc_init done\n);
} /* 初始化可编程中断控制器8259A */
static void pic_init(void) {/* 初始化主片 */outb (0x20, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.outb (0x21, 0x20); // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.outb (0x21, 0x04); // ICW3: IR2接从片. 对于主片ICW3 中置 1 的那一位对应的 IRQ 接口用于连接从片outb (0x21, 0x01); // ICW4: 8086模式, 正常EOI/* 初始化从片 */outb (0xa0, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.outb (0xa1, 0x28); // ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.outb (0xa1, 0x02); // ICW3: 设置从片连接到主片的IR2引脚outb (0xa1, 0x01); // ICW4: 8086模式, 正常EOI/* 打开主片上IR0,也就是目前只接受时钟产生的中断 */outb (0x21, 0xfe); //OCW1主片outb (0xa1, 0xff); //OCW1 从片put_str( pic_init done\n);
}void idt_init()
{put_str(idt_init start\n);idt_desc_init(); // 初始化中断描述符表pic_init(); // 初始化8259A//第0~15位是表界限即IDT减1可容纳8192个中段描述符第16~47位时IDT的基地址。 /* 加载idt */ unsigned long long int idt_operand ((sizeof(idt) - 1) | (( unsigned long long int)(unsigned int)idt 16));asm volatile(lidt %0 : : m (idt_operand));put_str(idt_init done\n);
}
//kernel.c
#include print.h
#include io.hvoid intr_entry_0()
{put_str(intr_entry_0\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_1()
{put_str(intr_entry_1\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_2()
{put_str(intr_entry_2\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_3()
{put_str(intr_entry_3\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_4()
{put_str(intr_entry_4\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_5()
{put_str(intr_entry_5\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_6()
{put_str(intr_entry_6\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_7()
{put_str(intr_entry_7\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_8()
{put_str(intr_entry_8\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_9()
{put_str(intr_entry_9\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_a()
{put_str(intr_entry_A\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_b()
{put_str(intr_entry_b\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_c()
{put_str(intr_entry_c\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_d()
{put_str(intr_entry_d\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_e()
{put_str(intr_entry_e\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_f()
{put_str(intr_entry_f\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_10()
{put_str(intr_entry_10\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_11()
{put_str(intr_entry_11\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_12()
{put_str(intr_entry_12\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_13()
{put_str(intr_entry_13\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_14()
{put_str(intr_entry_14\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_15()
{put_str(intr_entry_15\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_16()
{put_str(intr_entry_16\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_17()
{put_str(intr_entry_17\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_18()
{put_str(intr_entry_18\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_19()
{put_str(intr_entry_19\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_1a()
{put_str(intr_entry_1a\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_1b()
{put_str(intr_entry_1b\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_1c()
{put_str(intr_entry_1c\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_1d()
{put_str(intr_entry_1d\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_1e()
{put_str(intr_entry_1e\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (addl $4, %esp;iret);
}void intr_entry_1f()
{put_str(intr_entry_1f\n);// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}void intr_entry_20()
{static int i0;put_str(intr_entry_20\n);put_int(i);put_str(\n);i;// 向主片和从片发送中断结束命令EOIoutb(0xA0, 0x20); // 向从片发送EOIoutb(0x20, 0x20); // 向主片发送EOIasm volatile (leave);asm volatile (iret);
}int intr_entry_table[33]{
(int)intr_entry_0,
(int)intr_entry_1,
(int)intr_entry_2,
(int)intr_entry_3,
(int)intr_entry_4,
(int)intr_entry_5,
(int)intr_entry_6,
(int)intr_entry_7,
(int)intr_entry_8,
(int)intr_entry_9,
(int)intr_entry_a,
(int)intr_entry_b,
(int)intr_entry_c,
(int)intr_entry_d,
(int)intr_entry_e,
(int)intr_entry_f,
(int)intr_entry_10,
(int)intr_entry_11,
(int)intr_entry_12,
(int)intr_entry_13,
(int)intr_entry_14,
(int)intr_entry_15,
(int)intr_entry_16,
(int)intr_entry_17,
(int)intr_entry_18,
(int)intr_entry_19,
(int)intr_entry_1a,
(int)intr_entry_1b,
(int)intr_entry_1c,
(int)intr_entry_1d,
(int)intr_entry_1e,
(int)intr_entry_1f,
(int)intr_entry_20};
为什么要加“leave”
用objdump -d kernel.o命令查看反汇编代码可以看出编译器在函数的开头部分增加了保存旧的基址指针、建立新的基址指针以及为局部变量分配空间的代码。所以需要使用leave命令相当于mov esp, ebp pop ebp使堆栈平衡。