兰陵月 发表于 2017-12-11 11:43:39

X86汇编语言-从实模式到保护模式—笔记(40)-第14章 任务和特权级保护(6)

本帖最后由 兰陵月 于 2017-12-15 12:20 编辑

14.3内核程序的初始化
本章没有主引导程序,继续使用上一章的主引导程序。回顾一下主引导程序的过程。一、[第9行~第55行]首先是进入保护模式前,为自己运行保护模式,做相关准备工作。第9~11行,初始化主引导程序16位模式下使用的栈段为SS:SP=0000:7C00。第14~17行,在实模式下分解32位的GDT物理地址,分解后EAX中为商,也就是段地址,EDX中为余数,也就是偏移地址。第19~20行,将EAX中段地址的值给寄存器DS,将EDX中偏移地址的值给寄存器EBX。这样DS:EBX指向GDT的起始处。第22~37行,创建主引导程序转入保护模式后使用的各个段描述符。分别是1#描述符(基地址为0,段界限为0xFFFFF,粒度为4KB的向上扩展的数据段)、2#描述符(基地址为0x00007C00,段界限为0x1FF,粒度为字节的向上扩展的代码段)、3#描述符(保护模式下使用的堆栈段,基地址为0x00007C00,段界限为0xFFFFE,粒度为4KB的向下扩展的堆栈段)、4#描述符(保护模式下的显示缓冲区描述符,基地址0x000B8000,段界限0x07FFF,粒度为字节的向上扩展的数据段)。第40行,将段界限值39写入PGDT处,第42行,加载GDT表,使前面的设置生效。第44~46行,打开A20。第48行,清中断。第50~52行,置CR0的PE位。二、[第59行~第93行]进入保护模式,加载内核程序到内存指定目标地址处。第59~60行,切换DS指向0-4GB数据段。第62~64行,切换SS到保护模式下的堆栈段,并将ESP清零。第67~93行,将内核程序完整地从硬盘上加载到内存中目标位置处。内核加载后,在内存中的基地址0x00040000。三、[第99行~第135行]按照标准,为内核程序添加创建段描述符,并重新加载GDTR,使修改生效。第99~107行,为内核创建公共例程段描述符,并将其添加到5#槽位;第110~118行,为内核数据段创建段描述符,并将其添加到6#槽位;第121~129行,位内核代码段创建段描述符,并将其添加到7#槽位。第131行,修改GDT的界限值为63;第133行,重新加载GDTR,使修改生效;第135行,跳转到内核程序第775行,标号start处执行。
当程序执行到内核程序第775行时,主引导程序已经加载了内核,并对它进行了前期的初始化工作。因为加载的是内核程序,而内核应当工作在0特权级,所以主引导程序在初始化内核时所创建的描述符,其目标特权级DPL都为0,下图图14-009是当前GDT的布局。
这些描述符所指向的段,有的是代码段,有的是数据段。如果是数据段,则只有内核自己才能访问,因为其描述符的DPL是0,低特权级别的程序访问这些段时,会被阻止以防出现安全问题;如果是代码段,则通常只有0特权级的程序才能将控制转移到该段,也就是说,只能从内核其他正在执行的部分转移到该段执行,因为他们的特权级相同。第776~777行,切换DS指向内核的数据段;第779~780行,在屏幕上显示一个欢迎信息;第783~809行,在屏幕上显示处理器的相关信息。以上基本上第13章的程序一样,不再做重复说明。
【14.3.1 调用门】在上一章,内核赋予用户程序的特权级别是0,所以用户程序是在0特权级上运行的。也正是因为如此,当用户程序通过U-SALT表中的符号地址直接调用内核例程时,才会通过特权级检查。在本章中,内核也做同样的工作。不同之处在于,它将用户程序的特权级定为3,也就是最低特权级别。没有人愿意将自己的程序放在特权级3上,但系统核心一定会将它放在特权级3上。尽管保护模式非常复杂,但这并没有加重用户程序(应用程序)编写者(程序员)的负担,因为他们不必考虑底层的很多东西,这也是为什么本章没有提供用户程序代码清单的原因。事实上,本章将继续沿用第13章的用户程序,只不过要作为一个任务进行加载,加载的方法和上一章是不同的。而且,运行时的特权级别是3,不再是上一章中的0。为了方便应用程序的编写,内核通常要提供大量的例程供它们调用。例如,在第13章中,用户程序可以调用内核例程@PrintString和@ReadDiskData。为此,用户程序需要定义SALT表,并在表中填写例程的符号名。之后,再由内核将符号名转换成入口地址,也就是该例程所对应的段选择子和段内偏移量。例程是由内核提供的,它们的特权级通常就是内核的特权级。在上一章里,内核程序和用户程序都运行在0特权级,而且都是普通的段间控制转移,所以,在用户程序直接调用内核例程,这不会有任何问题。但在本章中,用户程序运行时的特权级别将会是3.由于处理器禁止将控制从特权级低的程序转移到特权级高的程序,因此,如果还像以前那样直接调用内核例程,百分之百不会成功,一定会引发处理器异常中断。但是,现实的需求也不能不予考虑,任何操作系统都应当提供大量的功能调用服务。为此,需要安装调用门。调用门(Call-Gate)用于在不同特权级的程序之间进行控制转移。本质上,它只是一个描述符,一个不同于代码段和数据段的描述符,可以安装在GDT或者LDT中。该描述符的格式如图14-010所示,下面是低32位,上面是高32位。
如上图,调用门描述符给出了例程所在代码段的选择子,而不是32位线性地址。有了段选择子,就能访问描述符表得到代码段的基地址,这样做无非是间接了一点,但却可以在通过调用门进行控制转移时,实施代码段描述符有效性、段界限和特权级的检查。例程在代码段中的偏移量也是在描述符中直接指定的,只是被分成了两个16位的部分。很显然,在通过调用门调用例程时,不使用指令中给出的偏移量。描述符中的TYPE字段用于标识门的类型,共4比特,值“1100”标识调用门。P位是有效位,通常应该是“1”。当它为“0”时,调用这样的门会导致处理器产生异常中断。对于操作系统来说,这个机关可能会很有用。比如,为了统计调用门的使用频率,可以将它置“0”。然后,每当因调用该门而产生异常中断时,它们属于故障中断,从中断处理过程返回时,处理器还会重新执行引起故障的指令。此时,因P已经为“1”,所以可以执行。就当前的例子而言,因为在提供调用门服务的同时,还要统计门的调用次数,故,可以在该调用门所对应的例程中将P位清零。这样,下一次该门被调用时,又会重复以上过程。通过调用门实施特权级之间的控制转移时,可以使用jmp far指令,也可以使用call far指令。如果是后者,会改变当前特权级CPL。因为栈段的特权级必须同当前特权级保持一致,因此,还要切换栈,即,从低特权级的栈切换到高特权级的栈。比如,一个特权级为3的程序必须使用自己的3特权级栈工作。当它通过调用门进入0特权级的代码段执行时,当前特权级由3变为0。此时,栈也要跟着切换,从3特权级的栈切换到0特权级的栈。这主要是为了防止因栈空间不足而产生不可预料的问题,同时也是为了防止栈数据的交叉引用。为了切换栈,每个任务除了自己固有的栈之外,还必须额外定义几套栈,具体数量取决于任务的特权级别。0特权级任务不需要额外的栈,它自己固有的栈就足够使用,因为除了调用返回外,不可能将控制转移到低特权级的段;1特权级的任务需要额外定义一个描述符特权级DPL为0的栈,以便将控制转移到0特权级时使用;2特权级的任务则需要额外定义两个栈,描述符特权级DPL分别是0和1,在控制转移到0特权级和1特权级时使用;3特权级的任务最多额外定义3个栈,描述符特权级分别是0、1和2,在控制转移到0、1和2特权级时使用。这些额外创建的栈,其描述符位于任务自己的LDT。同时,还要在任务的TSS中登记,原因是,栈切换是由处理器固件自动完成的,处理器需要根据TSS中的信息来完成这一过程。图14-001中,在TSS内,从偏移4~24处登记有特权级0到2的栈段选择子,以及相应的ESP初始值。任务自己固有的栈信息则位于偏移量为56(ESP)和80(SS)的地方。任务寄存器TR总是指向当前任务的任务状态段TSS,其内容为该TSS的基地址和界限。在切换栈时,处理器可以用TR找到当前的TSS,并从TSS中获取新栈的信息。通过调用门使用高特权级的例程服务时,调用者会传递一些参数给例程。如果是通过寄存器传送,这没有什么可说的,不过,要传递的参数很多时,更经常的做法是通过栈进行。调用者把参数压入栈,例程从栈中取出参数。在高级语言里,这是一贯的做法。例程需要什么参数,先压入哪个参数,后压入哪个参数,这是调用者和例程之间的约定,调用者是清楚的。否则,它不会调用这个例程。但是,这一切对于处理器来说是懵懂的。特别是,当栈切换时,参数还在旧栈中。为了使例程能获得参数,必须将参数从旧栈复制到新栈中。参数的复制工作是由处理器固件完成的,但它必须事先知道参数的个数,并根据该数量决定复制多少内容。所以,调用门描述符中还有一个参数个数字段,共5比特。就是说,至多允许传送31个参数。栈切换前,段寄存器SS指向的是旧栈,ESP指向旧栈的栈顶,即最后一个被压入的过程参数;栈切换后,处理器自动替换SS和ESP寄存器的内容,使它们分别为新栈的选择子和新栈的栈顶(最后一个被复制的参数)。这一切,对程序的编写者来说是透明的。所谓“透明”就是说,程序员不用关心栈的切换和参数的复制,他即使不知道还有栈切换这回事,也不会影响程序编写工作。因为,在栈切换前,pop edx,可以得到最后一个被压入的参数,在栈切换后,这条指令同样可以得到那个参数,尽管栈段和栈顶指针已经改变。调用门描述符中的DPL和目标代码段描述符的DPL用于决定哪些特权级的程序可以访问此门。具体的规则是必须同时符合以下两个条件才行:(一)当前特权级CPL和请求特权级RPL高于,或者和调用门描述符特权级DPL相同。即,在数值上CPL≤调用门描述符的DPLRPL≤调用门描述符的DPL(二)当前特权级CPL低于,或者和目标代码段描述符特权级DPL相同。即,在数值上CPL≥目标代码段描述符的DPL举个例子,如果调用门描述符的DPL为2,那么,只有特权级0、1和2的程序才允许使用该调用门,特权级为3的程序使用此门将引发处理器异常中断。如下图图14-011所示,调用门的DPL是特权级检查的下限。除此之外,目标代码段的特权级也是需要考虑的因素。调用门描述符中有目标代码段的选择子,它指向目标代码段的描述符。当一个程序通过调用门转移控制时,处理器还要检查目标代码段描述符的DPL,该DPL决定调用门特权级检查的上限。也就是说,只有那些特权级低于或者等于目标代码段DPL的程序才允许使用此门。                                 
调用门描述符中有一些字段没有使用,固定为“0”。
页: [1]
查看完整版本: X86汇编语言-从实模式到保护模式—笔记(40)-第14章 任务和特权级保护(6)