zoukankan      html  css  js  c++  java
  • 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:

     

           

  • 相关阅读:
    K
    CFileDialog的用法
    MFC编辑框换行实现
    MFC通过对话框窗口句柄获得对话框对象指针
    AfxGetMainWnd()函数用法
    this指针和m_hWnd的区别
    WinAPI: FindWindow、FindWindowEx
    深入浅出Hibernate(二)多对一关系映射
    JAVA 并发编程-读写锁之模拟缓存系统(十一)
    很easy的js双向绑定框架(二):控制器继承
  • 原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/5348808.html
Copyright © 2011-2022 走看看