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

 

 

版权声明:本文为Philip-Tell-Truth原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/Philip-Tell-Truth/p/5281869.html