程序执行过程中出现了页访问异常,中央处理器会做哪些事情?

1,异常处理的底层机制简述:

  首先,对于大部分处理器来说,异常,中断以及系统调用的处理都是同一种硬件机制,尤其是X86_32处理器————尽管异常,中断和系统调用触发源头都是不同的:中断是外周硬件异步触发的,在CPU外发生;系统调用和异常是CPU片上执行的软件指令流同步产生的,在CPU内发生。而硬件异步产生的中断用于外设硬件引起操作系统注意,以便驱动操作系统调取相关逻辑处理外设事件,可以说中断是CPU与外围硬件交互的重要方式,中断是驱动操作系统的重要方式

   我们将研究范围局限在在IA_32中,因为其它CPU虽然有着不同的指令集架构,但在这些方面保持着最低限度的相同。IA_32使用int 0x80 指令来向内核发起系统调用,同样的,异常也是使用int n的指令,只不过不同的中断号。int n指令的含义是向系统发起第n号中断,int指令用于将进程从用户态切换至内核态(如果是用户进程的话),切换过程内容包含:切换用户栈到内核栈,使处理器自动保存一些寄存器到新的内核栈上形成一个寄存器集合,寄存器集合包含切换前原用户栈的SS,ESP,切换前CPU的状态寄存器EFLAGS,切换前所运行程序的CS,EIP。接下来则由位于IDT表中对应位置的函数指针所指向的入口函数(汇编,一般命名为entry.S)接手,进一步保存CPU当前寄存器状态到内核栈上,在计算环境在入口函数的帮助下一切准备妥当后,这时,真正的中断处理函数才会登场————入口函数在最后一句指令中使用call汇编指令唤起一个C例程————即系统调用处理函数。

  所以,当发生页访问异常时,处理过程就是触发中断的一般流程

2,页表访问异常的原因简述:

      以Linux(IA_32)为例,在程序实际访问某个虚拟内存空间内的数据之前(无论是读访问/写访问),虚拟地址和物理内存的关联不会建立,我猜这里是出于稀疏页表的关系(减少地址转换机制的存储负担,故使用懒加载)。所以一个进程访问的虚拟地址空间尚且没有一个物理页帧尚未关联,处理器会自动触发一个缺页异常(一个CPU片上中断)。必须由内核处理这个异常。

9e6a460557e84150a4b4889e83a4415c.png

     这个处理过程(是一个C例程)是内存管理中最复杂的部分之一,必须考虑大量的细节,具体体现在这个C例程中存在着大量分支与判断。具体所以我们在这篇文章中分析几个最简单的情况,因为本文主要侧重说明异常,系统调用与中断共享同一个机器机制。

3,发生页访问异常的处理流程:(与中断,系统调用共享处理机制)

 无论和CPU片上正在执行的指令流是同步还是异步,中断源来自于CPU内部还是CPU外部,IA 32 的CPU只有一种机器机制处理所有情况。

  1. 异常发生,此时并不陷入内核,而是执行一条复杂的,有着多个CPU Action(CPU动作)的CPU指令 INT N:

          a,寻找中断处理函数的过程在这里完成:在X86中CPU寄存器IDTR(指示着中断处理表的起始地址)与偏移组合直接计算中断程序地址并fetch中断处理表中表项的内容,。

          b,鉴权。在x86中段描述符代表着某一个被访问对象(代码或是数据),而这个对象的访问所需权限在段描述符中描述,顾名思义段描述符,是这个对象的描述,包含了对象基址,对象的DPL等。所以先通过中断向量表取得对应的中断描述符,再获得段选择子,然后从GDT获得段描述符,段描述符中包含的DPL与CPU当前CS寄存器中的CPL域,判断当前的程序是否有足够的权限唤起此段代码。描述符就是代表着某种对象。具体过程为将当前CPU CS寄存器中的CPL域与目标段中的DPL域进行比较,鉴权不通过产生一个通用保护异常。

         c,鉴权通过,为内核处理函数 创造计算环境(主要是准备内核栈,安全性考虑),将CPU当前SS与ESP保存至CPU内部,从由TR寄存器指示的,在内存中存储的当前进程的TSS数据结构(104字节)中加载操作系统分配的内核栈的SS值与ESP值到CPU中SS寄存器与ESP寄存器,因此你可以明白:栈切换的本质就是更换CPU寄存器的值,以此来切换CPU的计算环境。

dd9d6588f6784f5896eabf68179fc707.png

       d,在新的堆栈(内核堆栈)中保存当前SS,ESP,EFLAG(当前CPU计算的执行状态),CS,EIP值。

          e,清除EFLAG中某些位,以控制是否屏蔽中断。(具体由门类型决定)

813173b11d674504871eadfa0c4ef507.png

5e2c0ee6b69c4c6d89716325254b05ec.png

       f,从中断描述符中选择子对应的段描述符中加载CS与EIP到CPU寄存器中。(光IDT不够,IDT需要通过GDT找出对应中断处理函数的所有信息)

2,至此 INT指令结束,INT已经成功设置CS与EIP以指示新的执行序列,并且成功在内核上创造了一个栈帧trapframe,将一些基本的寄存器push其中。接下来仍然需要一个汇编过程继续寄存器的保存过程(所以到这里为止也是机器相关的),将当前CPU寄存器的值暂存到栈上。一方面为了从执行结束后“无痕的”返回和恢复防止内核代码在CPU中执行时破坏污染CPU寄存器值(如果把进程的寄存器值没有保存的话),另一方面,为了允许内核函数可以访问到足够的信息。使用汇编指令call 调用C过例程,C例程可以通过操作栈来访问必要的信息与参数。

3,进入C过程(一个与硬件平台无关的部分)。奇妙的是,这里的缺页异常C例程与系统调用服务的C例程结构上都是很相似的:都是通过栈操作来获取必要的信息,内部结构都是充满了大量的分支,例程本身实际上扮演的是一个中枢的角色,用于派发(dispatch)请求:

       a,系统调用服务通过寄存器%eax中存储的调用号,来决定执行哪个分支,不同的系统调用实际上就是不同的执行分支,或是通过中断向量表进行下标索引。

      b,缺页异常通过entry.S或是处理器向内核栈中push的errorcode 来决定处理分支,有2的5次方种情况:

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值