x86汇编语言-从实模式到保护模式笔记

程序的动态加载和执行

Posted by BINBIN Blog on October 25, 2019

内核的结构、功能和加载

本章代码清单: 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: