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: