一个操作系统的实现(7)-获取机器内存并进行合理分页
2016-05-29 18:36
302 查看
在前面的程序中,我们用了4MB的空间来存放页表,并用它映射了4GB的内存空间,而我们的物理内存不见得有这么大,这显然是太浪费了。如果我们的内存总数只有16MB的话,只是页表就占用了25%的内存空间。而实际上,如果仅仅是对等映射的话,16MB的内存只要4个页表就够了。所以,我们有必要知道内存有多大,然后根据内存大小确定多少页表是够用的。而且,一个操作系统也必须知道内存的容量,以便进行内存管理。
这里利用中断
在调用中断15h之前,我们需要填充下列寄存器:
调用中断15h之后,结果存放于下列寄存器中:
上面提到的地址范围描述符结构(Address Range Descriptor Structure)如下表所示:
其中,Type的取值及其意义如下表所示:
由上面的说明我们看出,ax=0E820h时调用int 15h得到的不仅仅是内存的大小,还包括对不同内存段的一些描述。而且,这些描述都被保存在一个缓冲区中。所以,在我们调用int 15h之前,必须先有缓冲区。我们可以在每得到一次内存描述时都使用同一个缓冲区,然后对缓冲区里的数据进行处理,也可以将每次得到的数据放进不同的位置,比如一块连续的内存,然后在想要处理它们时再读取。后一种方式可能更方便一些,所以在这里定义了一块256字节的缓冲区(代码第65行),它最多可以存放12个20字节大小的结构体。我们现在还不知道它到底够不够用,这个大小仅仅是凭猜测设定。我们将把每次得到的内存信息连续写入这块缓冲区,形成一个结构体数组。然后在保护模式下把它们读出来,显示在屏幕上,并且凭借它们得到内存的容量。
下面是调用中断15h的代码:
可以看到,代码使用了一个循环,一旦CF被置位或者ebx为零,循环将结束。在第一次循环开始之前,eax为0000E820h,ebx为0,ecx为20,edx为0534D4150h,es:di指向_MemChkBuf的开始处。在每一次循环进行时,寄存器di的值将会递增,每次的增量为20字节。另外,eax、ecx和edx的值都不会变,ebx的值我们置之不理。同时,每次循环我们让_dwMCRNumber的值加1,这样到循环结
束时它的值会是循环的次数,同时也是地址范围描述符结构的个数。
接下来在保护模式下的32位代码中添加显示内存信息的过程。
对照右边注释中的C代码,可以很容易了解这段代码的目的:程序的主题是一个循环,循环的次数为地址范围描述符结构(下文用ARDStruct代替)的个数,每次循环将会读取一个ARDStruct。首先打印其中每一个成员的各项,然后根据当前结构的类型,得到可以被操作系统使用的内存的上限。结果会被存放在变量dwMemSize中,并在此模块的最后打印到屏幕。
其中,
DispInt和DispStr函数连同DispAL、DispReturn被放在了
这与直接把代码写进这个位置的效果是一样的,把他们单独放到一个文件有利于阅读。
在DispInt中,[esp+4]即为已经入栈的参数,函数通过4次对DispAL的调用显示了一个整数,并且最后显示一个灰色的字母“h”。函数DispStr通过一个循环来显示字符串,每一次复制一个字符入显存,遇到\0则结束循环。同时,DispStr加入了对回车的处理,遇到0Ah就会从下一行的开始处继续显示。由于这一点,DispReturn也做了简化,通过DispStr来处理回车。
在以前的程序中,我们用edi保存当前的显示位置,从这个程序开始,我们改为用变量dwDispPos来保存。这样我们就可以放心地使用edi这个寄存器。
至此,我们新增的内容已经准备得差不多了,另外还需要提到的一点是,在数据段中,几乎每个变量都有类似的两个符号,比如:
和
在实模式下应使用_dwMemSize,而在保护模式下应使用dwMemSize。因为程序是在实模式下编译的,地址只适用于实模式,在保护模式下,数据的地址应该是其相对于段基址的偏移。
接下来就是调用DispMemSize来显示内存信息啦:
在调用DispMemSize之前,我们显示了一个字符串作为将要打印的内存信息的表格头。现在来看看结果:
![](http://7qn8kg.com1.z0.glb.clouddn.com/OS-pmtest7-displayRAM.png)
上面的结果图,总共有六段内存被列出来,对列出的内存情况解释如下表:
从上面可以看出,操作系统能够使用的最大内存地址是01FEFFFFh,所以此极其拥有32MB-16KB的内存。而且幸运的是,我们指定的256字节的内存MemChkBuf是够用的。
你可能没有想到,得到内存容量还要这么多代码,不过,实际上我们除了得到了内存的大小,还得到了可用内存的分布信息。由于历史原因,系统可用内存分布得并不连续,所以在使用的时候,我们要根据得到的信息小心行事。
内存容量得到了,你是否还记得我们为什么要得到内存?我们是为了节约使用,不再初始化所有PDE和所有页表。现在,我们已经可以根据内存大小计算应初始化多少PDE以及多少页表,下面来修改一下函数SetupPaging。
在函数的开头,我们就用内存大小除以4MB来得到应初始化的PDE的个数(同时也是页表的个数)。在初始化页表的时候,通过刚刚计算出的页表个数乘以1024(每个页表含1024个PTE)得出要填充的PTE个数,然后通过循环完成对它的初始化。
这样一来,页表所占的空间就小得多,在本例中,32MB的内存实际上只要32KB的页表就够了,所以在GDT中,这样初始化页表段:
这样,程序所需的内存空间就小了许多。
克勤克俭用内存
这里利用中断15h来获取计算机的内存。
在调用中断15h之前,我们需要填充下列寄存器:
e a xint 15h可完成许多工作,主要由ax的值决定,我们想要获取内存信息,需要将ax赋值为0E820h。
e b x放置着“后续值(continuation value)”,第一次调用时ebx必须为0。
e s : d i指向一个地址范围描述符结构ARDS(Address Range Descriptor Structure),BIOS将会填充此结构。
e c xes:di所指向的地址范围描述符结构的大小,以字节为单位。无论es:di所指向的结构如何设置,BIOS最多将会填充ecx个字节。不过,通常情况下无论ecx为多大,BIOS只填充20字节,有些BIOS忽略ecx的值,总是填充20字节。
e d x0534D4150h(‘SMAP’)──BIOS将会使用此标志,对调用者将要请求的系统映像信息进行校验,这些信息会被BIOS放置到es:di所指向的结构中。
调用中断15h之后,结果存放于下列寄存器中:
C FCF=0表示没有错误,否则存在错误。
e a x0534D4150h(‘SMAP’)。
e s : d i返回的地址范围描述符结构指针,和输入值相同。
e c xBIOS填充在地址范围描述符中的字节数量,被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 | 这个地址范围的地址类型 |
取值 | 名称 | 意义 |
1 | AddressRangeMemory | 这个内存段是一段可以被OS使用的RAM |
2 | AddressRangeReserved | 这个地址段正在被使用,或者被系统保留, 所以一定不要被OS使用 |
其他 | 未定义 | 保留,为未来使用,任何其他置都必须被OS 认为是AddressRangeReserved |
下面是调用中断15h的代码:
可以看到,代码使用了一个循环,一旦CF被置位或者ebx为零,循环将结束。在第一次循环开始之前,eax为0000E820h,ebx为0,ecx为20,edx为0534D4150h,es:di指向_MemChkBuf的开始处。在每一次循环进行时,寄存器di的值将会递增,每次的增量为20字节。另外,eax、ecx和edx的值都不会变,ebx的值我们置之不理。同时,每次循环我们让_dwMCRNumber的值加1,这样到循环结
束时它的值会是循环的次数,同时也是地址范围描述符结构的个数。
接下来在保护模式下的32位代码中添加显示内存信息的过程。
对照右边注释中的C代码,可以很容易了解这段代码的目的:程序的主题是一个循环,循环的次数为地址范围描述符结构(下文用ARDStruct代替)的个数,每次循环将会读取一个ARDStruct。首先打印其中每一个成员的各项,然后根据当前结构的类型,得到可以被操作系统使用的内存的上限。结果会被存放在变量dwMemSize中,并在此模块的最后打印到屏幕。
其中,
DispInt函数定义如下:
DispStr定义如下:
DispInt和DispStr函数连同DispAL、DispReturn被放在了
lib.inc中,并且通过如下语句包含进pmtest7.asm中:
这与直接把代码写进这个位置的效果是一样的,把他们单独放到一个文件有利于阅读。
在DispInt中,[esp+4]即为已经入栈的参数,函数通过4次对DispAL的调用显示了一个整数,并且最后显示一个灰色的字母“h”。函数DispStr通过一个循环来显示字符串,每一次复制一个字符入显存,遇到\0则结束循环。同时,DispStr加入了对回车的处理,遇到0Ah就会从下一行的开始处继续显示。由于这一点,DispReturn也做了简化,通过DispStr来处理回车。
在以前的程序中,我们用edi保存当前的显示位置,从这个程序开始,我们改为用变量dwDispPos来保存。这样我们就可以放心地使用edi这个寄存器。
至此,我们新增的内容已经准备得差不多了,另外还需要提到的一点是,在数据段中,几乎每个变量都有类似的两个符号,比如:
和
在实模式下应使用_dwMemSize,而在保护模式下应使用dwMemSize。因为程序是在实模式下编译的,地址只适用于实模式,在保护模式下,数据的地址应该是其相对于段基址的偏移。
接下来就是调用DispMemSize来显示内存信息啦:
在调用DispMemSize之前,我们显示了一个字符串作为将要打印的内存信息的表格头。现在来看看结果:
![](http://7qn8kg.com1.z0.glb.clouddn.com/OS-pmtest7-displayRAM.png)
上面的结果图,总共有六段内存被列出来,对列出的内存情况解释如下表:
内存段 | 属性 | 是否可被OS使用 |
00000000h~0009EFFFh | AddressRangeMemory | 可 |
0009F000h~0009FFFFh | AddressRangeReserved | 不可 |
000E8000h~000FFFFFh | AddressRangeReserved | 不可 |
00100000h~01FEFFFFh | AddressRangeMemory | 可 |
01FF0000h~01FFFFFFh | 未定义 | 不可 |
FFFC0000h~FFFFFFFFh | AddressRangeReserved | 不可 |
你可能没有想到,得到内存容量还要这么多代码,不过,实际上我们除了得到了内存的大小,还得到了可用内存的分布信息。由于历史原因,系统可用内存分布得并不连续,所以在使用的时候,我们要根据得到的信息小心行事。
内存容量得到了,你是否还记得我们为什么要得到内存?我们是为了节约使用,不再初始化所有PDE和所有页表。现在,我们已经可以根据内存大小计算应初始化多少PDE以及多少页表,下面来修改一下函数SetupPaging。
在函数的开头,我们就用内存大小除以4MB来得到应初始化的PDE的个数(同时也是页表的个数)。在初始化页表的时候,通过刚刚计算出的页表个数乘以1024(每个页表含1024个PTE)得出要填充的PTE个数,然后通过循环完成对它的初始化。
这样一来,页表所占的空间就小得多,在本例中,32MB的内存实际上只要32KB的页表就够了,所以在GDT中,这样初始化页表段:
这样,程序所需的内存空间就小了许多。
源代码
lib.inc:
相关文章推荐
- 应用领航:盘点那些年我们一起追过的OS
- 无奇不有!盘点各国自己开发的操作系统
- Tomcat端口被占用解决方法(不用重启)
- more、less 和 most 的区别
- IE7降低内存和降低CPU的几个技巧
- “传奇”图象数据存储方式
- AJAX实现瀑布流触发分页与分页触发瀑布流的方法
- 十万条Access数据表分页的两个解决方法
- 如何高效的使用内存
- DOS下内存的配置
- XP/win2003下发现1G的内存比512M还慢的解决方法
- 可自定义oem的萝卜家园 Ghost XP 新春装机版 V200801 下载
- sqlserver关于分页存储过程的优化【让数据库按我们的意思执行查询计划】
- 高效的mysql分页方法及原理
- asp又一个分页的代码例子
- SqlServer 2000、2005分页存储过程整理第1/3页
- 超大数据量存储常用数据库分表分库算法总结
- PowerShell实现动态获取当前脚本运行时消耗的内存
- JQuery的Pager分页器实现代码
- C#实现把dgv里的数据完整的复制到一张内存表的方法