Chinaunix首页 | 论坛 | 博客
  • 博客访问: 367067
  • 博文数量: 83
  • 博客积分: 5322
  • 博客等级: 中校
  • 技术积分: 1057
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-11 11:27
个人简介

爱生活,爱阅读

文章分类

全部博文(83)

文章存档

2015年(1)

2013年(1)

2012年(80)

2011年(1)

分类: LINUX

2012-09-07 10:54:36

深入理解内存

       我们讨论的背景是:运行在AMD Opteron硬件上,采用64GNU/LinuxAICT Linux集群。如果你对于当前的材料有什么意见或者问题,请发送邮件到:

内容

程序链接一个静态的库,将会把该库的代码段与数据段整合到程序的ELF文件中。因而,两个链接了相同静态库的程序将该库分别映射到物理内存。参见图9.

拥有ELF格式的静态库文件以.a为扩展名。ELF格式也支持动态库文件(dynamic shared libraries)。此类文件以.so为扩展名。

动态库文件(dynamic shared library)与静态库文件(static library)有两方面不同:首先,仅仅将库的文件名记录在ELF文件中,不是代码段,或者数据段,这使得可执行文件更小。其次,仅仅有一份库的拷贝被加载到物理内存。这将节省了物理内存,同时加速了链接了动态库文件的程序的启动过程。图10描述了两个这样的程序

Fig. 10 Two programs linked with the same dynamic shared library.

当两个程序中的第一个开始执行时,系统查找到库,并通过将库的代码段,数据段映射到物理内存而更新页表(page table)。当第二个程序开始执行时,涉及到库的代码段的页表记录将被映射到已经存在的物理内存上。库的代码段允许被共享起来,同所有的代码段一样,是可读-可执行的(read-execute)。

最初,库的只读的数据段也是共享的。但是,当一个程序企图更改该段时候,就会生成一份私有的拷贝,并且程序的页表将映射该拷贝。这就是称作“写时拷贝”COW (copy on write)策略。图10显示了每个程序使用库数据段的拷贝情况。

在程序与进程部分,我们提到一个进程从其父进程中继承特定的内存限制,这里的父进程通常是一个shell。在AICT集群的情况下,bashtcsh shell设定了栈段(stack segment)大小为10兆字节的软限制(soft limit)。其它支持的内存参数、数据段、总的虚拟地址大小以及总的物理内存则没有限制。

尽管代码段,数据段,堆以及栈大体描述为相等的大小,但实际上,拥有一个或者多个巨大数组的典型高性能计算应用程序(high performance computing application),通常映射在bss或者栈,或者堆中。如果该巨大数组是基于栈的(stck-based),则一旦超出了继承的栈大小,则该应用就会退出。当发生此类现象时,进程将会异常终止,且发送“段越界”("Segmentation violation")信号。

为了避免出现此类结果,软限制(soft limit)可以通过内建的shell命令进行扩展。例如,bash 下的ulimit –s20480 或者tcsh下的limit stacksize 20480将栈大小设定为20兆字节,所以软限制可以增加到配置的硬限制(Soft limits can be increased up to the configured hard limit.)。在AICT集群中,栈大小的硬限制是“无限制的”(unlimited)。也就是说不加以限制。需要特别注意的是,如果在一个shell下更改了限制,则只有在该shell下启动的进程,新的限制才会生效。

Be aware that the soft and hard limits on shell resources vary greatly among systems.

注意,在不同的系统中的shell资源下,软限制和硬限制区别很大。

许多AICT集群节点拥有10G的内存。对于此类节点,操作系统大约预留1000兆字节的空间来维护进程信息,包括页表。磁盘上另一块1G大小,较慢的二级存储设备作为交换区(swap)。加上配置的swap空间,系统可用的内存数量大约为9800兆字节,这代表了所有内存,所有的进程的虚拟内存与物理内存的映射均可在该节点完成。

为了在图表中澄清该问题,一个交互运行的进程的栈大小的“软限制”(soft limit)在图11中描述

Fig. 11 Stack size soft limit imposed on an interactive process.

软限制(soft limit)就像一个围绕在页表周围,并映射了栈段的一个盒子(bounding box)。将软限制设定为“不限制”可以有效地移走该盒子。同时,堆分段的扩展仅仅受限于空闲的交换分区(SWAP)。

由于批作业是一个shell脚本,它可能需要包括一个适合的限制命令(limit command),以在调用计算程序之前增加栈的大小限制。另外,批作业被分配给拥有足够多的空闲交换区(SWAP)的节点上以满足请求的进程虚拟空间(pvmem)。为了保证一个作业不会超过指定的进程虚拟空间(pvmem),批处理系统减少了与之相关的shell的虚拟内存的软限制,通常设置之为“无限制”以满足进程虚拟内存值。当一个软限制(soft limit)更改后,硬限制(hard limit)自动重置与之相同。于是,一旦作业被批处理系统调整后,就不会再随后增加其虚拟内存软限制(soft limit)。

因此,一个大小(代码段+数据段+bss)超过了虚拟内存限制的作业是不允许运行的。当一个作业在运行时试图扩展其虚拟内存的限制时,将会被异常终止(terminated abnormally),同时发送“段越界”(segmentation violation)信号。为了正常地退出,C程序可以通过测试malloc()函数的返回值是否为NULL指针。对于Fortran 90/95程序,可以测试ALLOCATE语句的STAT参数是否为非零值。

对于一个在AICT集群下运行的批处理模式的进程,内存限制在图12中示意

由于页表(page table)代表了一程的虚拟内存脚印(footprint),虚拟内存的软限制被描述为一个围绕在页表周围的封闭盒子(bounding box)。注意,在特定的条件下,虚拟内存的软限制(soft limit)能够被基于栈的数据(stack-based data)破坏,尽管栈不会超过栈的大小限制。

一个作业的进程虚拟内存(process virtual memory)的规格,被批处理作业系统转化为专用于进程的交换区数量A job's pvmem specification translates as the amount of SWAP that is dedicated to the process by the batch job system.P。进程实际上使用的较少。尽管如此,考虑当一个节点调用另一个作业时,批处理系统负责将整个交换区(SWAP)的分配给所有当前节点上正在运行的其他作业。这种未使用的资源将导致浪费,除非进程虚拟内存的估计是精确的。

至此,我们对于页表(page table)的图形化描述均暗示虚拟内存页总是被映射到物理内存页帧。然而,由于一些虚拟内存可能永远不会用到,对于操作系统而言,在页(page)被引用的时候再将其映射到物理内存的方式是更高效的,这种方式的技术术语为“请求面调度(demand paging)”

例如,在Fortran 77上,一个常用的方式是编译一个最大的、预知的静态数组,然后使用该相同的可执行部分进行运算。在这种情况下,一个足够大的虚拟页总是在页表(page table)中预留,以容纳大的数组。但实际上,只有包含了被程序引用的数组元素的页才会在页帧中申请内存。所有申请的页帧(page frame)被称作固有集合大小(resident set size)。如在图13中显示的那样,RSS将小于等于进程的虚拟内存的脚印(footprint

 depicting resident set size.

当进程第一次引用一个虚拟页面(virtual page)时,就会产生缺页错误(page fault)。Linux则会从空闲的页面中获取一个物理的页帧,然后将之分配给进程页表(page table)中的虚拟页。另外,页表(page table)中最常用的512条记录被缓存(cached)在Opteron CPU的快速重编址缓冲器(TLB:translation lookaside buffer)中。引用一个在TLB记录中的页面比从内核内存中页表中获取的更迅速。

对于高性能的应用软件,最完美的方式是从可用的RAM中获得所有内存分配。然而,如果有如此之多的内存被占用,那么RAM将会被耗尽,此时一个其他进程的帧则会被“盗用”(“stolen”)。这个被“盗用的”帧将会转化为磁盘上的交换区,同时更新该进程的页表。换句话说,该页面被置换出去(swapped out),更精确一点,称作“换页出去”(paged out)。交换区中的帧不能被直接使用。于是,如果进程引用了该类型的一个页面(page),首先需要置换进来(swapped in),使得其他进程的一个页面被置换出去(swapped out)。由于需要更多的内存,更多的交换区被利用,这将导致更多的置换(swapping)。因为访问硬盘很慢,结果是明显降低了所有进程的性能。最后,如果最终的交换区耗尽了,则进程将会被终止以获取其页帧(page frame)。此时,该节点迅速变换为不可用。为了避免此种情况,批处理系统在实现进程虚拟内存规格时,保证了一个节点上的交换区(SWAP)永远不会越界。

实现细节

最后,让我们分析一个真实的内存映射来探索前面的一些概念是如何实现的。在Linux下,进程的内存映射在只读文件/proc/pid/maps中,其中pid为该进程的进程标识pid。考虑下面的包含不同的存储类型与可见范围的C语言程序。

点击(此处)折叠或打开

  1. /**
  2. * map.c
  3. */
  4. #include
  5. #include
  6. #include
  7. #include
  8. #define NSIZE (5*(1<<20))
  9. #define SLEEPT 10
  10. long gx[NSIZE];
  11. int
  12. main (int argc, char *argv[])
  13. {
  14. char c[NSIZE];
  15. int *px = malloc (NSIZE*sizeof(int));
  16. for (int i=0; i
  17. gx[i] = (long)i;
  18. px[i] = i;
  19. c[i] = 'c';
  20. }
  21. printf ("address of gx[0] = %012p\n", &gx[0]);
  22. printf ("address of px[0] = %012p\n", &px[0]);
  23. printf ("address of c[0] = %012p\n", &c[0]);
  24. printf ("memory map file: /proc/%d/maps\n", getpid());
  25. printf ("sleeping %d...", SLEEPT);
  26. fflush (NULL);
  27. sleep (SLEEPT);
  28. free (px);
  29. printf ("\ndone\n");
  30. exit (EXIT_SUCCESS);
  31. }








Maps文件输出的每一行均添加了标签,以便于后续的引用。

点击(此处)折叠或打开


  1. $ pgcc -c9x -o map map.c
  2. $ ls -l map
  3. -rwxr-xr-x 1 esumbar uofa 8176 Dec 1 09:24 map
  4. $ size map
  5. text data bss dec hex filename
  6. 1768 724 41943072 41945564 28009dc map
  7. $ ./map
  8. address of gx[0] = 0x0000500bc0
  9. address of px[0] = 0x2a9557a010
  10. address of c[0] = 0x7fbfaff460
  11. memory map file: /proc/23114/maps
  12. sleeping 10...^Z
  13. [2]+ Stopped ./map
  14. $ cat /proc/23114/maps
  15. (a) 00400000-00401000 r-xp ... /scratch/esumbar/map
  16. (b) 00500000-00501000 rw-p ... /scratch/esumbar/map
  17. (c) 00501000-02d01000 rwxp ...
  18. (d) 2a95556000-2a95557000 rw-p ...
  19. (e) 2a95578000-2a9697c000 rw-p ...
  20. (f) 330c300000-330c315000 r-xp ... /lib64/ld-2.3.4.so
  21. (g) 330c414000-330c416000 rw-p ... /lib64/ld-2.3.4.so
  22. (h) 330c500000-330c62a000 r-xp ... /lib64/tls/libc-2.3.4.so
  23. (i) 330c62a000-330c729000 ---p ... /lib64/tls/libc-2.3.4.so
  24. (j) 330c729000-330c72c000 r--p ... /lib64/tls/libc-2.3.4.so
  25. (k) 330c72c000-330c72f000 rw-p ... /lib64/tls/libc-2.3.4.so
  26. (l) 330c72f000-330c733000 rw-p ...
  27. (m) 330c800000-330c885000 r-xp ... /lib64/tls/libm-2.3.4.so
  28. (n) 330c885000-330c984000 ---p ... /lib64/tls/libm-2.3.4.so
  29. (o) 330c984000-330c986000 rw-p ... /lib64/tls/libm-2.3.4.so
  30. (p) 7fbfafe000-7fc0000000 rwxp ...
  31. (q) ffffffffff600000-ffffffffffe00000 ---p ...
  32. $ fg
  33. ./map
  34. done
      16而不是10,用数字0-9A-F标识。相应地,一个大小为4K字节的页用100016或者0x1000表示。注意,上图中每行左侧的地址均以三个零结束,这表明它们是页大小(page size)的整数倍。

不幸的是,maps文件并没有按照“代码段”(text)、“数据段”(data)等等的方式将内存区域标识出来。然而,在很多情况下,我们可以推断出这些标签(label)。例如,区域(a)是一个与ELF程序文件相关的只读-可执行的分段(read-executable segment),这必然是代码段(text segment)。注意,尽管实际的代码段(text)仅仅占用1768字节,但是仍然为它预留了一整页的内存。同样需要注意的是,代码段(text segment)从地址0x4000004兆字节)开始,而不是我们假定的从0开始。所有的进程均是这种情况,以保证零地址是作为一个无效的内存地址来对待的,防止被无意中使用。

