★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= 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], ;加载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 ]
  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,
  59. div ebx
  60.  
  61. cmp edx,
  62. jne @read_last_sector
  63. dec eax
  64. @read_last_sector:
  65. cmp eax,
  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,
  74. mov ebx,0x00020000
  75. xor esi,esi
  76.  
  77. _flush_PDT: ;清空页表
  78. mov dword[es:ebx+esi*],0x00000000
  79. inc esi
  80. loop _flush_PDT
  81.  
  82. ;页目录的最后一个32字节是指向自己的页表(这个页表就是页目录)
  83. mov dword[ebx+],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*],edx
  99. add eax,0x1000
  100. inc esi
  101. cmp esi,
  102. jl _make_page
  103.  
  104. mov eax,0x00020000
  105. mov cr3,eax ;把页目录基地址放在cr3,准备开启页功能
  106.  
  107. sgdt [pgdt_base]
  108. add dword[pgdt_base+],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+] ;都在一个段上了,直接近转移,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-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,
  159. .read:
  160. in ax,dx
  161. mov [edi],ax
  162. add edi,
  163. loop .read
  164.  
  165. pop ecx
  166.  
  167. ret
  168. ;======================================================================
  169. pgdt_base dw
  170. dd 0x00008000 ;GDT的物理地址
  171. ;======================================================================
  172. times -($-$$) db
  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 ;用户程序所在逻辑扇区
  8. User_Program_AddressB equ ;用户程序所在逻辑扇区
  9. Switch_Stack_Size equ ;切换栈段的大小
  10. Global_Page_Directory equ 0x80000000 ;给全局空间的映射地址
  11. ;----------------------------------------------------------------
  12. %macro alloc_core_page ;给内核程序安排页
  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 ;给用户程序安排页
  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
  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= vstart=0x80040000 ;注意代码段的开始现在是0x80040000了,映射的线性地址
  35. Program_Length dd Program_end ;内核总长度
  36. Code_Entry dd start ;注意偏移地址一定是32位的
  37. ;----------------------------------------------------------------
  38. [bits ]
  39. ;----------------------------------------------------------------
  40. ReadHarddisk: ;push128位磁盘号(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+*]
  50. movzx eax,word[ebp+*]
  51. mov ebx,[ebp+*]
  52. movzx edx,word[ebp+*]
  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,
  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,
  91. _read:
  92. in ax,dx
  93. mov [ebx],ax
  94. add ebx,
  95. loop _read
  96.  
  97. popad
  98. sti
  99. retf ;4个数据
  100. ;----------------------------------------------------------------
  101. put_string: ;ebx:偏移地址
  102. cli ;必须关中断
  103. pushad
  104.  
  105. _print:
  106. mov cl,[ebx]
  107. cmp cl,
  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,
  141. mov [0x800b8000+ebx],cl
  142. mov byte[0x800b8000+ebx+],0x07
  143. shr bx,
  144. inc bx ;以下将光标位置推进一个字符
  145. jmp _roll_screen
  146. _set_0x0d: ;回车
  147. mov ax,bx
  148. mov bl,
  149. div bl
  150. mul bl
  151. mov bx,ax
  152. jmp _set_cursor
  153. _set_0x0a: ;换行
  154. mov bx,ax
  155. add bx,
  156. jmp _roll_screen
  157. _roll_screen:
  158. cmp bx,
  159. jl _set_cursor
  160.  
  161. cld
  162. mov edi,0x800b8000 ;一定要记住,现在内存的地址全都是虚拟地址,偏移地址也是一样的
  163. mov esi,0x800b80a0
  164. mov ecx,
  165. rep movsw
  166. _cls:
  167. mov ebx,
  168. mov ecx,
  169. _print_blank:
  170. mov word[0x800b8000+ebx],0x0720
  171. add bx,
  172. loop _print_blank
  173. mov ebx, ;别总是忘了光标的位置!
  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,
  205. bswap edx
  206. or edx,ecx
  207.  
  208. shl eax,
  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,
  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], ;给gdt的段界限加上8(字节)
  253.  
  254. lgdt [pgdt_base_tmp] ;加载gdtgdtr的位置和实际表的位置无关
  255.  
  256. mov ax,[pgdt_base_tmp] ;得到段界限
  257. xor dx,dx
  258. mov bx, ;得到gdt大小
  259. div bx
  260. mov cx,ax
  261. shl cx, ;得到选择子,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,
  282. dec cx
  283.  
  284. mov [ebx+0x0a],cx
  285.  
  286. mov ax,cx
  287. xor dx,dx
  288. mov cx,
  289. div cx
  290.  
  291. shl ax,
  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*
  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, ;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,
  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, ;页表在页目录的偏移项
  350. and esi,0x003ff000 ;得到页表的偏移地址
  351. or esi,0xffc00000 ;指向页目录
  352.  
  353. and ebx,0x003ff000
  354. shr ebx, ;中间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,
  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,
  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 ;直接给8259EOI终止操作了
  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
  473. dd
  474. pidt_base_tmp: dw
  475. dd
  476. salt:
  477. salt_1: db '@Printf' ;@Printf函数(公用例程)
  478. times -($-salt_1) db
  479. dd put_string
  480. dw Core_Code_Segement
  481. dw ;参数个数
  482.  
  483. salt_2: db '@ReadHarddisk' ;@ReadHarddisk函数(公用例程)
  484. times -($-salt_2) db
  485. dd ReadHarddisk
  486. dw Core_Code_Segement
  487. dw ;参数个数
  488.  
  489. salt_3: db '@Stop_This_Program' ;@Stop_This_Program函数(公用例程)
  490. times -($-salt_3) db
  491. dd Stop_This_Program
  492. dw Core_Code_Segement
  493. dw ;参数个数
  494.  
  495. salt_length: equ $-salt_3
  496. salt_items_sum equ ($-salt)/salt_length ;得到项目总数
  497.  
  498. salt_tp: dw ;任务门,专门拿来给程序切换到全局空间的
  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,
  503. message_In_Gate db ' Hi!My name is Philip:',0x0d,0x0a,
  504. core_msg0 db ' System core task running!',0x0d,0x0a,
  505. No_More_Page db '********No more pages********',
  506. excep_msg db '********Exception encounted********',
  507.  
  508. bin_hex db '0123456789ABCDEF'
  509. ;put_hex_dword子过程用的查找表
  510. core_buf times db ;内核用的缓冲区(2048个字节(2MB))
  511.  
  512. core_tcb times db ;内核(程序管理器)的TCB
  513. ;假设只有2MB内存可以用的意思,正确的做法应该先读PCIE),然后再分配!
  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 ;任务控制块链头指针
  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*],0x00000000
  561. inc esi
  562. cmp esi,
  563. jl _flush_private
  564.  
  565. ;手动刷新页目录缓存
  566. mov eax,cr3 ;本来这个在16章就应该出现的,不刷新的话页目录的缓存还是旧的表项
  567. mov cr3,eax
  568.  
  569. mov esi,[ebp+*] ;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,
  577. test eax,0x00000fff
  578. cmovnz eax,ebx ;低12位不为0则使用向上取整的结果
  579.  
  580. mov ecx,eax
  581. shr ecx, ;获取占的页数
  582. mov edi,[ebp+*] ;获取tcb的线性基地址
  583. mov esi,[ebp+*] ;esi必须是逻辑扇区号
  584.  
  585. _loop_read_@1:
  586. ;注意这个过程和下面的不一样,要使用ediesi是逻辑号,不要用宏
  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, ;512*8==4096
  593. _loop_read_@2:
  594. Read_Data_From_Harddisk
  595. inc esi
  596. add ebx,
  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], ;无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+],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+],cx ;ESDSFSGS域,已经映射到全局空间了
  630. mov [edi+],cx
  631. mov [edi+],cx
  632. mov [edi+],cx
  633.  
  634. ;创建一系列栈
  635. ;创建自身特权级为3的栈
  636. alloc_user_page
  637. mov [edi+],cx ;cx是数据段的选择子
  638. mov edx,[esi+0x06] ;向上拓展的ESP的初始值
  639. mov [edi+],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+],cx ;cx是数据段的选择子
  652. mov edx,[esi+0x06] ;向上拓展的ESP0的初始值
  653. mov [edi+],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+],cx ;cx是数据段的选择子
  666. mov edx,[esi+0x06] ;向上拓展的ESP1的初始值
  667. mov [edi+],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+],cx ;cx是数据段的选择子
  680. mov edx,[esi+0x06] ;向上拓展的ESP2的初始值
  681. mov [edi+],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, ;比较256个字节
  702. repe cmpsd
  703. jne _re_match ;如果成功匹配,那么esiedi刚好会在数据区之后的
  704.  
  705. mov eax,[esi] ;偏移地址
  706. mov [es:edi-],eax ;把偏移地址填入用户程序的符号区
  707. mov ax,[esi+0x04] ;段的选择子
  708.  
  709. or ax,0x0002 ;把RPL改为3,代表(内核)赋予应用程序以特权级3
  710. mov [es:edi-],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,
  722. loop _loop_U_SALT
  723. ;---------------------------------------------------------------------
  724. ;----------------------填入临时中转任务门选择子-----------------------
  725. mov ax,[salt_tp]
  726. mov [0x14],ax ;填充任务门选择子
  727. ;---------------------------------------------------------------------
  728. mov esi,[ebp+*] ;重新获得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+],cx ;TSSLDT选择子
  741.  
  742. mov word[ebx+], ;填充反向链(任务切换的时候处理器会帮着填的,不用操心)
  743. mov dx,[esi+0x12] ;TSS段界限
  744. mov [ebx+],dx
  745. mov word[ebx+], ;T=0
  746.  
  747. mov eax,[0x04] ;从任务的4GB地址空间获取入口点
  748. mov [ebx+],eax ;填写TSSEIP
  749.  
  750. pushfd
  751. pop edx
  752. mov [ebx+],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+],eax ;填写PDBRCR3
  766.  
  767. popad
  768. ret ;相当于是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*],eax ;偏移量是8不是4
  781. mov [ebx+esi*+],edx
  782. inc esi
  783. cmp esi,
  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*],eax
  794. mov [ebx+esi*+],edx
  795. inc esi
  796. cmp esi,
  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*],eax ;填充实时时钟中断的中断门
  806. mov [ebx+0x70*+],edx
  807. ;---------------------------------------------------------------------
  808. mov word[pidt_base_tmp],*-
  809. mov dword[pidt_base_tmp+],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+]
  857. mov bx,[edi+] ;选择子
  858. mov cx,0xec00 ;门是特权级是3的门,那么任何程序都能调用
  859. or cx,[edi+] ;加上参数个数
  860.  
  861. call Core_Code_Segement:Make_Gate_Descriptor
  862. call Core_Code_Segement:Set_New_GDT
  863. mov [edi+],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+] ;调用门显示字符信息(忽略偏移地址(前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+], ;TI=0
  880. mov word[ebx+], ;任务管理器不需要I/O映射,要大于等于界限
  881. mov word[ebx+], ;任务允许没有自己的LDT
  882. mov eax,cr3
  883. mov dword[ebx+],eax ;设置CR3,注意不是0了!
  884. mov word[ebx+], ;没有前一个任务
  885.  
  886. mov eax,ebx
  887. mov ebx, ;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], ;任务状态:空闲
  907. mov dword [ebx+0x06], ;用户任务局部空间的分配从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], ;任务状态:空闲
  919. mov dword [ebx+0x06], ;用户任务局部空间的分配从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)/
  6. ;SALT条目数#0x0C
  7. TpBack: dd ;任务门的偏移地址没用,直接填充就可以了
  8. dw ;任务门的选择子#0x14
  9. Own_Page dd ;自己页面的物理地址#0x16
  10. ;-------------------------------------------------------------------------------
  11. ;符号地址检索表
  12. salt_begin:
  13. PrintString db '@Printf'
  14. times -($-PrintString) db
  15. TerminateProgram: db '@TerminateProgram'
  16. times -($-TerminateProgram) db
  17. ReadDiskData db '@ReadHarddisk'
  18. times -($-ReadDiskData) db
  19. Stop_This_Program db '@Stop_This_Program'
  20. times -($-Stop_This_Program) db
  21. salt_end:
  22. message_0 db ' User task A->;;;;;;;;;;;;; I am PhilipA ;;;;;;;;;;;;;;;;;;'
  23. db 0x0d,0x0a,
  24. ;-------------------------------------------------------------------------------
  25. [bits ]
  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)/
  41. ;SALT条目数#0x0C
  42. TpBack: dd ;任务门的偏移地址没用,直接填充就可以了
  43. dw ;任务门的选择子#0x14
  44. Own_Page dd ;自己页面的物理地址#0x16
  45. ;-------------------------------------------------------------------------------
  46. ;符号地址检索表
  47. salt_begin:
  48. PrintString db '@Printf'
  49. times -($-PrintString) db
  50. TerminateProgram: db '@TerminateProgram'
  51. times -($-TerminateProgram) db
  52. ReadDiskData db '@ReadHarddisk'
  53. times -($-ReadDiskData) db
  54. Stop_This_Program db '@Stop_This_Program'
  55. times -($-Stop_This_Program) db
  56. salt_end:
  57. message_0 db ' User task B->$$$$$$$$$$$$$ I am PhilipB $$$$$$$$$$$$$$$$$$'
  58. db 0x0d,0x0a,
  59. ;-------------------------------------------------------------------------------
  60. [bits ]
  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:

 

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

  1. ASM:《X86汇编语言-从实模式到保护模式》第15章:任务切换

    15章其实应该是和14章相辅相成的(感觉应该是作者觉得14章内容太多了然后切出来了一点).任务切换和14章的某些概念是分不开的. ★PART1:任务门与任务切换的方法 1. 任务管理程序 14章的时候 ...

  2. 设计模式之第17章-备忘录模式(Java实现)

    设计模式之第17章-备忘录模式(Java实现) 好男人就是我,我就是曾小贤.最近陈赫和张子萱事件闹得那是一个沸沸扬扬.想想曾经每年都有爱情公寓陪伴的我现如今过年没有了爱情公寓总是感觉缺少点什么.不知道 ...

  3. ASM:《X86汇编语言-从实模式到保护模式》第9章:实模式下中断机制和实时时钟

    中断是处理器一个非常重要的工作机制.第9章是讲中断在实模式下如何工作,第17章是讲中断在保护模式下如何工作. ★PART1:外部硬件中断 外部硬件中断是通过两个信号线引入处理器内部的,这两条线分别叫N ...

  4. ASM:《X86汇编语言-从实模式到保护模式》第11章:进入保护模式

    ★PART1:进入保护模式 1. 全局描述符表(Global Descriptor Table,GDT)        32位保护模式下,如果要使用一个段,必须先登记,登记的信息包括段的起始地址,段的 ...

  5. ASM:《X86汇编语言-从实模式到保护模式》5-7章:汇编基础

    第5-7章感觉是这一本书中比较奇怪的章节,可能是作者考虑到读者人群水平的差异,故意由浅入深地讲如何在屏幕上显示字符和使用mov,jmp指令等等,但是这样讲的东西有点重复,而且看了第六,第七章以后,感觉 ...

  6. ASM:《X86汇编语言-从实模式到保护模式》第14章:保护模式下的特权保护和任务概述

    ★PART1:32位保护模式下任务的隔离和特权级保护  这一章是全书的重点之一,这一张必须要理解特权级(包括CPL,RPL和DPL的含义)是什么,调用门的使用,还有LDT和TSS的工作原理(15章着重 ...

  7. ASM:《X86汇编语言-从实模式到保护模式》第10章:32位x86处理器的编程架构

    ★PART1:32位的x86处理器执行方式和架构 1. 寄存器的拓展(IA-32) 从80386开始,处理器内的寄存器从16位拓展到32位,命名其实就是在前面加上e(Extend)就好了,8个通用寄存 ...

  8. ASM:《X86汇编语言-从实模式到保护模式》第8章:实模式下硬盘的访问,程序重定位和加载

        第八章是一个非常重要的章节,讲述的是实模式下对硬件的访问(这一节主要讲的是硬盘),还有用户程序重定位的问题.现在整理出来刚好能和保护模式下的用户程序定位作一个对比. ★PART1:用户程序的重 ...

  9. ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

    第16章讲的是分页机制和动态页面分配的问题,说实话这个一开始接触是会把人绕晕的,但是这个的确太重要了,有了分页机制内存管理就变得很简单,而且能直接实现平坦模式. ★PART1:Intel X86基础分 ...

随机推荐

  1. 取文件MD5 WINAPI

    #include <windows.h> #include <wincrypt.h> #include <stdio.h> BOOL GetFileHash(LPC ...

  2. Sort using in VS

  3. C 语言学习 第三次作业总结

    本次作业内容: For循环的使用 If判断语句的使用 常用数学运算表达式的使用 数学函数库中几个常见函数的使用及自我实现 将操作代码提交到coding 作业总结: For循环是C语言中一种基本的循环语 ...

  4. Java构造和解析Json数据

    BaseResult wyComany = propertyService.getWyCompanyById(CommunityInfos.getWyCompany());//这里返回的是json字符 ...

  5. Geolocation API JavaScript访问用户的当前位置信息

    Geolocation API在浏览器中的实现是navigator.geolocation对象,常用的有以下方法. 1.第一个方法是getCurrentPosition() 调用这个方法就会触发请求用 ...

  6. windows 下 新建 点开头的文件和文件夹

    新建 .aaa文件夹 cmd:$ mkdir .aaa 新建 .aaa文件夹 echo " >> .aaa

  7. ppt2013技术整理

    1. 显示选择窗格 便于选择该页的所有元素.分组.隐藏与显示等. 位于:开始-编辑-选择-选择窗格 2. 显示动画窗格 便于调节页面中元素的动画状态. 位于:动画-高级动画-动画窗格 3. 绑定动画触 ...

  8. Samba服务器配置

    Samba服务器配置流程: (1)安装samba服务器先用#rpm -ivh samba-列出与samba有关的rpm包然后选择第一个包,用tab键补齐文件名 (2)创建新用户和其密码#useradd ...

  9. js轮播(qq幻灯片效果)

    <!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equ ...

  10. arcgis engine 监听element的添加、更新和删除事件(使用IGraphicsContainerEvents)

    IGraphicsContainerEvents Interface 如何监听 element事件? 如,当我们在Mapcontrol上添加.删除.更新了一个Element后,如何捕捉到这个事件?   ...