ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务
★PART1:中断和异常概述
1. 中断(Interrupt)
中断包括硬件中断和软中断。硬件中断是由外围设备发出的中断信号引发的,以请求处理器提供服务。当I/O接口发出中断请求的时候,会被像8259A和I/O APIC这样的中断寄存器手机,并发送给处理器。硬件中断完全是随机产生的,与处理器的执行并不同步。当中断发生的时候,处理器要先执行完当前的指令(指的是正在执行的指令),然后才能对中断进行处理。
软中断是由int n指令引发的中断处理器,n是中断号(类型码)。
2. 异常(Exception)
异常就是第9章略过的内部中断。内部中断是处理器内部产生的中断,表示在指令执行的时候遇到了错误。当处理器执行一条非法指令(引用一个不合标准的段,任务切换的时候TSS选择子不是有效的,访问了一个没有登记的页等等)。简单来说就是指令不能正常执行的时候,将引发这种类型的中断。
异常分为三种:
- 程序错误异常,指处理器在执行指令的过程中,检测到了程序中的错误,并由此引发的错误。
- 软件引发的异常。这类异常通常是into,int3和bound指令主动发起的,这些指令允许在指令流的当前点上检查实施异常处理跌条件是否满足。比如一个例子,into指令在执行的时候,将检查EFLAGS寄存器的OF标志位,如果满足为“1”的条件,那么就引发异常。
- 第三种是机器检查异常。这种异常是处理器型号相关的,也就是说,每种处理器都不太一样。无论如何,处理器提供了一种对硬件芯片内部和总线处理进行检查的机制,当检测到有错误的时候,将引发此异常。
根据异常情况的性质和严重性,异常又分为以下三种,并分别实施不同的特权保护。
- 故障(Faults)。故障通常是可以纠正的。最典型的就是处理器执行一个内存访问指令的时候,发现那个段或者页不在内存中(P=0),此时,可以在异常处理程序中予以纠正(分配内存,或者执行磁盘的换入换出操作)。返回时,程序可以重新启动并且不失连续性。当故障发生的时候,处理器把及其状态恢复到引发故障的那条指令之前的状态,在进入异常处理时,压入栈中的返回地址(CS和EIP的内容)是指向引起故障的那条指令的,而不像通常那样指向下一条指令。虚拟内存管理就是以异常为基础的。
- 陷阱(Traps)。陷阱中断通常在执行了解惑陷条件的指令立即产生,如果陷阱条件成立的话。陷阱通常用于调试目的,比如单步中断指令int3和溢出检测指令into。陷阱中断允许程序或者任务从中断处理过程返回后继续执行不失连续性。当陷阱异常发生的时候,转入异常处理程序之前,处理器在栈中压入陷阱截获指令的下一条指令地址。
- 终止(Aborts)。终止标志着最严重的错误,诸如硬件错误,系统表(GDT,LDT等)中的数据不一致或者无效。这种错误发生的时候,程序或者任务都不可能重新启动。
对于某些异常,处理器在转入异常处理程序之前,会在当前栈中压入一个称为错误代码的数值,帮助程序进一步诊断异常产生的位置和原因。
现在解释一下一些比较陌生的中断:
- 80486之后,处理器内部集成了浮点运算器x87 FPU,不需要再安装独立的数学协处理器,所以有些的浮点运算有关的异常不会产生(比如向量为9的协处理器段超越故障)。Wait和fwait指令用于主处理器和浮点处理部件(FPU)之间的同步。他们应当放在浮点指令之后,以捕捉任何浮点的异常。
- 从1993年的Pentium处理器开始,引入了用于加速多媒体处理的多媒体拓展技术(Multi-Media eXtension,MMX),该指令使用单指令多数据(Single-Instruction,Multiple-Data,SIMD)执行模式,以便在64位的寄存器内实施并行的整数运算。随着处理器的更新换代,这项技术也多次拓展,第一次被称为SSE(SIMD Extension),第二次是SSE2,第三次是SSE3。和SIMD有关的异常是从Pentium III处理器开始引入的。
- bound(Check Array Index Against Bounds)指令用于检查数组的索引是否在边界之内,其格式为
bound r16,m16(目的操作数是寄存器,包含了数组的索引,源操作数必须指向内存位置,里面包含了成对出现的字,分别十数组的上限和下限,如果数组索引不在上下限之内,则引发异常
bound r32,m32(和上面的基本一样,除了寄存器是32位的,而且内存位置是包含了成对出现的双字)。
- 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。(主要是为了防止软中断引发的越权操作)。
如果发生了特权级的转变(比如从局部空间转移到了全局空间)。那么要进行栈切换。压栈顺序如下:
- 根据处理器的特权级别,从当前任务的TSS中取得栈段选择子和栈指针。处理器把旧的栈的选择子和栈指针压入新栈。如果中断处理程序的特权级别和当前特权级别一致。则不用转换栈。
- 处理器把EFLGAS压入栈,然后把CS压栈,然后再压栈EIP。
- 如果有错误代码的异常,处理器还要将错误代码压入新栈,紧挨着EIP之后。
中断门和陷阱门的区别就是对IF位的处理不同。通过中断门进入中断处理程序的收,EFLAGS寄存器的IF位被处理器自动清零。以禁止嵌套的中断,当中断返回的时候,从栈中恢复EFLAGS的原始状态。陷阱中断的优先级比较低,当通过陷阱门进入中断处理程序的时候,EFLAGS寄存器的IF位不变,以允许其他中断的优先处理。EFLAGS寄存器的IF位仅影响硬件中断,对NMI,异常和int n形式的中断不起作用。
和GDT一样,如果要访问的位置超过了IDT的界限,那么就会产生常规保护异常(#GP)。
4. 中断任务
中断任务切换指的是通过在IDT的任务门发起的任务切换。硬件中断发生是客观的,可以用中断来实现抢占式的多任务系统(硬件调度机制,代价很大,需要保存大量的机器状态,现代操作系统都是用的软切换)。
可以想一下,如果在某个任务中发出了双重中断(#DF),是一种终止类型的中断,如果把双重中断的处理程序定义成任务,那么当双重故障发生的时候,可以执行任务切换返回内核,并且抹去出错程序,回收其内存空间,然后执行其他调度,这样会非常的自然。
中断机制使用任务门有以下特点:
- 被中断的程序或者任务的整个环境被保存到TSS中。
- 切换的新任务有自己的栈和虚拟内存空间,防止系统因为出错而崩溃。
中断或者异常发起的任务切换,不再保存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: