《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #14 虚拟存储子系统的调整

时间:2021-05-06 08:53:30

HACK #14 虚拟存储子系统的调整

本节介绍如何使用/proc进行虚拟存储子系统的调整。
虚拟空间存储方式
在Linux上向应用程序分配内存时,是通过以页面为单位的虚拟存储方式进行的。采用虚拟存储方式,在实际操作中具有不需要确保连续的物理内存(不用担心内存碎片)的优点。最近的处理器大部分都具备用于虚拟存储的处理器嵌入式TLB(Translation lookaside buffer,旁路转换缓冲区,或称为页表缓冲区)和处理不存在的页面访问的结构。除了部分嵌入式应用外,大多数Linux应用中都可以使用虚拟存储方式的内存管理。
使用虚拟存储方式的内存管理,具有下列特点。
程序使用的页面是在应用程序最初访问时由内核分配的。
如果分配的页面为程序文本、有初始值的数据(.data)区域或(被mmap的)数据文件区域,则在页面分配的同时从对应的文件读取数据,页面通过这些数据初始化。
如果对于不存在拥有初始值的数据的区域,则只进行页面分配处理。该页面作为匿名(anonymous)页面处理。
应用程序通过malloc()等分配可用的存储区时,不会立刻向该区域(空间)分配实际的页面。而是在必要时仅分配需要用到的页面。
多数应用程序一般都不会使用分配到的所有存储区。有时分配与最大数据量大小相等的缓冲区,有时也会需要为散列表(hash table)等分配没有实体的空间。这种情况下,使用虚拟存储方式延迟内存分配,就不需要向未使用的空间分配内存。
由于根据需要分配页面,因此应用程序启动时不能事先得知该应用程序最终使用的最大页面数,这是虚拟存储方式的缺点。进程在逻辑上的虚拟内存空间与实际分配给该空间(已使用)的实际内存空间之间不再有直接的关系,最大使用内存量会根据环境(处理的数据等)的变化而变化。
应用程序使用的内存量对系统稳定性有很大的影响,因此不能无限度地使用内存。
虚拟空间超额使用量的调整
只存在一个进程时,即使不知道该进程的最大内存使用量,也可以使用已有的进程单位的资源限制功能对内存使用量进行充分限制。实际安装的内存为10GB时,可以使用ulimiti命令将该进程的最大虚拟空间大小设置为10GB。考虑到应用程序与虚拟空间大小相比要使用多少物理内存,也可以设置比10GB更大的虚拟空间大小(在Linux中未安装进程的物理内存驻留大小限制功能,因此无法使用。)
但是,考虑到多个进程互相争夺内存的情况,就需要限制整个系统的虚拟空间量。如上所述,即使无限度地向进程分配虚拟空间,只要不实际使用也就没问题。分配给进程的虚拟空间的大小与本质上实际安装的物理量无关。但是,从系统的稳定性来看,分配的虚拟空间大小应该保证达到物理内存的量。将没有物理内存保证的虚拟空间进行大量分配,并实际访问时,如果同时分配大量的物理页面,系统就会崩溃。
在Linux中有规定“允许超过物理内存量分配多少虚拟空间”的参数,通过下列两个/proc入口来进行控制。

/proc/sys/vm/overcommit_memory
/proc/sys/vm/overcommit_ratio

/proc/sys/vm/overcommit_memory是控制虚拟空间分配的策略的参数,可以设置下列3种值。

OVERCOMMIT_GUESS(0)
OVERCOMMIT_ALWAYS(1)
OVERCOMMIT_NEVER(2)

默认为OVERCOMMIT_GUESS。

# cat/proc/sys/vm/overcommit_memory
0

设置为OVERCOMMIT_NEVER时,执行下列命令。

# echo 2 > /proc/sys/vm/overcommit_memory

可以在/proc/sys/vm/overcommit_ratio中指定允许过量使用的虚拟空间所占物理内存总量的百分比。默认为50%。可以分配的最大虚拟空间为总物理内存量的150%。
下面介绍overcommit_memory的不同取值对应的不同虚拟空间分配。

OVERCOMMIT_GUESS

overcommit_memory的默认值为OVERCOMMIT_GUESS。指定这个参数时,预测将空闲内存、页面缓存量、空闲交换区量、可回收slab(长字节)量等回收的页面数,虚拟空间要求分配的量比这个数小时,分配成功。
(请注意,在多个进程同时要求大量的虚拟空间时是无法正确预测的。下面所述的OVERCOMMIT_NEVER中就没有这种问题。)
在OVERCOMMIT_GUESS的情况下,可分配的虚拟空间大小基本就是物理内存大小和交换区大小的合计值。物理内存为2GB,交换区为2GB,当前消耗1GB时,还可以分配约3GB的虚拟空间。

OVERCOMMIT_ALWAYS

在OVERCOMMIT_ALWAYS的情况下,虚拟空间分配总是成功。即使对于过大的虚拟空间要求,也会分配虚拟空间。可以在与实际安装的物理内存量完全无关的形态下使用虚拟空间,如前面所述的散列表等。
OVERCOMMIT_NEVER
在OVERCOMMIT_NEVER的情况下,对可分配虚拟空间量的管理更加严格。
首先,记录下整个系统内已分配的虚拟空间量。这个值严格由系统进行集中管理,在分配或释放虚拟空间时重新计算。这个值为/proc/meminfo的Committed_AS。
对于虚拟空间大小的计算也比其他参数严格。例如,在OVERCOMMIT_GUESS的情况下,对mmap系统调用设置了MAP_NORESERVE的虚拟空间量不添加到Committed_AS中。但是,在OVERCOMMIT_NEVER的情况下会添加到Committed_AS中。指定了MAP_NORESERVE的区域也作为可能分配物理内存的虚拟空间处理。
将“所有物理内存量+总交换区量”加上通过/proc/sys/vm/overcommit_ratio指定的比例得到的值,作为可分配的虚拟空间总量。这个值可以使用/proc/meminfo的CommitLimit查看。CommitLimit的值仅在利用OVERCOMMIT_NEVER时有效。在OVERCOMMIT_GUESS、OVERCOMMIT_ALWAYS的情况下,这个项目没有意义。
要求分配虚拟空间时,如果“整个系统中已经分配(Committed_AS)”的虚拟空间量超过“可分配虚拟空间量(CommitLimit)”,则分配失败。
小结
本节介绍了虚拟空间的过量使用。根据系统的不同,需要对虚拟空间分配策略进行调整,管理要确保的内存量。
—Naohiro Ooiwa
关于进程的虚拟内存分配
在Hack #13中已经介绍过,用malloc()等预留内存后,就会分配虚拟内存。但使用mmap()时分配虚拟内存有一些限制条件。
例如,启动进程后,动态链接器就会进行动态库(library)的安装。由动态库的执行文件信息决定mmap的大小。接着,链接器会配置文本段和数据段,如果是64位操作系统,就会设置alignment,当文本段和数据段较小时,它们之间有约2MB的空间。
以适当的访问权限对代码段和数据段执行mmap命令,使用mprotect(PROT_NONE)让此外未使用的段无法访问。
下面是在CentOS 5.4 64位操作系统下启动apache时ps命令的结果。

# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
...
root 4469 8.5 0.5 238244 11136 ? Ss 10:27 0:00 /usr/sbin/httpd
apache 4471 0.0 0.3 238244 6516 ? S 10:27 0:00 /usr/sbin/httpd
apache 4472 0.0 0.3 238244 6512 ? S 10:27 0:00 /usr/sbin/httpd
apache 4473 0.0 0.3 238244 6512 ? S 10:27 0:00 /usr/sbin/httpd
...

每一个进程确保了200MB以上的虚拟内存(VSZ)。使用pmap命令查看详细内容。pmap命令显示的是进程的内存映射。

# pmap -d 4469
4469: /usr/sbin/httpd
Address Kbytes Mode Offset Device Mapping
...
00002aff7a62e000 20 r-x-- 0000000000000000 008:00005 mod_cgi.so 文本段
00002aff7a633000 2048 ----- 0000000000005000 008:00005 mod_cgi.so 剩余区域
00002aff7a833000 8 rw--- 0000000000005000 008:00005 mod_cgi.so 数据段
00002aff7a835000 8 r-x-- 0000000000000000 008:00005 mod_version.so
00002aff7a837000 2044 ----- 0000000000002000 008:00005 mod_version.so
00002aff7aa36000 8 rw--- 0000000000001000 008:00005 mod_version.so
00002aff7aa38000 216 r-x-- 0000000000000000 008:00005 mod_perl.so
00002aff7aa6e000 2044 ----- 0000000000036000 008:00005 mod_perl.so
00002aff7ac6d000 16 rw--- 0000000000035000 008:00005 mod_perl.so
00002aff7ac71000 1200 r-x-- 0000000000000000 008:00005 libperl.so
00002aff7ad9d000 2044 ----- 000000000012c000 008:00005 libperl.so
00002aff7af9c000 36 rw--- 000000000012b000 008:00005 libperl.so
...
00002aff85b6e000 3844 rw--- 00002aff85b6e000 000:00000 [ anon ]
00007fff79a9c000 84 rw--- 00007ffffffea000 000:00000 [ stack ]
ffffffffff600000 8192 ----- 0000000000000000 000:00000 [ anon ]
mapped: 246436K writeable/private: 6580K shared: 692K

在32位的CentOS下的情况如下所示。

# ps aux
...
root 2715 6.6 7.6 23168 9488 ? S 10:58 0:00 /usr/sbin/httpd
apache 2718 0.0 3.8 23168 4796 ? S 10:58 0:00 /usr/sbin/httpd
apache 2719 0.0 3.8 23168 4796 ? S 10:58 0:00 /usr/sbin/httpd
apache 2720 0.0 3.8 23168 4796 ? S 10:58 0:00 /usr/sbin/httpd
...

每一个进程的虚拟内存(VSZ)为约23MB。使用pmap命令查看内存映射,内容如下。

# pmap -d 2715
...
00508000 28 r-x-- 0000000000000000 008:00002 mod_proxy_ftp.so
0050f000 8 rwx-- 0000000000006000 008:00002 mod_proxy_ftp.so
00511000 4 r-x-- 0000000000000000 008:00002 mod_suexec.so
00512000 8 rwx-- 0000000000000000 008:00002 mod_suexec.so
00514000 16 r-x-- 0000000000000000 008:00002 mod_disk_cache.so
00518000 8 rwx-- 0000000000004000 008:00002 mod_disk_cache.so
0051a000 8 r-x-- 0000000000000000 008:00002 mod_file_cache.so
0051c000 8 rwx-- 0000000000001000 008:00002 mod_file_cache.so
0051e000 20 r-x-- 0000000000000000 008:00002 mod_cgi.so
00523000 8 rwx-- 0000000000004000 008:00002 mod_cgi.so
00525000 8 r-x-- 0000000000000000 008:00002 libutil-2.5.so
00527000 4 r-x-- 0000000000001000 008:00002 libutil-2.5.so
00528000 4 rwx-- 0000000000002000 008:00002 libutil-2.5.so
...
mapped: 23168K writeable/private: 4916K shared: 684K

在32位操作系统的情况下不存在访问权限为-----(PROT_NONE)的区域。这是因为32位操作系统、64位操作系统中链接器执行共享库映射的策略不同。将上述pmap命令的结果进行比较,看起来像是64位操作系统浪费了虚拟内存,但不用担心。
映射的区域中没有访问权限(PROT_NONE)的区域、写入权限(PROT_WRITE)或不是共享(MAP_SHARED)的区域不添加到Committed_AS中。因此在64位操作系统的情况下,即使消耗虚拟内存地址空间,也不会消耗实际的虚拟内存。