★PART1:中断和异常概述
1. 中断(Interrupt)
指的是正在执行的指令),然后才能对中断进行处理。
软中断是由int n指令引发的中断处理器,n是中断号(类型码)。
2. 异常(Exception)
访问了一个没有登记的页等等)。简单来说就是指令不能正常执行的时候,将引发这种类型的中断。
异常分为三种:
- 程序错误异常,指处理器在执行指令的过程中,检测到了程序中的错误,并由此引发的错误。
- 软件引发的异常。这类异常通常是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芯片的初始化
这个已经在我转的一篇文章写的很清楚了,我们初始化只要按照上面的来就可以了,比教材讲的详细多了,(看这里)。
最后我们来用代码实现一遍:
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: