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等)中的数据不一致或者无效。这种错误发生的时候,程序或者任务都不可能重新启动。

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

 

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

  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的储存格式是一样的。中断门,陷阱门描述符格式和中段描述符表寄存器的结构如下:

  中断门:

 

  陷阱门:

 

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

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

 

  中断描述符表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. 错误代码

 

  错误代码如上图所示,错误代码的高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)。安装也很简单,也就是一堆门而已。

          

  

  

       通用的异常处理程序和中断程序的处理都很简单,异常直接停机(注意异常不是每次都会有错误代码),普通中断就直接返回就好了。我们的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看这里)。

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

    

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

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

 

       在分页模式下,当段部件发出一个线性地址的时候,处理器用线性地址的高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)的页表无效。

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

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

       5. 宏汇编技术(Macro)

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

  1. 单行宏%define:

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

  

       2. 多行宏%macro:

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

  

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

★PART3:本章的程序

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

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

1. 主引导程序MBR      

  1 ;========================保护模式主引导扇区代码========================
  2         core_phy_base:             equ 0x00040000        ;内核加载地址
  3         core_sector_address:     equ 0x00000001        ;内核所在扇区
  4 ;======================================================================
  5 SECTION mbr align=16 vstart=0x00007c00            ;注意起始地址已经变成了0x7c00了
  6         mov ax,cs
  7         mov ss,ax
  8         mov sp,0x7c00
  9         
 10         mov eax,[cs:pgdt_base+0x02]
 11         xor edx,edx
 12         mov ebx,0x10
 13         div ebx
 14         
 15         mov ds,eax                                ;让ds指向gdt位置进行操作
 16         mov ebx,edx                                ;别忘了还有可能出现偏移地址
 17         ;---------------------描述符#0---------------------
 18         mov dword [ebx+0x00],0x00000000            ;空描述符
 19         mov dword [ebx+0x04],0x00000000
 20         ;---------------------描述符#1---------------------
 21         mov dword [ebx+0x08],0x0000ffff            ;4GB代码段,特权级为0
 22         mov dword [ebx+0x0c],0x00cf9800
 23         ;---------------------描述符#2---------------------
 24         mov dword [ebx+0x10],0x0000ffff            ;4GB向上拓展数据段和栈段,特权级为0
 25         mov dword [ebx+0x14],0x00cf9200
 26         
 27         mov word[cs:pgdt_base],23                ;加载gdt
 28         lgdt [cs:pgdt_base]
 29         
 30         in al,0x92                                ;快速开启A20
 31         or al,0x02                                ;是写入2,不要搞错了,写入1就是重启了
 32         out 0x92,al
 33         cli                                        ;关掉中断
 34         
 35         mov eax,cr0
 36         or eax,0x01                                ;设置PE位
 37         mov cr0,eax
 38         
 39         jmp dword 0x0008:flush                    ;进入保护模式
 40         
 41         [bits 32]
 42     flush:
 43         mov eax,0x0010                            
 44         mov ds,eax
 45         mov es,eax
 46         mov fs,eax
 47         mov gs,eax
 48         mov ss,eax                                ;栈段也是向上拓展的
 49         mov esp,0x7000                            
 50         
 51         ;接下来开始读取内核头部
 52         mov esi,core_sector_address
 53         mov edi,core_phy_base
 54         call read_harddisk_0
 55         
 56         mov eax,[core_phy_base]                    ;读取用户总长度
 57         xor edx,edx
 58         mov ebx,512
 59         div ebx
 60         
 61         cmp edx,0
 62         jne @read_last_sector
 63         dec eax
 64         @read_last_sector:
 65             cmp eax,0
 66             je @setup
 67             mov ecx,eax
 68             .read_last:
 69                 inc esi
 70                 call read_harddisk_0
 71             loop .read_last
 72         @setup:                                            ;下面准备开启页管理    
 73             mov ecx,1024
 74             mov ebx,0x00020000
 75             xor esi,esi
 76             
 77             _flush_PDT:                                    ;清空页表
 78                 mov dword[es:ebx+esi*4],0x00000000    
 79                 inc esi
 80             loop _flush_PDT
 81             
 82             ;页目录的最后一个32字节是指向自己的页表(这个页表就是页目录)
 83             mov dword[ebx+4092],0x00020003            ;属性:存在于物理内存,只允许内核自己访问
 84             
 85             ;页目录的第一个页表指示最底下1MB内存的4KB页(内核代码,必须虚拟地址和物理地址一致)
 86             mov edx,0x00021003
 87             mov dword[ebx+0x000],edx                    ;低端映射(临时的,创建用户目录的时候就没了)
 88             mov dword[ebx+0x800],edx                    ;高端映射
 89             
 90             ;现在0x00020000的页是第0个页表(指示页目录),0x00021000是第一个页表(指示底下1MB的东西)
 91             mov ebx,0x00021000
 92             xor eax,eax
 93             xor esi,esi
 94             
 95             _make_page:
 96                 mov edx,eax
 97                 or edx,0x00000003                        ;属性:存在于物理内存,只允许内核自己访问
 98                 mov [ebx+esi*4],edx                
 99                 add eax,0x1000
100                 inc esi
101                 cmp esi,256
102             jl _make_page
103             
104             mov eax,0x00020000
105             mov cr3,eax                                    ;把页目录基地址放在cr3,准备开启页功能
106             
107             sgdt [pgdt_base]
108             add dword[pgdt_base+2],0x80000000            ;设定GDT为高地址
109             lgdt [pgdt_base]
110             
111             mov eax,cr0
112             or eax,0x80000000
113             mov cr0,eax                                    ;置PG位,开启页功能
114             
115             ;将堆栈映射到高端,这是非常容易被忽略的一件事。应当把内核的所有东西
116             ;都移到高端,否则,一定会和正在加载的用户任务局部空间里的内容冲突,
117             ;而且很难想到问题会出在这里。 
118             add esp,0x80000000                            ;因为已经处于平坦模式了,所以内核栈指针也要映射 
119             
120         jmp [core_phy_base+0x80000000+4]            ;都在一个段上了,直接近转移,start在偏移量是4的地方
121 ;=============================函数部分=================================
122 read_harddisk_0:                                ;esi存了28位的硬盘号
123         push ecx
124         
125         mov edx,0x1f2                            ;读取一个扇区
126         mov al,0x01
127         out dx,al
128         
129         mov eax,esi                                ;0~7位,0x1f3端口
130         inc edx
131         out dx,al
132         
133         mov al,ah                                ;8~15位,0x1f4端口
134         inc edx
135         out dx,al
136         
137         shr eax,16                                ;16-23位,0x1f5端口                
138         inc edx
139         out dx,al
140         
141         mov al,ah                                ;24-28位,LBA模式主硬盘
142         inc edx
143         and al,0x0f
144         or al,0xe0
145         out dx,al                                
146         
147         inc edx                                    ;读命令,0x1f7端口
148         mov al,0x20                                
149         out dx,al
150         
151         .wait:
152             in al,dx
153             and al,0x88
154             cmp al,0x08
155             jne .wait
156         
157         mov dx,0x1f0
158         mov ecx,256
159         .read:
160             in ax,dx
161             mov [edi],ax
162             add edi,2
163             loop .read
164         
165         pop ecx
166         
167         ret
168 ;======================================================================
169     pgdt_base             dw 0
170                         dd 0x00008000                    ;GDT的物理地址
171 ;======================================================================
172     times 510-($-$$)     db 0
173                         dw 0xaa55

2. 内核程序

  1 ;============================内核程序=================================
  2         ;定义内核所要用到的选择子
  3         All_4GB_Segment         equ 0x0018        ;4GB的全内存区域
  4         Core_Code_Segement        equ 0x0008        ;内核代码段
  5         IDT_Liner_Address        equ 0x8001F000    ;IDT线性地址
  6         ;----------------------------------------------------------------
  7         User_Program_AddressA    equ 50            ;用户程序所在逻辑扇区
  8         User_Program_AddressB    equ 80            ;用户程序所在逻辑扇区
  9         Switch_Stack_Size        equ 4096        ;切换栈段的大小
 10         Global_Page_Directory    equ 0x80000000     ;给全局空间的映射地址
 11     ;----------------------------------------------------------------
 12         %macro alloc_core_page    0                 ;给内核程序安排页
 13             mov ebx,[core_tcb+0x06]                
 14             add dword[core_tcb+0x06],0x1000        ;注意这里是加法指令,写错了就会页故障(因为已经清空页了,访问一个不存在的页)
 15             call Core_Code_Segement:alloc_inst_a_page
 16         %endmacro
 17     ;----------------------------------------------------------------    
 18         %macro alloc_user_page    0                 ;给用户程序安排页
 19             mov ebx,[esi+0x06]                
 20             add dword[esi+0x06],0x1000
 21             call Core_Code_Segement:alloc_inst_a_page
 22         %endmacro
 23     ;----------------------------------------------------------------
 24         %macro Read_Data_From_Harddisk 0
 25             push esi
 26             push ds
 27             push ebx
 28             push cs
 29             call Core_Code_Segement:ReadHarddisk
 30         %endmacro
 31 ;=========================================================================
 32 ;============================公用例程区===================================
 33 ;=========================================================================
 34 SECTION Code align=16 vstart=0x80040000            ;注意代码段的开始现在是0x80040000了,映射的线性地址
 35         Program_Length             dd    Program_end    ;内核总长度
 36         Code_Entry                dd    start        ;注意偏移地址一定是32位的
 37     ;----------------------------------------------------------------
 38                             [bits 32]
 39     ;----------------------------------------------------------------
 40     ReadHarddisk:                                ;push1:28位磁盘号(esi)
 41                                                 ;push2:应用程序数据段选择子(ax->ds)
 42                                                 ;push3: 偏移地址(ebx)
 43                                                 ;push4: 应用程序代码段选择子(dx)
 44         cli                                        
 45         pushad
 46         
 47         mov ebp,esp
 48         
 49         mov esi,[ebp+13*4]
 50         movzx eax,word[ebp+12*4]
 51         mov ebx,[ebp+11*4]
 52         movzx edx,word[ebp+10*4]
 53         
 54         arpl ax,dx
 55         mov ds,ax
 56         
 57         mov dx,0x1f2
 58         mov al,0x01        ;读一个扇区                                
 59         out dx,al
 60         
 61         inc edx            ;0-7位
 62         mov eax,esi
 63         out dx,al
 64         
 65         inc edx            ;8-15位
 66         mov al,ah
 67         out dx,al
 68         
 69         inc edx            ;16-23位
 70         shr eax,16
 71         out dx,al
 72         
 73         inc edx            ;24-28位,主硬盘,LBA模式
 74         mov al,ah
 75         and al,0x0f
 76         or al,0xe0
 77         out dx,al
 78         
 79         inc edx
 80         mov al,0x20
 81         out dx,al
 82         
 83         _wait:
 84             in al,dx
 85             and al,0x88
 86             cmp al,0x08
 87             jne _wait
 88         
 89         mov dx,0x1f0
 90         mov ecx,256
 91         _read:
 92             in ax,dx
 93             mov [ebx],ax
 94             add ebx,2
 95         loop _read
 96         
 97         popad
 98         sti
 99         retf 16            ;4个数据
