zoukankan      html  css  js  c++  java
  • ASM:《X86汇编语言-从实模式到保护模式》第14章:保护模式下的特权保护和任务概述

    ★PART1:32位保护模式下任务的隔离和特权级保护

       这一章是全书的重点之一,这一张必须要理解特权级(包括CPL,RPL和DPL的含义)是什么,调用门的使用,还有LDT和TSS的工作原理(15章着重讲TSS如何进行任务切换)。

    1. 任务,任务的LDT和TSS

      程序是记录在载体上的指令和数据,其正在执行的一个副本,叫做任务(Task)。如果一个程序多个副本正在内存中运行,那么他对应多个任务,每一个副本都是一个任务。为了有效地在任务之间进行隔离,处理器建议每个任务都应该具有他自己的描述符表,称为局部描述符表LDT(Local Descriptor Table)。LDT和GDT一样也是用来储存描述符的,但是LDT是只属于某个任务的。每个任务是有的段,都应该在LDT中进行描述,和GDT不同的是,LDT的0位也是有效的,也可以使用。

      LDT可以有很多个(有多少个任务就有多少个LDT),处理器使用局部描述符寄存器(LDT Register: LDTR)。在一个多任务的系统中,会有很多任务在轮流执行,正在执行中的那个任务,称为当前任务(Current Task)。因为LDTR只有一个,所以他用于指向当前任务的LDT,当发生任务切换(会在15章讲),LDTR会被自动更新成新的任务的LDT,和GDTR一样,LDTR包含了32位线性基地址字段和16位段界限。以指示当前LDT的位置和大小。如果要访问LDT中的一个描述符,和访问GDT的时候是差不多的,也是要向段寄存器传输一个16位的段选择子,只是和指向GDT的选择子不同,指向LDT的选择子的TI位是1。

      因为索引号只能是13位的,所以每个LDT所能容纳的描述符个数为213,也就是8192个。又因为每个描述符是8个字节,所以LDT最大长度是64KB。

      同时,为了保存任务的状态,并且在下次重新执行的时候恢复他们,每个任务都应该用一个额外的内存区域保存相关信息,这就叫做任务状态段(Task State Segment: TSS)。如图:

        

      任务状态段TSS是具有上图的固定格式的,最小尺寸是104(这就是为什么图上的I/O映射基地址只有16位的原因,其实是可以32位的)。图中标注的偏移量都是10进制的。和LDT一样,处理器用TR(Task Register: TR)寄存器来指向当前的任务TSS。TR也是只有一个。当任务进行切换的时候,处理器将当前任务的现场信息保存到TR指向的TSS中,然后,再使TR寄存器指向新的任务TSS,并从新任务的TSS中恢复现场。

    2. 任务的全局空间和局部空间

           每个任务都包含两个部分:全局部分和私有部分。全局部分是所有任务共有的,含有操作系统的软件和库程序,以及可以调用的系统服务和数据。私有部分则是每个任务自己的数据和代码,与任务要解决的具体问题有关,彼此各不相同。每个任务的LDT可以登记8192个段,GDT可以登记8191个段(0不能用),这样的话每个用户程序可以有64TB的总空间。在操作系统中,允许程序使用逻辑地址来访问内存,而不是实际地址,所以这64TB内存是虚拟地址空间(要开启页功能,16章讲)(全局地址空间可以有32TB,一个任务的局部空间为32TB,也就是一个任务的总空间可以是64TB,但是操作系统允许程序的编写者使用虚拟地址(逻辑地址)来访问内存。同一块内存,可以让多任务,或者是每个任务的不同段来使用。当执行或者访问一个新的段的时候,如果它不在物理内存中,而且也没有空闲的物理内存来加载它的时候,操作系统会挑出一个暂时不用的段,把它换到磁盘中,并把空间腾出来分配给马上要访问的段。并修改段描述符,使之指向这一段内存空间。当要使用这个段的时候再把段置换回物理内存中。)操作系统本身要进行虚拟内存管理。

    3. 任务和特权级保护,调用门

      X86架构下,Intel引进了4个特权级,分别是0-3,权限从0到3逐次递减。操作系统处于0特权级,系统服务程序一般在0-2特权级,普通的应用程序一般在3特权级。这里要特别注意的是:特权级不是指的任务的特权级,而是指的组成任务的各个部分的特权级。比如任务的全局部分一般是0,1和2特权级别的,任务的私有部分一般是3特权级的。

           处理器给每个可管理的对象都赋予一个特权级,以决定谁能访问他,确保各种操作的相对安全性。比如系统的一些敏感指令(如hlt,对控制寄存器的读写指令,lgdt,ltr等),必须通过具有0特权级的对象来操作。除了敏感指令,I/O端口的读写操作也是通过特权管理来进行的,这里所说的特权管理,通常是指的I/O端口访问许可权。由EFLAGS中的13位和12位决定(I/O Privilege Level:  IOPL),它代表着当前任务的I/O特权级别,I/O端口的访问权限控制等下再讲。

           接下来我们要分清楚DPL,RPL和CPL之间的联系。

    每个在GDT或者在LDT中的描述符,都有一个DPL位,这就是这个描述符所指的段的特权级(又叫做描述符特权级Descriptor Privilege Level: DPL)。

    每个段的选择子的0和1位是一个RPL(Request Privilege Level: RPL)位,对应着当前操作请求的特权级。

    当处理器正在一个代码段中取指令和执行指令时,那个代码段的特权级是当前特权级(Current Privilege Level: CPL)。正在执行的这个代码段的选择子位于段寄存器CS中,其最低两位就是当前特权级的值。

           对于数据段,如果一个数据段,其描述符的DPL位2,那么只有特权级为0,1和2的程序才能访问他,如果特权级为3的程序访问这个数据段,那么处理器会阻止并引发异常中断。也就是在数值上要有:

    CPL<=DPL(数值上比较,目标数据段DPL)

    RPL<=DPL(数值上比较,目标数据段DPL)

           对于代码段,处理器对代码段的检查是非常严格的,一般控制转移只允许发生在两个特权级相同的代码段之间。但是处理器允许通过将权限转移到依从的代码段或者通过调用门将当前权限变高。但是除了通过门切换或者从门返回,处理器不允许从特权级高的代码段转移到特权级低的代码段(可以理解为处理器不相信可靠性低的代码)。

           如果当前程序想通过直接转移从特权级低的代码段到依从的特权级高的代码段,则必须满足:

    CPL>=DPL(数值上比较,目标代码段DPL,代码段必须是依从的代码段)

    RPL>=DPL(数值上比较,目标代码段DPL,代码段必须是依从的代码段)

           程序还可以通过门来进行代码段的转移,14章讲的是调用门切换,但是总感觉讲的好啰嗦。说白了调用门其实也是一个描述符。如下图:

      通常一些内核例程都是特权0级的(特别是那些需要访问硬盘的程序),所以调用门可以给用户程序便捷的调用例程的手段(用户程序在特权3级,用户程序只要知道例程函数名就可以了,不需要知道例程实现细节,而且可以做一些特权3级做不到的东西)。

      调用门描述符给出了例程所在的代码段的选择子,通过这个选择子就可以访问到描述符表相应的代码段,然后通过门调用实施代码段描述符的有效性,段界限和特权级的检查。例程开始偏移是直接在调用门描述符中指定的。

    描述符的TYPE位用于表示门的类型,‘1100’表示的是调用门(这里注意S位,门的S位固定是0,说明这个是一个系统段,当检查了S为为0后处理器会继续检查TYPE为看是什么系统段(门或者LDT描述符,TSS描述符等))。

      描述符P位是有效位,正常来讲应该是‘1’,当它是0的时候,调用这样的门,会导致处理器产生中断。但是P=0这对于处理器来说是属于故障中断,从中断处理过程返回时,处理器还会重新执行引起故障的指令。对于操作系统来说,可以利用这个特性来统计门的调用次数,在中断程序中,每当某个门调用失败,就把该门的调用次数+1。

      通过调用门实施特权级之间控制转移时,可以使用jmp far指令,也可以使用call far指令,这两种方式的调用有差别。具体看下表:

      如果使用了call执行调用门,则可能会改变CPL,因为栈段的特权级必须同当前的特权级保持一致,因此执行调用门时还需要对栈进行切换。也就是从低特权级的栈转移到高特权级的栈,比如一个特权级为3的程序通过调用门执行特权级为0的代码段,则栈段要从特权级3转移到特权级0。这是为了防止因为栈空间不足而产生不可预料的问题,同时也是为了防止数据的交叉引用。为了切换栈,每个任务除了自己固有的栈,还必须额外定义几个栈,具体数量去决定于当前任务的特权级,要补充比当前特权级高的栈段。这些额外创建的栈段必须放在任务自己的LDT中。同时,还要在TSS中进行登记。

      通过调用门使用高级权限的例程时,调用者会传递一些参数给例程。一般的方法是通过栈段来传递,但是因为切换栈段后栈段的指针会被初始化为一个固定的值,也就是如果你不说,处理器其实并不知道你通过栈传递了多少参数给例程,这个时候需要在调用门上说明传递了多少个参数,而参数的复制是通过处理器固件完成的,然后根据参数个数来把调用者栈中的数据复制到切换的栈段中去(程序员不需要管栈究竟切换到哪了,只要知道某个被传递的参数在调用门之前是什么时候压入栈的,用push和pop指令可以像没有经过切换栈一样得到传递那个参数,尽管栈指针和栈段已经改变)。(这个非常重要,课后习题第二题有体现)

      用call far指令通过调用门转移控制时,如果改变当前特权级,则完整的切换栈段的过程书上讲的很明白了,如下:

      S1:使用目标代码段的DPL到当前任务的TSS中选择一个栈,包括栈段的选择子和栈指针。

      S2:从TSS中独缺所选择的段的选择子和栈指针,并用该选择子和栈指针,并用该选择子读取栈段描述符,在此期间,任何违反段界限的行为都会引起处理器引发异常中断(无效TSS)。

      S3:检查栈段描述符的特权级和类型,并可能引发处理器异常中断(无效TSS)。

      S4:临时保存当前栈段寄存器SS和栈指针ESP的内容。

      S5:把新的栈段选择子和栈指针带入SS和ESP寄存器,切换到新栈。

      S6:将刚才临时保存的SS和ESP内容压入当前栈

      S7:依据调用门描述符“参数个数”字段的指示,从旧栈中所有的参数都复制到新栈中,如果参数为0则不复制。

      S8:将当前段寄存器CS和指针寄存器EIP压入新栈(因为是远调用)。

     

      S9:从调用门描述符中依次将目标代码选择子和段内偏移传送到CS和ESP寄存器,开始执行调用过程。

      注:如果不进行段切换,那么SS不会变,直接压入CS和EIP就好

      使用jmp far来通过调用门转移控制,则没有特权级的变化,所以也不会进行栈段的切换,而且不能使用retf来返回控制到调用者。

      使用call far来通过调用门转移控制,可以使用retf来将控制返回,retf或者ret指令都是可以带参数的,带参数的返回其实就是高级语言的_stdcall的原型。具体可以看这篇文章:

    http://www.cnblogs.com/Philip-Tell-Truth/articles/5294369.html

      带参数的返回是用于解决通过栈传递参数的过程最后维持栈平衡的问题,过程编写者知道进入过程前栈中有多少个元素,如果需要把这些元素在结束过程后弹出,则可以使用ret imm16或者retf imm16,这两条指令都带16位立即数为操作数,前者为近返回,后者为远返回,而且数值总是为2的倍数或者4的倍数(使用SP则位2的倍数,使用ESP则为4的倍数),代表将控制返回调用者之前,应当从栈中弹出多少字节的数据。假如弹出的数值为k,则过程结束后会执行ESP<-ESP+k,相当于弹出了k/4个参数。

          

      要求特权级变化的远返回,只能返回到较低特权级别上。控制返回的全部过程如下:

      S1:检查占中保存的CS寄存器的内容,根据其RPL字段决定返回时是否需要改变特权级别。

      S2:从当前栈中读取CS和EIP的内容,并针对代码段描述符和代码段选择子的RPL段进行特权检查。从同一特权级返回时,处理器任然会进行一次特权检查。

      S3:如果远返回指令是带参数的,则将参数和ESP寄存器的当前值相加,以跳过栈中的参数部分。最后的结果是ESP寄存器指向调用者SS和ESP的压栈值。注意,retf指令的字节计数值必须等于调用门的参数个数乘以参数长度。最后恢复调用者调用过程前(包括压入参数)的栈。

      S4:如果返回时需要改变特权级,则从栈中将SS和ESP的压栈值带入段寄存器SS和指令指针寄存器ESP,切换到调用者的栈,在此期间,一旦检测到有任何界限违反情况都会引发处理器异常中断。

      S5:如果返回时需要改变特权级,检查DS,ES,FS和GS寄存器的内容,根据他们找到相应的段描述符,要是有任何一个段描述符的DPL高于调用者的特权级(返回时新的CPL,数值上段描述符DPL<返回后的CPL),处理器将会把数值0传入该段寄存器(因为数据段的特权级检查只会在把段寄存器的选择子传入段寄存器时检查的,在这之后任何访问数据段指向的内存都不会进行特权级检查,如果当前的数据段特权级高于调用者,不在返回的时候把0传给段寄存器而任然不变的话,那么调用者将有权利使用高特权级的数据段,可能会引发不安全的操作)。

           特别需要注意的是,TSS中的SS0,EP0,SS1,EP1,SS2和EP2域都是静态的,除非软件进行修改,否则处理器不会改变他们。

           好了讲了那么多,我们来看下如何创建一个调用门

    首先符号表每个项下面要加多一个东西,就是参数个数(教材上没有写这个东西,默认参数个数为0)。

      这就是安装门的过程,其实和安装GDT描述符差不多(调用门一般为公用例程,所以安装在GDT上),注意调用门的过程,我们使用的是call far,再进行门调用时,无论是间接远调用还是绝对远调用,处理器只会用选择子部分,而偏移地址部分会被忽略。在之后的给用户程序填充门描述符时,一定要记得把门描述符的特权级改为应用程序特权级3,,门调用时候处理器要按以下规则检查(数值上):

           CPL<=调用门描述符的DPL

           RPL<=调用门描述符的DPL

           CPL>=目标代码段的DPL

      上述规则都满足,才能成功调用门,否则会引发异常中断。

      这里还有一个地方值得注意的是处理器对调用者的请求特权级RPL的问题,RPL事实上是处理器和操作系统的一个协议,处理器本身只会负责检查特权级的RPL,判断其是否有权限访问目标段。并不检查RPL是否是正确的,对RPL的正确性检查是操作系统的事情。换句话说,操作系统总是会把RPL改为真正发起调用的任务的特权级,以防止低特权级任务通过门等越权操作。为了帮助内核或者操作系统检查真正调用者的身份,并提供正确的RPL的值,处理器提供了arpl(Adjust RPL Field of Segment Selector)指令,以方便地调整段选择子的RPL字段,格式为:

    arpl r/m16,r16

      该指令的目的操作数包含了16位段选择子的通用寄存器,或者指向一个16位的内存单元。源操作数只能是包含了段选择子的16位通用寄存器。

      该执行执行时,处理器检查目的操作数的RPL字段,如果它在数值上小于源操作数的RPL字段,则设置ZF标志,并调整目的操作数的RPL字段为源操作数的RPL,否则,清零ZF,且除此之外不进行任何操作。这个指令是典型的操作系统指令,它常用于调整应用程序传递给操作系统的段选择子,使其RPL字段的值和应用程序的特权级相匹配,这个指令也可以在应用程序上用。

      比如一个采用压栈传递参数,且通过门调用的读取磁盘函数前一段可以这么写:

    ★PART2:加载用户程序并创建任务

    1. 记录任务数据临时块(TCB)(非处理器要求)

      一个合格的操作系统应该有能力跟踪程序的大小,加载的位置,使用内存的多少等,当然在现代流行的操作系统中,这个管理是非常复杂的。教材作者用一个简单的例子来做这个事情(和之前的符号表,内存管理什么的都一样,都是作者自己的私货),能解决问题就好了,暂时不需要考虑太复杂的东西。这个例子就是TCB链(Task Contorl Block,TCB)。

    在内和数据段加多一个上面的双字空间。然后创建TCB,也就是简单的分配内存

    因为TCB是链表,所以每加一个TCB,就要寻链表一次,简单的遍历过程如下:

     

    2. 创建和加载LDT到GDT中

      14章和13章不同的地方在于,因为14章要按照特权级3加载程序,要使用LDT,所以要给程序分配LDT的内存,而且要把一些描述符(包括那些用来切换的栈段)写入LDT,我们先把这些信息写入TCB。

    上面的代码的最后是安装LDT到GDT中,其实LDT的描述符和普通的段描述符长得差不多,就是他的S位必须是0,TYPE位必须是0010。

    3. 创建和加载TSS到GDT中

      要加载TSS,首先就要向TSS填入所需要的信息,再看一下TSS,除了段寄存器和段指针这些我们已经很熟悉了,我们来看一下我们不熟悉的部分,CR3(PDBR)和分页有关,这个将在16章讲述,LDT段选择子就是指的是当前任务的LDT在GDT中的选择子,任务切换时要用到(15章讲述),T位是软件调试位,如果T位是1,那么每次切换到该任务,就会引发一个调试异常中断,可以用来调试,现在只需要把这个清零就可以了。

      之前我们说过,EFLAGS中有一个IOPL位,是用来管理端口的读写问题的,而TSS恰好就有一个EFALGS段,那么现在我们就来搞明白这个域是怎么使用的。

      一个典型的例子就是硬件端口输入输出指令in和out,他们应该对特权级别位1的程序开放,因为设备驱动程序就工作在这个特权级别,不过,特权级3和特权级2的程序也同样需要快速访问端口,通过调用门来使用内核程序访问的方法太慢,而处理器是可以访问65536个硬件端口的。处理器允许只对应用程序开放那些他们需要的端口,而屏蔽敏感端口,这有利于设备的统一管理。每个任务都有EFLAGS寄存器的副本,其内容在任务创建的时候由内核或者操作系统初始化,在多任务系统中,每当任务恢复运行时,就由处理器固件自动从TSS恢复。

      EFLAGS寄存器的IPOL位决定了当前任务的I/O特权级别,如果CPL高于或者和任务的I/O特权级IOPL相同,也就是在数值上

                  CPL<=IOPL

      则虽有的I/O操作都是允许的,针对任何硬件端口的访问都可以通过。

      如果CPL低于任务的IOPL,则处理器要检索I/O许可位串,看是否允许这个任务访问这个接口。

      

      如图所示,I/O许可串(I/O Perission Bit String)是一个比特序列,最多允许65536个比特(8KB),每一个比特代表一个端口,如果该比特为1,则这个比特对应的端口禁止访问,如果为0,则允许访问。这里要非常注意的是,I/O端口是按字节编址的,每个端口仅被设计者用来读写一个字节的数据,当以字或者双字访问时,实际上是连续访问2个或者4个端口,比如,从端口n读取一个字时,相当于从端口n和端口n+1各读取一个字节。

      TSS还可以包括一个I/O许可串,他所占用的区域称为I/O许可串映射区,处于TSS的偏移地址位102D的那个字单元,从TSS起始位置(0)开始算起,如果这个字单元的内容大于或者等于(教材是小于等于,有误)TSS的段界限(在TSS描述符中),则表明没有I/0许可串(且不能超过DFFFH),在这种情况下,如果CPL低于当前IOPL,执行任何硬件I/O都会引发处理器中断,TSS的界限值应该包括I/O许可映射区在内,I/O许可映射区在以TSS的初始位置(0)位起始点,按照I/O许可映射区的值作为偏移值所对应的内存的首地址,一直到TSS的段界限为止就是I/O映射区,且映射区的最后一个字节必须是0xFF,如果中间有空余,可以留给软件使用(TSS的尺寸最小是104)。

      处理器提供pushf和popf来给EFLAGS进行读写,pushf在16位模式下(实模式和16位保护模式),将16位的FLAGS压栈(16位),在32位保护模式下,将EFLAGS压栈(32位),pushf是一个字节的指令。则如果在16位模式下,pushf是压入EFLAGS的低16位,如果要压入32位EFLAGS,则要添加指令前缀66(指令:66 9C,66是前缀)为了区分EFLAGS在16位模式下的两种压栈方式,nasm提供了pushfd指令,它无论在16位模式还是在32位模式下,都会压入32位EFLAGS,而且在16为模式下,会给指令添加66(其实pushfd和pushf的作用是一样的)。popf和popfd同理。

      注意能够修改IOPL位和IF位的两个标志位的指令时popf(popfd),iret,cli,sti。注意没有pushf(pushfd),但是这是个指令都不是特权指令。处理器通过IOPL位来控制它们的使用。

      当且仅当:CPL<=IOPL(数值上),则允许执行上面4个指令和访问所有硬件端口。否则,当当前特权级低于I/O特权级,则执行iret和popf时,会引发异常中断,执行cli和sti时,不会引发异常中断,但不会改变标志寄存器的IF位,同时,能否安稳特定的I/O端口,要参考TSS中的I/O许可位映射串。

      接下来就是填写TSS和把TSS描述符写入GDT了,TSS描述符长得和LDT描述符差不多,只是TYPE位是10B1(B位是忙(Busy)位,一般由处理器填写,操作系统应该写这个位为0,和任务切换有关,15章讲)。

      

    注:因为本章不用任务切换,所以TSS的其他段可以先不用填写。

    ★PART3:以特权级3加载用户程序

    1. 加载任务寄存器TR和局部描述符寄存器LDTR

           和段寄存器一样,TR和LDTR寄存器都包括16位选择子部分,以及描述符高速缓存器部分,选择子部分是TR和LDT的描述符选择子,描述符高速缓存器同样是64位的(32位的线性基地址,20位的段界限,12位的段属性)。

           加载任务寄存器TR需要用到ltr命令,指令格式为ltr r/m16,这条指令的操作数可以是16位的通用寄存器或者是16位的内存单元,包含16位的TSS选择子。当TSS加载到TR后,处理器自动将TSS的B位置1,但不进行任务切换。该指令不影响EFLAGS的任何标志位,但属于只能在0特权级下执行的特权命令。

           加载据不描述符寄存器LDTR要用到lldt命令,指令格式为lldt r/m16,这条指令的操作数可以是16位的通用寄存器或者是16位的内存单元,包含16位的LDT选择子。lldt命令会把在GDT的ldt描述符的段界限和段基地址加载到LDTR的描述符高速缓存器部分,CS,SS,DS,ES,FS和GS寄存器的当前内容不受该指令的影响,包括TSS内的LDT选择子字段。

           如果执行这条指令时,代入LDT选择器部分的选择子高14位全部都是0,则LDT寄存器的内容会被标记为无效,而该指令的执行会结束,且不会引发异常中断,但是后面的对LDT内描述符的操作都会引发异常中断。

           处理器在加载TR和LDTR时,都要检查描述符的有效性。包括审查是不是TSS或者LDT描述符。

           当然这一章没有将任务门,教材用了一个非常奇葩的方法(把切换到特权级3任务看成是从某个调用门的返回),非常别扭,等一下看代码就知道了,用户程序是直接用一个调用门返回的,而且教材给的例程返回后一定会引发常中断,因为其返回时用的是jmp返回,特权级还是3,最后教材那里要切换到内核数据区(DPL=3),肯定是不行的。

    2. 显示处理器信息

           上一章忘记这茬了,这里补一补,主要是讲如何用cpuid来显示处理器信息

           cpuid(CPU Identification)指令是用于返回处理器标识和特性信息的,EAX是用于指定要返回什么样的信息,也就是功能,有时候还要用到ECX寄存器,但是无论如何cpuid指令执行后处理器将返回的信息放在EAX,EBX,ECX或者EDX中,cpuid指令是在(80486以后开始支持的,原则上使用cpuid之前要检测处理器是否支持该指令,再检测是否支持所需功能)。这个问题就出在EFLAGS上,EFLAGS的ID标志位(21位),如果可以被设置和清除,则他不支持cpuid指令,反之则可以支持。

    通常情况下不需要检查处理器是否支持cpuid(毕竟80486这款CPU年代太久远了),为了探测处理器能支持的最大的功能号,应该先用0号功能来执行cpuid指令:

      指令执行后,EAX寄存器返回最大可以支持的功能号,同时,还在EBX,ECX和ED中返回处理器供应商的信息,对于Intel来说,返回的信息就是“GenuineIntel”。

      要返回处理器品牌信息,需要使用0x80000002~0x80000004功能,分三次进行,该功能仅被奔腾4(Pentium4)之后的处理器支持,可以在内核数据区存放之后再显示。

    ★PART4:14章的相关程序

    这里直接课后习题两道题一起做了,感觉都是差不多的,主要是欣赏一下那个别扭的调用方法。

     

      1 ;===============================内核程序=================================
      2         ;定义内核所要用到的选择子
      3         All_4GB_Segment         equ 0x0008        ;4GB的全内存区域
      4         Stack_Segement             equ 0x0018        ;内核栈区
      5         Print_Segement            equ 0x0020        ;显存映射区
      6         Sys_Routine_Segement     equ 0x0028        ;公用例程段
      7         Core_Data_Segement        equ 0x0030        ;内核数据区
      8         Core_Code_Segement        equ 0x0038        ;内核代码段
      9         ;----------------------------------------------------------------
     10         User_Program_Address    equ 50            ;用户程序所在逻辑扇区
     11         Switch_Stack_Size        equ 4096        ;切换栈段的大小
     12 ;=============================内核程序头部===============================
     13 SECTION header vstart=0
     14         Program_Length             dd    Program_end                    ;内核总长度
     15         Sys_Routine_Seg         dd  section.Sys_Routine.start    ;公用例程段线性地址
     16         Core_Data_Seg             dd  section.Core_Data.start        ;内核数据区线性地址
     17         Core_Code_Seg             dd  section.Core_Code.start        ;内核代码区线性地址
     18         Code_Entry                dd    start                        ;注意偏移地址一定是32位的
     19                                 dw  Core_Code_Segement
     20     ;----------------------------------------------------------------
     21                             [bits 32]
     22 ;=========================================================================
     23 ;============================公用例程区===================================
     24 ;=========================================================================
     25 SECTION Sys_Routine align=16 vstart=0
     26     ReadHarddisk:                            ;push1:28位磁盘号(esi)
     27                                             ;push2:应用程序数据段选择子(ax->ds)
     28                                             ;push3: 偏移地址(ebx)
     29                                             ;push4: 应用程序代码段选择子(dx)
     30         pushad
     31         push ds
     32         push es
     33         
     34         mov ebp,esp
     35         
     36         mov esi,[ebp+15*4]
     37         movzx eax,word[ebp+14*4]
     38         mov ebx,[ebp+13*4]
     39         movzx edx,word[ebp+12*4]
     40         
     41         arpl ax,dx
     42         mov ds,ax
     43         
     44         mov dx,0x1f2
     45         mov al,0x01        ;读一个扇区                                
     46         out dx,al
     47         
     48         inc edx            ;0-7位
     49         mov eax,esi
     50         out dx,al
     51         
     52         inc edx            ;8-15位
     53         mov al,ah
     54         out dx,al
     55         
     56         inc edx            ;16-23位
     57         shr eax,16
     58         out dx,al
     59         
     60         inc edx            ;24-28位,主硬盘,LBA模式
     61         mov al,ah
     62         and al,0x0f
     63         or al,0xe0
     64         out dx,al
     65         
     66         inc edx
     67         mov al,0x20
     68         out dx,al
     69         
     70         _wait:
     71             in al,dx
     72             and al,0x88
     73             cmp al,0x08
     74             jne _wait
     75         
     76         mov dx,0x1f0
     77         mov ecx,256
     78         
     79         _read:
     80             in ax,dx
     81             mov [ebx],ax
     82             add ebx,2
     83             loop _read
     84         
     85         pop es
     86         pop ds
     87         popad
     88         retf 16        ;4个数据
     89     ;----------------------------------------------------------------
     90     put_string:                                                    ;ebx:偏移地址
     91         pushad
     92         push ds
     93         push es
     94         
     95         _print:
     96             mov cl,[ebx]
     97             cmp cl,0
     98             je _exit
     99             call put_char
    100             inc ebx
    101             jmp _print
    102         _exit:
    103             pop es
    104             pop ds
    105             popad
    106             retf            ;段间返回
    107         ;--------------------------------------------------------------    
    108         put_char:            ;cl就是要显示的字符
    109             push ebx
    110             push es
    111             push ds
    112             
    113             mov dx,0x3d4
    114             mov al,0x0e        ;高8位
    115             out dx,al
    116             mov dx,0x3d5
    117             in al,dx
    118             mov ah,al        ;先把高8位存起来
    119             mov dx,0x3d4
    120             mov al,0x0f        ;低8位
    121             out dx,al
    122             mov dx,0x3d5
    123             in al,dx        ;现在ax就是当前光标的位置
    124             
    125             _judge:
    126                 cmp cl,0x0a
    127                 je _set_0x0a
    128                 cmp cl,0x0d
    129                 je _set_0x0d
    130             _print_visible:
    131                 mov bx,ax
    132                 mov eax,Print_Segement
    133                 mov es,eax
    134                 shl bx,1     ;注意这里一定要把ebx变成原来的两倍,实际位置是光标位置的两倍
    135                 mov [es:bx],cl            ;注意这里是屏幕!
    136                 mov byte[es:bx+1],0x07        
    137                 add bx,2
    138                 shr bx,1
    139                 jmp _roll_screen
    140             _set_0x0d:        ;回车
    141                 mov bl,80
    142                 div bl
    143                 mul bl
    144                 mov bx,ax
    145                 jmp _set_cursor
    146             _set_0x0a:        ;换行
    147                 mov bx,ax
    148                 add bx,80
    149                 jmp _roll_screen
    150             _roll_screen:
    151                 cmp bx,2000
    152                 jl _set_cursor
    153                 mov eax,Print_Segement
    154                 mov ds,eax
    155                 mov es,eax
    156                 
    157                 cld
    158                 mov edi,0x00
    159                 mov esi,0xa0
    160                 mov ecx,1920
    161                 rep movsw
    162             _cls:
    163                 mov bx,3840
    164                 mov ecx,80
    165                 _print_blank:
    166                     mov word[es:bx],0x0720
    167                     add bx,2
    168                     loop _print_blank    
    169                 mov bx,1920    ;别总是忘了光标的位置!
    170             _set_cursor:        ;改变后的光标位置在bx上
    171             mov dx,0x3d4
    172             mov al,0x0f        ;低8位
    173             out dx,al
    174             
    175             mov al,bl
    176             mov dx,0x3d5
    177             out dx,al
    178             
    179             mov dx,0x3d4
    180             mov al,0x0e     ;高8位
    181             out dx,al
    182             
    183             mov al,bh
    184             mov dx,0x3d5
    185             out dx,al
    186             
    187             pop ds
    188             pop es
    189             pop ebx
    190             ret
    191     ;----------------------------------------------------------------        
    192     allocate_memory:                            ;简易内存分配策略
    193                                                 ;输入ecx:想要分配的总字节数
    194                                                 ;输出ecx:分配的线性基地址
    195         push ds
    196         push eax
    197         push ebx
    198         call Cal_User_Mem
    199             
    200         mov eax,Core_Data_Segement
    201         mov ds,eax
    202         mov eax,[ram_alloc]
    203         mov edx,eax                                ;edx暂存一下eax
    204         add eax,ecx
    205         
    206         cmp eax,edx                                ;发现新分配的现地址比原来的还小,说明已经溢出
    207         jge _alloc
    208             mov ebx,mem_alloc_fail
    209             call Sys_Routine_Segement:put_string
    210             mov ecx,0                        ;分配为0说明已经分配失败
    211             jmp _exit1
    212         _alloc:
    213             
    214         mov ebx,eax
    215         and ebx,0xfffffffc
    216         add ebx,4                             ;强行向上取整
    217         test eax,0x00000003
    218         cmovnz eax,ebx
    219         mov ecx,[ram_alloc]                    ;要返回要分配的初始地址
    220         mov [ram_alloc],eax                    ;下一次分配的线性基地址
    221             
    222         _exit1:
    223         pop ebx
    224         pop eax
    225         pop ds
    226         
    227         retf
    228     ;----------------------------------------------------------------
    229     recycled_memory_and_gdt:
    230         mov eax,[ram_recycled]
    231         sub [ram_alloc],eax
    232         mov dword[ram_recycled],0                ;因为我们还没学到多任务,先这样简单地清零
    233         
    234         sgdt [pgdt_base_tmp]
    235         sub word[pgdt_base_tmp],16                ;应用程序的LDT,TSS
    236         lgdt [pgdt_base_tmp]                    ;重新加载内核
    237         retf
    238     ;----------------------------------------------------------------
    239     Cal_User_Mem:                                ;输入ecx:应用程序用到的内存(字节)
    240         add [ram_recycled],ecx
    241         ret
    242     ;----------------------------------------------------------------    
    243     PrintDword:                                ;显示edx内容的一个调试函数
    244         pushad
    245         push ds
    246         
    247         mov eax,Core_Data_Segement
    248         mov ds,eax
    249         
    250         mov ebx,bin_hex
    251         mov ecx,8
    252         
    253         _query:
    254             rol edx,4
    255             mov eax,edx
    256             and eax,0x0000000f
    257             xlat
    258             
    259             push ecx
    260             mov cl,al
    261             call put_char
    262             pop ecx
    263             
    264         loop _query
    265             
    266         pop ds
    267         popad
    268         
    269         retf
    270     ;----------------------------------------------------------------
    271     Make_Seg_Descriptor:                        ;构造段描述符
    272                                             ;输入:
    273                                             ;eax:线性基地址
    274                                             ;ebx:段界限
    275                                             ;ecx:属性
    276                                             ;输出:
    277                                             ;eax:段描述符低32位
    278                                             ;edx:段描述符高32位
    279         mov edx,eax
    280         and edx,0xffff0000
    281         rol edx,8
    282         bswap edx
    283         or edx,ecx
    284         
    285         shl eax,16
    286         or ax,bx
    287         and ebx,0x000f0000
    288         or edx,ebx
    289         retf                
    290     ;----------------------------------------------------------------        
    291     Make_Gate_Descriptor:                    ;构造门描述符
    292                                             ;输入:
    293                                             ;eax:段内偏移地址
    294                                             ;bx: 段的选择子
    295                                             ;cx: 段的属性
    296                                             ;输出:
    297                                             ;eax:门描述符低32位
    298                                             ;edx:门描述符高32位
    299         push ebx
    300         push ecx
    301         
    302         mov edx,eax
    303         and edx,0xffff0000                    ;要高16位
    304         or dx,cx
    305         
    306         shl ebx,16
    307         and eax,0x0000ffff
    308         or eax,ebx
    309         
    310         pop ecx
    311         pop ebx
    312         
    313         retf                
    314     ;----------------------------------------------------------------
    315     Set_New_GDT:                            ;装载新的全局描述符
    316                                             ;输入:edx:eax描述符
    317                                             ;输出:cx选择子
    318         push ds
    319         push es
    320         
    321         mov ebx,Core_Data_Segement
    322         mov ds,ebx
    323         
    324         mov ebx,All_4GB_Segment
    325         mov es,ebx
    326         
    327         sgdt [pgdt_base_tmp]
    328         
    329         movzx ebx,word[pgdt_base_tmp]
    330         inc bx                                ;注意这里要一定是inc bx而不是inc ebx,因为gdt段界限初始化是0xffff的
    331                                             ;要用到回绕特性
    332         add ebx,[pgdt_base_tmp+0x02]        ;得到pgdt的线性基地址
    333         
    334         mov [es:ebx],eax
    335         mov [es:ebx+0x04],edx                ;装载新的gdt符
    336                                             ;装载描述符要装载到实际位置上
    337         
    338         add word[pgdt_base_tmp],8            ;给gdt的段界限加上8(字节)
    339         
    340         lgdt [pgdt_base_tmp]                ;加载gdt到gdtr的位置和实际表的位置无关
    341         
    342         mov ax,[pgdt_base_tmp]                ;得到段界限
    343         xor dx,dx
    344         mov bx,8                            ;得到gdt大小
    345         div bx
    346         mov cx,ax
    347         shl cx,3                            ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级)
    348         
    349         pop es
    350         pop ds
    351         retf
    352     ;----------------------------------------------------------------
    353     Set_New_LDT_To_TCB:                        ;装载新的局部描述符
    354                                             ;输入:edx:eax描述符
    355                                             ;     : ebx:TCB线性基地址
    356                                             ;输出:cx选择子
    357         
    358         push edi
    359         push eax
    360         push ebx
    361         push edx
    362         push ds
    363         
    364         mov ecx,All_4GB_Segment
    365         mov ds,ecx
    366         
    367         mov edi,[ebx+0x0c]                    ;LDT的线性基地址
    368         movzx ecx,word[ebx+0x0a]
    369         inc cx                                ;得到实际的LDT的大小(界限还要-1)
    370         
    371         mov [edi+ecx+0x00],eax
    372         mov [edi+ecx+0x04],edx
    373         
    374         add cx,8
    375         dec cx
    376         
    377         mov [ebx+0x0a],cx
    378         
    379         mov ax,cx
    380         xor dx,dx
    381         mov cx,8
    382         div cx
    383         
    384         shl ax,3
    385         mov cx,ax
    386         or cx,0x0004                        ;LDT,第三位TI位一定是1
    387         
    388         pop ds
    389         pop edx
    390         pop ebx
    391         pop eax
    392         pop edi
    393         retf
    394 ;=========================================================================
    395 ;===========================内核数据区====================================
    396 ;=========================================================================
    397 SECTION Core_Data align=16 vstart=0
    398 ;-------------------------------------------------------------------------------
    399         pgdt_base_tmp:          dw  0                             ;这一章的用户程序都是从GDT中加载的
    400                                 dd  0
    401 
    402         ram_alloc:              dd  0x00100000                    ;下次分配内存时的起始地址(直接暴力从0x00100000开始分配了)
    403         ram_recycled            dd  0                              ;这里储存程序实际用的大小            
    404         salt:
    405         salt_1:                    db    '@Printf'                    ;@Printf函数(公用例程)
    406         times 256-($-salt_1)    db    0
    407                                 dd    put_string
    408                                 dw    Sys_Routine_Segement
    409                                 dw  0                            ;参数个数
    410                                 
    411         salt_2:                    db    '@ReadHarddisk'                ;@ReadHarddisk函数(公用例程)
    412         times 256-($-salt_2)    db    0
    413                                 dd    ReadHarddisk
    414                                 dw    Sys_Routine_Segement
    415                                 dw  4                            ;参数个数
    416                                 
    417         salt_3:                    db    '@PrintDwordAsHexString'    ;@PrintDwordAsHexString函数(公用例程)
    418         times 256-($-salt_3)    db    0
    419                                 dd    PrintDword
    420                                 dw    Sys_Routine_Segement
    421                                 dw  0                            ;参数个数
    422                                 
    423         salt_4:                    db    '@TerminateProgram'            ;@TerminateProgram函数(内核例程)
    424         times 256-($-salt_4)    db    0
    425                                 dd    _return_point
    426                                 dw    Core_Code_Segement
    427                                 dw  0                            ;参数个数
    428                                 
    429         salt_length:            equ    $-salt_4
    430         salt_items_sum            equ    ($-salt)/salt_length        ;得到项目总数
    431         
    432         message_1                db  '   If you seen this message,that means we '
    433                                 db  'are now in protect mode,and the system '
    434                                 db  'core is loaded,and the video display '
    435                                 db  'routine works perfectly.',0x0d,0x0a,0
    436 
    437         message_2                db  '   Loading user program...',0
    438 
    439         do_status                db  'Done.',0x0d,0x0a,0
    440 
    441         message_3                db  0x0d,0x0a,0x0d,0x0a,0x0d,0x0a
    442                                 db  '   User program terminated,control returned.'
    443                                 db  0x0d,0x0a,0x0d,0x0a,0
    444         message_4                db  '   We have been backed to kernel.',0x0d,0x0a,0
    445         message_5                db  '   The GDT and memory have benn recycled.',0
    446         message_6                db  '   From the system wide gate:',0x0d,0x0a,0
    447         message_7                db  '   Setting the gate discriptor...',0
    448         message_In_Gate            db  '   Hi!My name is Philip:',0x0d,0x0a,0
    449 
    450         bin_hex                  db '0123456789ABCDEF'
    451                                                                 ;put_hex_dword子过程用的查找表
    452         core_buf        times 2048 db 0                             ;内核用的缓冲区(2049个字节(2MB))
    453 
    454         esp_pointer             dd 0                              ;内核用来临时保存自己的栈指针
    455 
    456         cpu_brnd0                db 0x0d,0x0a,'  ',0
    457         cpu_brand         times 52 db 0
    458         cpu_brnd1                db 0x0d,0x0a,0x0d,0x0a,0  
    459         mem_alloc_fail            db    'The Program is too large to load'
    460         core_ss                    dw 0
    461         core_sp                    dd 0
    462         
    463          
    464         tcb_chain                dd  0                            ;任务控制块链头指针
    465 ;=========================================================================
    466 ;===========================内核代码区====================================
    467 ;=========================================================================
    468 SECTION Core_Code align=16 vstart=0        
    469     ;---------------------------------------------------------------------
    470     append_to_tcb:                        ;写入新的TCB链
    471                                         ;输入:ecx新的TCB线性基地址
    472         pushad
    473         
    474         push ds
    475         push es
    476         
    477         mov eax,All_4GB_Segment
    478         mov es,eax
    479         
    480         mov eax,Core_Data_Segement
    481         mov ds,eax
    482         
    483         mov eax,[tcb_chain]
    484         cmp eax,0x00
    485         je _notcb
    486         
    487         _search_tcb:
    488             mov edx,[tcb_chain+0x00]
    489             mov eax,[es:edx]
    490             cmp eax,0x00
    491         jne _search_tcb
    492         
    493         mov [es:edx+0x00],ecx
    494         jmp _out_tcb_search
    495         
    496         _notcb:
    497         mov [tcb_chain],ecx
    498         
    499         _out_tcb_search:
    500         pop es
    501         pop ds
    502         
    503         popad
    504         ret
    505     ;---------------------------------------------------------------------    
    506     load_program:                        ;输入push1:逻辑扇区号
    507                                         ;     push2:    线性基地址
    508         pushad
    509         push ds
    510         push es
    511         
    512         mov ebp,esp                        ;别忘了把参数传给ebp
    513         
    514         mov eax,Core_Data_Segement
    515         mov ds,eax                        ;切换到内核数据段
    516         
    517         mov eax,All_4GB_Segment
    518         mov es,eax
    519         
    520         mov edi,[ebp+11*4]                ;获取tcb的线性基地址,别忘了调用相对近调用还要有1个push
    521         
    522         mov ecx,160
    523         call Sys_Routine_Segement:allocate_memory
    524         mov [es:edi+0x0c],ecx
    525         mov word[es:edi+0x0a],0xffff    ;初始化LDT界限位0xffff
    526 
    527         mov esi,[ebp+12*4]                ;esi必须是逻辑扇区号
    528         mov ebx,core_buf                ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了)
    529         
    530         push esi
    531         push ds
    532         push ebx
    533         push cs
    534         call Sys_Routine_Segement:ReadHarddisk
    535         
    536         mov eax,[core_buf]                ;读取用户程序长度
    537         
    538         mov ebx,eax                        
    539         and ebx,0xfffffe00                ;清空低9位(强制对齐512)
    540         add ebx,512                        
    541         test eax,0x000001ff                
    542         cmovnz eax,ebx                    ;低9位不为0则使用向上取整的结果
    543         
    544         mov ecx,eax                        ;eax是整个程序的向上取整的大小
    545         call Sys_Routine_Segement:allocate_memory    
    546                                         ;先分配内存给整个程序,再分配内存给栈区
    547         mov ebx,ecx                                    
    548         mov [es:edi+0x06],ecx            ;tcb 0x06:程序加载基地址
    549         
    550         xor edx,edx
    551         mov ecx,512                        ;千万不要改掉ebx
    552         div ecx
    553         mov ecx,eax
    554         
    555         mov eax,All_4GB_Segment            ;切换到4GB段区域(平坦模式)
    556         mov ds,eax
    557         
    558         _loop_read:
    559             push esi
    560             push ds
    561             push ebx
    562             push cs
    563             call Sys_Routine_Segement:ReadHarddisk    ;esi还是User_Program_Address
    564             inc esi
    565             add ebx,512
    566         loop _loop_read
    567         
    568         mov esi,edi                        ;esi:把TCB的线性基地址
    569         mov edi,[es:esi+0x06]            ;程序加载的线性基地址
    570         
    571         ;建立头部描述符
    572         mov eax,edi
    573         mov ebx,[edi+0x04]
    574         dec ebx                            ;段界限
    575         mov ecx,0x0040f200
    576         call Sys_Routine_Segement:Make_Seg_Descriptor
    577         mov ebx,esi
    578         call Sys_Routine_Segement:Set_New_LDT_To_TCB
    579         or cx,0x0003                    ;特权级3
    580         mov [es:esi+0x44],cx            ;记得要登记头部的选择子
    581         mov [edi+0x04],cx
    582         
    583         ;建立代码段描述符
    584         mov eax,edi
    585         add eax,[edi+0x14]
    586         mov ebx,[edi+0x18]
    587         dec ebx
    588         mov ecx,0x0040f800
    589         call Sys_Routine_Segement:Make_Seg_Descriptor
    590         mov ebx,esi
    591         call Sys_Routine_Segement:Set_New_LDT_To_TCB
    592         or cx,0x0003
    593         mov [edi+0x14],cx 
    594         
    595         ;建立数据段描述符
    596         mov eax,edi
    597         add eax,[edi+0x1c]
    598         mov ebx,[edi+0x20]
    599         dec ebx
    600         mov ecx,0x0040f200
    601         call Sys_Routine_Segement:Make_Seg_Descriptor
    602         mov ebx,esi
    603         call Sys_Routine_Segement:Set_New_LDT_To_TCB
    604         or cx,0x0003
    605         mov [edi+0x1c],cx 
    606         
    607         ;建立栈段描述符
    608         mov ecx,[edi+0x0c]
    609         mov ebx,0x000fffff
    610         sub ebx,ecx
    611         mov eax,4096                    ;4KB粒度
    612         mul ecx
    613         mov ecx,eax
    614         call Sys_Routine_Segement:allocate_memory
    615         mov eax,ecx                        ;eax是栈段的线性基地址
    616         mov ecx,0x00c0f600
    617         call Sys_Routine_Segement:Make_Seg_Descriptor
    618         mov ebx,esi
    619         call Sys_Routine_Segement:Set_New_LDT_To_TCB
    620         or cx,0x0003
    621         mov [edi+0x08],cx
    622         
    623         ;现在开始重定位API符号表
    624         ;---------------------------------------------------------------------
    625         mov eax,All_4GB_Segment            ;因为这个时候用户头部在LDT,而LDT还没有被加载,只能通过4GB空间访问
    626         mov es,eax
    627         mov eax,Core_Data_Segement
    628         mov ds,eax
    629         
    630         cld
    631         mov ecx,[es:edi+0x24]            ;得到用户程序符号表的条数
    632         add edi,0x28                    ;用户符号表的偏移地址是0x28
    633 
    634         _loop_U_SALT:                    
    635             push edi
    636             push ecx
    637             
    638             mov ecx,salt_items_sum
    639             mov esi,salt
    640             
    641             _loop_C_SALT:
    642                 push edi
    643                 push esi
    644                 push ecx
    645                 
    646                 mov ecx,64                ;比较256个字节
    647                 repe cmpsd
    648                 jne _re_match            ;如果成功匹配,那么esi和edi刚好会在数据区之后的
    649                 
    650                 mov eax,[esi]            ;偏移地址
    651                 mov [es:edi-256],eax    ;把偏移地址填入用户程序的符号区
    652                 mov ax,[esi+0x04]        ;段的选择子
    653                 
    654                 or ax,0x0002            ;把RPL改为3,代表(内核)赋予应用程序以特权级3
    655                 mov [es:edi-252],ax        ;把段的选择子填入用户程序的段选择区
    656                 
    657                 _re_match:
    658                 pop ecx
    659                 pop esi
    660                 add esi,salt_length
    661                 pop edi
    662             loop _loop_C_SALT
    663             
    664             pop ecx
    665             pop edi
    666             add edi,256
    667         loop _loop_U_SALT
    668         ;---------------------------------------------------------------------
    669         
    670         mov esi,[ebp+11*4]                ;重新获得TCB的线性基地址
    671         
    672         ;现在设置所有的特权级栈段,并且把特权级栈段放到TCB中(为了等一下设置TSS)
    673         ;设置TSS特权0级栈段(暂存在TCB中)            
    674         mov ecx,Switch_Stack_Size
    675         mov eax,ecx
    676         mov [es:esi+0x1a],ecx
    677         shr dword[es:esi+0x1a],12         ;相当于除以4096
    678         call Sys_Routine_Segement:allocate_memory
    679         add eax,ecx                        ;得到最高地址
    680         mov [es:esi+0x1e],eax            ;登记线性基地址
    681         mov ebx,0x000fffff
    682         sub ebx,[es:esi+0x1a]            
    683         mov ecx,0x00c09600                ;特权级0
    684         call Sys_Routine_Segement:Make_Seg_Descriptor
    685         mov ebx,esi
    686         call Sys_Routine_Segement:Set_New_LDT_To_TCB
    687         or cx,0x0000                     ;RPL为0
    688         mov [es:esi+0x22],cx
    689         mov dword[es:esi+0x24],0
    690         
    691         ;设置TSS特权1级栈段(暂存在TCB中)                
    692         mov ecx,Switch_Stack_Size
    693         mov eax,ecx
    694         mov [es:esi+0x28],ecx
    695         shr dword[es:esi+0x28],12         ;相当于除以4096
    696         call Sys_Routine_Segement:allocate_memory
    697         add eax,ecx                        ;得到最高地址
    698         mov [es:esi+0x2c],eax            ;登记线性基地址
    699         mov ebx,0x000fffff
    700         sub ebx,[es:esi+0x28]            
    701         mov ecx,0x00c0b600                ;特权级1
    702         call Sys_Routine_Segement:Make_Seg_Descriptor
    703         mov ebx,esi
    704         call Sys_Routine_Segement:Set_New_LDT_To_TCB
    705         or cx,0x0001                     ;RPL为1
    706         mov [es:esi+0x30],cx
    707         mov dword[es:esi+0x32],0
    708         
    709         ;设置TSS特权2级栈段(暂存在TCB中)                    
    710         mov ecx,Switch_Stack_Size
    711         mov eax,ecx
    712         mov [es:esi+0x36],ecx
    713         shr dword[es:esi+0x36],12         ;相当于除以4096
    714         call Sys_Routine_Segement:allocate_memory
    715         add eax,ecx                        ;得到最高地址
    716         mov [es:esi+0x3a],eax            ;登记线性基地址
    717         mov ebx,0x000fffff
    718         sub ebx,[es:esi+0x36]            
    719         mov ecx,0x00c0d600                ;特权级2
    720         call Sys_Routine_Segement:Make_Seg_Descriptor
    721         mov ebx,esi
    722         call Sys_Routine_Segement:Set_New_LDT_To_TCB
    723         or cx,0x0002                     ;RPL为2
    724         mov [es:esi+0x3e],cx
    725         mov dword[es:esi+0x40],0
    726         
    727         ;在GDT中存入LDT信息
    728         mov eax,[es:esi+0x0c]
    729         movzx ebx,word[es:esi+0x0a]
    730         mov ecx,0x00408200                ;LDT描述符,特权级0级
    731         call Sys_Routine_Segement:Make_Seg_Descriptor
    732         call Sys_Routine_Segement:Set_New_GDT
    733         mov [es:esi+0x10],cx            ;在TCB放入LDT选择子
    734         
    735         ;在TCB中登记TSS的信息
    736         mov ecx,104                        ;创建一个最小尺寸的TSS
    737         mov [es:esi+0x12],cx
    738         dec word[es:esi+0x12]            ;记得-1,要的是段界限
    739         call Sys_Routine_Segement:allocate_memory
    740         mov [es:esi+0x14],ecx            ;TSS基地址
    741         
    742         ;登记基本的TSS表格内容
    743         mov word [es:ecx+0],0           ;反向链=0
    744       
    745         mov edx,[es:esi+0x24]           ;登记0特权级堆栈初始ESP
    746         mov [es:ecx+4],edx              ;到TSS中
    747       
    748         mov dx,[es:esi+0x22]            ;登记0特权级堆栈段选择子
    749         mov [es:ecx+8],dx               ;到TSS中
    750       
    751         mov edx,[es:esi+0x32]           ;登记1特权级堆栈初始ESP
    752         mov [es:ecx+12],edx             ;到TSS中
    753 
    754         mov dx,[es:esi+0x30]            ;登记1特权级堆栈段选择子
    755         mov [es:ecx+16],dx              ;到TSS中
    756 
    757         mov edx,[es:esi+0x40]           ;登记2特权级堆栈初始ESP
    758         mov [es:ecx+20],edx             ;到TSS中
    759 
    760         mov dx,[es:esi+0x3e]            ;登记2特权级堆栈段选择子
    761         mov [es:ecx+24],dx              ;到TSS中
    762 
    763         mov dx,[es:esi+0x10]            ;登记任务的LDT选择子
    764         mov [es:ecx+96],dx              ;到TSS中
    765       
    766         mov dx,[es:esi+0x12]            ;登记任务的I/O位图偏移
    767         mov [es:ecx+102],dx             ;到TSS中 
    768       
    769         mov word [es:ecx+100],0         ;T=0
    770         
    771         ;在GDT中存入TSS信息
    772         mov eax,[es:esi+0x14]
    773         movzx ebx,word[es:esi+0x12]
    774         mov ecx,0x00408900
    775         call Sys_Routine_Segement:Make_Seg_Descriptor
    776         call Sys_Routine_Segement:Set_New_GDT
    777         mov [es:esi+0x18],cx
    778         
    779         pop es
    780         pop ds
    781         popad
    782         ret 8                            ;相当于是stdcall,过程清栈
    783         ;---------------------------------------------------------------------
    784     start:
    785         mov eax,Core_Data_Segement
    786         mov ds,eax
    787         
    788         mov ebx,message_1
    789         call Sys_Routine_Segement:put_string
    790         
    791         mov eax,0                    
    792         cpuid
    793         cmp eax,0x80000004            ;判断是否有0x80000002-0x80000004功能    
    794         jl _@load
    795         
    796         ;显示处理器品牌信息,从80486的后期版本开始引入
    797         mov eax,0x80000002
    798         cpuid
    799         mov [cpu_brand+0x00],eax
    800         mov [cpu_brand+0x04],ebx
    801         mov [cpu_brand+0x08],ecx
    802         mov [cpu_brand+0x0c],edx
    803         
    804         mov eax,0x80000003
    805         cpuid
    806         mov [cpu_brand+0x10],eax
    807         mov [cpu_brand+0x14],ebx
    808         mov [cpu_brand+0x18],ecx
    809         mov [cpu_brand+0x1c],edx
    810         
    811         mov eax,0x80000004
    812         cpuid
    813         mov [cpu_brand+0x20],eax
    814         mov [cpu_brand+0x24],ebx
    815         mov [cpu_brand+0x28],ecx
    816         mov [cpu_brand+0x2c],edx
    817         
    818         mov ebx,cpu_brnd0
    819         call Sys_Routine_Segement:put_string
    820         mov ebx,cpu_brand
    821         call Sys_Routine_Segement:put_string
    822         mov ebx,cpu_brnd1
    823         call Sys_Routine_Segement:put_string
    824 
    825         _@load:
    826         mov ebx,message_7
    827         call Sys_Routine_Segement:put_string
    828         ;----------------------------安装门------------------------------------
    829         mov edi,salt
    830         mov ecx,salt_items_sum
    831         _set_gate:
    832             push ecx
    833             mov eax,[edi+256]
    834             mov bx,[edi+260]        ;选择子
    835             mov cx,0xec00            ;门是特权级是3的门,那么任何程序都能调用
    836             or cx,[edi+262]            ;加上参数个数
    837             
    838             call Sys_Routine_Segement:Make_Gate_Descriptor
    839             call Sys_Routine_Segement:Set_New_GDT
    840             mov [edi+260],cx        ;回填选择子
    841             add edi,salt_length
    842             pop ecx
    843         loop _set_gate
    844         ;----------------------------------------------------------------------        
    845         mov ebx,do_status
    846         call far [salt_1+256]
    847         mov ebx,message_6
    848         call far [salt_1+256]
    849         mov ebx,message_In_Gate
    850         call far [salt_1+256]            ;调用门显示字符信息(忽略偏移地址(前4字节))
    851         
    852         mov ebx,message_4
    853         call far [salt_1+256]
    854         mov ebx,message_2
    855         call far [salt_1+256]
    856         
    857         ;创建TCB链:TCB不是所有内核的要求,但是必须要有记录任务的机制
    858         mov ecx,0x46
    859         call Sys_Routine_Segement:allocate_memory
    860         call append_to_tcb
    861         
    862         ;用栈去传数据,80386以后支持直接压一个双字,地址自己算
    863         push dword User_Program_Address    ;用户程序所在的逻辑地址
    864         push ecx                        ;传入TCB线性基地址
    865         
    866         call load_program
    867         mov ebx,do_status
    868         call Sys_Routine_Segement:put_string
    869         
    870         ;下一章讲任务切换的时候再改下面
    871         
    872         mov [core_ss],ss
    873         mov [core_sp],esp
    874         mov eax,All_4GB_Segment
    875         mov ds,eax
    876         
    877         ltr    [ecx+0x18]
    878         lldt [ecx+0x10]
    879         
    880         mov eax,[ecx+0x44]                ;用户头部选择子
    881         mov ds,eax
    882         
    883         push dword[0x08]                ;栈段寄存器
    884         push dword 0
    885         
    886         push dword[0x14]
    887         push dword[0x10]                ;切换特权栈段
    888         
    889         retf
    890         
    891         _return_point:
    892         pop eax
    893         pop eax                            ;清除残存在栈段CS和EIP
    894         
    895         mov eax,Core_Data_Segement
    896         mov ds,eax
    897         
    898         mov ss,[core_ss]
    899         mov esp,[core_sp]
    900         mov eax,Stack_Segement
    901         mov ss,eax                        ;重新设置数据段和栈段
    902         mov esp,[esp_pointer]
    903         mov ebx,message_4
    904         call Sys_Routine_Segement:put_string
    905         
    906         call Sys_Routine_Segement:recycled_memory_and_gdt
    907         mov ecx,[ram_alloc]
    908     
    909         mov ebx,message_5
    910         call Sys_Routine_Segement:put_string
    911         cli
    912         hlt
    913 ;=========================================================================
    914 SECTION core_trail
    915 ;----------------------------------------------------------------
    916 Program_end:
    ;==============================用户程序=======================================
    SECTION header vstart=0
    
            program_length   dd program_end          ;程序总长度#0x00
             
            head_len         dd header_end           ;程序头部的长度#0x04
    
            stack_seg        dd 0                    ;用于接收堆栈段选择子#0x08
            stack_len        dd 1                    ;程序建议的堆栈大小#0x0c
                                                     ;以4KB为单位
                                                      
            prgentry         dd start                ;程序入口#0x10 
            code_seg         dd section.code.start   ;代码段位置#0x14
            code_len         dd code_end             ;代码段长度#0x18
    
            data_seg         dd section.data.start   ;数据段位置#0x1c
            data_len         dd data_end             ;数据段长度#0x20
    ;-------------------------------------------------------------------------------
            ;符号地址检索表
            salt_items       dd (header_end-salt)/256 ;#0x24
             
            salt:                                     ;#0x28
            Printf:           db  '@Printf'
                         times 256-($-Printf) db 0
                         
            TerminateProgram:db  '@TerminateProgram'
                         times 256-($-TerminateProgram) db 0
                         
            ReadHarddisk:    db  '@ReadHarddisk'
                         times 256-($-ReadHarddisk) db 0
                     
    header_end:
    ;===============================================================================
    SECTION data align=16 vstart=0    
                             
            buffer times 1024 db  0         ;缓冲区
    
            message_1         db  0x0d,0x0a,0x0d,0x0a
                              db  '**********User program is runing**********'
                              db  0x0d,0x0a,0
            message_2         db  '  Disk data:',0x0d,0x0a,0
    
    data_end:
    
    ;===============================================================================
          [bits 32]
    ;===============================================================================
    SECTION code align=16 vstart=0
    start:
            User_Data_File     equ 100            ;数据文件存放地点
            mov eax,ds
            mov fs,eax
         
            mov eax,[stack_seg]
            mov ss,eax
            mov esp,0
         
            mov eax,[data_seg]
            mov ds,eax
         
            mov ebx,message_1
            call far [fs:Printf]
         
            mov esi,User_Data_File              
            mov ebx,buffer                      ;缓冲区偏移地址
            
            push esi
            push ds
            push ebx
            push cs
            call far [fs:ReadHarddisk]          ;相当于调用函数
         
            mov ebx,message_2
            call far [fs:Printf]
         
            mov ebx,buffer 
            call far [fs:Printf]           
         
            call far [fs:TerminateProgram]       ;将控制权返回到系统 
          
    code_end:
    
    ;===============================================================================
    SECTION trail
    ;-------------------------------------------------------------------------------
    program_end:

     

  • 相关阅读:
    VMWare的The network bridge on device VMnet0 is not running故障解决
    函数索引 ORA30553: The function is not deterministic 解决方法
    Oracle 9i EXP XDB.DBMS_XDBUTIL_INT must be declared
    Event 10053 执行计划 绑定变量 Bind peeking
    Oracle Data Guard 理论知识
    Oracle 9i ORA04062 timestamp of package SYS DBMS_SNAPSHOT_UTL has been changed ORA06512 at SYS DBMS_SNAPSHOT
    Oracle Data Guard Linux 平台 Logical Standby 创建实例
    Oracle RAC 归档 与 非归档 切换
    Oracle 9i EXP XDB.DBMS_XDBUTIL_INT must be declared
    ORA16014: log string sequence# string not archived, no available destinations Flash Recovery Area 空间不足
  • 原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/5281869.html
Copyright © 2011-2022 走看看