AIX 5L 上的共享库内存占用

了解 IBM® AIX® 上的共享库机制和内存占用情况。本文对于开发人员编写服务器代码或管理员管理生产 AIX 系统来说非常重要。本文为开发人员和管理员提供了分析 AIX 上的服务器进程的内存需求所需的命令、技巧和知识。本文还有助于开发人员和管理员避免出现使用 ps 或 topas 等其他标准运行时分析工具时无法识别的资源短缺。本文是面向 AIX 系统管理员或本机应用程序开发人员。

George Cross (george.cross@excite.com), 高级软件开发人员, Business Objects Americas

George Cross 是 Business Objects Americas 的一名高级软件开发人员,他拥有在 UNIX 上使用 C++ 开发服务器应用程序的经验。他拥有加拿大西蒙菲莎大学的计算机科学学士学位。



2008 年 7 月 03 日

引言

本文阐述 32 位 AIX 5L™ (5.3) 上共享库占用内存的方式,并演示下列命令:

  • ps
  • svmon
  • slibclean
  • procldd
  • procmap
  • genkld
  • genld

本文讨论进程的虚拟地址空间和内核共享库段,以及如何查看它们和如何解释上述多种诊断实用工具的输出。本文还讨论如何诊断内核共享段完全占用的情况,以及解决该问题的可能方法。

在贯穿全文的示例中,我们碰巧使用了来自软件产品 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 的内存空间,但其中大部分空间都不会被使用。因此,操作系统仅为每个特定进程加载或交换所需数量的代码和数据。

图 1. 虚拟内存的概念图
vmm

该机制通常称为虚拟内存和虚拟地址空间。

在可执行文件运行时,操作系统的虚拟内存管理器查看文件包含的代码和数据,并决定将其中哪些部分加载到内存或交换空间中,或从文件系统引用。同时,虚拟内存管理器建立一些结构以将物理位置映射到 4GB 范围内的虚拟位置。其中 4GB 范围表示进程的最大理论范围(有时包括用于表示它的 VMM 结构),被称为进程的虚拟地址空间。

在 AIX 上,将 4GB 虚拟地址空间划分为 16 个 256MB 的段。这些段具有预定的功能,下面对其中一些段进行说明:

  • 段 0 预留给内核相关数据。
  • 段 1 预留给代码。
  • 段 2 预留给堆栈和动态内存分配。
  • 段 3 预留给内存映射文件,即 mmap。
  • 段 d 预留给共享库代码。
  • 段 f 预留给共享库数据。

与之相比,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 <<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 中,因此我们仅查找带有 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 上:

  • 共享段 0xd 中的空间不足(如上所述)。
  • 共享库没有组或其他角色的执行权限。您可以指定 rwxr-xr-x 权限来更正此问题;但是,开发人员将希望使用私有权限(例如 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 系统中运行的进程的内存占用构成情况。

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • Performance Management Guide:Using Shared Memory 提供了对 32 位 AIX 中的段以及使用 EXTSHM、shmat() 和 mmap() 子例程的一般性说明。
  • When Segments Collide提供对 AIX 中的虚拟地址空间和体系结构的深入分析,以及通过 Java 虚拟机演示的运行时使用情况。
  • BIN DIRECTORY FILLING UP AFTER COMPILES OF SHARED LIBRARY 讨论 slibclean 和 genkld 命令的简短故障排除提示。
  • General Programming Concepts:Writing and Debugging Programs 第 19 章“Shared Libraries and Shared Memory”提供有关为共享库和内存分配提供的操作系统工具的信息。
  • Solaris Internals:Solaris 10 and OpenSolaris Kernel Architecture,Richard McDougall,Jim Mauro,2006 年 7 月,第 21 章的第 4 部分提供了有关比较 Unix 实现的虚拟内存体系结构的大量信息。第 14 章提供了分析活动 VM 段中的文件映射的优秀示例。
  • AIX 5L Porting Guide 详细说明了将应用程序从其他基于 Unix 的平台迁移到 AIX 5L 操作系统时最容易出现的问题类型。
  • AIX and UNIX 专区:developerWorks 的“AIX and UNIX 专区”提供了大量与 AIX 系统管理的所有方面相关的信息,您可以利用它们来扩展自己的 UNIX 技能。
  • AIX and UNIX 新手入门:访问“AIX and UNIX 新手入门”页面可了解更多关于 AIX 和 UNIX 的内容。
  • AIX and UNIX 专题汇总:AIX and UNIX 专区已经为您推出了很多的技术专题,为您总结了很多热门的知识点。我们在后面还会继续推出很多相关的热门专题给您,为了方便您的访问,我们在这里为你把本专区的所有专题进行汇总,让您更方便的找到你需要的内容。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=318226
ArticleTitle=AIX 5L 上的共享库内存占用
publish-date=07032008