100     ;----------------------------------------------------------------
101     put_string:                                    ;ebx:偏移地址
102         cli                                        ;必须关中断
103         pushad
104         
105         _print:
106             mov cl,[ebx]
107             cmp cl,0
108             je _exit
109             call put_char
110             inc ebx
111             jmp _print
112         _exit:        
113             popad
114             sti                                ;记得把中断开了
115             retf                                
116         ;--------------------------------------------------------------    
117         put_char:            ;cl就是要显示的字符
118             pushad
119             
120             mov dx,0x3d4
121             mov al,0x0e        ;高8位
122             out dx,al
123             mov dx,0x3d5
124             in al,dx
125             mov ah,al        ;先把高8位存起来
126             mov dx,0x3d4
127             mov al,0x0f        ;低8位
128             out dx,al
129             mov dx,0x3d5
130             in al,dx        ;现在ax就是当前光标的位置
131             mov bx,ax
132             and ebx,0x0000ffff    ;准备用32位寻址来显示
133             
134             _judge:
135                 cmp cl,0x0a
136                 je _set_0x0a
137                 cmp cl,0x0d
138                 je _set_0x0d
139             _print_visible:
140                 shl bx,1
141                 mov [0x800b8000+ebx],cl
142                 mov byte[0x800b8000+ebx+1],0x07
143                 shr bx,1
144                 inc bx                ;以下将光标位置推进一个字符
145                 jmp _roll_screen
146             _set_0x0d:                ;回车
147                 mov ax,bx
148                 mov bl,80
149                 div bl
150                 mul bl
151                 mov bx,ax
152                 jmp _set_cursor
153             _set_0x0a:                ;换行
154                 mov bx,ax
155                 add bx,80
156                 jmp _roll_screen
157             _roll_screen:
158                 cmp bx,2000
159                 jl _set_cursor
160                 
161                 cld
162                 mov edi,0x800b8000    ;一定要记住,现在内存的地址全都是虚拟地址,偏移地址也是一样的
163                 mov esi,0x800b80a0
164                 mov ecx,1920
165                 rep movsw
166             _cls:
167                 mov ebx,3840
168                 mov ecx,80
169                 _print_blank:
170                     mov word[0x800b8000+ebx],0x0720
171                     add bx,2
172                     loop _print_blank    
173                 mov ebx,1920    ;别总是忘了光标的位置!
174             _set_cursor:        ;改变后的光标位置在bx上
175             mov dx,0x3d4
176             mov al,0x0f        ;低8位
177             out dx,al
178             
179             mov al,bl
180             mov dx,0x3d5
181             out dx,al
182             
183             mov dx,0x3d4
184             mov al,0x0e     ;高8位
185             out dx,al
186             
187             mov al,bh
188             mov dx,0x3d5
189             out dx,al
190             
191             popad
192             ret
193     ;----------------------------------------------------------------        
194     Make_Seg_Descriptor:                    ;构造段描述符
195                                             ;输入:
196                                             ;eax:线性基地址
197                                             ;ebx:段界限
198                                             ;ecx:属性
199                                             ;输出:
200                                             ;eax:段描述符低32位
201                                             ;edx:段描述符高32位
202         mov edx,eax
203         and edx,0xffff0000
204         rol edx,8
205         bswap edx
206         or edx,ecx
207         
208         shl eax,16
209         or ax,bx
210         and ebx,0x000f0000
211         or edx,ebx
212         retf                
213     ;----------------------------------------------------------------        
214     Make_Gate_Descriptor:                    ;构造门描述符
215                                             ;输入:
216                                             ;eax:段内偏移地址
217                                             ;bx: 段的选择子
218                                             ;cx: 段的属性
219                                             ;输出:
220                                             ;eax:门描述符低32位
221                                             ;edx:门描述符高32位
222         push ebx
223         push ecx
224         
225         mov edx,eax
226         and edx,0xffff0000                    ;要高16位
227         or dx,cx
228         
229         shl ebx,16
230         and eax,0x0000ffff
231         or eax,ebx
232         
233         pop ecx
234         pop ebx
235         
236         retf                
237     ;----------------------------------------------------------------
238     Set_New_GDT:                            ;装载新的全局描述符
239                                             ;输入:edx:eax描述符
240                                             ;输出:cx选择子
241         sgdt [pgdt_base_tmp]
242         
243         movzx ebx,word[pgdt_base_tmp]
244         inc bx                                ;注意这里要一定是inc bx而不是inc ebx,因为gdt段界限初始化是0xffff的
245                                             ;要用到回绕特性
246         add ebx,[pgdt_base_tmp+0x02]        ;得到pgdt的线性基地址
247         
248         mov [es:ebx],eax
249         mov [es:ebx+0x04],edx                ;装载新的gdt符
250                                             ;装载描述符要装载到实际位置上
251         
252         add word[pgdt_base_tmp],8            ;给gdt的段界限加上8(字节)
253         
254         lgdt [pgdt_base_tmp]                ;加载gdt到gdtr的位置和实际表的位置无关
255         
256         mov ax,[pgdt_base_tmp]                ;得到段界限
257         xor dx,dx
258         mov bx,8                            ;得到gdt大小
259         div bx
260         mov cx,ax
261         shl cx,3                            ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级)
262         
263         retf
264     ;----------------------------------------------------------------
265     Set_New_LDT_To_TCB:                        ;装载新的局部描述符
266                                             ;输入:edx:eax描述符
267                                             ;     : ebx:TCB线性基地址
268                                             ;输出:cx选择子
269         push edi
270         push eax
271         push ebx
272         push edx
273         
274         mov edi,[ebx+0x0c]                    ;LDT的线性基地址
275         movzx ecx,word[ebx+0x0a]
276         inc cx                                ;得到实际的LDT的大小(界限还要-1)
277         
278         mov [edi+ecx+0x00],eax
279         mov [edi+ecx+0x04],edx
280         
281         add cx,8
282         dec cx
283         
284         mov [ebx+0x0a],cx
285         
286         mov ax,cx
287         xor dx,dx
288         mov cx,8
289         div cx
290         
291         shl ax,3
292         mov cx,ax
293         or cx,0x0004                        ;LDT,第三位TI位一定是1
294         
295         pop edx
296         pop ebx
297         pop eax
298         pop edi
299         retf
300     ;----------------------------------------------------------------
301     allocate_4KB_page:                            ;输入:无
302                                                 ;输出eax:页的物理地址
303                                                 ;注意这个是近调用
304         push ebx
305         push ecx
306         push edx
307         xor eax,eax
308         
309         _search_pages:
310             bts [page_bit_map],eax
311             jnc _found_not_uesd
312             inc eax
313             cmp eax,page_map_len*8
314         jl _search_pages
315         
316         mov ebx,No_More_Page
317         call Core_Code_Segement:put_string
318         hlt                                     ;无可用页,直接停机
319         
320         _found_not_uesd:
321         shl eax,12                                ;eax相当于是选择子,乘以一个4KB得到物理地址
322         
323         pop edx
324         pop ecx
325         pop ebx
326         ret
327     ;----------------------------------------------------------------
328     alloc_inst_a_page:                            ;分配一个页,并安装在当前活动的层级分页结构中
329                                                 ;输入:EBX=页的线性地址
330                                                 ;输出:无
331         push eax
332         push ebx
333         push edi
334         push esi
335         
336         _test_P:                                ;在页目录中看是否存在这个页表
337             mov esi,ebx
338             and esi,0xffc00000
339             shr esi,20                                         
340             or esi,0xfffff000                    ;指向页目录本身
341             test dword[esi],0x00000001
342             jnz _get_page_and_create_new_page
343         _create_new_page_directory:
344             call allocate_4KB_page
345             or eax,0x00000007                    ;存在于主存,可读可写,允许特权级3程序访问
346             mov [esi],eax        
347         _get_page_and_create_new_page:    
348             mov esi,ebx
349             shr esi,10                            ;页表在页目录的偏移项
350             and esi,0x003ff000                    ;得到页表的偏移地址
351             or esi,0xffc00000                    ;指向页目录
352             
353             and ebx,0x003ff000
354             shr ebx,10                            ;中间10位是页目录-页表-表内偏移量(注意这里的层次理解)
355             or esi,ebx                            ;esi就是页的对应线性地址
356             call allocate_4KB_page
357             or eax,0x00000007                    ;存在于主存,可读可写,允许特权级3程序访问
358             mov [esi],eax
359             
360         pop esi
361         pop edi
362         pop ebx
363         pop eax
364         retf
365     ;----------------------------------------------------------------
366     Copy_Page:                                    ;把在创建的包含全局和私有部分的页表复制一份给用户程序用
367                                                 ;输入:无
368                                                 ;输出eax:页的物理地址
369         push edi
370         push esi
371         push ebx
372         push ecx
373         push edx
374         
375         mov edx,[task_pos]                        ;注意这里不需要加上内核的偏移了,因为程序开始的时候已经有了start
376         sub edx,4
377         add edx,0xfffff000
378         invlpg [edx]                            ;刷新单条TLB
379         mov edi,[page_soft_header]
380         sub edi,0x1000
381         mov esi,0xfffff000                        ;指向全局页目录
382         
383         call allocate_4KB_page
384         mov ebx,eax
385         or ebx,0x00000007
386         mov [edx],ebx
387                             
388         mov ecx,1024
389         cld
390         repe movsd
391         
392         pop edx
393         pop ecx
394         pop ebx
395         pop esi
396         pop edi
397         retf
398     ;----------------------------------------------------------------
399     ;-------------------------------------------------------------------------------
400     general_interrupt_handler:                      ;通用的中断处理过程
401         push eax
402         mov al,0x20                                    ;中断结束命令EOI 
403         out 0xa0,al                                    ;向从片发送 
404         out 0x20,al                                    ;向主片发送
405         pop eax
406         iretd
407     ;-------------------------------------------------------------------------------
408     general_exception_handler:                      ;通用的异常处理过程
409         mov ebx,excep_msg
410         call Core_Code_Segement:put_string
411         hlt
412     ;-------------------------------------------------------------------------------     
413     rtm_0x70_interrupt_handle:                      ;实时时钟中断处理过程
414         pushad
415         
416         mov al,0x20                                    ;直接给8259发EOI终止操作了
417         out 0x20,al
418         out 0xa0,al
419         
420         mov al,0x0c                                    ;允许NMI中断
421         out 0x70,al
422         in al,0x71                                    ;读一下RTC的寄存器C,否则只发生一次中断
423         
424         mov eax,tcb_chain
425         
426         _Search_Not_In_Service:
427             mov ebx,[eax]
428             cmp ebx,0x00000000
429             je _out_interrupt                        ;说明已经到链表的末尾了,直接就推出中断就好了
430             cmp word[ebx+0x04],0xffff                ;任务状态为忙
431             je _found_current_task
432             mov eax,ebx
433         jmp _Search_Not_In_Service
434         
435         _found_current_task:
436             mov ecx,[ebx]
437             mov [eax],ecx                            ;拆除节点,ebx就是节点地址了
438         _Last_Pos:            
439             mov edx,[eax]                            
440             cmp edx,0x00000000
441             je _Hang
442             mov eax,edx
443             jmp _Last_Pos
444         _Hang:
445             mov [eax],ebx                            ;挂到末端
446             mov dword[ebx],0x00000000                ;节点的末尾标记一下
447             
448             mov eax,tcb_chain
449         _found_free_task:
450             mov eax,[eax]
451             cmp eax,0x00000000
452             je _out_interrupt
453             cmp word[eax+0x04],0x0000
454         jne _found_free_task                        ;找到第一个空闲任务
455         
456         ;取反任务状态
457         not word[eax+0x04]
458         not word[ebx+0x04]
459         jmp far[eax+0x14]                            ;直接任务切换
460             
461         _out_interrupt:
462         popad
463         iretd
464     ;-------------------------------------------------------------------------------
465     Stop_This_Program:
466         hlt
467         retf
468     ;-------------------------------------------------------------------------------    
469 ;=========================================================================
470 ;===========================内核数据区====================================
471 ;=========================================================================
472         pgdt_base_tmp:          dw  0                             
473                                 dd  0
474         pidt_base_tmp:          dw  0
475                                 dd  0
476         salt:
477         salt_1:                    db    '@Printf'                    ;@Printf函数(公用例程)
478         times 256-($-salt_1)    db    0
479                                 dd    put_string
480                                 dw    Core_Code_Segement
481                                 dw  0                            ;参数个数
482                                 
483         salt_2:                    db    '@ReadHarddisk'                ;@ReadHarddisk函数(公用例程)
484         times 256-($-salt_2)    db    0
485                                 dd    ReadHarddisk
486                                 dw    Core_Code_Segement
487                                 dw  4                            ;参数个数
488                                 
489         salt_3:                    db    '@Stop_This_Program'        ;@Stop_This_Program函数(公用例程)
490         times 256-($-salt_3)    db    0
491                                 dd    Stop_This_Program
492                                 dw    Core_Code_Segement
493                                 dw  0                            ;参数个数
494                                 
495         salt_length:            equ    $-salt_3
496         salt_items_sum            equ    ($-salt)/salt_length        ;得到项目总数
497         
498         salt_tp:                dw    0                            ;任务门,专门拿来给程序切换到全局空间的
499         
500         message_start            db  '  Working in system core with protection '
501                                 db  'and paging are all enabled.System core is mapped '
502                                 db  'to address 0x80000000.',0x0d,0x0a,0
503         message_In_Gate            db  '   Hi!My name is Philip:',0x0d,0x0a,0
504         core_msg0                db  '  System core task running!',0x0d,0x0a,0
505         No_More_Page            db  '********No more pages********',0
506         excep_msg                db  '********Exception encounted********',0
507 
508         bin_hex                  db '0123456789ABCDEF'
509                                                                 ;put_hex_dword子过程用的查找表
510         core_buf        times 2048 db  0                             ;内核用的缓冲区(2048个字节(2MB))
511         
512         core_tcb     times 32   db  0                            ;内核(程序管理器)的TCB
513         ;假设只有2MB内存可以用的意思,正确的做法应该先读PCI(E),然后再分配!
514         page_bit_map             db  0xff,0xff,0xff,0xff,0xff,0x55,0x55,0xff
515                                 db  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
516                                 db  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
517                                 db  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
518                                 db  0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
519                                 db  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
520                                 db  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
521                                 db  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
522         page_map_len             equ $-page_bit_map
523         core_next_laddr          dd  0x80100000                    ;内核空间中下一个可分配的线性地址
524         task_pos                dd  0x00000ffc                     ;任务程序的页表在全局页目录的偏移
525         page_soft_header        dd  0xfffff000                     ;加载页目录地址
526          
527         tcb_chain                dd  0                            ;任务控制块链头指针
528 ;=========================================================================
529 ;===========================内核代码区====================================
530 ;=========================================================================    
531     ;---------------------------------------------------------------------
532     append_to_tcb:                        ;写入新的TCB链
533                                         ;输入:ecx新的TCB线性基地址
534         cli                                ;必须关中断,如果过程中间发生了0x70中断那么新内核就会崩溃
535         pushad
536         
537         mov eax,tcb_chain
538         _search_tcb:
539             mov ebx,[eax]
540             cmp ebx,0x00000000
541             je _out_tcb_search
542             mov eax,ebx
543         jmp _search_tcb
544         _out_tcb_search:
545             mov [eax],ecx
546             mov dword[ecx],0x00000000
547         popad
548         sti
549         ret
550     ;---------------------------------------------------------------------    
551     load_program:                        ;输入push1:逻辑扇区号
552                                         ;     push2:    线性基地址
553         pushad
554         
555         mov ebp,esp                        ;别忘了把参数传给ebp
556         
557         mov ebx,0xfffff000
558         xor esi,esi
559         _flush_private:                    ;必须清空!
560             mov dword[ebx+esi*4],0x00000000
561             inc esi
562             cmp esi,512
563         jl _flush_private
564         
565         ;手动刷新页目录缓存
566         mov eax,cr3                        ;本来这个在16章就应该出现的,不刷新的话页目录的缓存还是旧的表项
567         mov cr3,eax
568         
569         mov esi,[ebp+10*4]                ;esi必须是逻辑扇区号
570         mov ebx,core_buf                ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了)
571         Read_Data_From_Harddisk
572         
573         mov eax,[core_buf]                ;读取用户程序长度
574         mov ebx,eax                        
575         and ebx,0xfffff000                ;清空低12位(强制对齐4096:4KB)
576         add ebx,4096                        
577         test eax,0x00000fff                
578         cmovnz eax,ebx                    ;低12位不为0则使用向上取整的结果
579         
580         mov ecx,eax
581         shr ecx,12                        ;获取占的页数
582         mov edi,[ebp+9*4]                ;获取tcb的线性基地址
583         mov esi,[ebp+10*4]                ;esi必须是逻辑扇区号
584         
585         _loop_read_@1:
586             ;注意这个过程和下面的不一样,要使用edi,esi是逻辑号,不要用宏
587             mov ebx,[edi+0x06]            
588             mov dword[edi+0x06],0x1000
589             call Core_Code_Segement:alloc_inst_a_page
590             
591             push ecx
592             mov ecx,8                    ;512*8==4096
593             _loop_read_@2:
594                 Read_Data_From_Harddisk
595                 inc esi
596                 add ebx,512
597             loop _loop_read_@2
598             pop ecx
599         loop _loop_read_@1
600         
601         mov esi,edi                        ;esi: TCB的线性基地址
602         
603         alloc_core_page                    ;给TSS分配内核页
604         mov [esi+0x14],ebx                ;填写TSS的线性基地址
605         mov word[esi+0x12],103            ;无I/O映射
606         
607         alloc_user_page                    ;LDT的用户页
608         mov [esi+0x0c],ebx                ;填写LDT的线地址
609         mov edi,[esi+0x14]                ;edi就是TSS的线性地址
610         
611         ;代码段
612         mov eax,0x00000000
613         mov ebx,0x000fffff
614         mov ecx,0x00c0f800
615         call Core_Code_Segement:Make_Seg_Descriptor
616         mov ebx,esi
617         call Core_Code_Segement:Set_New_LDT_To_TCB
618         or cx,0x0003                    ;特权级为3
619         mov [edi+76],cx                ;CS域
620         
621         ;数据段
622         mov eax,0x00000000
623         mov ebx,0x000fffff
624         mov ecx,0x00c0f200
625         call Core_Code_Segement:Make_Seg_Descriptor
626         mov ebx,esi
627         call Core_Code_Segement:Set_New_LDT_To_TCB
628         or cx,0x0003                    ;特权级为3
629         mov [edi+72],cx                    ;ES,DS,FS,GS域,已经映射到全局空间了
630         mov [edi+84],cx                    
631         mov [edi+88],cx                    
632         mov [edi+92],cx                    
633         
634         ;创建一系列栈
635         ;创建自身特权级为3的栈
636         alloc_user_page
637         mov [edi+80],cx                                    ;cx是数据段的选择子
638         mov edx,[esi+0x06]                            ;向上拓展的ESP的初始值
639         mov [edi+56],edx            
640         
641         ;创建特权级0的栈
642         alloc_user_page
643         mov eax,0x00000000
644         mov ebx,0x000fffff
645         mov ecx,0x00c09200                                 ;4KB粒度的堆栈段描述符,特权级0
646         call Core_Code_Segement:Make_Seg_Descriptor
647         mov ebx,esi
648         call Core_Code_Segement:Set_New_LDT_To_TCB
649         or cx,0x0000                                    ;选择子特权级为0
650         
651         mov [edi+8],cx                                ;cx是数据段的选择子
652         mov edx,[esi+0x06]                            ;向上拓展的ESP0的初始值
653         mov [edi+4],edx            
654         
655         ;创建特权级1的栈
656         alloc_user_page
657         mov eax,0x00000000
658         mov ebx,0x000fffff
659         mov ecx,0x00c0b200                                 ;4KB粒度的堆栈段描述符,特权级1
660         call Core_Code_Segement:Make_Seg_Descriptor
661         mov ebx,esi
662         call Core_Code_Segement:Set_New_LDT_To_TCB
663         or cx,0x0001                                    ;选择子特权级为1
664         
665         mov [edi+16],cx                                ;cx是数据段的选择子
666         mov edx,[esi+0x06]                            ;向上拓展的ESP1的初始值
667         mov [edi+12],edx        
668         
669         ;创建特权级2的栈
670         alloc_user_page
671         mov eax,0x00000000
672         mov ebx,0x000fffff
673         mov ecx,0x00c0d200                                 ;4KB粒度的堆栈段描述符,特权级2
674         call Core_Code_Segement:Make_Seg_Descriptor
675         mov ebx,esi
676         call Core_Code_Segement:Set_New_LDT_To_TCB
677         or cx,0x0002                                    ;选择子特权级为2
678         
679         mov [edi+24],cx                                ;cx是数据段的选择子
680         mov edx,[esi+0x06]                            ;向上拓展的ESP2的初始值
681         mov [edi+20],edx        
682         
683         ;现在开始重定位API符号表
684         ;---------------------------------------------------------------------
685         cld
686         mov ecx,[0x0c]
687         mov edi,[0x08]
688 
689         _loop_U_SALT:                    
690             push edi
691             push ecx
692             
693             mov ecx,salt_items_sum
694             mov esi,salt
695             
696             _loop_C_SALT:
697                 push edi
698                 push esi
699                 push ecx
700                 
701                 mov ecx,64                ;比较256个字节
702                 repe cmpsd
703                 jne _re_match            ;如果成功匹配,那么esi和edi刚好会在数据区之后的
704                 
705                 mov eax,[esi]            ;偏移地址
706                 mov [es:edi-256],eax    ;把偏移地址填入用户程序的符号区
707                 mov ax,[esi+0x04]        ;段的选择子
708                 
709                 or ax,0x0002            ;把RPL改为3,代表(内核)赋予应用程序以特权级3
710                 mov [es:edi-252],ax        ;把段的选择子填入用户程序的段选择区
711                 
712                 _re_match:
713                 pop ecx
714                 pop esi
715                 add esi,salt_length
716                 pop edi
717             loop _loop_C_SALT
718             
719             pop ecx
720             pop edi
721             add edi,256
722         loop _loop_U_SALT
723         ;---------------------------------------------------------------------
724         ;----------------------填入临时中转任务门选择子-----------------------
725         mov ax,[salt_tp]
726         mov [0x14],ax                            ;填充任务门选择子
727         ;---------------------------------------------------------------------
728         mov esi,[ebp+9*4]                        ;重新获得TCB的线性基地址
729         
730         ;在GDT中存入LDT信息
731         mov eax,[esi+0x0c]
732         movzx ebx,word[esi+0x0a]
733         mov ecx,0x00408200                        ;LDT描述符,特权级0级
734         call Core_Code_Segement:Make_Seg_Descriptor
735         call Core_Code_Segement:Set_New_GDT
736         mov [esi+0x10],cx                    ;在TCB放入LDT选择子
737         
738         ;构建TSS剩下的信息表
739         mov ebx,[esi+0x14]
740         mov [ebx+96],cx                        ;TSS中LDT选择子
741         
742         mov word[ebx+0],0                    ;填充反向链(任务切换的时候处理器会帮着填的,不用操心)
743         mov dx,[esi+0x12]                    ;TSS段界限
744         mov [ebx+102],dx
745         mov word[ebx+100],0                    ;T=0
746       
747         mov eax,[0x04]                          ;从任务的4GB地址空间获取入口点 
748         mov [ebx+32],eax                        ;填写TSS的EIP域 
749         
750         pushfd
751         pop edx
752         mov [ebx+36],edx                           ;EFLAGS 
753         
754         ;在GDT中存入TSS信息
755         mov eax,[esi+0x14]
756         movzx ebx,word[esi+0x12]
757         mov ecx,0x00408900
758         call Core_Code_Segement:Make_Seg_Descriptor
759         call Core_Code_Segement:Set_New_GDT
760         mov [esi+0x18],cx
761         
762         ;复制一份页表
763         call Core_Code_Segement:Copy_Page
764         mov ebx,[esi+0x14]
765         mov [ebx+28],eax                        ;填写PDBR(CR3)
766         
767         popad
768         ret 8                                    ;相当于是stdcall,过程清栈
769         ;---------------------------------------------------------------------
770     start:
771         ;---------------------------------------------------------------------
772         ;安装通用异常处理中断程序
773         mov eax,general_exception_handler
774         mov bx,Core_Code_Segement
775         mov cx,0x8e00                            ;中断门
776         call Core_Code_Segement:Make_Gate_Descriptor
777         mov ebx,IDT_Liner_Address
778         xor esi,esi
779         IDT_EXCEPTION:
780             mov [ebx+esi*8],eax                    ;偏移量是8不是4
781             mov [ebx+esi*8+4],edx
782             inc esi
783             cmp esi,19
784         jle IDT_EXCEPTION
785         ;---------------------------------------------------------------------
786         ;安装通用中断处理中断程序
787         mov eax,general_interrupt_handler
788         mov bx,Core_Code_Segement
789         mov cx,0x8e00                            ;中断门
790         call Core_Code_Segement:Make_Gate_Descriptor
791         mov ebx,IDT_Liner_Address
792         IDT_GENERAL:
793             mov [ebx+esi*8],eax
794             mov [ebx+esi*8+4],edx
795             inc esi
796             cmp esi,255
797         jle IDT_GENERAL
798         ;---------------------------------------------------------------------
799         RTC_SET:                                ;实时时钟中断的填写
800         mov eax,rtm_0x70_interrupt_handle
801         mov bx,Core_Code_Segement
802         mov cx,0x8e00
803         call Core_Code_Segement:Make_Gate_Descriptor
804         mov ebx,IDT_Liner_Address
805         mov [ebx+0x70*8],eax                    ;填充实时时钟中断的中断门
806         mov [ebx+0x70*8+4],edx
807         ;---------------------------------------------------------------------
808         mov word[pidt_base_tmp],256*8-1
809         mov dword[pidt_base_tmp+2],IDT_Liner_Address
810         lidt [pidt_base_tmp]
811         
812         ;初始化8259A
813         mov al,0x11
814         out 0x20,al                                ;ICW1: 级联
815         mov al,0x20                                
816         out 0x21,al                                ;ICW2: 中断向量0x20-0x27
817         mov al,0x04
818         out 0x21,al                                ;ICW3: 从片接在主片的引脚2上
819         mov al,0x01
820         out 0x21,al                                ;ICW4: 全缓冲,手动EOI模式
821         
822         mov al,0x11
823         out 0xa0,al                                ;ICW1:边沿触发/级联方式
824         mov al,0x70
825         out 0xa1,al                                ;ICW2: 起始中断向量
826         mov al,0x04
827         out 0xa1,al                                ;ICW3: 从片级联到IR2,这里主片一样是巧合
828         mov al,0x01
829         out 0xa1,al                                ;ICW4: 非总线缓冲,全嵌套,正常EOI
830         
831         ;设置和时钟中断相关的硬件 
832         mov al,0x0b                                ;RTC寄存器B
833         or al,0x80                                ;阻断NMI
834         out 0x70,al                                ;0x70是索引端口
835         mov al,0x12
836         out 0x71,al                                ;设置寄存器B,禁止周期性中断,开放更新结束后中断,BCD码,24小时制
837         
838         mov al,0xfe    
839         out 0xa1,al                                  ;只留从片的IR0  
840         mov ax,0xfb
841         out 0x21,al
842 
843         mov al,0x0c
844         out 0x70,al
845         in al,0x71                                ;读一下寄存器C
846             
847         ;一定要注意,一定要在中断装完之后才能用put_string,因为这个过程里面有sti
848         mov ebx,message_start                    
849         call Core_Code_Segement:put_string    
850         _@load:
851         ;----------------------------安装门------------------------------------
852         mov edi,salt
853         mov ecx,salt_items_sum
854         _set_gate:
855             push ecx
856             mov eax,[edi+256]
857             mov bx,[edi+260]        ;选择子
858             mov cx,0xec00            ;门是特权级是3的门,那么任何程序都能调用
859             or cx,[edi+262]            ;加上参数个数
860             
861             call Core_Code_Segement:Make_Gate_Descriptor
862             call Core_Code_Segement:Set_New_GDT
863             mov [edi+260],cx        ;回填选择子
864             add edi,salt_length
865             pop ecx
866         loop _set_gate
867         
868         mov ebx,message_In_Gate
869         call far [salt_1+256]            ;调用门显示字符信息(忽略偏移地址(前4字节))
870         ;-------------------------初始化任务管理器-----------------------------
871         mov word[core_tcb+0x04],0xffff    ;状态忙碌
872         mov dword[core_tcb+0x06],0x80100000
873         mov word[core_tcb+0x0a],0xffff    ;LDT初始界限
874         mov ecx,core_tcb                ;添加到TCB链中
875         call append_to_tcb
876         
877         alloc_core_page                    ;为用户管理程序的TSS创造空间
878         
879         mov word[ebx+100],0             ;TI=0
880         mov word[ebx+102],103            ;任务管理器不需要I/O映射,要大于等于界限
881         mov word[ebx+96],0                ;任务允许没有自己的LDT
882         mov eax,cr3
883         mov dword[ebx+28],eax            ;设置CR3,注意不是0了!    
884         mov word[ebx+0],0                ;没有前一个任务
885         
886         mov eax,ebx
887         mov ebx,103                        ;TSS段界限
888         mov ecx,0x00408900
889         call Core_Code_Segement:Make_Seg_Descriptor
890         call Core_Code_Segement:Set_New_GDT
891         mov [core_tcb+0x18],cx
892         
893         ltr    cx                            ;启动任务
894         sti                                ;开中断
895         ;------------------安装用户管理程序的临时返回任务门--------------------        
896         mov eax,0x0000                    ;TSS不需要偏移地址
897         mov bx,[core_tcb+0x18]            ;TSS的选择子
898         mov cx,0xe500
899         
900         call Core_Code_Segement:Make_Gate_Descriptor        
901         call Core_Code_Segement:Set_New_GDT
902         mov [salt_tp],cx                ;填入临时中转任务门选择子,注意不需要加260了
903         ;----------------------------------------------------------------------
904         ;创建用户任务的任务A控制块 
905         alloc_core_page                        ;TCB属于内核的东西
906         mov word [ebx+0x04],0                  ;任务状态:空闲 
907         mov dword [ebx+0x06],0                 ;用户任务局部空间的分配从0开始。
908         mov word [ebx+0x0a],0xffff             ;登记LDT初始的界限到TCB中
909         
910         push dword User_Program_AddressA
911         push ebx
912         call load_program
913         mov ecx,ebx
914         call append_to_tcb
915         ;----------------------------------------------------------------------
916         ;创建用户任务的任务B控制块 
917         alloc_core_page                        ;TCB属于内核的东西
918         mov word [ebx+0x04],0                  ;任务状态:空闲 
919         mov dword [ebx+0x06],0                 ;用户任务局部空间的分配从0开始。
920         mov word [ebx+0x0a],0xffff             ;登记LDT初始的界限到TCB中
921         
922         push dword User_Program_AddressB
923         push ebx
924         call load_program    
925         mov ecx,ebx
926         call append_to_tcb
927         ;----------------------------------------------------------------------
928         _core:
929             mov ebx,core_msg0
930             call Core_Code_Segement:put_string
931             hlt
932         jmp _core
933         ;----------------------------------------------------------------------
934 ;=========================================================================
935 SECTION core_trail
936 ;----------------------------------------------------------------
937 Program_end:

