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: