内核的结构、功能和加载
本章代码清单: 13-1(主引导扇区程序),源程序文件: c13_mbr.asm 本章代码清单: 13-2(微型内核),源程序文件: c13_core.asm 本章代码清单: 13-3(被加载的用户程序),源程序文件: c13.asm
内核分为四个部分,分别是初始化代码、内核代码段、内核数据段和内核例程段,主引导程序也是初始化代码的组成部分。
初始化代码用于从 BIOS 那里接管处理器和计算机硬件的控制权,安装最基本的段描述符,初始化最初的执行环境。然后,从硬盘上读取和加载内核的剩余部分,创建组成内核的各个内存段。初始化代码大部分位于代码清单 13-1 中。
内核的代码和数据位于代码清单 13-2 中。如图 13-1 所示,内核代码段是在第 385 行定义的,用于分配内存,读取和加载用户程序,控制用户程序的执行。
内核数据段是在第 330 行定义的,提供了一段可读写的内存空间,供内核自己使用。 内核例程段是在第 34 行定义的,用于提供各种用途和功能的子过程以简化代码的编写。这些例程既可以用于内核,也供用户程序调用。
除了以上的内容之外,内核文件还包括一个头部,记录了各个段的汇编位置,这些统计数据用于告诉初始化代码如何加载内核。
内核的加载
现在来看代码清单 13-1,也就是主引导程序。
代码清单:
1 ;代码清单13-1
2 ;文件名:c13_mbr.asm
3 ;文件说明:硬盘主引导扇区代码
4 ;创建日期:2011-10-28 22:35 ;设置堆栈段和栈指针
5
6 core_base_address equ 0x00040000 ;常数,内核加载的起始内存地址
7 core_start_sector equ 0x00000001 ;常数,内核的起始逻辑扇区号
8
9 mov ax,cs
10 mov ss,ax
11 mov sp,0x7c00
12
13 ;计算GDT所在的逻辑段地址
14 mov eax,[cs:pgdt+0x7c00+0x02] ;GDT的32位物理地址
15 xor edx,edx
16 mov ebx,16
17 div ebx ;分解成16位逻辑地址
18
19 mov ds,eax ;令DS指向该段以进行操作
20 mov ebx,edx ;段内起始偏移地址
21
22 ;跳过0#号描述符的槽位
23 ;创建1#描述符,这是一个数据段,对应0~4GB的线性地址空间
24 mov dword [ebx+0x08],0x0000ffff ;基地址为0,段界限为0xFFFFF
25 mov dword [ebx+0x0c],0x00cf9200 ;粒度为4KB,存储器段描述符
26
27 ;创建保护模式下初始代码段描述符
28 mov dword [ebx+0x10],0x7c0001ff ;基地址为0x00007c00,界限0x1FF
29 mov dword [ebx+0x14],0x00409800 ;粒度为1个字节,代码段描述符
30
31 ;建立保护模式下的堆栈段描述符 ;基地址为0x00007C00,界限0xFFFFE
32 mov dword [ebx+0x18],0x7c00fffe ;粒度为4KB
33 mov dword [ebx+0x1c],0x00cf9600
34
35 ;建立保护模式下的显示缓冲区描述符
36 mov dword [ebx+0x20],0x80007fff ;基地址为0x000B8000,界限0x07FFF
37 mov dword [ebx+0x24],0x0040920b ;粒度为字节
38
39 ;初始化描述符表寄存器GDTR
40 mov word [cs: pgdt+0x7c00],39 ;描述符表的界限
41
42 lgdt [cs: pgdt+0x7c00]
43
44 in al,0x92 ;南桥芯片内的端口
45 or al,0000_0010B
46 out 0x92,al ;打开A20
47
48 cli ;中断机制尚未工作
49
50 mov eax,cr0
51 or eax,1
52 mov cr0,eax ;设置PE位
53
54 ;以下进入保护模式... ...
55 jmp dword 0x0010:flush ;16位的描述符选择子:32位偏移
56 ;清流水线并串行化处理器
57 [bits 32]
58 flush:
59 mov eax,0x0008 ;加载数据段(0..4GB)选择子
60 mov ds,eax
61
62 mov eax,0x0018 ;加载堆栈段选择子
63 mov ss,eax
64 xor esp,esp ;堆栈指针 <- 0
65
66 ;以下加载系统核心程序
67 mov edi,core_base_address
68
69 mov eax,core_start_sector
70 mov ebx,edi ;起始地址
71 call read_hard_disk_0 ;以下读取程序的起始部分(一个扇区)
72
73 ;以下判断整个程序有多大
74 mov eax,[edi] ;核心程序尺寸
75 xor edx,edx
76 mov ecx,512 ;512字节每扇区
77 div ecx
78
79 or edx,edx
80 jnz @1 ;未除尽,因此结果比实际扇区数少1
81 dec eax ;已经读了一个扇区,扇区总数减1
82 @1:
83 or eax,eax ;考虑实际长度≤512个字节的情况
84 jz setup ;EAX=0 ?
85
86 ;读取剩余的扇区
87 mov ecx,eax ;32位模式下的LOOP使用ECX
88 mov eax,core_start_sector
89 inc eax ;从下一个逻辑扇区接着读
90 @2:
91 call read_hard_disk_0
92 inc eax
93 loop @2 ;循环读,直到读完整个内核
94
95 setup:
96 mov esi,[0x7c00+pgdt+0x02] ;不可以在代码段内寻址pgdt,但可以
97 ;通过4GB的段来访问
98 ;建立公用例程段描述符
99 mov eax,[edi+0x04] ;公用例程代码段起始汇编地址
100 mov ebx,[edi+0x08] ;核心数据段汇编地址
101 sub ebx,eax
102 dec ebx ;公用例程段界限
103 add eax,edi ;公用例程段基地址
104 mov ecx,0x00409800 ;字节粒度的代码段描述符
105 call make_gdt_descriptor
106 mov [esi+0x28],eax
107 mov [esi+0x2c],edx
108
109 ;建立核心数据段描述符
110 mov eax,[edi+0x08] ;核心数据段起始汇编地址
111 mov ebx,[edi+0x0c] ;核心代码段汇编地址
112 sub ebx,eax
113 dec ebx ;核心数据段界限
114 add eax,edi ;核心数据段基地址
115 mov ecx,0x00409200 ;字节粒度的数据段描述符
116 call make_gdt_descriptor
117 mov [esi+0x30],eax
118 mov [esi+0x34],edx
119
120 ;建立核心代码段描述符
121 mov eax,[edi+0x0c] ;核心代码段起始汇编地址
122 mov ebx,[edi+0x00] ;程序总长度
123 sub ebx,eax
124 dec ebx ;核心代码段界限
125 add eax,edi ;核心代码段基地址
126 mov ecx,0x00409800 ;字节粒度的代码段描述符
127 call make_gdt_descriptor
128 mov [esi+0x38],eax
129 mov [esi+0x3c],edx
130
131 mov word [0x7c00+pgdt],63 ;描述符表的界限
132
133 lgdt [0x7c00+pgdt]
134
135 jmp far [edi+0x10]
136
137 ;-------------------------------------------------------------------------------
138 read_hard_disk_0: ;从硬盘读取一个逻辑扇区
139 ;EAX=逻辑扇区号
140 ;DS:EBX=目标缓冲区地址
141 ;返回:EBX=EBX+512
142 push eax
143 push ecx
144 push edx
145
146 push eax
147
148 mov dx,0x1f2
149 mov al,1
150 out dx,al ;读取的扇区数
151
152 inc dx ;0x1f3
153 pop eax
154 out dx,al ;LBA地址7~0
155
156 inc dx ;0x1f4
157 mov cl,8
158 shr eax,cl
159 out dx,al ;LBA地址15~8
160
161 inc dx ;0x1f5
162 shr eax,cl
163 out dx,al ;LBA地址23~16
164
165 inc dx ;0x1f6
166 shr eax,cl
167 or al,0xe0 ;第一硬盘 LBA地址27~24
168 out dx,al
169
170 inc dx ;0x1f7
171 mov al,0x20 ;读命令
172 out dx,al
173
174 .waits:
175 in al,dx
176 and al,0x88
177 cmp al,0x08
178 jnz .waits ;不忙,且硬盘已准备好数据传输
179
180 mov ecx,256 ;总共要读取的字数
181 mov dx,0x1f0
182 .readw:
183 in ax,dx
184 mov [ebx],ax
185 add ebx,2
186 loop .readw
187
188 pop edx
189 pop ecx
190 pop eax
191
192 ret
193
194 ;-------------------------------------------------------------------------------
195 make_gdt_descriptor: ;构造描述符
196 ;输入:EAX=线性基地址
197 ; EBX=段界限
198 ; ECX=属性(各属性位都在原始
199 ; 位置,其它没用到的位置0)
200 ;返回:EDX:EAX=完整的描述符
201 mov edx,eax
202 shl eax,16
203 or ax,bx ;描述符前32位(EAX)构造完毕
204
205 and edx,0xffff0000 ;清除基地址中无关的位
206 rol edx,8
207 bswap edx ;装配基址的31~24和23~16 (80486+)
208
209 xor bx,bx
210 or edx,ebx ;装配段界限的高4位
211
212 or edx,ecx ;装配属性
213
214 ret
215
216 ;-------------------------------------------------------------------------------
217 pgdt dw 0
218 dd 0x00007e00 ;GDT的物理地址
219 ;-------------------------------------------------------------------------------
220 times 510-($-$$) db 0
221 db 0x55,0xaa
-
第 6 行和第 7 行声明了两个常数,分别是内核程序在硬盘上的位置,以及它将要被加载的物理内存地址。声明常数的好处你也知道,将来改起来方便。
-
从第 9 行开始,一直到第 55 行,是为进入保护模式做准备。如图 13-2 所示,因为主引导程序的加载位置是物理地址 0x00007C00,所以,从这个位置往上是 512 字节的初始化代码段,从这个位置往下是 4KB 的内核堆栈。
内核程序的大小也是不定的,但可以规定它的起始位置。在这里,我们决定将它加载到从物理内存地址 0x00040000 开始的地方。从这个地方往上,一直到 0x0009FFFF,都是它的地盘,取决于它到底有多大,想用多少就用多少。从 0x000A0000 往上,是 ROM BIOS,硬件专有的!
从 0x000B8000 往上的 32KB,是文本模式的显示缓冲区。
最后,从 1MB 开始的大量空间是留给用户程序用的,具体数量取决于你到底安装了多少物理内存。
在进入保护模式之前,初始化程序已经在全局描述符表(GDT)中安装了几个必要的描述符。
下面开始加载内核:
首先是初始化各个段寄存器以访问相应的内存段。
- 第 59、 60 行,使 DS 指向全部 4GB 的内存空间;
- 第 62~64 行,使 SS 指向初始的堆栈空间,并初始化堆栈指针寄存器 ESP 的内容为 0。第一个数据压入时,因为堆栈的操作是先减 ESP 的值,再保存数据,所以,如果是压入一个字,ESP 的内容为 0xFFFFFFFE;如果压入的是双字, ESP 的内容为 0xFFFFFFFC。
接下来是从硬盘把内核程序读入内存。
-
第 67~69 行, 它在硬盘上的起始逻辑扇区号和物理内存地址已经由两个常数给出,现分别将它们传送到 EAX 和 EDI 寄存器。
-
第 69~93 行, 取得内核的长度,并计算它所占用的扇区数。
安装内核的段描述符
要使内核工作起来,首要的任务是为它的各个段创建描述符。换句话说,还要为 GDT 续添新的描述符。进入保护模式前,我们在代码清单 13-1 的第 42 行使用指令! ` lgdt [cs: pgdt+0x7c00] `
来加载全局描述符表寄存器(GDTR),标号 pgdt 所指向的内存位置包含了 GDT 的基地址和大小。
我们的任务是重新从标号 pgdt 处取得 GDT 的基地址,为其添加描述符,并修改它的大小,然后用 lgdt 指令重新加载一遍 GDTR 寄存器,使修改生效。
标号 pgdt 所指向的内存区域位于主引导程序内,而我们当前正在保护模式下执行主引导程序。保护模式下的代码段只是用来执行的,是否能读出,取决于其描述符的类别字段, 但无论如何它都不能写入!
我们拥有一个指向全部 4GB 内存空间的描述符,标号 pgdt 所指向的内 存位置不单单是在主引导程序内,同时也是 4GB 内存空间的一部分!
如图 13-4 所示,标号 pgdt 在数值上等于它距离段首的偏移量,也就是编译阶段的汇编地址。
主引导程序的物理起始地址是 0x00007C00,故 pgdt 在 4GB 段内的偏移量是 0x00007C00+pgdt。
这样,为了得到 GDT 的基地址,代码清单 13-1 第 96 行,使用了指令
mov esi,[0x7c00+pgdt+0x02]
注意,指令中的表达式是在编译阶段计算的。默认的段寄存器是 DS,当这条指令执行时,处理器用 DS 描述符高速缓存器中的 32 位线性基地址 0x00000000 加上用该表达式计算出的偏移量来访问内存。
现在可以创建与内核相关的其他段描述符。首先是公共例程段。如图 13-5 所示,内核头部偏移 0x04 处的一个双字,就是公共例程段的起始汇编地址。由于内核被加载的物理地址是由 EDI寄存器指向的,所以,第 99 行,直接访问 4GB 内存段,从该偏移位置取出公共例程段的起始汇编地址。
现在可以创建与内核相关的其他段描述符。首先是公共例程段。如图 13-5 所示,内核头部偏移 0x04 处的一个双字,就是公共例程段的起始汇编地址。由于内核被加载的物理地址是由 EDI寄存器指向的,所以,第 99 行,直接访问 4GB 内存段,从该偏移位置取出公共例程段的起始汇编地址!
既然是灵活的方法,还能以不变应万变,就应该定义成过程,以方便在需要的时候随时调用。在这里,我们的方法是使用过程 ake_gdt_descriptor。 过程 make_gdt_descriptor 位于代码清单 13-1 的 195~217 行。
第 104 行,将段属性值 0x00409800 传送到 ECX 寄存器。
第 105 行,调用过程创建描述符,下面来看看具体的创建过程。
代码清单 13-1 的第 201~203 行用于构造描述符的低 32 位。
基地址在 EDX 寄存器中有备份,执行第 205~207 行的指令后,会使基地址部分在两边就位。
最后,第 212 行,将 ECX 寄存器中的段属性与 EDX 寄存器中的描述符高 32 位合并。至此,我们就在 EDX:EAX 中得到了完整的 64 位描述符。第 214 行, ret 指令将控制返回到调用者。 现在,回到主程序,来看第 106、 107 行, ESI 寄存器的内容是 GDT 的基地址,这两条指令访问 4GB 的段,定位到 GDT,在原先的基础上,再添加一个描述符,就是我们刚刚创建的描述符。 第 110~129 行,用于安装内核数据段和内核代码段的描述符,也采用了相同的过程,不再一一讲解。 第 131 行,通过 4GB 的数据段访问 pgdt,修改它的界限值。现在, GDT 中已经有 8 个描述符,故其总长度为 64 字节。相应地,界限值为 63。 第 133 行,通过 4GB 的数据段访问 pgdt,重新加载 GDTR,使上面那些对 GDT 的修改生效。至此,内核已经全部加载完毕,图 13-7 是内核加载完成之后的 GDT 布局。 第 136 行,通过 4GB 的数据段访问内核的头部,用间接远转移指令从给定的入口进入内核执行。
在内核中执行
代码清单13-2
1 ;代码清单13-2
2 ;文件名:c13_core.asm
3 ;文件说明:保护模式微型核心程序
4 ;创建日期:2011-10-26 12:11
5
6 ;以下常量定义部分。内核的大部分内容都应当固定
7 core_code_seg_sel equ 0x38 ;内核代码段选择子
8 core_data_seg_sel equ 0x30 ;内核数据段选择子
9 sys_routine_seg_sel equ 0x28 ;系统公共例程代码段的选择子
10 video_ram_seg_sel equ 0x20 ;视频显示缓冲区的段选择子
11 core_stack_seg_sel equ 0x18 ;内核堆栈段选择子
12 mem_0_4_gb_seg_sel equ 0x08 ;整个0-4GB内存的段的选择子
13
14 ;-------------------------------------------------------------------------------
15 ;以下是系统核心的头部,用于加载核心程序
16 core_length dd core_end ;核心程序总长度#00
17
18 sys_routine_seg dd section.sys_routine.start
19 ;系统公用例程段位置#04
20
21 core_data_seg dd section.core_data.start
22 ;核心数据段位置#08
23
24 core_code_seg dd section.core_code.start
25 ;核心代码段位置#0c
26
27
28 core_entry dd start ;核心代码段入口点#10
29 dw core_code_seg_sel
30
31 ;===============================================================================
32 [bits 32]
33 ;===============================================================================
34 SECTION sys_routine vstart=0 ;系统公共例程代码段
35 ;-------------------------------------------------------------------------------
36 ;字符串显示例程
37 put_string: ;显示0终止的字符串并移动光标
38 ;输入:DS:EBX=串地址
39 push ecx
40 .getc:
41 mov cl,[ebx]
42 or cl,cl
43 jz .exit
44 call put_char
45 inc ebx
46 jmp .getc
47
48 .exit:
49 pop ecx
50 retf ;段间返回
51
52 ;-------------------------------------------------------------------------------
53 put_char: ;在当前光标处显示一个字符,并推进
54 ;光标。仅用于段内调用
55 ;输入:CL=字符ASCII码
56 pushad
57
58 ;以下取当前光标位置
59 mov dx,0x3d4
60 mov al,0x0e
61 out dx,al
62 inc dx ;0x3d5
63 in al,dx ;高字
64 mov ah,al
65
66 dec dx ;0x3d4
67 mov al,0x0f
68 out dx,al
69 inc dx ;0x3d5
70 in al,dx ;低字
71 mov bx,ax ;BX=代表光标位置的16位数
72
73 cmp cl,0x0d ;回车符?
74 jnz .put_0a
75 mov ax,bx
76 mov bl,80
77 div bl
78 mul bl
79 mov bx,ax
80 jmp .set_cursor
81
82 .put_0a:
83 cmp cl,0x0a ;换行符?
84 jnz .put_other
85 add bx,80
86 jmp .roll_screen
87
88 .put_other: ;正常显示字符
89 push es
90 mov eax,video_ram_seg_sel ;0xb8000段的选择子
91 mov es,eax
92 shl bx,1
93 mov [es:bx],cl
94 pop es
95
96 ;以下将光标位置推进一个字符
97 shr bx,1
98 inc bx
99
100 .roll_screen:
101 cmp bx,2000 ;光标超出屏幕?滚屏
102 jl .set_cursor
103
104 push ds
105 push es
106 mov eax,video_ram_seg_sel
107 mov ds,eax
108 mov es,eax
109 cld
110 mov esi,0xa0 ;小心!32位模式下movsb/w/d
111 mov edi,0x00 ;使用的是esi/edi/ecx
112 mov ecx,1920
113 rep movsd
114 mov bx,3840 ;清除屏幕最底一行
115 mov ecx,80 ;32位程序应该使用ECX
116 .cls:
117 mov word[es:bx],0x0720
118 add bx,2
119 loop .cls
120
121 pop es
122 pop ds
123
124 mov bx,1920
125
126 .set_cursor:
127 mov dx,0x3d4
128 mov al,0x0e
129 out dx,al
130 inc dx ;0x3d5
131 mov al,bh
132 out dx,al
133 dec dx ;0x3d4
134 mov al,0x0f
135 out dx,al
136 inc dx ;0x3d5
137 mov al,bl
138 out dx,al
139
140 popad
141 ret
142
143 ;-------------------------------------------------------------------------------
144 read_hard_disk_0: ;从硬盘读取一个逻辑扇区
145 ;EAX=逻辑扇区号
146 ;DS:EBX=目标缓冲区地址
147 ;返回:EBX=EBX+512
148 push eax
149 push ecx
150 push edx
151
152 push eax
153
154 mov dx,0x1f2
155 mov al,1
156 out dx,al ;读取的扇区数
157
158 inc dx ;0x1f3
159 pop eax
160 out dx,al ;LBA地址7~0
161
162 inc dx ;0x1f4
163 mov cl,8
164 shr eax,cl
165 out dx,al ;LBA地址15~8
166
167 inc dx ;0x1f5
168 shr eax,cl
169 out dx,al ;LBA地址23~16
170
171 inc dx ;0x1f6
172 shr eax,cl
173 or al,0xe0 ;第一硬盘 LBA地址27~24
174 out dx,al
175
176 inc dx ;0x1f7
177 mov al,0x20 ;读命令
178 out dx,al
179
180 .waits:
181 in al,dx
182 and al,0x88
183 cmp al,0x08
184 jnz .waits ;不忙,且硬盘已准备好数据传输
185
186 mov ecx,256 ;总共要读取的字数
187 mov dx,0x1f0
188 .readw:
189 in ax,dx
190 mov [ebx],ax
191 add ebx,2
192 loop .readw
193
194 pop edx
195 pop ecx
196 pop eax
197
198 retf ;段间返回
199
200 ;-------------------------------------------------------------------------------
201 ;汇编语言程序是极难一次成功,而且调试非常困难。这个例程可以提供帮助
202 put_hex_dword: ;在当前光标处以十六进制形式显示
203 ;一个双字并推进光标
204 ;输入:EDX=要转换并显示的数字
205 ;输出:无
206 pushad
207 push ds
208
209 mov ax,core_data_seg_sel ;切换到核心数据段
210 mov ds,ax
211
212 mov ebx,bin_hex ;指向核心数据段内的转换表
213 mov ecx,8
214 .xlt:
215 rol edx,4
216 mov eax,edx
217 and eax,0x0000000f
218 xlat
219
220 push ecx
221 mov cl,al
222 call put_char
223 pop ecx
224
225 loop .xlt
226
227 pop ds
228 popad
229 retf
230
231 ;-------------------------------------------------------------------------------
232 allocate_memory: ;分配内存
233 ;输入:ECX=希望分配的字节数
234 ;输出:ECX=起始线性地址
235 push ds
236 push eax
237 push ebx
238
239 mov eax,core_data_seg_sel
240 mov ds,eax
241
242 mov eax,[ram_alloc]
243 add eax,ecx ;下一次分配时的起始地址
244
245 ;这里应当有检测可用内存数量的指令
246
247 mov ecx,[ram_alloc] ;返回分配的起始地址
248
249 mov ebx,eax
250 and ebx,0xfffffffc
251 add ebx,4 ;强制对齐
252 test eax,0x00000003 ;下次分配的起始地址最好是4字节对齐
253 cmovnz eax,ebx ;如果没有对齐,则强制对齐
254 mov [ram_alloc],eax ;下次从该地址分配内存
255 ;cmovcc指令可以避免控制转移
256 pop ebx
257 pop eax
258 pop ds
259
260 retf
261
262 ;-------------------------------------------------------------------------------
263 set_up_gdt_descriptor: ;在GDT内安装一个新的描述符
264 ;输入:EDX:EAX=描述符
265 ;输出:CX=描述符的选择子
266 push eax
267 push ebx
268 push edx
269
270 push ds
271 push es
272
273 mov ebx,core_data_seg_sel ;切换到核心数据段
274 mov ds,ebx
275
276 sgdt [pgdt] ;以便开始处理GDT
277
278 mov ebx,mem_0_4_gb_seg_sel
279 mov es,ebx
280
281 movzx ebx,word [pgdt] ;GDT界限
282 inc bx ;GDT总字节数,也是下一个描述符偏移
283 add ebx,[pgdt+2] ;下一个描述符的线性地址
284
285 mov [es:ebx],eax
286 mov [es:ebx+4],edx
287
288 add word [pgdt],8 ;增加一个描述符的大小
289
290 lgdt [pgdt] ;对GDT的更改生效
291
292 mov ax,[pgdt] ;得到GDT界限值
293 xor dx,dx
294 mov bx,8
295 div bx ;除以8,去掉余数
296 mov cx,ax
297 shl cx,3 ;将索引号移到正确位置
298
299 pop es
300 pop ds
301
302 pop edx
303 pop ebx
304 pop eax
305
306 retf
307 ;-------------------------------------------------------------------------------
308 make_seg_descriptor: ;构造存储器和系统的段描述符
309 ;输入:EAX=线性基地址
310 ; EBX=段界限
311 ; ECX=属性。各属性位都在原始
312 ; 位置,无关的位清零
313 ;返回:EDX:EAX=描述符
314 mov edx,eax
315 shl eax,16
316 or ax,bx ;描述符前32位(EAX)构造完毕
317
318 and edx,0xffff0000 ;清除基地址中无关的位
319 rol edx,8
320 bswap edx ;装配基址的31~24和23~16 (80486+)
321
322 xor bx,bx
323 or edx,ebx ;装配段界限的高4位
324
325 or edx,ecx ;装配属性
326
327 retf
328
329 ;===============================================================================
330 SECTION core_data vstart=0 ;系统核心的数据段
331 ;-------------------------------------------------------------------------------
332 pgdt dw 0 ;用于设置和修改GDT
333 dd 0
334
335 ram_alloc dd 0x00100000 ;下次分配内存时的起始地址
336
337 ;符号地址检索表
338 salt:
339 salt_1 db '@PrintString'
340 times 256-($-salt_1) db 0
341 dd put_string
342 dw sys_routine_seg_sel
343
344 salt_2 db '@ReadDiskData'
345 times 256-($-salt_2) db 0
346 dd read_hard_disk_0
347 dw sys_routine_seg_sel
348
349 salt_3 db '@PrintDwordAsHexString'
350 times 256-($-salt_3) db 0
351 dd put_hex_dword
352 dw sys_routine_seg_sel
353
354 salt_4 db '@TerminateProgram'
355 times 256-($-salt_4) db 0
356 dd return_point
357 dw core_code_seg_sel
358
359 salt_item_len equ $-salt_4
360 salt_items equ ($-salt)/salt_item_len
361
362 message_1 db ' If you seen this message,that means we '
363 db 'are now in protect mode,and the system '
364 db 'core is loaded,and the video display '
365 db 'routine works perfectly.',0x0d,0x0a,0
366
367 message_5 db ' Loading user program...',0
368
369 do_status db 'Done.',0x0d,0x0a,0
370
371 message_6 db 0x0d,0x0a,0x0d,0x0a,0x0d,0x0a
372 db ' User program terminated,control returned.',0
373
374 bin_hex db '0123456789ABCDEF'
375 ;put_hex_dword子过程用的查找表
376 core_buf times 2048 db 0 ;内核用的缓冲区
377
378 esp_pointer dd 0 ;内核用来临时保存自己的栈指针
379
380 cpu_brnd0 db 0x0d,0x0a,' ',0
381 cpu_brand times 52 db 0
382 cpu_brnd1 db 0x0d,0x0a,0x0d,0x0a,0
383
384 ;===============================================================================
385 SECTION core_code vstart=0
386 ;-------------------------------------------------------------------------------
387 load_relocate_program: ;加载并重定位用户程序
388 ;输入:ESI=起始逻辑扇区号
389 ;返回:AX=指向用户程序头部的选择子
390 push ebx
391 push ecx
392 push edx
393 push esi
394 push edi
395
396 push ds
397 push es
398
399 mov eax,core_data_seg_sel
400 mov ds,eax ;切换DS到内核数据段
401
402 mov eax,esi ;读取程序头部数据
403 mov ebx,core_buf
404 call sys_routine_seg_sel:read_hard_disk_0
405
406 ;以下判断整个程序有多大
407 mov eax,[core_buf] ;程序尺寸
408 mov ebx,eax
409 and ebx,0xfffffe00 ;使之512字节对齐(能被512整除的数,
410 add ebx,512 ;低9位都为0
411 test eax,0x000001ff ;程序的大小正好是512的倍数吗?
412 cmovnz eax,ebx ;不是。使用凑整的结果
413
414 mov ecx,eax ;实际需要申请的内存数量
415 call sys_routine_seg_sel:allocate_memory
416 mov ebx,ecx ;ebx -> 申请到的内存首地址
417 push ebx ;保存该首地址
418 xor edx,edx
419 mov ecx,512
420 div ecx
421 mov ecx,eax ;总扇区数
422
423 mov eax,mem_0_4_gb_seg_sel ;切换DS到0-4GB的段
424 mov ds,eax
425
426 mov eax,esi ;起始扇区号
427 .b1:
428 call sys_routine_seg_sel:read_hard_disk_0
429 inc eax
430 loop .b1 ;循环读,直到读完整个用户程序
431
432 ;建立程序头部段描述符
433 pop edi ;恢复程序装载的首地址
434 mov eax,edi ;程序头部起始线性地址
435 mov ebx,[edi+0x04] ;段长度
436 dec ebx ;段界限
437 mov ecx,0x00409200 ;字节粒度的数据段描述符
438 call sys_routine_seg_sel:make_seg_descriptor
439 call sys_routine_seg_sel:set_up_gdt_descriptor
440 mov [edi+0x04],cx
441
442 ;建立程序代码段描述符
443 mov eax,edi
444 add eax,[edi+0x14] ;代码起始线性地址
445 mov ebx,[edi+0x18] ;段长度
446 dec ebx ;段界限
447 mov ecx,0x00409800 ;字节粒度的代码段描述符
448 call sys_routine_seg_sel:make_seg_descriptor
449 call sys_routine_seg_sel:set_up_gdt_descriptor
450 mov [edi+0x14],cx
451
452 ;建立程序数据段描述符
453 mov eax,edi
454 add eax,[edi+0x1c] ;数据段起始线性地址
455 mov ebx,[edi+0x20] ;段长度
456 dec ebx ;段界限
457 mov ecx,0x00409200 ;字节粒度的数据段描述符
458 call sys_routine_seg_sel:make_seg_descriptor
459 call sys_routine_seg_sel:set_up_gdt_descriptor
460 mov [edi+0x1c],cx
461
462 ;建立程序堆栈段描述符
463 mov ecx,[edi+0x0c] ;4KB的倍率
464 mov ebx,0x000fffff
465 sub ebx,ecx ;得到段界限
466 mov eax,4096
467 mul dword [edi+0x0c]
468 mov ecx,eax ;准备为堆栈分配内存
469 call sys_routine_seg_sel:allocate_memory
470 add eax,ecx ;得到堆栈的高端物理地址
471 mov ecx,0x00c09600 ;4KB粒度的堆栈段描述符
472 call sys_routine_seg_sel:make_seg_descriptor
473 call sys_routine_seg_sel:set_up_gdt_descriptor
474 mov [edi+0x08],cx
475
476 ;重定位SALT
477 mov eax,[edi+0x04]
478 mov es,eax ;es -> 用户程序头部
479 mov eax,core_data_seg_sel
480 mov ds,eax
481
482 cld
483
484 mov ecx,[es:0x24] ;用户程序的SALT条目数
485 mov edi,0x28 ;用户程序内的SALT位于头部内0x2c处
486 .b2:
487 push ecx
488 push edi
489
490 mov ecx,salt_items
491 mov esi,salt
492 .b3:
493 push edi
494 push esi
495 push ecx
496
497 mov ecx,64 ;检索表中,每条目的比较次数
498 repe cmpsd ;每次比较4字节
499 jnz .b4
500 mov eax,[esi] ;若匹配,esi恰好指向其后的地址数据
501 mov [es:edi-256],eax ;将字符串改写成偏移地址
502 mov ax,[esi+4]
503 mov [es:edi-252],ax ;以及段选择子
504 .b4:
505
506 pop ecx
507 pop esi
508 add esi,salt_item_len
509 pop edi ;从头比较
510 loop .b3
511
512 pop edi
513 add edi,256
514 pop ecx
515 loop .b2
516
517 mov ax,[es:0x04]
518
519 pop es ;恢复到调用此过程前的es段
520 pop ds ;恢复到调用此过程前的ds段
521
522 pop edi
523 pop esi
524 pop edx
525 pop ecx
526 pop ebx
527
528 ret
529
530 ;-------------------------------------------------------------------------------
531 start:
532 mov ecx,core_data_seg_sel ;使ds指向核心数据段
533 mov ds,ecx
534
535 mov ebx,message_1
536 call sys_routine_seg_sel:put_string
537
538 ;显示处理器品牌信息
539 mov eax,0x80000002
540 cpuid
541 mov [cpu_brand + 0x00],eax
542 mov [cpu_brand + 0x04],ebx
543 mov [cpu_brand + 0x08],ecx
544 mov [cpu_brand + 0x0c],edx
545
546 mov eax,0x80000003
547 cpuid
548 mov [cpu_brand + 0x10],eax
549 mov [cpu_brand + 0x14],ebx
550 mov [cpu_brand + 0x18],ecx
551 mov [cpu_brand + 0x1c],edx
552
553 mov eax,0x80000004
554 cpuid
555 mov [cpu_brand + 0x20],eax
556 mov [cpu_brand + 0x24],ebx
557 mov [cpu_brand + 0x28],ecx
558 mov [cpu_brand + 0x2c],edx
559
560 mov ebx,cpu_brnd0
561 call sys_routine_seg_sel:put_string
562 mov ebx,cpu_brand
563 call sys_routine_seg_sel:put_string
564 mov ebx,cpu_brnd1
565 call sys_routine_seg_sel:put_string
566
567 mov ebx,message_5
568 call sys_routine_seg_sel:put_string
569 mov esi,50 ;用户程序位于逻辑50扇区
570 call load_relocate_program
571
572 mov ebx,do_status
573 call sys_routine_seg_sel:put_string
574
575 mov [esp_pointer],esp ;临时保存堆栈指针
576
577 mov ds,ax
578
579 jmp far [0x10] ;控制权交给用户程序(入口点)
580 ;堆栈可能切换
581
582 return_point: ;用户程序返回点
583 mov eax,core_data_seg_sel ;使ds指向核心数据段
584 mov ds,eax
585
586 mov eax,core_stack_seg_sel ;切换回内核自己的堆栈
587 mov ss,eax
588 mov esp,[esp_pointer]
589
590 mov ebx,message_6
591 call sys_routine_seg_sel:put_string
592
593 ;这里可以放置清除用户程序各种描述符的指令
594 ;也可以加载并启动其它程序
595
596 hlt
597
598 ;===============================================================================
599 SECTION core_trail
600 ;-------------------------------------------------------------------------------
601 core_end: