一个操作系统的实现(7)-获取机器内存并进行合理分页

在前面的程序中,我们用了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 结束

你可能感兴趣的:(操作系统,分页,内存,存储)