看《Windows汇编》的一些笔记(内存管理篇)

     这几天在看《Windows环境下32位汇编程序设计》,接触到了一些计算机底层的东西,是我以前了解不到的,由于知识比较繁烦,所以特把一些东西记录下来,方便以后查阅。
      在DOS中,汇编中最先开始接触的就是内存寻址了,DOS状态下,操作系统的内存管理与Windows的内存管理有着截然不同的方法。其实,说白了,Win32编程相对于DOS编程最大的不同就是内存的使用。
      对于DOS来说,是运行于实模式下的,实模式是相对于8086的机器,寻址范围只有1M的空间,也就是00000H-FFFFFH,凡获得的地址值都不可在这个界区之外。而对于这短短的1M空间来说,系统硬件地址的安排又占去了内存高端的一些空间(从A0000H-FDC000H,计384KB),在系统内存的低端,安排了中断向量表和BIOS数据区(000000H-000500H,计1K字节),剩下的不到640K就是操作系统和应用程序所共享的内存(此谓著名的640KB内存限制)。
      DOS下的寻址是根据段寄存器DS×10h+段偏移得出来的,得出来的值就是物理内存中相应地址的值。如XXXX:YYYY格式的虚拟地址在内存中的实际位置就是XXXX*10h+YYYY。

       Windows的内存管理和DOS内存管理有着很大的不同,Windows可以运行在实模式,保护模式,虚拟8086模式下,当运行在实模式下时,寻址是跟DOS没有任何分别的,寻址为1M空间,但是在保护模式与虚拟8086模式下,则有着本质的不同,因为在这些模式下,32位的地址线可访问达4GB的内存地址,以前的段地址加段偏移无法覆盖如此大的空间。因为在386的结构中,寄存器已扩至32位,所以,寄存器的值就可以直接表示内存中某个地址,而无需用一个段寄存器来作间接寻址。
       那么,在386模式下,段寄存器起什么作用呢?它不是用作基址,而是用作段选择器,在保护模式下,一个地址空间是否可以被写入,可以被多少优先级写入,是不是允许执行等保护的问题都是需要考虑的。所以,必须对一个地址空间定义一些安全上的属性,段描述符就是被派上作此用途。段描述符是64位,但在386中,段寄存器却是16位,空间不够,放不下。解决办法是把段描述符组成一个表结构,而用段寄存器来作索引。所以,在386中,段寄存器也叫做了段选择器。
      在386中,引入了两个新的寄存器来管理段描述符,一个是48位的全局描述表寄存器GDTR,一个是16位的局部描述表寄存器LDTR。在windows中,内存的表示是以xxxx:yyyyyyyy表示,下面介绍一下他是怎么寻址的:
      首先,xxxx是无法表示段的基址的,对于这个地址,首先要看xxxx的TI位是否为0(即xxxx的第二位),如果是,则从GDTR中获取GDT的基址,然后在GDT中以段选择器xxxx的高12位得出索引,根据索引偏移找到相应的段描述符,段描述符包括段的基址,限长,优先级各种属性,这就得到了段的起始地址,加上yyyyyyyy即是要找的内存的线性地址zzzzzzzz。  如果TI位为1,则表示段描述符放在LDT中,第一步的操作还是从GDTR中获取GDT的基址,然后从LDTR寄存器获得索引(非XXXX的高十二位),注意,这时根据索引偏移得到得并不是段描述符,而是得到LDT段的位置,然后根据xxxx的高十二位从LDT段中获得段描述。再以这个段描述符信息得到段基址,再加上偏移yyyyyyyy得到要找的线性地址zzzzzzzz。可以写个简单的模拟程式来表示:

       if( xxxx的第二位==1 ) //段描述符的位置在GDTR中
       {
           A1=(GDTR的前三十二位); //把GDTR的基址给A1
           段描述符=A1+(xxxx的高12位);  //可获得段描述符
           线性地址 = 段描述符中的基址+yyyyyyyy;
        }
        else  //TI位为0,表示在段描述符在LDT中
        {
           A1=(GDTR的前三十二位);
           A2=A1+LDTR;  //A2即是LDT描述符表的入口,注:LDTR是16位的
           段描述符=A2+(xxxx的高十二位) //LDT描述符表入口加上偏移,即是相应的段描述符
     线性地址 = 段描述符中的基址+yyyyyyyy;
         }

       得出线性地址之后,是否就是物理内存中相应地址的值呢?那要看是否启用分页机制了。如果没有启用分页机制,则线性地址就是相应的物理地址。而如果启用分页机制,则不代表相应的物理地址。还需要经过一番折腾才能得到相应的实际物理地址,从而把里面相应的值给取出来。
       80386启用分页机制,可以很好的解决内存碎片的问题。以前的程序,都是在连续的物理内存空间中的存在的,这样,经过一段时间后,多个程序的建立与退出,则会出现空闲内存的总和很大,而任何一片连续的内存都小到无法装了执行程序的地步,这就是内存碎片。386把4KB大小的一块内存当做一页内存,每页物理内存可以根据页目录与页表,而随意映射到不同的线性地址上。这样,就可以将物理地址不连续的内存映射到一起,在线性地址上被视为连续的地址空间。在386中,除了CR3使用了物理地址,其余的都是用线性地址表示内存的。
       386中是否启用分页是以CR0寄存器中的位32(PG位)表示的,如果PG=0,则分页机制不启用,反之则启用。内存分页只可在保护模式下实现。
        从物理内存中的层次看,Windows操作系统和DOS一样,也是所有的内容共享内存,如操作系统使用的代码和数据,但是从应用程序代码的层次看,则Windows被看作一个分时的多任务的操作系统,CPU被分成一个个的时间片,分配给不同的应用程序,在一个程序的时间片中,和这个程序执行无关的东西(如其它程序的代码和数据),并不被映射到相应的线性地址中去,这样,各个程序就独立开了,在这个应用程序的线性地址内,并不能访问别的程序所使用的线性地址空间。
       书上提出了一些Win32编程中的一些重要概念:

      1.每个应用程序都有自己的4GB的寻址空间,该空间可以存放操作系统,系统DLL和用户DLL的代码,它们之中有各种函数供应用程序调用。再除去一些其它的空间,余下的就是应用程序的代码,数据和可以分配的地址空间。
       2.不同的应用程序的线性地址空间是隔离的,虽然它们在物理内存中同时存在,但是在某个程序的时间片中,其它应用程序的代码和数据没有被映射到相应的可寻址的线性地址中,所以是不可访问的。程序可以使用自己的4GB的寻址空间,这个空间完全是程序私有的。
       3.DLL程序没有自己的私有空间,它们总是被映射到其它应用程序的地址空间中,当做其它应用程序一起运行。原因很简单,如果它不和其它程序同属一个地址空间,应用程序如何才能调用得到它??

posted @ 2005-03-13 15:10  shipfi  阅读(1187)  评论(0编辑  收藏