3. 两个用户程序

 1 ;================================用户程序A=======================================
 2         program_length               dd program_end          ;程序总长度#0x00
 3         entry_point                  dd start                ;程序入口点#0x04
 4         salt_position                dd salt_begin           ;SALT表起始偏移量#0x08 
 5         salt_items                   dd (salt_end-salt_begin)/256 
 6                                                             ;SALT条目数#0x0C
 7         TpBack:                         dd  0                    ;任务门的偏移地址没用,直接填充就可以了
 8                                     dw    0                    ;任务门的选择子#0x14
 9         Own_Page                    dd    0                    ;自己页面的物理地址#0x16
10 ;-------------------------------------------------------------------------------
11         ;符号地址检索表
12         salt_begin:                                     
13         PrintString                  db  '@Printf'
14                             times 256-($-PrintString) db 0
15         TerminateProgram:            db  '@TerminateProgram'
16                             times 256-($-TerminateProgram) db 0
17         ReadDiskData                 db  '@ReadHarddisk'
18                             times 256-($-ReadDiskData) db 0
19         Stop_This_Program              db  '@Stop_This_Program'
20                             times 256-($-Stop_This_Program) db 0
21         salt_end:
22         message_0                    db  '  User task A->;;;;;;;;;;;;; I am PhilipA ;;;;;;;;;;;;;;;;;;'
23                                     db  0x0d,0x0a,0
24 ;-------------------------------------------------------------------------------
25       [bits 32]
26 ;-------------------------------------------------------------------------------
27     start:
28         mov ebx,message_0
29         call far [PrintString]
30         call far [Stop_This_Program]
31         jmp start
32         
33         jmp far [fs:TpBack]
34 ;-------------------------------------------------------------------------------
35 program_end:
36 ;================================用户程序B=======================================
37         program_length               dd program_end          ;程序总长度#0x00
38         entry_point                  dd start                ;程序入口点#0x04
39         salt_position                dd salt_begin           ;SALT表起始偏移量#0x08 
40         salt_items                   dd (salt_end-salt_begin)/256 
41                                                             ;SALT条目数#0x0C
42         TpBack:                         dd  0                    ;任务门的偏移地址没用,直接填充就可以了
43                                     dw    0                    ;任务门的选择子#0x14
44         Own_Page                    dd    0                    ;自己页面的物理地址#0x16
45 ;-------------------------------------------------------------------------------
46         ;符号地址检索表
47         salt_begin:                                     
48         PrintString                  db  '@Printf'
49                             times 256-($-PrintString) db 0
50         TerminateProgram:            db  '@TerminateProgram'
51                             times 256-($-TerminateProgram) db 0
52         ReadDiskData                 db  '@ReadHarddisk'
53                             times 256-($-ReadDiskData) db 0
54         Stop_This_Program              db  '@Stop_This_Program'
55                             times 256-($-Stop_This_Program) db 0
56         salt_end:
57         message_0                    db  '  User task B->$$$$$$$$$$$$$ I am PhilipB $$$$$$$$$$$$$$$$$$'
58                                     db  0x0d,0x0a,0
59 ;-------------------------------------------------------------------------------
60       [bits 32]
61 ;-------------------------------------------------------------------------------
62     start:
63         mov ebx,message_0
64         call far [PrintString]
65         call far [Stop_This_Program]
66         jmp start
67         
68         jmp far [fs:TpBack]
69 ;-------------------------------------------------------------------------------
70 program_end:

 

 

 

       

posted @ 2016-04-03 02:17  PhiliAI  阅读(1816)  评论(0编辑  收藏  举报