程序员人生 网站导航

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

栏目:服务器时间:2016-06-23 08:37:00

在前面的程序中,我们用了4MB的空间来寄存页表,并用它映照了4GB的内存空间,而我们的物理内存不见得有这么大,这明显是太浪费了。如果我们的内存总数只有16MB的话,只是页表就占用了25%的内存空间。而实际上,如果仅仅是对等映照的话,16MB的内存只要4个页表就够了。所以,我们有必要知道内存有多大,然后根据内存大小肯定多少页表是够用的。而且,1个操作系统也必须知道内存的容量,以便进行内存管理。

克勤克俭用内存

这里利用中断15h来获得计算机的内存。

在调用中断15h之前,我们需要填充以下寄存器:

  • e a x int 15h可完成许多工作,主要由ax的值决定,我们想要获得内存信息,需要将ax赋值为0E820h。

  • e b x 放置着“后续值(continuation value)”,第1次调用时ebx必须为0。

  • e s : d i 指向1个地址范围描写符结构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 这里放置着为等到下1个地址描写符所需要的后续值,这个值的实际情势依赖于具体的BIOS的实现,调用者没必要关心它的具体情势,只需在下次迭代时将其原封不动地放置到ebx中,就能够通过它获得下1个地址范围描写符。如果它的值为0,并且CF没有进位,表示它是最后1个地址范围描写符。

上面提到的地址范围描写符结构(Address Range Descriptor Structure)以下表所示:

偏移名称意义
0BaseAddrLow基地址的低32位
4BaseAddrHigh基地址的高32位
8LengthLow长度(字节)的低32位
12LengthHigh长度(字节)的高32位
16Type这个地址范围的地址类型

其中,Type的取值及其意义以下表所示:

取值名称意义
1AddressRangeMemory这个内存段是1段可以被OS使用的RAM
2AddressRangeReserved这个地址段正在被使用,或被系统保存,
所以1定不要被OS使用
其他未定义保存,为未来使用,任何其他置都必须被OS
认为是AddressRangeReserved

由上面的说明我们看出,ax=0E820h时调用int 15h得到的不单单是内存的大小,还包括对不同内存段的1些描写。而且,这些描写都被保存在1个缓冲区中。所以,在我们调用int 15h之前,必须先有缓冲区。我们可以在每得到1次内存描写时都使用同1个缓冲区,然后对缓冲区里的数据进行处理,也能够将每次得到的数据放进不同的位置,比如1块连续的内存,然后在想要处理它们时再读取。后1种方式可能更方便1些,所以在这里定义了1块256字节的缓冲区(代码第65行),它最多可以寄存12个20字节大小的结构体。我们现在还不知道它到底够不够用,这个大小仅仅是凭猜想设定。我们将把每次得到的内存信息连续写入这块缓冲区,构成1个结构体数组。然后在保护模式下把它们读出来,显示在屏幕上,并且凭仗它们得到内存的容量。

下面是调用中断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:

可以看到,代码使用了1个循环,1旦CF被置位或ebx为零,循环将结束。在第1次循环开始之前,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++)//每次得到1个ARDS 312 .loop: ;{ 313 mov edx, 5 ; for(int j=0;j<5;j++) //每次得到1个ARDS中的成员 314 mov edi, ARDStruct ; {//顺次显示BaseAddrLow,BaseAddrHigh,LengthLow, 315 .1: ; LengthHigh,Type 316 push dword [esi] ; 317 call DispInt ; DispInt(MemChkBuf[j*4]); //显示1个成员 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代码,可以很容易了解这段代码的目的:程序的主题是1个循环,循环的次数为地址范围描写符结构(下文用ARDStruct代替)的个数,每次循环将会读取1个ARDStruct。首先打印其中每个成员的各项,然后根据当前结构的类型,得到可以被操作系统使用的内存的上限。结果会被寄存在变量dwMemSize中,并在此模块的最后打印到屏幕。

其中,

DispInt函数定义以下:

42 ;; 显示1个整形数 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 ;; 显示1个字符串 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"

这与直接把代码写进这个位置的效果是1样的,把他们单独放到1个文件有益于浏览。

在DispInt中,[esp+4]即为已入栈的参数,函数通过4次对DispAL的调用显示了1个整数,并且最后显示1个灰色的字母“h”。函数DispStr通过1个循环来显示字符串,每次复制1个字符入显存,遇到\0则结束循环。同时,DispStr加入了对回车的处理,遇到0Ah就会从下1行的开始处继续显示。由于这1点,DispReturn也做了简化,通过DispStr来处理回车。

在之前的程序中,我们用edi保存当前的显示位置,从这个程序开始,我们改成用变量dwDispPos来保存。这样我们就能够放心肠使用edi这个寄存器。

至此,我们新增的内容已准备得差不多了,另外还需要提到的1点是,在数据段中,几近每一个变量都有类似的两个符号,比如:

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之前,我们显示了1个字符串作为将要打印的内存信息的表格头。现在来看看结果:

上面的结果图,总共有6段内存被列出来,对列出的内存情况解释以下表:

内存段属性是不是可被OS使用
00000000h~0009EFFFhAddressRangeMemory
0009F000h~0009FFFFhAddressRangeReserved不可
000E8000h~000FFFFFhAddressRangeReserved不可
00100000h~01FEFFFFhAddressRangeMemory
01FF0000h~01FFFFFFh未定义不可
FFFC0000h~FFFFFFFFhAddressRangeReserved不可

从上面可以看出,操作系统能够使用的最大内存地址是01FEFFFFh,所以此极为具有32MB⑴6KB的内存。而且荣幸的是,我们指定的256字节的内存MemChkBuf是够用的。

你可能没有想到,得到内存容量还要这么多代码,不过,实际上我们除得到了内存的大小,还得到了可用内存的散布信息。由于历史缘由,系统可用内存散布得其实不连续,所以在使用的时候,我们要根据得到的信息谨慎行事。

内存容量得到了,你是不是还记得我们为何要得到内存?我们是为了节俭使用,不再初始化所有PDE和所有页表。现在,我们已可以根据内存大小计算应初始化多少PDE和多少页表,下面来修改1下函数SetupPaging。

249 ; 启动分页机制 -------------------------------------------------------------- 250 SetupPaging: 251 ; 根据内存大小计算应初始化多少PDE和多少页表 252 xor edx, edx 253 mov eax, [dwMemSize] 254 mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 1个页表对应的内存大小 255 div ebx 256 mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应当的个数 257 test edx, edx 258 jz .no_remainder 259 inc ecx ; 如果余数不为 0 就需增加1个页表 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个数,然后通过循环完成对它的初始化。

这样1来,页表所占的空间就小很多,在本例中,32MB的内存实际上只要32KB的页表就够了,所以在GDT中,这样初始化页表段:

LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 4096*8⑴,DA_DRW

这样,程序所需的内存空间就小了许多。

源代码

; ========================================== ; pmtest7.asm ; 编译方法:nasm pmtest7.asm -o pmtest7.com ; ========================================== %include "pm.inc" ; 常量, 宏, 和1些说明 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 ; 非1致代码段, 32 LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非1致代码段, 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 ; 履行这1句会把 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 ; 下面显示1个字符串 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, 1个页表对应的内存大小 div ebx mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应当的个数 test edx, edx jz .no_remainder inc ecx ; 如果余数不为 0 就需增加1个页表 .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++)//每次得到1个ARDS .loop: ;{ mov edx, 5 ; for(int j=0;j<5;j++) //每次得到1个ARDS中的成员 mov edi, ARDStruct ; {//顺次显示BaseAddrLow,BaseAddrHigh,LengthLow, .1: ; LengthHigh,Type push dword [esi] ; call DispInt ; DispInt(MemChkBuf[j*4]); //显示1个成员 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 结束 ;; 显示1个整形数 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 结束 ;; 显示1个字符串 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 结束
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