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:

 

 

posted @ 2016-03-16 00:22  PhiliAI  阅读(2137)  评论(0编辑  收藏  举报