在前面的程序中,我们用了4MB的空间来存放页表,并用它映射了4GB的内存空间,而我们的物理内存不见得有这么大,这显然是太浪费了。如果我们的内存总数只有16MB的话,只是页表就占用了25%的内存空间。而实际上,如果仅仅是对等映射的话,16MB的内存只要4个页表就够了。所以,我们有必要知道内存有多大,然后根据内存大小确定多少页表是够用的。而且,一个操作系统也必须知道内存的容量,以便进行内存管理。
克勤克俭用内存
这里利用中断15h
来获取计算机的内存。
在调用中断15h之前,我们需要填充下列寄存器:
e a x
int 15h可完成许多工作,主要由ax的值决定,我们想要获取内存信息,需要将ax赋值为0E820h。e b x
放置着“后续值(continuation value)”,第一次调用时ebx必须为0。e s : d i
指向一个地址范围描述符结构ARDS(Address Range Descriptor Structure),BIOS将会填充此结构。e c x
es:di所指向的地址范围描述符结构的大小,以字节为单位。无论es:di所指向的结构如何设置,BIOS最多将会填充ecx个字节。不过,通常情况下无论ecx为多大,BIOS只填充20字节,有些BIOS忽略ecx的值,总是填充20字节。e d x
0534D4150h(‘SMAP’)──BIOS将会使用此标志,对调用者将要请求的系统映像信息进行校验,这些信息会被BIOS放置到es:di所指向的结构中。
调用中断15h之后,结果存放于下列寄存器中:
C F
CF=0表示没有错误,否则存在错误。e a x
0534D4150h(‘SMAP’)。e s : d i
返回的地址范围描述符结构指针,和输入值相同。e c x
BIOS填充在地址范围描述符中的字节数量,被BIOS所返回的最小值是20字节。e b x
这里放置着为等到下一个地址描述符所需要的后续值,这个值的实际形势依赖于具体的BIOS的实现,调用者不必关心它的具体形式,只需在下次迭代时将其原封不动地放置到ebx中,就可以通过它获取下一个地址范围描述符。如果它的值为0,并且CF没有进位,表示它是最后一个地址范围描述符。
上面提到的地址范围描述符结构(Address Range Descriptor Structure)如下表所示:
偏移 | 名称 | 意义 |
0 | BaseAddrLow | 基地址的低32位 |
4 | BaseAddrHigh | 基地址的高32位 |
8 | LengthLow | 长度(字节)的低32位 |
12 | LengthHigh | 长度(字节)的高32位 |
16 | Type | 这个地址范围的地址类型 |
其中,Type的取值及其意义如下表所示:
取值 | 名称 | 意义 |
1 | AddressRangeMemory | 这个内存段是一段可以被OS使用的RAM |
2 | AddressRangeReserved | 这个地址段正在被使用,或者被系统保留,所以一定不要被OS使用 |
其他 | 未定义 | 保留,为未来使用,任何其他置都必须被OS认为是AddressRangeReserved |
由上面的说明我们看出,ax=0E820h时调用int 15h得到的不仅仅是内存的大小,还包括对不同内存段的一些描述。而且,这些描述都被保存在一个缓冲区中。所以,在我们调用int 15h之前,必须先有缓冲区。我们可以在每得到一次内存描述时都使用同一个缓冲区,然后对缓冲区里的数据进行处理,也可以将每次得到的数据放进不同的位置,比如一块连续的内存,然后在想要处理它们时再读取。后一种方式可能更方便一些,所以在这里定义了一块256字节的缓冲区(代码第65行),它最多可以存放12个20字节大小的结构体。我们现在还不知道它到底够不够用,这个大小仅仅是凭猜测设定。我们将把每次得到的内存信息连续写入这块缓冲区,形成一个结构体数组。然后在保护模式下把它们读出来,显示在屏幕上,并且凭借它们得到内存的容量。
下面是调用中断15h的代码:
65 _MemChkBuf: times 256 db 0
...
111 ; 得到内存数
112 mov ebx, 0
113 mov di, _MemChkBuf
114 .loop:
115 mov eax, 0E820h
116 mov ecx, 20
117 mov edx, 0534D4150h
118 int 15h
119 jc LABEL_MEM_CHK_FAIL
120 add di, 20
121 inc dword [_dwMCRNumber]
122 cmp ebx, 0
123 jne .loop
124 jmp LABEL_MEM_CHK_OK
125 LABEL_MEM_CHK_FAIL:
126 mov dword [_dwMCRNumber], 0
127 LABEL_MEM_CHK_OK:
可以看到,代码使用了一个循环,一旦CF被置位或者ebx为零,循环将结束。在第一次循环开始之前,eax为0000E820h,ebx为0,ecx为20,edx为0534D4150h,es:di指向_MemChkBuf的开始处。在每一次循环进行时,寄存器di的值将会递增,每次的增量为20字节。另外,eax、ecx和edx的值都不会变,ebx的值我们置之不理。同时,每次循环我们让_dwMCRNumber的值加1,这样到循环结
束时它的值会是循环的次数,同时也是地址范围描述符结构的个数。
接下来在保护模式下的32位代码中添加显示内存信息的过程。
305 DispMemSize:
306 push esi
307 push edi
308 push ecx
309
310 mov esi, MemChkBuf
311 mov ecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++)//每次得到一个ARDS
312 .loop: ;{
313 mov edx, 5 ; for(int j=0;j<5;j++) //每次得到一个ARDS中的成员
314 mov edi, ARDStruct ; {//依次显示BaseAddrLow,BaseAddrHigh,LengthLow,
315 .1: ; LengthHigh,Type
316 push dword [esi] ;
317 call DispInt ; DispInt(MemChkBuf[j*4]); //显示一个成员
318 pop eax ;
319 stosd ; ARDStruct[j*4] = MemChkBuf[j*4];
320 add esi, 4 ;
321 dec edx ;
322 cmp edx, 0 ;
323 jnz .1 ; }
324 call DispReturn ; printf("\n");
325 cmp dword [dwType], 1 ; if(Type == AddressRangeMemory)
326 jne .2 ; {
327 mov eax, [dwBaseAddrLow];
328 add eax, [dwLengthLow];
329 cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize)
330 jb .2 ;
331 mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow;
332 .2: ; }
333 loop .loop ;}
334 ;
335 call DispReturn ;printf("\n");
336 push szRAMSize ;
337 call DispStr ;printf("RAM size:");
338 add esp, 4 ;
339 ;
340 push dword [dwMemSize] ;
341 call DispInt ;DispInt(MemSize);
342 add esp, 4 ;
343
344 pop ecx
345 pop edi
346 pop esi
347 ret
对照右边注释中的C代码,可以很容易了解这段代码的目的:程序的主题是一个循环,循环的次数为地址范围描述符结构(下文用ARDStruct代替)的个数,每次循环将会读取一个ARDStruct。首先打印其中每一个成员的各项,然后根据当前结构的类型,得到可以被操作系统使用的内存的上限。结果会被存放在变量dwMemSize中,并在此模块的最后打印到屏幕。
其中,
DispInt
函数定义如下:
42 ;; 显示一个整形数
43 DispInt:
44 mov eax, [esp + 4]
45 shr eax, 24
46 call DispAL
47
48 mov eax, [esp + 4]
49 shr eax, 16
50 call DispAL
51
52 mov eax, [esp + 4]
53 shr eax, 8
54 call DispAL
55
56 mov eax, [esp + 4]
57 call DispAL
58
59 mov ah, 07h ; 0000b: 黑底 0111b: 灰字
60 mov al, ‘h‘
61 push edi
62 mov edi, [dwDispPos]
63 mov [gs:edi], ax
64 add edi, 4
65 mov [dwDispPos], edi
66 pop edi
67
68 ret
69 ;; DispInt 结束
DispStr
定义如下:
71 ;; 显示一个字符串
72 DispStr:
73 push ebp
74 mov ebp, esp
75 push ebx
76 push esi
77 push edi
78
79 mov esi, [ebp + 8] ; pszInfo
80 mov edi, [dwDispPos]
81 mov ah, 0Fh
82 .1:
83 lodsb
84 test al, al
85 jz .2
86 cmp al, 0Ah ; 是回车吗?
87 jnz .3
88 push eax
89 mov eax, edi
90 mov bl, 160
91 div bl
92 and eax, 0FFh
93 inc eax
94 mov bl, 160
95 mul bl
96 mov edi, eax
97 pop eax
98 jmp .1
99 .3:
100 mov [gs:edi], ax
101 add edi, 2
102 jmp .1
103
104 .2:
105 mov [dwDispPos], edi
106
107 pop edi
108 pop esi
109 pop ebx
110 pop ebp
111 ret
112 ;; DispStr 结束
113
114 ;; 换行
115 DispReturn:
116 push szReturn
117 call DispStr ;printf("\n");
118 add esp, 4
119
120 ret
121 ;; DispReturn 结束
DispInt和DispStr函数连同DispAL、DispReturn被放在了lib.inc
中,并且通过如下语句包含进pmtest7.asm中:
%include "lib.inc"
这与直接把代码写进这个位置的效果是一样的,把他们单独放到一个文件有利于阅读。
在DispInt中,[esp+4]即为已经入栈的参数,函数通过4次对DispAL的调用显示了一个整数,并且最后显示一个灰色的字母“h”。函数DispStr通过一个循环来显示字符串,每一次复制一个字符入显存,遇到\0则结束循环。同时,DispStr加入了对回车的处理,遇到0Ah就会从下一行的开始处继续显示。由于这一点,DispReturn也做了简化,通过DispStr来处理回车。
在以前的程序中,我们用edi保存当前的显示位置,从这个程序开始,我们改为用变量dwDispPos来保存。这样我们就可以放心地使用edi这个寄存器。
至此,我们新增的内容已经准备得差不多了,另外还需要提到的一点是,在数据段中,几乎每个变量都有类似的两个符号,比如:
57 _dwMemSize: dd 0
和
73 dwMemSize equ _dwMemSize - $$
在实模式下应使用_dwMemSize,而在保护模式下应使用dwMemSize。因为程序是在实模式下编译的,地址只适用于实模式,在保护模式下,数据的地址应该是其相对于段基址的偏移。
接下来就是调用DispMemSize来显示内存信息啦:
238 push szMemChkTitle
239 call DispStr
240 add esp, 4
241
242 call DispMemSize ; 显示内存信息
在调用DispMemSize之前,我们显示了一个字符串作为将要打印的内存信息的表格头。现在来看看结果:
上面的结果图,总共有六段内存被列出来,对列出的内存情况解释如下表:
内存段 | 属性 | 是否可被OS使用 |
00000000h~0009EFFFh | AddressRangeMemory | 可 |
0009F000h~0009FFFFh | AddressRangeReserved | 不可 |
000E8000h~000FFFFFh | AddressRangeReserved | 不可 |
00100000h~01FEFFFFh | AddressRangeMemory | 可 |
01FF0000h~01FFFFFFh | 未定义 | 不可 |
FFFC0000h~FFFFFFFFh | AddressRangeReserved | 不可 |
从上面可以看出,操作系统能够使用的最大内存地址是01FEFFFFh,所以此极其拥有32MB-16KB的内存。而且幸运的是,我们指定的256字节的内存MemChkBuf是够用的。
你可能没有想到,得到内存容量还要这么多代码,不过,实际上我们除了得到了内存的大小,还得到了可用内存的分布信息。由于历史原因,系统可用内存分布得并不连续,所以在使用的时候,我们要根据得到的信息小心行事。
内存容量得到了,你是否还记得我们为什么要得到内存?我们是为了节约使用,不再初始化所有PDE和所有页表。现在,我们已经可以根据内存大小计算应初始化多少PDE以及多少页表,下面来修改一下函数SetupPaging。
249 ; 启动分页机制 --------------------------------------------------------------
250 SetupPaging:
251 ; 根据内存大小计算应初始化多少PDE以及多少页表
252 xor edx, edx
253 mov eax, [dwMemSize]
254 mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
255 div ebx
256 mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
257 test edx, edx
258 jz .no_remainder
259 inc ecx ; 如果余数不为 0 就需增加一个页表
260 .no_remainder:
261 push ecx ; 暂存页表个数
262
263 ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
264
265 ; 首先初始化页目录
266 mov ax, SelectorPageDir ; 此段首地址为 PageDirBase
267 mov es, ax
268 xor edi, edi
269 xor eax, eax
270 mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
271 .1:
272 stosd
273 add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
274 loop .1
275
276 ; 再初始化所有页表
277 mov ax, SelectorPageTbl ; 此段首地址为 PageTblBase
278 mov es, ax
279 pop eax ; 页表个数
280 mov ebx, 1024 ; 每个页表 1024 个 PTE
281 mul ebx
282 mov ecx, eax ; PTE个数 = 页表个数 * 1024
283 xor edi, edi
284 xor eax, eax
285 mov eax, PG_P | PG_USU | PG_RWW
286 .2:
287 stosd
288 add eax, 4096 ; 每一页指向 4K 的空间
289 loop .2
290
291 mov eax, PageDirBase
292 mov cr3, eax
293 mov eax, cr0
294 or eax, 80000000h
295 mov cr0, eax
296 jmp short .3
297 .3:
298 nop
299
300 ret
301 ; 分页机制启动完毕 ----------------------------------------------------------
在函数的开头,我们就用内存大小除以4MB来得到应初始化的PDE的个数(同时也是页表的个数)。在初始化页表的时候,通过刚刚计算出的页表个数乘以1024(每个页表含1024个PTE)得出要填充的PTE个数,然后通过循环完成对它的初始化。
这样一来,页表所占的空间就小得多,在本例中,32MB的内存实际上只要32KB的页表就够了,所以在GDT中,这样初始化页表段:
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 4096*8-1,DA_DRW
这样,程序所需的内存空间就小了许多。
源代码
; ==========================================
; pmtest7.asm
; 编译方法:nasm pmtest7.asm -o pmtest7.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
PageDirBase equ 200000h ; 页目录开始地址: 2M
PageTblBase equ 201000h ; 页表开始地址: 2M + 4K
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW ; Page Directory
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 4096 * 8 - 1, DA_DRW ; Page Tables
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16
LABEL_DESC_DATA: Descriptor 0, DataLen - 1, DA_DRW ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32 ; Stack, 32 位
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorPageDir equ LABEL_DESC_PAGE_DIR - LABEL_GDT
SelectorPageTbl equ LABEL_DESC_PAGE_TBL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
; 实模式下使用这些符号
; 字符串
_szPMMessage: db "In Protect Mode now. ^-^", 0Ah, 0Ah, 0
; 进入保护模式后显示此字符串
_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0
; 进入保护模式后显示此字符串
_szRAMSize db "RAM size:", 0
_szReturn db 0Ah, 0
; 变量
_wSPValueInRealMode dw 0
_dwMCRNumber: dd 0 ; Memory Check Result
_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。
_dwMemSize: dd 0
_ARDStruct: ; Address Range Descriptor Structure
_dwBaseAddrLow: dd 0
_dwBaseAddrHigh: dd 0
_dwLengthLow: dd 0
_dwLengthHigh: dd 0
_dwType: dd 0
_MemChkBuf: times 256 db 0
; 保护模式下使用这些符号
szPMMessage equ _szPMMessage - $$
szMemChkTitle equ _szMemChkTitle - $$
szRAMSize equ _szRAMSize - $$
szReturn equ _szReturn - $$
dwDispPos equ _dwDispPos - $$
dwMemSize equ _dwMemSize - $$
dwMCRNumber equ _dwMCRNumber - $$
ARDStruct equ _ARDStruct - $$
dwBaseAddrLow equ _dwBaseAddrLow - $$
dwBaseAddrHigh equ _dwBaseAddrHigh - $$
dwLengthLow equ _dwLengthLow - $$
dwLengthHigh equ _dwLengthHigh - $$
dwType equ _dwType - $$
MemChkBuf equ _MemChkBuf - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
; END of [SECTION .gs]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL+3], ax
mov [_wSPValueInRealMode], sp
; 得到内存数
mov ebx, 0
mov di, _MemChkBuf
.loop:
mov eax, 0E820h
mov ecx, 20
mov edx, 0534D4150h
int 15h
jc LABEL_MEM_CHK_FAIL
add di, 20
inc dword [_dwMCRNumber]
cmp ebx, 0
jne .loop
jmp LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
mov dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:
; 初始化 16 位代码段描述符
mov ax, cs
movzx eax, ax
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word [LABEL_DESC_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE16 + 4], al
mov byte [LABEL_DESC_CODE16 + 7], ah
; 初始化 32 位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 初始化数据段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
mov word [LABEL_DESC_DATA + 2], ax
shr eax, 16
mov byte [LABEL_DESC_DATA + 4], al
mov byte [LABEL_DESC_DATA + 7], ah
; 初始化堆栈段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK
mov word [LABEL_DESC_STACK + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK + 4], al
mov byte [LABEL_DESC_STACK + 7], ah
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0
; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [_wSPValueInRealMode]
in al, 92h ; ┓
and al, 11111101b ; ┣ 关闭 A20 地址线
out 92h, al ; ┛
sti ; 开中断
mov ax, 4c00h ; ┓
int 21h ; ┛回到 DOS
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov ax, SelectorData
mov es, ax
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
; 下面显示一个字符串
push szPMMessage
call DispStr
add esp, 4
push szMemChkTitle
call DispStr
add esp, 4
call DispMemSize ; 显示内存信息
call SetupPaging ; 启动分页机制
; 到此停止
jmp SelectorCode16:0
; 启动分页机制 --------------------------------------------------------------
SetupPaging:
; 根据内存大小计算应初始化多少PDE以及多少页表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
div ebx
mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
test edx, edx
jz .no_remainder
inc ecx ; 如果余数不为 0 就需增加一个页表
.no_remainder:
push ecx ; 暂存页表个数
; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
; 首先初始化页目录
mov ax, SelectorPageDir ; 此段首地址为 PageDirBase
mov es, ax
xor edi, edi
xor eax, eax
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
.1:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .1
; 再初始化所有页表
mov ax, SelectorPageTbl ; 此段首地址为 PageTblBase
mov es, ax
pop eax ; 页表个数
mov ebx, 1024 ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE个数 = 页表个数 * 1024
xor edi, edi
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .2
mov eax, PageDirBase
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax
jmp short .3
.3:
nop
ret
; 分页机制启动完毕 ----------------------------------------------------------
DispMemSize:
push esi
push edi
push ecx
mov esi, MemChkBuf
mov ecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++)//每次得到一个ARDS
.loop: ;{
mov edx, 5 ; for(int j=0;j<5;j++) //每次得到一个ARDS中的成员
mov edi, ARDStruct ; {//依次显示BaseAddrLow,BaseAddrHigh,LengthLow,
.1: ; LengthHigh,Type
push dword [esi] ;
call DispInt ; DispInt(MemChkBuf[j*4]); //显示一个成员
pop eax ;
stosd ; ARDStruct[j*4] = MemChkBuf[j*4];
add esi, 4 ;
dec edx ;
cmp edx, 0 ;
jnz .1 ; }
call DispReturn ; printf("\n");
cmp dword [dwType], 1 ; if(Type == AddressRangeMemory)
jne .2 ; {
mov eax, [dwBaseAddrLow];
add eax, [dwLengthLow];
cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize)
jb .2 ;
mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow;
.2: ; }
loop .loop ;}
;
call DispReturn ;printf("\n");
push szRAMSize ;
call DispStr ;printf("RAM size:");
add esp, 4 ;
;
push dword [dwMemSize] ;
call DispInt ;DispInt(MemSize);
add esp, 4 ;
pop ecx
pop edi
pop esi
ret
%include "lib.inc" ; 库函数
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and eax, 7FFFFFFEh ; PE=0, PG=0
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
lib.inc
:
;; lib.inc
;; 显示 AL 中的数字
DispAL:
push ecx
push edx
push edi
mov edi, [dwDispPos]
mov ah, 0Fh ; 0000b: 黑底 1111b: 白字
mov dl, al
shr al, 4
mov ecx, 2
.begin:
and al, 01111b
cmp al, 9
ja .1
add al, ‘0‘
jmp .2
.1:
sub al, 0Ah
add al, ‘A‘
.2:
mov [gs:edi], ax
add edi, 2
mov al, dl
loop .begin
;add edi, 2
mov [dwDispPos], edi
pop edi
pop edx
pop ecx
ret
;; DispAL 结束
;; 显示一个整形数
DispInt:
mov eax, [esp + 4]
shr eax, 24
call DispAL
mov eax, [esp + 4]
shr eax, 16
call DispAL
mov eax, [esp + 4]
shr eax, 8
call DispAL
mov eax, [esp + 4]
call DispAL
mov ah, 07h ; 0000b: 黑底 0111b: 灰字
mov al, ‘h‘
push edi
mov edi, [dwDispPos]
mov [gs:edi], ax
add edi, 4
mov [dwDispPos], edi
pop edi
ret
;; DispInt 结束
;; 显示一个字符串
DispStr:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov esi, [ebp + 8] ; pszInfo
mov edi, [dwDispPos]
mov ah, 0Fh
.1:
lodsb
test al, al
jz .2
cmp al, 0Ah ; 是回车吗?
jnz .3
push eax
mov eax, edi
mov bl, 160
div bl
and eax, 0FFh
inc eax
mov bl, 160
mul bl
mov edi, eax
pop eax
jmp .1
.3:
mov [gs:edi], ax
add edi, 2
jmp .1
.2:
mov [dwDispPos], edi
pop edi
pop esi
pop ebx
pop ebp
ret
;; DispStr 结束
;; 换行
DispReturn:
push szReturn
call DispStr ;printf("\n");
add esp, 4
ret
;; DispReturn 结束