本文研究了共享库如何在32位AIX 5L™(5.3)上占用内存,并演示了以下命令:
ps svmon slibclean 禁止 进程图 Genkld 风格本文讨论了进程的虚拟地址空间,以及内核共享库段,如何检查它们以及如何解释上述各种诊断实用程序的输出。 本文还讨论了如何诊断内核共享段已满的情况,以及解决该情况的可能方法。
在整个示例中,我们碰巧使用了软件产品Business Objects EnterpriseXir2®中的流程。 这是任意的,因为这些概念将适用于在AIX 5L上运行的所有进程。
恰好我们都处于同一思维框架中,让我们回顾一下32位体系结构。 为此,我将采用最有用的“ bc”命令行计算器。
在32位处理器中,寄存器能够保存2 ^ 32个可能的值,
$ bc 2^32 4294967296 obase=16 2^32 100000000这是4 GB的范围。 这意味着在系统上运行的程序能够访问0到2 ^ 32-1范围内的任何功能或数据地址。
$ bc 2^32 - 1 FFFFFFFF obase=10 2^32 - 1 4294967295如您所知,现在任何操作系统都可能同时运行数百个程序。 即使他们每个人都能够访问4GB的内存范围,但这并不意味着他们每个人都有自己的4GB物理RAM分配。 那将是不切实际的。 而是,OS实施了一种复杂的方案,在中等数量的物理RAM与文件系统的指定为交换(或分页)空间的区域之间交换代码和数据。 而且,即使每个进程都能够访问4GB的内存空间,许多进程甚至都不使用其中的大部分。 因此,操作系统仅为每个特定进程加载或交换所需数量的代码和数据。
这种机制通常称为虚拟内存和虚拟地址空间。
运行可执行文件时,操作系统的虚拟内存管理器将查看组成文件的代码和数据,并决定将哪些部分加载到RAM中,或加载到交换文件中,或从文件系统中引用。 同时,它建立了一些结构以将物理位置映射到4GB范围内的虚拟位置。 这个4GB的范围代表了进程的最大理论范围,并且(有时也与代表它的VMM的结构一起)被称为进程的虚拟地址空间。
在AIX上,4GB虚拟地址空间分为16个256 MB的段。 这些段具有预定功能,下面描述其中一些功能:
段0用于与内核相关的数据。 段1用于代码。 段2用于堆栈和动态内存分配。 段3用于映射文件的内存,mmap的内存。 段d用于共享库代码。 段f用于共享库数据。相比之下,在HP-UX®上,地址空间分为四个象限。 如果使用带有+ q3p启用和+ q4p启用选项的chatr命令指定了第三象限和第四象限,则它们可用于共享库映射。
共享库自然是要共享的。 更具体地说,二进制映像的只读部分(即代码(也称为“文本”))和只读数据(const数据以及可以写时复制的数据)可以一次加载到物理文件中。内存,并多次映射到需要它的任何进程中。
为了说明这一点,请使用一台正在运行的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代码段(标记为已读/执行)已加载到段0xd中。 如上所述,该段在AIX上指定为共享库代码的段。 也就是说,内核将这个共享库的可共享段加载到由同一内核上运行的所有进程共享的区域中。
您可能会注意到,数据段也被加载到同一段:共享库段0xf。 但这并不意味着每个进程还共享libpthreads.a的数据部分。 松散地定义,这种安排将行不通,因为不同的流程将需要在不同的时间维护不同的数据值。 即使虚拟内存地址相同,段0xf对于使用libpthreads.a每个进程也是不同的。
svmon命令可以在虚拟内存管理器(Vsid)中为进程显示段ID。 我们将看到共享库代码段均具有相同的Vsid,而共享库数据段均具有不同的Vsid。 Esid表示有效细分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 <<EOF > 39798104/(1024^2) > EOF 37也就是说,当前正在使用37MB。 让我们启动XIr2,并进行比较:
$ echo "ibase=16; $(genkld | egrep ^\ \{8\} | awk '{print $2}' | tr '[a-f]' '[A-F]' \ | tr '\n' '+' ) 0" | bc 266069692 $ $ bc <<EOF > 266069692/(1024^2) > EOF 253现在使用了253MB。 这非常接近256MB的限制。 让我们选择一个随机过程,例如WIReportServer,看看有多少个共享库进入共享空间,以及有多少个必须私有映射。 由于我们知道共享段从地址0xd000000开始,因此我们可以将其过滤出procmap的输出。 请记住,只有代码段被映射到段0xd,所以我们只寻找可读取/执行的行:
$ 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上私有映射:
它在共享段0xd中空间不足(如上所述)。 共享库没有组或其他的执行权限。 您可以使用rwxr-xr-x m的权限指定来纠正此问题; 但是,开发人员可能希望使用私有权限(例如rwx ------),因此不必在每次重新编译共享库并将其部署进行测试时都运行slibclean。 一些文档说共享库是通过nfs加载的。如果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系统上运行的进程的内存占用量的组成部分。
翻译自: https://www.ibm.com/developerworks/aix/library/au-slib_memory/index.html