• 中断和异常的处理与抢占式多任务


            1.了解保护模式下x86处理器中断和异常中断的工作机制,知道中断和异常中断的分类,认识中断描述符表IDT,中断门,陷阱门。

            2.使本章程序完全在平坦内存模型下工作,进一步加深对分页机制,TLB,虚拟地址空间等概念的理解,了解在平坦内存模型下进行汇编语言程序设计的特点。

            3.使用硬件中断实施抢占式多任务切换,彻底理解任务切换的原理和过程。在这个过程中,学习一种简单的任务调度算法,及单向链表的遍历,节点的追加,插入,删除,移动。

            4.学习宏汇编技术

            5.学习几条新的x86处理器指令,包括lidt,invlpg,bound和ud2等。

    17.1.中断和异常

    17.1.1.中断和异常概述

            中断和异常的作用是指示系统中的某个地方发生了一些事件,需引起处理器(包括正执行中的程序和任务)的注意。中断和异常发生时,典型的结果是迫使处理器将控制从当前正执行的程序或任务转移到另一个例程或任务中去。该例程叫中断处理程序,或者异常处理程序。如果是一个任务,则发生任务切换。

            1.中断

            硬件中断:

            软中断

            外围硬件设备发出的中断信号引发,以请求处理器提供服务。当I/O接口发出中断请求时,会被像8259A和I/O APIC这样的中断控制器收集,并发送到处理器。

            硬件中断完全随机,与处理器的执行并不同步。当中断发生时,处理器要先执行完当前的指令,然后才对中断进行处理。

            软中断是由int n 指令引发的中断处理,n是中断号或类型码。

            2.异常

            异常是内部中断,处理器内部产生。

            处理器执行一条非法指令,或因条件不具备,指令不能正常执行时, 将引发这种类型的中断。

            异常分为三种,

            第一种是程序错误异常,指处理器在执行指令的过程中,检测到了程序中的错误,并由此引发的异常。

            第二种是软件引发的异常。这类异常通常由into,int3和bound指令主动发起。这些指令允许在指令流的当前点上检查实施异常处理的条件是否满足。

            举例,into指令在执行时,将检查EFLAGS寄存器的OF标志位,如满足为"1"的条件,则引发异常。

            第三种是机器检查异常。这种异常是处理器型号相关的,也即,每种处理器都不太一样。无论如何,处理器提供了一种对硬件芯片内部和总线处理进行检查的机制,当检测到有错误时,将引发此类异常。

            根据异常情况的性质和严重性。又分为:

            1.故障

            如处理器执行访问内存指令,发现那个段或页不在内存。

            先进行异常处理,然后再次执行异常指令。

            2.陷阱

            常用于调试,比如单步中断指令int3和溢出检测指令into。

            陷阱中断允许程序或任务在从中断处理过程返回之后,继续进行而不失连续性。

            异常处理,然后执行下一条指令。

            3.终止

            最严重的错误。

            诸如硬件错误,系统表(gdt,ldt)中数据不一致或无效。

            操作系统通常只能把任务从系统中抹去。

            

            当中断和异常发生时,NMI和异常的向量是由处理器自动给出的;硬件中断的向量是由I/O中断控制器芯片送给处理器的;软中断的向量是由指令中的操作数给出的。

            从80486之后开始,处理器内部一般集成了浮点运算部件x87 FPU,不再需要安装独立的数学协处理器,所以,有些和浮点运算有关的异常可能不会产生。wait和fwait指令用于主处理器和浮点处理部件之间的同步,它们应当放在浮点指令之后, 以捕获任何浮点异常。

            从1993的pentium处理器开始,引入了用于加速多媒体处理的多媒体扩展技术,该技术使用单指令多数据执行模式,以便于在64位的寄存器内实施并行的整数计算。

            和SIMD有关的异常从Pentium 3处理器开始引入。

            bound指令用于检查数组的索引是否在边界之内,其格式为

    bound r16, m16

    bound r32, m32

           其具有两个操作数,目的操作数是寄存器,包含了数组的索引;源操作数必须指向内存位置,在那里包含了两个成对出现的字或双字,分别是数组索引的下限和上限。如执行bound指令时,数组的索引小于下标的下限,或大于下标的上限,则产生异常。

            ud2指令从Pentium Pro处理器开始引入,它只有操作码而没操作数,机器代码为0F 0B

    ud2

           执行该指令时,会引发一个无效操作码异常。

            该指令典型地用于软件测试。异常处理后,再次执行触发异常指令。

    17.1.2.中断描述符表,中断门,陷阱门

            实模式下,内存最低端1KB内存,是中断向量表,定义了256种中断的入口地址。包括16位段地址,16位段内偏移量。

            中断发生时,处理器要么自发产生一个中断向量,要么从int n得到,或者从外部的中断控制器得到。

            保护模式下,用中断描述符表。在这个表里,保存的是中断处理过程有关的描述符,包括中断门,陷阱门,任务门。

            中断门和陷阱门描述符只允许存放在IDT内,任务门可位于GDT,LDT和IDT中。

            和实模式下的中断向量表不同,保护模式下的IDT不要求必须位于内存的最低端。

            处理器内部有一个48位的中断描述符表寄存器(IDTR),保存着中断描述符表在内存中的线性基地址和界限。因为一个系统只有一个IDT,故IDTR不需要选择子。

    中断门:

    中断处理过程在目标代码段内的偏移量31~16 P DPL 0 D 1 1 0 0 0 0 不使用(5位) 

    目标代码段描述符选择子(16位) 中断处理过程在目标代码段内的偏移量15~0

    陷阱门:

    中断处理过程在目标代码段内的偏移量31~16 P DPL 0 D 1 1 1 0 0 0 不使用(5位)

    目标代码段描述符选择子(16位) 中断处理过程在目标代码段内的偏移量15~0

            D位为0时,表示16位模式下的门,用于兼容早期的16位保护模式;为1时,表示32位的门。

    中断描述符表寄存器:

    GDTR:

    32位的基地址 16位的表界限

    IDTR:

    32位的基地址 16位的表界限 

            处理器复位时,IDTR的基地址部分为0,界限部分的值为0xFFFF。16位的表界限意味着IDT和GDT,LDT一样,表的大小可以是64KB。但处理器只能识别256种中断,故常只使用2KB,其他空余槽位应将描述符的P位清零。IDT的第一个描述符也是有效的。

    17.1.3.中断和异常处理程序的保护

            当目标代码段描述符的特权级(可用门描述符中的段选择子,从GDT或LDT中找到)低于当前特权级时,不允许将控制转移到中断或异常处理程序。违反此规则将引发常规保护异常。

            不过,中断和异常处理程序的特权级保护也有一些特别之处。

            1.中断和异常的向量中没RPL字段,故当处理器进入中断或异常处理程序,或通过任务门发起任务切换时,不检查RPL。

            2.中断门,陷阱门也有自己的描述符特权级DPL,即门的DPL。但,通常情况下不针对该DPL进行检查,除了用软中断int n和单步中断int 3,及into引发的中断和异常。这种情况下,当前特权级必须高于或者和门特权级DPL相同。

            为了防止用户程序用软中断越界访问。

            对硬件中断,处理的检测的异常不检查门的DPL。

            中断产生时,当处理器将控制转移到中断或异常处理程序时,如处理程序运行在较高的特权级别上,则,将转换栈:

            1.根据处理程序特权级别,从当前任务的TSS取得栈段选择子和栈指针。处理器把旧的栈的选择子,栈指针压入新栈。

            2.处理器把EFLAGS,CS和EIP压入新栈

            3.对有错误代码的异常,处理器还要把错误代码压入新栈。

            4.如中断处理程序的特权级别和当前特权级别一致,则不用转换栈。

            5.处理器把EFLAGS,CS和EIP的当前状态压入当前栈。

            6.对有错误代码的异常,处理器还要把错误代码压入当前栈,紧挨着EIP之后。

            中断门和陷阱门区别不大,通过中断门进入中断处理程序时,EFLAGS寄存器的IF位被处理器自动清零,以禁止嵌套的中断,当中断返回时,将从栈中恢复EFLAGS寄存器的原始状态。

            陷阱中断的优先级较低,当通过陷阱门进入中断处理程序时,EFLAGS寄存器的IF位不变,以允许其他中断优先处理。

            EFLAGS寄存器的IF位仅影响硬件中断,对NMI,异常,int n形式的软件中断不起作用。

    17.1.4.中断任务

            中断和异常发生时,如根据中断向量从IDT中找到的描述符是任务门,则不是进行一般的中断处理过程,而是发起任务切换。

            当双重故障发生时,执行任务切换,切换到内核任务中去,从容地把发生故障的任务从系统中抹去,回收内存空间,并重新调度其他任务的执行,会是最好的解决办法。

            1.被中断的那个程序或任务的整个执行环境可被完整保存下来(保存到它的TSS)

            2.由于接管控制的是新任务,可用一个全新的0特权级栈,可有效防止当前任务0特权级栈遭破坏使得系统崩溃

            3.切换到的新任务有独立地址空间。

            利用中断发起任务切换,耗时。

            因中断和异常而发起任务切换时,不再保存CS,EIP。在任务切换完成后,处理器要把错误代码压入新任务的栈。

            任务是不可重入的。在进入中断任务后和执行iretd之前,必须关中断。

            作为对任务门的保护,和中断门,陷阱门一样,只对通过int 3, int n和into指令发起的任务切换实施特权级检查。

            其他异常,硬件中断下,不检查任务门的特权级。任务切换时不检查目标代码段特权级。

    17.1.5.错误代码

            有些异常产生时,处理器会在异常处理程序或中断任务的栈中压入一个错误代码。通常,这意味着异常和特定的段选择子或中断向量有关。

    保留(16位) 段选择子的索引部分 TI IDT EXT 

            EXT意思是异常由外部事件引发。此位置位时,表示异常由NMI,硬件中断等引发。

            IDT用于指示描述符的位置。为"1"时,表示段选择子的索引部分指向中断描述符表的;为0时,表示段选择子的索引部分指向GDT或LDT。

            TI位仅在IDT位是0下才有意义。是0表示段选择子索引部分指向GDT,否则,LDT

    17.2.本章代码清单

    17.3.内核的加载和初始化

    17.3.1.彻底终结多段模型

            为了使程序在平坦模型下方便地引用内存地址,定义了段mbr,并要求段的虚拟地址从0x00007c00开始。

            在GDT中安装最基本的两个段描述符,一个是4GB的代码段,另一个是4GB的数据段。段的基地址都是0x00000000,段界限都是0xFFFFF,粒度为4KB。

            内核拥有自己独立的2GB内存空间,且还要被映射到每个任务的高2GB.完了完成这种映射,最简单的做法是在内核代码中做手脚,让所有对内存地址的引用(主要是对标号的引用)都从0x80000000开始。

            本章内核工作在平坦模型下,不可能在依赖段基地址实施重定位。为了使它能正常工作,需用vstart子句向编译器声明它的虚拟地址。不过,vstart子句给出的虚拟地址必须和程序运行时的虚拟地址相同。

            内核占据着物理内存的低端1MB,其核心部分的加载地址是0x00040000。在开启页功能后,内核需将自己映射到从虚拟地址0x80000000开始的高端。

            很自然的,内核在高端的虚拟地址就是0x80040000了。

            一旦知道了内核运行时的虚拟地址,内核代码编译时,vstart子句的虚拟地址也需与之相同。

            内核运行时,不管是执行内核中的指令,还是访问内核中的数据,段部件所发出的线性地址都会高于0x80040000,正好位于每个任务虚拟地址空间的高端;而经页部件转换后,得到的物理地址又变成原始的真实地址0x00040000。

            创建内核的页目录表和页表,并初始化必要的目录项和页表项。页目录表的物理地址是0x00020000,先令最后一个页目录表指向页目录表自己。接着,在页目录表内创建两个目录项,分别对应两个不同的起始线性地址0x00000000和0x80000000。但实际上,它们指向同一个页表。其中,前一个目录项只在开启页功能时候使用,作为临时过渡。

            页表的物理地址是0x00021000,它的前256个表项必须一一对应于物理内存最低端的256个页,这是内核正常工作的基本要求。

            将内核页目录表的物理地址传送到控制寄存器CR3。

            将全局描述符表映射到虚拟内存的高端。

            内核已经从硬盘加载完毕,页目录表和页表也已经创建。 开启分页。

            关于在分页模式下工作,所有该做的工作都做了,但还是忽略了一个问题,那就是内核栈,应当将它映射到虚拟内存的高端。

            将控制转移到内核。平坦模型下,所有东西都在一个大的4GB段内,从一个地方转移到另一个位置去执行,自然是段内转移了。

            事实上,这条指令有两个功能,一个是转到内核去执行,二是将处理器的执行流转移到虚拟内存的高端。内核已经从硬盘上加载,加载的位置是线性地址0x80040000.

            当这条jmp指令执行时,处理器要先访问当前代码段,从线性地址0x80040004处取得一个32位的段内偏移量,传送到EIP寄存器。内核就开始执行了。

    17.3.2.创建中断描述符表

            准备保护模式下的中断系统。

            进入保护模式前,已经用cli关掉了外部硬件中断。

            只有在创建了中断描述符表,并安装了中断处理程序后,才能使用sti指令开放硬件中断,并享受中断的好处。

            我们把---next 

  • 相关阅读:
    【云原生】Kubeadmin部署Kubernetes集群
    自己对 RepVGG 的一点理解
    【ACM算法竞赛日常训练】DAY10题解与分析【月月给华华出题】【华华给月月出题】| 筛法 | 欧拉函数 | 数论
    docker+nginx+redis部署前后端分离项目!!!
    【ROOTFS】1-构建rootfs与nfs调试
    21、设计模式之备忘录模式(Memento)
    测试工作管理与规范
    MySQL(7) Innodb 原理和日志
    洛谷 P3370 【模板】字符串哈希题解
    正则验证用户名和跨域postmessage
  • 原文地址:https://blog.csdn.net/x13262608581/article/details/126131979