顺便提一下,在权限位“r-xp”中的“p”权限位的意思是该区域是私有的(private)。通过shmat()或者mmap()函数管理共享内存(shared memory)的进程将有一个或者多个“s.”权限位。

区域(b)是读-写权限的数据段(data segment)。鉴于代码段(text segment)的结束位置为:0x4010004MB+4KB),数据段直到0x500000开始。所以,邻接的段之间并非需要连续的。(映射的物理页帧(page frame)也不是连续的)。根据静态数组gx的地址可以判断,bss段从0x500bc0或者附近的区域(b)内,向着区域(c)延伸,具有精确的0x280000040兆字节)。

接下来是两个非连续的区域(d)和(e)。区域(e)快读为0x140400020兆字节+16K字节),足够用于容纳动态申请的内存。的确,指针px的值就在该区域的开始出附近。另一方面,区域(d)是个神秘地带(a mystery)。考虑到它与区域(e)邻接,它可能与进程管理该动态内存有关系。

区域(f)和区域(g)分别用于映射动态加载库的代码段与数据段。这是操作系统在调用程序之前首先要执行的代码。它在调用main之前查找并加载被程序连接的动态共享库。尽管在编译的时候没有指定任何的库,程序隐式(implicitly)引用标准的C库(libc.so)和数据库(libm.so)作为对ldd命令输出的确认。

区域(h)到区域(k)分别代表libc的代码段,内部数据段,只读的数据,以及可读-可写的数据。而区域(m)到区域(o)代表libm的代码段,内部数据段,可读-可写数据。区域(I)是另一个神秘的区域。

最后,栈(stack segment)映射到区域(p),其大小为0x5020005兆字节+8k字节)。观察其上限虚拟地址为0x7fffffffff,这与栈顶(the top of the stack)并不一致。同样的,变量c的起始地址接近栈底(“bottom of the stack)。区域(p)(8M字节)并非进程可以直接存取的。然而,在内核调用服务时会间接地使用它,例如,打开一个文件。

  1. Understanding the Linux Kernel, by Daniel P. Bovet and Marco Cesati, 2001 O'Reilly
  2. Linkers and Loaders, by John R. Levine, 2000 Morgan Kaufmann
  3. System V Application Binary Interface: AMD64 Architecture Processor Supplement, Draft Version 0.96, by Michael Matz, Jan Hubi欁愀, Andreas Jaeger, and Mark Mitchell

2008 University of Alberta






阅读(1232) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~