ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

时间:2023-03-09 13:21:34
ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

★PART1:中断和异常概述

1. 中断(Interrupt)

  中断包括硬件中断和软中断。硬件中断是由外围设备发出的中断信号引发的,以请求处理器提供服务。当I/O接口发出中断请求的时候,会被像8259A和I/O APIC这样的中断寄存器手机,并发送给处理器。硬件中断完全是随机产生的,与处理器的执行并不同步。当中断发生的时候,处理器要先执行完当前的指令(指的是正在执行的指令),然后才能对中断进行处理。

  软中断是由int n指令引发的中断处理器,n是中断号(类型码)。

2. 异常(Exception)

  异常就是第9章略过的内部中断。内部中断是处理器内部产生的中断,表示在指令执行的时候遇到了错误。当处理器执行一条非法指令(引用一个不合标准的段,任务切换的时候TSS选择子不是有效的,访问了一个没有登记的页等等)。简单来说就是指令不能正常执行的时候,将引发这种类型的中断。

    异常分为三种:

  1. 程序错误异常,指处理器在执行指令的过程中,检测到了程序中的错误,并由此引发的错误。
  2. 软件引发的异常。这类异常通常是into,int3和bound指令主动发起的,这些指令允许在指令流的当前点上检查实施异常处理跌条件是否满足。比如一个例子,into指令在执行的时候,将检查EFLAGS寄存器的OF标志位,如果满足为“1”的条件,那么就引发异常。
  3. 第三种是机器检查异常。这种异常是处理器型号相关的,也就是说,每种处理器都不太一样。无论如何,处理器提供了一种对硬件芯片内部和总线处理进行检查的机制,当检测到有错误的时候,将引发此异常。

  根据异常情况的性质和严重性,异常又分为以下三种,并分别实施不同的特权保护。

  1. 故障(Faults)。故障通常是可以纠正的。最典型的就是处理器执行一个内存访问指令的时候,发现那个段或者页不在内存中(P=0),此时,可以在异常处理程序中予以纠正(分配内存,或者执行磁盘的换入换出操作)。返回时,程序可以重新启动并且不失连续性。当故障发生的时候,处理器把及其状态恢复到引发故障的那条指令之前的状态,在进入异常处理时,压入栈中的返回地址(CS和EIP的内容)是指向引起故障的那条指令的,而不像通常那样指向下一条指令。虚拟内存管理就是以异常为基础的。
  2. 陷阱(Traps)。陷阱中断通常在执行了解惑陷条件的指令立即产生,如果陷阱条件成立的话。陷阱通常用于调试目的,比如单步中断指令int3和溢出检测指令into。陷阱中断允许程序或者任务从中断处理过程返回后继续执行不失连续性。当陷阱异常发生的时候,转入异常处理程序之前,处理器在栈中压入陷阱截获指令的下一条指令地址。
  3. 终止(Aborts)。终止标志着最严重的错误,诸如硬件错误,系统表(GDT,LDT等)中的数据不一致或者无效。这种错误发生的时候,程序或者任务都不可能重新启动。

  对于某些异常,处理器在转入异常处理程序之前,会在当前栈中压入一个称为错误代码的数值,帮助程序进一步诊断异常产生的位置和原因。

ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

  现在解释一下一些比较陌生的中断:

  1. 80486之后,处理器内部集成了浮点运算器x87 FPU,不需要再安装独立的数学协处理器,所以有些的浮点运算有关的异常不会产生(比如向量为9的协处理器段超越故障)。Wait和fwait指令用于主处理器和浮点处理部件(FPU)之间的同步。他们应当放在浮点指令之后,以捕捉任何浮点的异常。
  2. 从1993年的Pentium处理器开始,引入了用于加速多媒体处理的多媒体拓展技术(Multi-Media eXtension,MMX),该指令使用单指令多数据(Single-Instruction,Multiple-Data,SIMD)执行模式,以便在64位的寄存器内实施并行的整数运算。随着处理器的更新换代,这项技术也多次拓展,第一次被称为SSE(SIMD Extension),第二次是SSE2,第三次是SSE3。和SIMD有关的异常是从Pentium III处理器开始引入的。
  3. bound(Check Array Index Against Bounds)指令用于检查数组的索引是否在边界之内,其格式为

bound r16,m16(目的操作数是寄存器,包含了数组的索引,源操作数必须指向内存位置,里面包含了成对出现的字,分别十数组的上限和下限,如果数组索引不在上下限之内,则引发异常

bound r32,m32(和上面的基本一样,除了寄存器是32位的,而且内存位置是包含了成对出现的双字)。

  1. ud2(Undefined Instruction)指令是从Pentium Pro处理器开始引入的,他只有操作码没有操作数,执行该指令时会引发一个无效操作码的异常(用于软件测试),这个异常触发时压入的是指向本身的指令指针。

3. 中断描述符表,中断门和陷阱门,中断和异常处理程序

  在保护模式下,处理器不是用的中断向量表来处理中断的,取而代之的是中断描述符表(Interrupt Descriptor Table,IDT),中断描述符表存放的是中断门,陷阱门和任务门。其中中断门和陷阱门是只能放在IDT中。和IVT不一样的是,IDT不要求必须位于内存的最低端。在处理器内部,有一个48位的中断描述符表寄存器(Interrupt Descriptor Table Register,IDTR),保存着中断描述符表在内存中的线性基地址和界限,IDTR只有一个,和GDTR的储存格式是一样的。中断门,陷阱门描述符格式和中段描述符表寄存器的结构如下:

  中断门:

ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

  陷阱门:

ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

  注意,D位是0时,表示的是16位模式下的门,用于兼容早期的16位保护模式;为1时,就是表示32位的门

  中断描述符表寄存器,长得和GDTR差不多:

ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

  中断描述符表IDT可以位于内存的任何地方,只要IDTR指向了它,整个终端系统就可以正常的工作。为了利用高速缓存使处理器的工作性能最大化,处理器建议IDT的基地址是8字节对齐的。处理器复位的时候,IDTR的基地址部分是0,界限部分是0xFFFF(和GDTR是一样的)。处理器只识别256个中断,所以LDT通常只用2KB。和GDT不一样的是,IDT的第一个槽位可以不是0描述符。

  在保护模式下处理器执行中断的时候,先根据相应的中断号乘以8加上IDT的基地址得到相应的中段描述符的位置(如果有页映射也是根据页的映射规则来找到相应的描述符),和通过调用门试试的控制转移一样,处理器也要对中断和异常处理程序进行特权级的保护。但是在中断和异常的特权级检查中有特殊的情况。因为中断和异常的理想两没有RPL,所以处理器在进入中断或者异常处理程序的时候,或者通过人物们发起任务切换的时候,不检查RPL。和普通的门调用一样,CPL要在数值上小于等于目标代码段的DPL才可以执行代码段的切换,但是对于门的DPL的检查中,除了软中断int n和单步中断int3以及into引发的中断和异常外,处理器不对门的DPL进行特权级检查,如果是以上三种中断命令引发的中断,则要求CPL<=门描述符的DPL。(主要是为了防止软中断引发的越权操作)。

如果发生了特权级的转变(比如从局部空间转移到了全局空间)。那么要进行栈切换。压栈顺序如下:

    1. 根据处理器的特权级别,从当前任务的TSS中取得栈段选择子和栈指针。处理器把旧的栈的选择子和栈指针压入新栈。如果中断处理程序的特权级别和当前特权级别一致。则不用转换栈。
    2. 处理器把EFLGAS压入栈,然后把CS压栈,然后再压栈EIP。
    3. 如果有错误代码的异常,处理器还要将错误代码压入新栈,紧挨着EIP之后。

中断门和陷阱门的区别就是对IF位的处理不同。通过中断门进入中断处理程序的收,EFLAGS寄存器的IF位被处理器自动清零。以禁止嵌套的中断,当中断返回的时候,从栈中恢复EFLAGS的原始状态。陷阱中断的优先级比较低,当通过陷阱门进入中断处理程序的时候,EFLAGS寄存器的IF位不变,以允许其他中断的优先处理。EFLAGS寄存器的IF位仅影响硬件中断,对NMI,异常和int n形式的中断不起作用。

和GDT一样,如果要访问的位置超过了IDT的界限,那么就会产生常规保护异常(#GP)。

4. 中断任务

中断任务切换指的是通过在IDT的任务门发起的任务切换。硬件中断发生是客观的,可以用中断来实现抢占式的多任务系统(硬件调度机制,代价很大,需要保存大量的机器状态,现代操作系统都是用的软切换)。

可以想一下,如果在某个任务中发出了双重中断(#DF),是一种终止类型的中断,如果把双重中断的处理程序定义成任务,那么当双重故障发生的时候,可以执行任务切换返回内核,并且抹去出错程序,回收其内存空间,然后执行其他调度,这样会非常的自然。

中断机制使用任务门有以下特点:

  1. 被中断的程序或者任务的整个环境被保存到TSS中。
  2. 切换的新任务有自己的栈和虚拟内存空间,防止系统因为出错而崩溃。

中断或者异常发起的任务切换,不再保存CS和EIP的状态,但是任务切换后,如果有错误代码,还是要把错误代码压入新任务要栈中。要注意的是,任务是不可以重入的,在执行中断任务之后和执行其iret之前,必须关中断,以防止因为相同的中断而产生常规保护异常(#GP)。

5. 错误代码

ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

  错误代码如上图所示,错误代码的高16位是不用的。

  EXT位表示,异常是由外部事件引发的(External Event)。此位是1的时候,表示异常是由NMI,硬件中断引发的。

  IDT位用于指示描述符的位置(Descriptor Location)。为1时则表示段选择子的索引部分是存在于中段描述符IDT的;为0时,则表示在GDT或者LDT中。

  TI位仅仅在IDT为0的时候才有意义,当此位是0时,则表示段的选择子的索引部分是存在于GDT的,否则在LDT。

  当错误代码全都是0的时候,这表示异常的产生并非是由于引用一个段产生的(比如也有可能是页错误,访问了一个没有登记的页),也有可能是因为应用了一个空描述符的时候发生的。需要注意的是,在执行iret指令从中断处理程序返回的时候,处理器并不会自动弹出错误代码,对于那些会压入错误代码的处理过程来说,在执行iret时,必须把错误代码弹出。

  特别注意,对于外部异常(通过处理器引脚触发),以及用软中断指令int n引发的异常,处理器不会压入错误代码,即使是有错误代码的异常。分配给外部中断的向量号在31~255之间,处于特殊目的,外部的8259A或者I/O APIC芯片可能给出一个0~19的向量号,比如13(常规异常保护#GP),并希望进行异常处理。在这种情况下处理器不会压入错误代码。如果用软中断有意引发的异常,也不会压入错误代码。

★PART2:加载内核和用户程序

1. 平坦模式

一旦使用了页管理,很多事情都会得到简化了,比如段管理模型,每次操作内存都要注意引用的段有没有错误,太麻烦了,所以我们直接用平坦模式,在平坦模式下,程序的数据段4GB,代码段也是4GB的,从0开始分段,一直到4GB最高端。把程序改成平坦模式然后使用页管理,能大量减少代码量。

具体从代码上就可以看到怎么实现了,其实也很简单稍微改下就好了,现在就是注意几个坑就好了,由于使用了平坦模式,内核无法重定位,所以内核的vstart一定要是0x80040000(注意使用了页管理以后,所有对内存的操作都要页映射,包括内核的段起始地址。

2. 创建中断描述符表

  进入内核以后,第一件事情就是先把中断先设置好了,然后才能关中断(注意在所有的中断处理程序没安装完之前,千万不能开中断,否则就是gate_interrupt)。安装也很简单,也就是一堆门而已。

  ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

  ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

  ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

通用的异常处理程序和中断程序的处理都很简单,异常直接停机(注意异常不是每次都会有错误代码),普通中断就直接返回就好了。我们的DEMO演示的是时钟中断,这个中断在第九章就已经讲过了,中断号是0x70,所以现在我们就可以对这个中断进行特殊处理,让他可以进行任务切换,TCB和上一章的TCB是一样的,这里实现的原理就是不断遍历链表,然后找到第一个不忙的任务进行切换,然后把被切换的任务的TCB挂到链表的最后。这个和C写出来的遍历链表的思想是一样的。

注意我们的内核的TCB规定一个任务如果是忙,那么任务状态位(0x04)就是0xffff,如果是空闲那么是0x0000,所以才有取反指令的存在。事实上这样的找任务的方法是很慢的,每一次遍历链表都要花费O(n)的时间复杂度,很慢,在Linux等高级操作系统中,使用红黑树来等数据结构来管理程序,而且用的是软切换(不用TSS硬切换,不用保存大量的机器状态)。

3. 8259A芯片的初始化

  这个已经在我转的一篇文章写的很清楚了,我们初始化只要按照上面的来就可以了,比教材讲的详细多了,(http://www.cnblogs.com/Philip-Tell-Truth/articles/5169767.html看这里)。

  最后我们来用代码实现一遍:

  ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

4. 转换后援缓冲器(Translation Lookaside Buffer,TLB)

开启页功能的时候,处理器页部件要把线性地址转换成物理地址,而访问页目录和页表是相当费时间的,因此,把页表项预先放到处理器中,可以加快转换处理,为此,处理器专门够早了一个特殊的高速缓存器,叫做转换后援缓冲器。如图所示:

ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

在分页模式下,当段部件发出一个线性地址的时候,处理器用线性地址的高20位来查找TLB,如果直接找到匹配项,那么直接用其数据部分的物理地址作为转换用的地址;如果检索不成功,那么就按照页目录-页表-页的顺序来找到相应的页。并把它填写到TLB中。TLB的容量是有限的,如果装满了处理器就会将一些项给清除掉。

TLB中的属性为来自页表项,比如页表项的D位(Dirty);访问权位来自页目录项的对应页表项。比如RW和US位。在分页机制中,对页的访问控制按照最严格的访问权执行。对于某个线性地址,如果其页目录项的位是“0”,而页表项的RW位是1,那么就按照RW是0来存储(TLB的访问权对应页表和页目录项的逻辑与)。

处理器仅仅会缓存那些P位是1的那些页表项,而且,TLB的工作和CR3寄存器的PCD位和PWT是无关的。对于页表项的修改不会同时反映到TLB中,一定要刷新TLB,不然对页表的设置就是无效的。TLB是软件不可直接访问的,只能通过显式刷新CR3,或者任务切换隐式刷新TLB,这样刷新过后TLB的所有条目都会是无效的,但是要注意的是,这样的刷新方法对于那些标记为全局(G=1)的页表无效。

ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

TLB还可以单个刷新,利用invlpg命令(invalidate TLB Entry)。invlpg的格式为invlpg m32,当执行这条指令的时候,处理器会用给出的线性地址搜索TLB,找到那个条目,然后从内存中重新加载其内容到相应的TLB页表数据中。invlpg是特权指令,必须要在CPL为特权0级执行,该指令不影响任何标志位。

我们的内核进行刷新TLB的是在加载程序之前复制页目录的时候做的。但是我自己写的程序加载位置是可变的,其实不刷新也没什么关系。教材那个就一定要刷新。具体看代码。

5. 宏汇编技术(Macro)

  所谓的宏汇编技术,其实和C的宏是一样的,就是一个字符串代替一堆东西而已,当然了也可以带参数。

  1. 单行宏%define:

  顾名思义这种宏只能定义单行的比如:

  ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

2. 多行宏%macro:

这种宏的后面都要带%endmacro作为指定宏结束的位置。而且多行宏可以指定参数个数

  ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务

参数的个数直接定义在宏名称的后面,使用的时候宏内参数由%加对应数字引用参数,上面的例子已经说得很清楚了,如果没有参数,那么参数个数直接设为0。

★PART3:本章的程序

说实话本章的练习题没什么好写的,就把例程写一遍就好了,我自己写的时候自己写了一个很大的坑就是我的宏写错了,导致自己访问内存的时候一直显示页错误(其实是调试了很久才知道是页错误,访问了一个没有登记的页)。而且要注意的是,一些关键的过程,比如put_string,读硬盘和TCB的链接这些过程,一定要关中断,不然会引发系统严重错误。

教材上用的中断只是关闭了从片的中断,我改了一下只留时钟中断,而且是更新结束中断,然后程序可以停机然后给时间中断唤醒,这样感觉会更清晰一点。

1. 主引导程序MBR      

 ;========================保护模式主引导扇区代码========================
core_phy_base: equ 0x00040000 ;内核加载地址
core_sector_address: equ 0x00000001 ;内核所在扇区
;======================================================================
SECTION mbr align= vstart=0x00007c00 ;注意起始地址已经变成了0x7c00了
mov ax,cs
mov ss,ax
mov sp,0x7c00 mov eax,[cs:pgdt_base+0x02]
xor edx,edx
mov ebx,0x10
div ebx mov ds,eax ;让ds指向gdt位置进行操作
mov ebx,edx ;别忘了还有可能出现偏移地址
;---------------------描述符#0---------------------
mov dword [ebx+0x00],0x00000000 ;空描述符
mov dword [ebx+0x04],0x00000000
;---------------------描述符#1---------------------
mov dword [ebx+0x08],0x0000ffff ;4GB代码段,特权级为0
mov dword [ebx+0x0c],0x00cf9800
;---------------------描述符#2---------------------
mov dword [ebx+0x10],0x0000ffff ;4GB向上拓展数据段和栈段,特权级为0
mov dword [ebx+0x14],0x00cf9200 mov word[cs:pgdt_base], ;加载gdt
lgdt [cs:pgdt_base] in al,0x92 ;快速开启A20
or al,0x02 ;是写入2,不要搞错了,写入1就是重启了
out 0x92,al
cli ;关掉中断 mov eax,cr0
or eax,0x01 ;设置PE位
mov cr0,eax jmp dword 0x0008:flush ;进入保护模式 [bits ]
flush:
mov eax,0x0010
mov ds,eax
mov es,eax
mov fs,eax
mov gs,eax
mov ss,eax ;栈段也是向上拓展的
mov esp,0x7000 ;接下来开始读取内核头部
mov esi,core_sector_address
mov edi,core_phy_base
call read_harddisk_0 mov eax,[core_phy_base] ;读取用户总长度
xor edx,edx
mov ebx,
div ebx cmp edx,
jne @read_last_sector
dec eax
@read_last_sector:
cmp eax,
je @setup
mov ecx,eax
.read_last:
inc esi
call read_harddisk_0
loop .read_last
@setup: ;下面准备开启页管理
mov ecx,
mov ebx,0x00020000
xor esi,esi _flush_PDT: ;清空页表
mov dword[es:ebx+esi*],0x00000000
inc esi
loop _flush_PDT ;页目录的最后一个32字节是指向自己的页表(这个页表就是页目录)
mov dword[ebx+],0x00020003 ;属性:存在于物理内存,只允许内核自己访问 ;页目录的第一个页表指示最底下1MB内存的4KB页(内核代码,必须虚拟地址和物理地址一致)
mov edx,0x00021003
mov dword[ebx+0x000],edx ;低端映射(临时的,创建用户目录的时候就没了)
mov dword[ebx+0x800],edx ;高端映射 ;现在0x00020000的页是第0个页表(指示页目录),0x00021000是第一个页表(指示底下1MB的东西)
mov ebx,0x00021000
xor eax,eax
xor esi,esi _make_page:
mov edx,eax
or edx,0x00000003 ;属性:存在于物理内存,只允许内核自己访问
mov [ebx+esi*],edx
add eax,0x1000
inc esi
cmp esi,
jl _make_page mov eax,0x00020000
mov cr3,eax ;把页目录基地址放在cr3,准备开启页功能 sgdt [pgdt_base]
add dword[pgdt_base+],0x80000000 ;设定GDT为高地址
lgdt [pgdt_base] mov eax,cr0
or eax,0x80000000
mov cr0,eax ;置PG位,开启页功能 ;将堆栈映射到高端,这是非常容易被忽略的一件事。应当把内核的所有东西
;都移到高端,否则,一定会和正在加载的用户任务局部空间里的内容冲突,
;而且很难想到问题会出在这里。
add esp,0x80000000 ;因为已经处于平坦模式了,所以内核栈指针也要映射 jmp [core_phy_base+0x80000000+] ;都在一个段上了,直接近转移,start在偏移量是4的地方
;=============================函数部分=================================
read_harddisk_0: ;esi存了28位的硬盘号
push ecx mov edx,0x1f2 ;读取一个扇区
mov al,0x01
out dx,al mov eax,esi ;0~7位,0x1f3端口
inc edx
out dx,al mov al,ah ;8~15位,0x1f4端口
inc edx
out dx,al shr eax, ;16-23位,0x1f5端口
inc edx
out dx,al mov al,ah ;24-28位,LBA模式主硬盘
inc edx
and al,0x0f
or al,0xe0
out dx,al inc edx ;读命令,0x1f7端口
mov al,0x20
out dx,al .wait:
in al,dx
and al,0x88
cmp al,0x08
jne .wait mov dx,0x1f0
mov ecx,
.read:
in ax,dx
mov [edi],ax
add edi,
loop .read pop ecx ret
;======================================================================
pgdt_base dw
dd 0x00008000 ;GDT的物理地址
;======================================================================
times -($-$$) db
dw 0xaa55

2. 内核程序

 ;============================内核程序=================================
;定义内核所要用到的选择子
All_4GB_Segment equ 0x0018 ;4GB的全内存区域
Core_Code_Segement equ 0x0008 ;内核代码段
IDT_Liner_Address equ 0x8001F000 ;IDT线性地址
;----------------------------------------------------------------
User_Program_AddressA equ ;用户程序所在逻辑扇区
User_Program_AddressB equ ;用户程序所在逻辑扇区
Switch_Stack_Size equ ;切换栈段的大小
Global_Page_Directory equ 0x80000000 ;给全局空间的映射地址
;----------------------------------------------------------------
%macro alloc_core_page ;给内核程序安排页
mov ebx,[core_tcb+0x06]
add dword[core_tcb+0x06],0x1000 ;注意这里是加法指令,写错了就会页故障(因为已经清空页了,访问一个不存在的页)
call Core_Code_Segement:alloc_inst_a_page
%endmacro
;----------------------------------------------------------------
%macro alloc_user_page ;给用户程序安排页
mov ebx,[esi+0x06]
add dword[esi+0x06],0x1000
call Core_Code_Segement:alloc_inst_a_page
%endmacro
;----------------------------------------------------------------
%macro Read_Data_From_Harddisk
push esi
push ds
push ebx
push cs
call Core_Code_Segement:ReadHarddisk
%endmacro
;=========================================================================
;============================公用例程区===================================
;=========================================================================
SECTION Code align= vstart=0x80040000 ;注意代码段的开始现在是0x80040000了,映射的线性地址
Program_Length dd Program_end ;内核总长度
Code_Entry dd start ;注意偏移地址一定是32位的
;----------------------------------------------------------------
[bits ]
;----------------------------------------------------------------
ReadHarddisk: ;push1:28位磁盘号(esi)
;push2:应用程序数据段选择子(ax->ds)
;push3: 偏移地址(ebx)
;push4: 应用程序代码段选择子(dx)
cli
pushad mov ebp,esp mov esi,[ebp+*]
movzx eax,word[ebp+*]
mov ebx,[ebp+*]
movzx edx,word[ebp+*] arpl ax,dx
mov ds,ax mov dx,0x1f2
mov al,0x01 ;读一个扇区
out dx,al inc edx ;0-7位
mov eax,esi
out dx,al inc edx ;8-15位
mov al,ah
out dx,al inc edx ;16-23位
shr eax,
out dx,al inc edx ;24-28位,主硬盘,LBA模式
mov al,ah
and al,0x0f
or al,0xe0
out dx,al inc edx
mov al,0x20
out dx,al _wait:
in al,dx
and al,0x88
cmp al,0x08
jne _wait mov dx,0x1f0
mov ecx,
_read:
in ax,dx
mov [ebx],ax
add ebx,
loop _read popad
sti
retf ;4个数据
;----------------------------------------------------------------
put_string: ;ebx:偏移地址
cli ;必须关中断
pushad _print:
mov cl,[ebx]
cmp cl,
je _exit
call put_char
inc ebx
jmp _print
_exit:
popad
sti ;记得把中断开了
retf
;--------------------------------------------------------------
put_char: ;cl就是要显示的字符
pushad mov dx,0x3d4
mov al,0x0e ;高8位
out dx,al
mov dx,0x3d5
in al,dx
mov ah,al ;先把高8位存起来
mov dx,0x3d4
mov al,0x0f ;低8位
out dx,al
mov dx,0x3d5
in al,dx ;现在ax就是当前光标的位置
mov bx,ax
and ebx,0x0000ffff ;准备用32位寻址来显示 _judge:
cmp cl,0x0a
je _set_0x0a
cmp cl,0x0d
je _set_0x0d
_print_visible:
shl bx,
mov [0x800b8000+ebx],cl
mov byte[0x800b8000+ebx+],0x07
shr bx,
inc bx ;以下将光标位置推进一个字符
jmp _roll_screen
_set_0x0d: ;回车
mov ax,bx
mov bl,
div bl
mul bl
mov bx,ax
jmp _set_cursor
_set_0x0a: ;换行
mov bx,ax
add bx,
jmp _roll_screen
_roll_screen:
cmp bx,
jl _set_cursor cld
mov edi,0x800b8000 ;一定要记住,现在内存的地址全都是虚拟地址,偏移地址也是一样的
mov esi,0x800b80a0
mov ecx,
rep movsw
_cls:
mov ebx,
mov ecx,
_print_blank:
mov word[0x800b8000+ebx],0x0720
add bx,
loop _print_blank
mov ebx, ;别总是忘了光标的位置!
_set_cursor: ;改变后的光标位置在bx上
mov dx,0x3d4
mov al,0x0f ;低8位
out dx,al mov al,bl
mov dx,0x3d5
out dx,al mov dx,0x3d4
mov al,0x0e ;高8位
out dx,al mov al,bh
mov dx,0x3d5
out dx,al popad
ret
;----------------------------------------------------------------
Make_Seg_Descriptor: ;构造段描述符
;输入:
;eax:线性基地址
;ebx:段界限
;ecx:属性
;输出:
;eax:段描述符低32位
;edx:段描述符高32位
mov edx,eax
and edx,0xffff0000
rol edx,
bswap edx
or edx,ecx shl eax,
or ax,bx
and ebx,0x000f0000
or edx,ebx
retf
;----------------------------------------------------------------
Make_Gate_Descriptor: ;构造门描述符
;输入:
;eax:段内偏移地址
;bx: 段的选择子
;cx: 段的属性
;输出:
;eax:门描述符低32位
;edx:门描述符高32位
push ebx
push ecx mov edx,eax
and edx,0xffff0000 ;要高16位
or dx,cx shl ebx,
and eax,0x0000ffff
or eax,ebx pop ecx
pop ebx retf
;----------------------------------------------------------------
Set_New_GDT: ;装载新的全局描述符
;输入:edx:eax描述符
;输出:cx选择子
sgdt [pgdt_base_tmp] movzx ebx,word[pgdt_base_tmp]
inc bx ;注意这里要一定是inc bx而不是inc ebx,因为gdt段界限初始化是0xffff的
;要用到回绕特性
add ebx,[pgdt_base_tmp+0x02] ;得到pgdt的线性基地址 mov [es:ebx],eax
mov [es:ebx+0x04],edx ;装载新的gdt符
;装载描述符要装载到实际位置上 add word[pgdt_base_tmp], ;给gdt的段界限加上8(字节) lgdt [pgdt_base_tmp] ;加载gdt到gdtr的位置和实际表的位置无关 mov ax,[pgdt_base_tmp] ;得到段界限
xor dx,dx
mov bx, ;得到gdt大小
div bx
mov cx,ax
shl cx, ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级) retf
;----------------------------------------------------------------
Set_New_LDT_To_TCB: ;装载新的局部描述符
;输入:edx:eax描述符
; : ebx:TCB线性基地址
;输出:cx选择子
push edi
push eax
push ebx
push edx mov edi,[ebx+0x0c] ;LDT的线性基地址
movzx ecx,word[ebx+0x0a]
inc cx ;得到实际的LDT的大小(界限还要-1) mov [edi+ecx+0x00],eax
mov [edi+ecx+0x04],edx add cx,
dec cx mov [ebx+0x0a],cx mov ax,cx
xor dx,dx
mov cx,
div cx shl ax,
mov cx,ax
or cx,0x0004 ;LDT,第三位TI位一定是1 pop edx
pop ebx
pop eax
pop edi
retf
;----------------------------------------------------------------
allocate_4KB_page: ;输入:无
;输出eax:页的物理地址
;注意这个是近调用
push ebx
push ecx
push edx
xor eax,eax _search_pages:
bts [page_bit_map],eax
jnc _found_not_uesd
inc eax
cmp eax,page_map_len*
jl _search_pages mov ebx,No_More_Page
call Core_Code_Segement:put_string
hlt ;无可用页,直接停机 _found_not_uesd:
shl eax, ;eax相当于是选择子,乘以一个4KB得到物理地址 pop edx
pop ecx
pop ebx
ret
;----------------------------------------------------------------
alloc_inst_a_page: ;分配一个页,并安装在当前活动的层级分页结构中
;输入:EBX=页的线性地址
;输出:无
push eax
push ebx
push edi
push esi _test_P: ;在页目录中看是否存在这个页表
mov esi,ebx
and esi,0xffc00000
shr esi,
or esi,0xfffff000 ;指向页目录本身
test dword[esi],0x00000001
jnz _get_page_and_create_new_page
_create_new_page_directory:
call allocate_4KB_page
or eax,0x00000007 ;存在于主存,可读可写,允许特权级3程序访问
mov [esi],eax
_get_page_and_create_new_page:
mov esi,ebx
shr esi, ;页表在页目录的偏移项
and esi,0x003ff000 ;得到页表的偏移地址
or esi,0xffc00000 ;指向页目录 and ebx,0x003ff000
shr ebx, ;中间10位是页目录-页表-表内偏移量(注意这里的层次理解)
or esi,ebx ;esi就是页的对应线性地址
call allocate_4KB_page
or eax,0x00000007 ;存在于主存,可读可写,允许特权级3程序访问
mov [esi],eax pop esi
pop edi
pop ebx
pop eax
retf
;----------------------------------------------------------------
Copy_Page: ;把在创建的包含全局和私有部分的页表复制一份给用户程序用
;输入:无
;输出eax:页的物理地址
push edi
push esi
push ebx
push ecx
push edx mov edx,[task_pos] ;注意这里不需要加上内核的偏移了,因为程序开始的时候已经有了start
sub edx,
add edx,0xfffff000
invlpg [edx] ;刷新单条TLB
mov edi,[page_soft_header]
sub edi,0x1000
mov esi,0xfffff000 ;指向全局页目录 call allocate_4KB_page
mov ebx,eax
or ebx,0x00000007
mov [edx],ebx mov ecx,
cld
repe movsd pop edx
pop ecx
pop ebx
pop esi
pop edi
retf
;----------------------------------------------------------------
;-------------------------------------------------------------------------------
general_interrupt_handler: ;通用的中断处理过程
push eax
mov al,0x20 ;中断结束命令EOI
out 0xa0,al ;向从片发送
out 0x20,al ;向主片发送
pop eax
iretd
;-------------------------------------------------------------------------------
general_exception_handler: ;通用的异常处理过程
mov ebx,excep_msg
call Core_Code_Segement:put_string
hlt
;-------------------------------------------------------------------------------
rtm_0x70_interrupt_handle: ;实时时钟中断处理过程
pushad mov al,0x20 ;直接给8259发EOI终止操作了
out 0x20,al
out 0xa0,al mov al,0x0c ;允许NMI中断
out 0x70,al
in al,0x71 ;读一下RTC的寄存器C,否则只发生一次中断 mov eax,tcb_chain _Search_Not_In_Service:
mov ebx,[eax]
cmp ebx,0x00000000
je _out_interrupt ;说明已经到链表的末尾了,直接就推出中断就好了
cmp word[ebx+0x04],0xffff ;任务状态为忙
je _found_current_task
mov eax,ebx
jmp _Search_Not_In_Service _found_current_task:
mov ecx,[ebx]
mov [eax],ecx ;拆除节点,ebx就是节点地址了
_Last_Pos:
mov edx,[eax]
cmp edx,0x00000000
je _Hang
mov eax,edx
jmp _Last_Pos
_Hang:
mov [eax],ebx ;挂到末端
mov dword[ebx],0x00000000 ;节点的末尾标记一下 mov eax,tcb_chain
_found_free_task:
mov eax,[eax]
cmp eax,0x00000000
je _out_interrupt
cmp word[eax+0x04],0x0000
jne _found_free_task ;找到第一个空闲任务 ;取反任务状态
not word[eax+0x04]
not word[ebx+0x04]
jmp far[eax+0x14] ;直接任务切换 _out_interrupt:
popad
iretd
;-------------------------------------------------------------------------------
Stop_This_Program:
hlt
retf
;-------------------------------------------------------------------------------
;=========================================================================
;===========================内核数据区====================================
;=========================================================================
pgdt_base_tmp: dw
dd
pidt_base_tmp: dw
dd
salt:
salt_1: db '@Printf' ;@Printf函数(公用例程)
times -($-salt_1) db
dd put_string
dw Core_Code_Segement
dw ;参数个数 salt_2: db '@ReadHarddisk' ;@ReadHarddisk函数(公用例程)
times -($-salt_2) db
dd ReadHarddisk
dw Core_Code_Segement
dw ;参数个数 salt_3: db '@Stop_This_Program' ;@Stop_This_Program函数(公用例程)
times -($-salt_3) db
dd Stop_This_Program
dw Core_Code_Segement
dw ;参数个数 salt_length: equ $-salt_3
salt_items_sum equ ($-salt)/salt_length ;得到项目总数 salt_tp: dw ;任务门,专门拿来给程序切换到全局空间的 message_start db ' Working in system core with protection '
db 'and paging are all enabled.System core is mapped '
db 'to address 0x80000000.',0x0d,0x0a,
message_In_Gate db ' Hi!My name is Philip:',0x0d,0x0a,
core_msg0 db ' System core task running!',0x0d,0x0a,
No_More_Page db '********No more pages********',
excep_msg db '********Exception encounted********', bin_hex db '0123456789ABCDEF'
;put_hex_dword子过程用的查找表
core_buf times db ;内核用的缓冲区(2048个字节(2MB)) core_tcb times db ;内核(程序管理器)的TCB
;假设只有2MB内存可以用的意思,正确的做法应该先读PCI(E),然后再分配!
page_bit_map db 0xff,0xff,0xff,0xff,0xff,0x55,0x55,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
db 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
page_map_len equ $-page_bit_map
core_next_laddr dd 0x80100000 ;内核空间中下一个可分配的线性地址
task_pos dd 0x00000ffc ;任务程序的页表在全局页目录的偏移
page_soft_header dd 0xfffff000 ;加载页目录地址 tcb_chain dd ;任务控制块链头指针
;=========================================================================
;===========================内核代码区====================================
;=========================================================================
;---------------------------------------------------------------------
append_to_tcb: ;写入新的TCB链
;输入:ecx新的TCB线性基地址
cli ;必须关中断,如果过程中间发生了0x70中断那么新内核就会崩溃
pushad mov eax,tcb_chain
_search_tcb:
mov ebx,[eax]
cmp ebx,0x00000000
je _out_tcb_search
mov eax,ebx
jmp _search_tcb
_out_tcb_search:
mov [eax],ecx
mov dword[ecx],0x00000000
popad
sti
ret
;---------------------------------------------------------------------
load_program: ;输入push1:逻辑扇区号
; push2: 线性基地址
pushad mov ebp,esp ;别忘了把参数传给ebp mov ebx,0xfffff000
xor esi,esi
_flush_private: ;必须清空!
mov dword[ebx+esi*],0x00000000
inc esi
cmp esi,
jl _flush_private ;手动刷新页目录缓存
mov eax,cr3 ;本来这个在16章就应该出现的,不刷新的话页目录的缓存还是旧的表项
mov cr3,eax mov esi,[ebp+*] ;esi必须是逻辑扇区号
mov ebx,core_buf ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了)
Read_Data_From_Harddisk mov eax,[core_buf] ;读取用户程序长度
mov ebx,eax
and ebx,0xfffff000 ;清空低12位(强制对齐4096:4KB)
add ebx,
test eax,0x00000fff
cmovnz eax,ebx ;低12位不为0则使用向上取整的结果 mov ecx,eax
shr ecx, ;获取占的页数
mov edi,[ebp+*] ;获取tcb的线性基地址
mov esi,[ebp+*] ;esi必须是逻辑扇区号 _loop_read_@1:
;注意这个过程和下面的不一样,要使用edi,esi是逻辑号,不要用宏
mov ebx,[edi+0x06]
mov dword[edi+0x06],0x1000
call Core_Code_Segement:alloc_inst_a_page push ecx
mov ecx, ;512*8==4096
_loop_read_@2:
Read_Data_From_Harddisk
inc esi
add ebx,
loop _loop_read_@2
pop ecx
loop _loop_read_@1 mov esi,edi ;esi: TCB的线性基地址 alloc_core_page ;给TSS分配内核页
mov [esi+0x14],ebx ;填写TSS的线性基地址
mov word[esi+0x12], ;无I/O映射 alloc_user_page ;LDT的用户页
mov [esi+0x0c],ebx ;填写LDT的线地址
mov edi,[esi+0x14] ;edi就是TSS的线性地址 ;代码段
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0f800
call Core_Code_Segement:Make_Seg_Descriptor
mov ebx,esi
call Core_Code_Segement:Set_New_LDT_To_TCB
or cx,0x0003 ;特权级为3
mov [edi+],cx ;CS域 ;数据段
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0f200
call Core_Code_Segement:Make_Seg_Descriptor
mov ebx,esi
call Core_Code_Segement:Set_New_LDT_To_TCB
or cx,0x0003 ;特权级为3
mov [edi+],cx ;ES,DS,FS,GS域,已经映射到全局空间了
mov [edi+],cx
mov [edi+],cx
mov [edi+],cx ;创建一系列栈
;创建自身特权级为3的栈
alloc_user_page
mov [edi+],cx ;cx是数据段的选择子
mov edx,[esi+0x06] ;向上拓展的ESP的初始值
mov [edi+],edx ;创建特权级0的栈
alloc_user_page
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c09200 ;4KB粒度的堆栈段描述符,特权级0
call Core_Code_Segement:Make_Seg_Descriptor
mov ebx,esi
call Core_Code_Segement:Set_New_LDT_To_TCB
or cx,0x0000 ;选择子特权级为0 mov [edi+],cx ;cx是数据段的选择子
mov edx,[esi+0x06] ;向上拓展的ESP0的初始值
mov [edi+],edx ;创建特权级1的栈
alloc_user_page
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0b200 ;4KB粒度的堆栈段描述符,特权级1
call Core_Code_Segement:Make_Seg_Descriptor
mov ebx,esi
call Core_Code_Segement:Set_New_LDT_To_TCB
or cx,0x0001 ;选择子特权级为1 mov [edi+],cx ;cx是数据段的选择子
mov edx,[esi+0x06] ;向上拓展的ESP1的初始值
mov [edi+],edx ;创建特权级2的栈
alloc_user_page
mov eax,0x00000000
mov ebx,0x000fffff
mov ecx,0x00c0d200 ;4KB粒度的堆栈段描述符,特权级2
call Core_Code_Segement:Make_Seg_Descriptor
mov ebx,esi
call Core_Code_Segement:Set_New_LDT_To_TCB
or cx,0x0002 ;选择子特权级为2 mov [edi+],cx ;cx是数据段的选择子
mov edx,[esi+0x06] ;向上拓展的ESP2的初始值
mov [edi+],edx ;现在开始重定位API符号表
;---------------------------------------------------------------------
cld
mov ecx,[0x0c]
mov edi,[0x08] _loop_U_SALT:
push edi
push ecx mov ecx,salt_items_sum
mov esi,salt _loop_C_SALT:
push edi
push esi
push ecx mov ecx, ;比较256个字节
repe cmpsd
jne _re_match ;如果成功匹配,那么esi和edi刚好会在数据区之后的 mov eax,[esi] ;偏移地址
mov [es:edi-],eax ;把偏移地址填入用户程序的符号区
mov ax,[esi+0x04] ;段的选择子 or ax,0x0002 ;把RPL改为3,代表(内核)赋予应用程序以特权级3
mov [es:edi-],ax ;把段的选择子填入用户程序的段选择区 _re_match:
pop ecx
pop esi
add esi,salt_length
pop edi
loop _loop_C_SALT pop ecx
pop edi
add edi,
loop _loop_U_SALT
;---------------------------------------------------------------------
;----------------------填入临时中转任务门选择子-----------------------
mov ax,[salt_tp]
mov [0x14],ax ;填充任务门选择子
;---------------------------------------------------------------------
mov esi,[ebp+*] ;重新获得TCB的线性基地址 ;在GDT中存入LDT信息
mov eax,[esi+0x0c]
movzx ebx,word[esi+0x0a]
mov ecx,0x00408200 ;LDT描述符,特权级0级
call Core_Code_Segement:Make_Seg_Descriptor
call Core_Code_Segement:Set_New_GDT
mov [esi+0x10],cx ;在TCB放入LDT选择子 ;构建TSS剩下的信息表
mov ebx,[esi+0x14]
mov [ebx+],cx ;TSS中LDT选择子 mov word[ebx+], ;填充反向链(任务切换的时候处理器会帮着填的,不用操心)
mov dx,[esi+0x12] ;TSS段界限
mov [ebx+],dx
mov word[ebx+], ;T=0 mov eax,[0x04] ;从任务的4GB地址空间获取入口点
mov [ebx+],eax ;填写TSS的EIP域 pushfd
pop edx
mov [ebx+],edx ;EFLAGS ;在GDT中存入TSS信息
mov eax,[esi+0x14]
movzx ebx,word[esi+0x12]
mov ecx,0x00408900
call Core_Code_Segement:Make_Seg_Descriptor
call Core_Code_Segement:Set_New_GDT
mov [esi+0x18],cx ;复制一份页表
call Core_Code_Segement:Copy_Page
mov ebx,[esi+0x14]
mov [ebx+],eax ;填写PDBR(CR3) popad
ret ;相当于是stdcall,过程清栈
;---------------------------------------------------------------------
start:
;---------------------------------------------------------------------
;安装通用异常处理中断程序
mov eax,general_exception_handler
mov bx,Core_Code_Segement
mov cx,0x8e00 ;中断门
call Core_Code_Segement:Make_Gate_Descriptor
mov ebx,IDT_Liner_Address
xor esi,esi
IDT_EXCEPTION:
mov [ebx+esi*],eax ;偏移量是8不是4
mov [ebx+esi*+],edx
inc esi
cmp esi,
jle IDT_EXCEPTION
;---------------------------------------------------------------------
;安装通用中断处理中断程序
mov eax,general_interrupt_handler
mov bx,Core_Code_Segement
mov cx,0x8e00 ;中断门
call Core_Code_Segement:Make_Gate_Descriptor
mov ebx,IDT_Liner_Address
IDT_GENERAL:
mov [ebx+esi*],eax
mov [ebx+esi*+],edx
inc esi
cmp esi,
jle IDT_GENERAL
;---------------------------------------------------------------------
RTC_SET: ;实时时钟中断的填写
mov eax,rtm_0x70_interrupt_handle
mov bx,Core_Code_Segement
mov cx,0x8e00
call Core_Code_Segement:Make_Gate_Descriptor
mov ebx,IDT_Liner_Address
mov [ebx+0x70*],eax ;填充实时时钟中断的中断门
mov [ebx+0x70*+],edx
;---------------------------------------------------------------------
mov word[pidt_base_tmp],*-
mov dword[pidt_base_tmp+],IDT_Liner_Address
lidt [pidt_base_tmp] ;初始化8259A
mov al,0x11
out 0x20,al ;ICW1: 级联
mov al,0x20
out 0x21,al ;ICW2: 中断向量0x20-0x27
mov al,0x04
out 0x21,al ;ICW3: 从片接在主片的引脚2上
mov al,0x01
out 0x21,al ;ICW4: 全缓冲,手动EOI模式 mov al,0x11
out 0xa0,al ;ICW1:边沿触发/级联方式
mov al,0x70
out 0xa1,al ;ICW2: 起始中断向量
mov al,0x04
out 0xa1,al ;ICW3: 从片级联到IR2,这里主片一样是巧合
mov al,0x01
out 0xa1,al ;ICW4: 非总线缓冲,全嵌套,正常EOI ;设置和时钟中断相关的硬件
mov al,0x0b ;RTC寄存器B
or al,0x80 ;阻断NMI
out 0x70,al ;0x70是索引端口
mov al,0x12
out 0x71,al ;设置寄存器B,禁止周期性中断,开放更新结束后中断,BCD码,24小时制 mov al,0xfe
out 0xa1,al ;只留从片的IR0
mov ax,0xfb
out 0x21,al mov al,0x0c
out 0x70,al
in al,0x71 ;读一下寄存器C ;一定要注意,一定要在中断装完之后才能用put_string,因为这个过程里面有sti
mov ebx,message_start
call Core_Code_Segement:put_string
_@load:
;----------------------------安装门------------------------------------
mov edi,salt
mov ecx,salt_items_sum
_set_gate:
push ecx
mov eax,[edi+]
mov bx,[edi+] ;选择子
mov cx,0xec00 ;门是特权级是3的门,那么任何程序都能调用
or cx,[edi+] ;加上参数个数 call Core_Code_Segement:Make_Gate_Descriptor
call Core_Code_Segement:Set_New_GDT
mov [edi+],cx ;回填选择子
add edi,salt_length
pop ecx
loop _set_gate mov ebx,message_In_Gate
call far [salt_1+] ;调用门显示字符信息(忽略偏移地址(前4字节))
;-------------------------初始化任务管理器-----------------------------
mov word[core_tcb+0x04],0xffff ;状态忙碌
mov dword[core_tcb+0x06],0x80100000
mov word[core_tcb+0x0a],0xffff ;LDT初始界限
mov ecx,core_tcb ;添加到TCB链中
call append_to_tcb alloc_core_page ;为用户管理程序的TSS创造空间 mov word[ebx+], ;TI=0
mov word[ebx+], ;任务管理器不需要I/O映射,要大于等于界限
mov word[ebx+], ;任务允许没有自己的LDT
mov eax,cr3
mov dword[ebx+],eax ;设置CR3,注意不是0了!
mov word[ebx+], ;没有前一个任务 mov eax,ebx
mov ebx, ;TSS段界限
mov ecx,0x00408900
call Core_Code_Segement:Make_Seg_Descriptor
call Core_Code_Segement:Set_New_GDT
mov [core_tcb+0x18],cx ltr cx ;启动任务
sti ;开中断
;------------------安装用户管理程序的临时返回任务门--------------------
mov eax,0x0000 ;TSS不需要偏移地址
mov bx,[core_tcb+0x18] ;TSS的选择子
mov cx,0xe500 call Core_Code_Segement:Make_Gate_Descriptor
call Core_Code_Segement:Set_New_GDT
mov [salt_tp],cx ;填入临时中转任务门选择子,注意不需要加260了
;----------------------------------------------------------------------
;创建用户任务的任务A控制块
alloc_core_page ;TCB属于内核的东西
mov word [ebx+0x04], ;任务状态:空闲
mov dword [ebx+0x06], ;用户任务局部空间的分配从0开始。
mov word [ebx+0x0a],0xffff ;登记LDT初始的界限到TCB中 push dword User_Program_AddressA
push ebx
call load_program
mov ecx,ebx
call append_to_tcb
;----------------------------------------------------------------------
;创建用户任务的任务B控制块
alloc_core_page ;TCB属于内核的东西
mov word [ebx+0x04], ;任务状态:空闲
mov dword [ebx+0x06], ;用户任务局部空间的分配从0开始。
mov word [ebx+0x0a],0xffff ;登记LDT初始的界限到TCB中 push dword User_Program_AddressB
push ebx
call load_program
mov ecx,ebx
call append_to_tcb
;----------------------------------------------------------------------
_core:
mov ebx,core_msg0
call Core_Code_Segement:put_string
hlt
jmp _core
;----------------------------------------------------------------------
;=========================================================================
SECTION core_trail
;----------------------------------------------------------------
Program_end:

3. 两个用户程序

 ;================================用户程序A=======================================
program_length dd program_end ;程序总长度#0x00
entry_point dd start ;程序入口点#0x04
salt_position dd salt_begin ;SALT表起始偏移量#0x08
salt_items dd (salt_end-salt_begin)/
;SALT条目数#0x0C
TpBack: dd ;任务门的偏移地址没用,直接填充就可以了
dw ;任务门的选择子#0x14
Own_Page dd ;自己页面的物理地址#0x16
;-------------------------------------------------------------------------------
;符号地址检索表
salt_begin:
PrintString db '@Printf'
times -($-PrintString) db
TerminateProgram: db '@TerminateProgram'
times -($-TerminateProgram) db
ReadDiskData db '@ReadHarddisk'
times -($-ReadDiskData) db
Stop_This_Program db '@Stop_This_Program'
times -($-Stop_This_Program) db
salt_end:
message_0 db ' User task A->;;;;;;;;;;;;; I am PhilipA ;;;;;;;;;;;;;;;;;;'
db 0x0d,0x0a,
;-------------------------------------------------------------------------------
[bits ]
;-------------------------------------------------------------------------------
start:
mov ebx,message_0
call far [PrintString]
call far [Stop_This_Program]
jmp start jmp far [fs:TpBack]
;-------------------------------------------------------------------------------
program_end:
;================================用户程序B=======================================
program_length dd program_end ;程序总长度#0x00
entry_point dd start ;程序入口点#0x04
salt_position dd salt_begin ;SALT表起始偏移量#0x08
salt_items dd (salt_end-salt_begin)/
;SALT条目数#0x0C
TpBack: dd ;任务门的偏移地址没用,直接填充就可以了
dw ;任务门的选择子#0x14
Own_Page dd ;自己页面的物理地址#0x16
;-------------------------------------------------------------------------------
;符号地址检索表
salt_begin:
PrintString db '@Printf'
times -($-PrintString) db
TerminateProgram: db '@TerminateProgram'
times -($-TerminateProgram) db
ReadDiskData db '@ReadHarddisk'
times -($-ReadDiskData) db
Stop_This_Program db '@Stop_This_Program'
times -($-Stop_This_Program) db
salt_end:
message_0 db ' User task B->$$$$$$$$$$$$$ I am PhilipB $$$$$$$$$$$$$$$$$$'
db 0x0d,0x0a,
;-------------------------------------------------------------------------------
[bits ]
;-------------------------------------------------------------------------------
start:
mov ebx,message_0
call far [PrintString]
call far [Stop_This_Program]
jmp start jmp far [fs:TpBack]
;-------------------------------------------------------------------------------
program_end:

ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务