分类:
2009-06-16 18:21:28
级别: 初级
George Cross (), 高级软件开发人员, Business Objects Americas
2008 年 7 月 03 日
了解 IBM® AIX® 上的共享库机制和内存占用情况。本文对于开发人员编写服务器代码或管理员管理生产 AIX 系统来说非常重要。本文为开发人员和管理员提供了分析 AIX 上的服务器进程的内存需求所需的命令、技巧和知识。本文还有助于开发人员和管理员避免出现使用 ps 或 topas 等其他标准运行时分析工具时无法识别的资源短缺。本文是面向 AIX 系统管理员或本机应用程序开发人员。
本文阐述 32 位 AIX 5L™ (5.3) 上共享库占用内存的方式,并演示下列命令:
本文讨论进程的虚拟地址空间和内核共享库段,以及如何查看它们和如何解释上述多种诊断实用工具的输出。本文还讨论如何诊断内核共享段完全占用的情况,以及解决该问题的可能方法。
在贯穿全文的示例中,我们碰巧使用了来自软件产品 Business Objects Enterprise Xir2® 的进程。这种选择无关紧要,因为这些概念适用于在 AIX 5L 上运行的所有进程。
|
为了保持思维同步,让我们简单回顾一下 32 位体系结构。为了达到目标,我将采用最有用的“bc”命令行计算器。
在 32 位处理器中,寄存器能够保存 2^32 大小的值,
$ bc 2^32 4294967296 obase=16 2^32 100000000 |
这是 4GB 的范围。这表示在系统中运行的程序能够访问 0 到 2^32 – 1 范围内的任何函数或数据地址。
$ bc 2^32 - 1 FFFFFFFF obase=10 2^32 - 1 4294967295 |
现在,如您所知,任何操作系统都可能同时运行数百个程序。即使其中每个系统都能访问 4GB 的内存范围,这并不表示它们各自拥有 4GB 的物理内存分配。这是不切实际的。实际上,操作系统在适量物理内存和文件系统中被指定为交换(或分页)空间的区域之间实现了非常复杂的代码和数据交换方案。而且,虽然每个进程能够访问 4GB 的内存空间,但其中大部分空间都不会被使用。因此,操作系统仅为每个特定进程加载或交换所需数量的代码和数据。
该机制通常称为虚拟内存和虚拟地址空间。
在可执行文件运行时,操作系统的虚拟内存管理器查看文件包含的代码和数据,并决定将其中哪些部分加载到内存或交换空间中,或从文件系统引用。同时,虚拟内存管理器建立一些结构以将物理位置映射到 4GB 范围内的虚拟位置。其中 4GB 范围表示进程的最大理论范围(有时包括用于表示它的 VMM 结构),被称为进程的虚拟地址空间。
在 AIX 上,将 4GB 虚拟地址空间划分为 16 个 256MB 的段。这些段具有预定的功能,下面对其中一些段进行说明:
与之相比,HP-UX® 将地址空间划分为 4 个象限(quadrants)。如果使用 chatr 命令并采用 +q3p enable 和 +q4p enable 选项进行指定,则象限 3 和象限 4 可用于共享库映射。
|
共享库本身是用于共享的。具体而言,二进制映像的只读部分(即代码,也称为“文本”)和只读数据(常量数据,以及可以写入时复制的数据)可以一次加载到物理内存中,然后将其多次映射到需要该数据的任何进程中。
为了演示此概念,请准备一台运行 AIX 的计算机,然后查看已加载的共享库:
> su # genkld Text address Size File d1539fe0 1a011 /usr/lib/libcurses.a[shr.o] d122f100 36732 /usr/lib/libptools.a[shr.o] d1266080 297de /usr/lib/libtrace.a[shr.o] d020c000 5f43 /usr/lib/nls/loc/iconv/ISO8859-1_UCS-2 d7545000 161ff /usr/java14/jre/bin/libnet.a d7531000 135e2 /usr/java14/jre/bin/libzip.a .... [ lots more libs ] .... d1297108 3a99 /opt/rational/clearcase/shlib/libatriastats_svr.a [atriastats_svr-shr.o] d1bfa100 2bcdf /opt/rational/clearcase/shlib/libatriacm.a[atriacm-shr.o] d1bbf100 2cf3c /opt/rational/clearcase/shlib/libatriaadm.a[atriaadm-shr.o] .... [ lots more libs ] .... d01ca0f8 17b6 /usr/lib/libpthreads_compat.a[shr.o] d10ff000 30b78 /usr/lib/libpthreads.a[shr.o] d00f0100 1fd2f /usr/lib/libC.a[shr.o] d01293e0 25570 /usr/lib/libC.a[shrcore.o] d01108a0 18448 /usr/lib/libC.a[ansicore_32.o] .... [ lots more libs ] .... d04a2100 fdb4b /usr/lib/libX11.a[shr4.o] d0049000 365c4 /usr/lib/libpthreads.a[shr_xpg5.o] d0045000 3c52 /usr/lib/libpthreads.a[shr_comm.o] d05bb100 5058 /usr/lib/libIM.a[shr.o] d05a7100 139c1 /usr/lib/libiconv.a[shr4.o] d0094100 114a2 /usr/lib/libcfg.a[shr.o] d0081100 125ea /usr/lib/libodm.a[shr.o] d00800f8 846 /usr/lib/libcrypt.a[shr.o] d022d660 25152d /usr/lib/libc.a[shr.o] |
结果很有趣,我们立刻可以看到这台计算机正在运行 Clearcase 和 Java™。让我们从中任选一个公共库,假定选择 libpthreads.a
。浏览该库并查看它实现了哪些函数:
# dump -Tv /usr/lib/libpthreads.a | grep EXP [278] 0x00002808 .data EXP RW SECdef [noIMid] pthread_attr_default [279] 0x00002a68 .data EXP RW SECdef [noIMid] pthread_mutexattr_default [280] 0x00002fcc .data EXP DS SECdef [noIMid] pthread_create [281] 0x0000308c .data EXP DS SECdef [noIMid] pthread_cond_init [282] 0x000030a4 .data EXP DS SECdef [noIMid] pthread_cond_destroy [283] 0x000030b0 .data EXP DS SECdef [noIMid] pthread_cond_wait [284] 0x000030bc .data EXP DS SECdef [noIMid] pthread_cond_broadcast [285] 0x000030c8 .data EXP DS SECdef [noIMid] pthread_cond_signal [286] 0x000030d4 .data EXP DS SECdef [noIMid] pthread_setcancelstate [287] 0x000030e0 .data EXP DS SECdef [noIMid] pthread_join .... [ lots more stuff ] .... |
嗯,真棒!现在,我们来查看目前系统中有哪些正在运行的进程加载了该库:
# for i in $(ps -o pid -e | grep ^[0-9] ) ; do j=$(procldd $i | grep libpthreads.a); \ if [ -n "$j" ] ; then ps -p $i -o comm | grep -v COMMAND; fi ; done portmap rpc.statd automountd rpc.mountd rpc.ttdbserver dtexec dtlogin radiusd radiusd radiusd dtexec dtterm procldd : no such process : 24622 dtterm xmwlm dtwm dtterm dtgreet dtexec ttsession dtterm dtexec rdesktop procldd : no such process : 34176 java dtsession dtterm dtexec dtexec |
真棒!现在,我们以更简洁的方式显示相同的结果:
# cat prev.command.out.txt | sort | uniq automountd dtexec dtgreet dtlogin dtsession dtterm dtwm java portmap radiusd rdesktop rpc.mountd rpc.statd rpc.ttdbserver ttsession xmwlm |
于是,我们得到了目前正在执行并加载了 libpthreads.a
库的二进制文件的排序列表。请注意,列出的进程只占当时系统中正在运行的进程的一小部分:
# ps -e | wc -l 85 |
接下来,我们看一下每个进程加载 libpthreads.a
的位置:
# ps -e | grep java 34648 - 4:13 java # # procmap 34648 | grep libpthreads.a d0049000 217K read/exec /usr/lib/libpthreads.a[shr_xpg5.o] f03e6000 16K read/write /usr/lib/libpthreads.a[shr_xpg5.o] d0045000 15K read/exec /usr/lib/libpthreads.a[shr_comm.o] f03a3000 265K read/write /usr/lib/libpthreads.a[shr_comm.o] # # ps -e | grep automountd 15222 - 1:00 automountd 25844 - 0:00 automountd # # procmap 15222 | grep libpthreads.a d0049000 217K read/exec /usr/lib/libpthreads.a[shr_xpg5.o] f03e6000 16K read/write /usr/lib/libpthreads.a[shr_xpg5.o] d0045000 15K read/exec /usr/lib/libpthreads.a[shr_comm.o] f03a3000 265K read/write /usr/lib/libpthreads.a[shr_comm.o] d10ff000 194K read/exec /usr/lib/libpthreads.a[shr.o] f0154000 20K read/write /usr/lib/libpthreads.a[shr.o] # # ps -e | grep portmap 12696 - 0:06 portmap 34446 - 0:00 portmap # # procmap 12696 | grep libpthreads.a d0045000 15K read/exec /usr/lib/libpthreads.a[shr_comm.o] f03a3000 265K read/write /usr/lib/libpthreads.a[shr_comm.o] d10ff000 194K read/exec /usr/lib/libpthreads.a[shr.o] f0154000 20K read/write /usr/lib/libpthreads.a[shr.o] # # ps -e | grep dtlogin 6208 - 0:00 dtlogin 6478 - 2:07 dtlogin 20428 - 0:00 dtlogin # # procmap 20428 | grep libpthreads.a d0045000 15K read/exec /usr/lib/libpthreads.a[shr_comm.o] f03a3000 265K read/write /usr/lib/libpthreads.a[shr_comm.o] d0049000 217K read/exec /usr/lib/libpthreads.a[shr_xpg5.o] f03e6000 16K read/write /usr/lib/libpthreads.a[shr_xpg5.o] |
请注意,每个进程每次都在相同的地址加载该库。不要为库中的 .o 的构成列表感到困惑。在 AIX 上,您可以共享归档库(通常是 .a 文件)以及动态共享库(通常是 .so 文件)。这样做的目的是能够在链接时绑定符号,就像传统的归档链接,但不需要将构成对象(归档中的 .o 文件)复制到最终二进制映像中。但是,不执行动态(或运行时)符号解析,动态共享库(.so/.sl 文件)也是如此。
还要注意,libpthreads.a
代码部分(那些标记为 read/exec 的部分)被加载到段 0xd 中。如上所述,该段在 AIX 中被指定为预留给共享库代码。也就是说,内核将该共享库的可共享段加载到在同一内核上运行的所有进程所共享的区域。
您可能注意到数据部分也加载到同一段中:共享库段 0xf。但是,这并不表示每个进程也共享 libpthreads.a
的数据部分。这一点没有明确定义(这样的安排方式无法正常工作),因为不同的进程将需要在不同的时间维护不同的数据值。段 0xf 对于每个使用 libpthreads.a
的进程是独立的,即使虚拟内存地址相同也是如此。
svmon 命令可以显示进程在虚拟内存管理器中的段 ID (Vsid)。我们将看到共享库的代码段都具有相同的 Vsid,而共享库的数据段都具有不同的 Vsid。Esid 表示有效段 ID (Effective Segment ID),是位于进程地址空间范围内的段 ID(仅仅是术语,不要为此感到困惑)。
# svmon -P 17314 ------------------------------------------------------------------------------- Pid Command Inuse Pin Pgsp Virtual 64-bit Mthrd 16MB 17314 dtexec 20245 9479 12 20292 N N N Vsid Esid Type Description PSize Inuse Pin Pgsp Virtual 0 0 work kernel segment s 14361 9477 0 14361 6c01b d work shared library text s 5739 0 9 5786 19be6 f work shared library data s 83 0 1 87 21068 2 work process private s 56 2 2 58 18726 1 pers code,/dev/hd2:65814 s 5 0 - - 40c1 - pers /dev/hd4:2 s 1 0 - - # # svmon -P 20428 ------------------------------------------------------------------------------- Pid Command Inuse Pin Pgsp Virtual 64-bit Mthrd 16MB 20428 dtlogin 20248 9479 23 20278 N N N Vsid Esid Type Description PSize Inuse Pin Pgsp Virtual 0 0 work kernel segment s 14361 9477 0 14361 6c01b d work shared library text s 5735 0 9 5782 7869e 2 work process private s 84 2 10 94 parent=786be 590b6 f work shared library data s 37 0 4 41 parent=7531d 6c19b 1 pers code,/dev/hd2:65670 s 29 0 - - 381ae - pers /dev/hd9var:4157 s 1 0 - - 40c1 - pers /dev/hd4:2 s 1 0 - - 4c1b3 - pers /dev/hd9var:4158 s 0 0 - - |
|
看看目前共享段 0xd 中有多少空间。我们将再次使用 bc 计算器工具。理所当然,我们将验证段 0xd 的大小。
# bc ibase=16 E0000000-D0000000 268435456 ibase=A 268435456/(1024^2) 256 |
看起来不错。如上所述,每个段为 256MB。接下来,看看目前使用了多少容量。
$ echo "ibase=16; $(genkld | egrep ^\ \{8\} | awk '{print $2}' | tr '[a-f]' '[A-F]' \ | tr '\n' '+' ) 0" | bc 39798104 $ $ bc < |
也就是说,目前使用了 37MB。然后启动 XIr2,并进行比较:
$ echo "ibase=16; $(genkld | egrep ^\ \{8\} | awk '{print $2}' | tr '[a-f]' '[A-F]' \ | tr '\n' '+' ) 0" | bc 266069692 $ $ bc < |
现在使用了 253MB。这非常接近于限值 256MB。任意选择一个进程,如 WIReportServer,然后查看有多少共享库已放入共享空间,以及有多少共享库必须独立映射。因为我们了解共享段是从地址 0xd000000 开始的,因此我们可以将其从 procmap 的输出结果中筛选出来。请记住,只有代码段会映射到段 0xd 中,因此我们仅查找带有 read/exec 的行:
$ procmap 35620 | grep read/exec | grep -v ^d 10000000 10907K read/exec boe_fcprocd 31ad3000 14511K read/exec /crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/libEnterpriseFramework.so 3167b000 3133K read/exec /crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/libcpi18nloc.so 3146c000 1848K read/exec /crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/libBOCP_1252.so 31345000 226K read/exec /crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/btlat300.so |
看起来上面四个库无法映射到共享段。因此,它们映射到私有段 0x3 中,该私有段供通过调用 mmap() 例程分配的任何通用内存使用。
有几种条件会强制共享库独立映射到 32 位 AIX 上:
如果同一个库来自不同的位置,那么 AIX 内核甚至会将该库两次加载到共享内存中:
sj2e652a-chloe:~/e652_r>genkld | grep libcplib.so d5180000 678c6 /space2/home/sj2e652a/e652_r/lib/libcplib.so d1cf5000 678c6 /home/sj1e652a/xir2_r/lib/libcplib.so |
|
如果运行部署在不同目录中的 XIr2 的另一个实例,我们将看到进程占用内存有明显区别:
$ ps -e -o pid,vsz,user,comm | grep WIReportServer 28166 58980 jbrown WIReportServer 46968 152408 sj1xir2a WIReportServer 48276 152716 sj1xir2a WIReportServer 49800 152788 sj1xir2a WIReportServer 50832 152708 sj1xir2a WIReportServer |
帐户“jbrown”的实例首先启动,然后启动帐户“sj1xir2a”的实例。如果我们要执行一些微小、存在风险的操作,例如在我们的 bobje/setup/env.sh 文件中的适当位置进行设置:
LIBPATH=~jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000:$LIBPATH |
在启动第二个实例之前,我们将看到内存占用恢复正常了(我改用进程 boe_fcprocd,因为在这项 LIBPATH 测试中无法启动 WIReportServer)。
$ ps -e -o pid,vsz,user,comm | grep boe_fcprocd 29432 65036 jbrown boe_fcprocd 35910 67596 jbrown boe_fcprocd 39326 82488 sj1xir2a boe_fcprocd 53470 64964 sj1xir2a boe_fcprocd |
我们看到 procmap 显示文件按预期从 ~jbrown 加载:
53470 : /crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/boe_fcprocd -name vanpg 10000000 10907K read/exec boe_fcprocd 3000079c 1399K read/write boe_fcprocd d42c9000 1098K read/exec /home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcrypto.so 33e34160 167K read/write /home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcrypto.so 33acc000 3133K read/exec /home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcpi18nloc.so 33ddc697 349K read/write /home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcpi18nloc.so |
|
关闭应用程序后,共享库可能仍然驻留在共享段 0xd 中。在这种情况下,您可以使用实用工具“slibclean”卸载不再引用的任何共享库。该实用工具不需要参数:
slibclean |
还可以使用实用工具 genld,在传递 -l 选项时,该工具可以显示类似于 procmap 的结果,但它会显示系统中的所有现有进程:
genld -l |
有时,在运行 slibclean 后,您可能仍然无法复制共享库。例如:
$ cp /build/dev/bin/release/libc3_calc.so /runtime/app/lib/ cp: /runtime/app/lib/libc3_calc.so: Text file busy |
您可能已经运行了 slibclean,并且运行“genld –l”时未显示任何进程加载了该库。但是系统仍然在保护该文件。您可以通过以下方法解除此限制:首先删除目标位置的共享库,然后复制新的共享库:
$ rm /runtime/app/lib/libc3_calc.so $ cp /build/dev/bin/release/libc3_calc.so /runtime/app/lib/ |
在开发共享库时,如果您要执行重复的编译、链接、执行和测试练习,您可以通过将共享库设置为只有所有者才能执行(例如,r_xr__r__)来避免在每个周期中运行 slibclean 的麻烦。这将导致您用于测试的进程单独地加载和映射您的共享库。但是,请确保让所有人都可以执行它(例如,产品发布时设为 r_xr_xr_x)。
|
我希望您能够更详细地了解共享库占用内存的方式以及可用于检测它们的实用工具。这样您就能更好地评估应用程序的规模需求,并分析在 AIX 系统中运行的进程的内存占用构成情况。