统计输出
top命令只是给出了一个进程大概占用了多少的内存,而pmap能更详细的给出内存都是被谁占用了。pmap命令输出的内容来自于/proc/[pid]/maps和/proc/[pid]/smaps这两个文件,第一个文件包含了每段的一个大概描述,而后一个文件包含了更详细的信息。
xxxxxxxxxx101[root@666 ~]# less -N /proc/14224/2arch_status coredump_filter io mountstats personality smaps_rollup timers3attr/ cpuset limits net/ projid_map stack timerslack_ns4autogroup cwd/ loginuid ns/ root/ stat uid_map5auxv environ map_files/ numa_maps sched statm wchan6cgroup exe maps oom_adj schedstat status7clear_refs fd/ mem oom_score sessionid syscall8cmdline fdinfo/ mountinfo oom_score_adj setgroups task/9comm gid_map mounts pagemap smaps timens_offsets10[root@666 ~]#
xxxxxxxxxx271[root@666 ~]# yum whatprovides pmap23procps-ng-3.3.10-28.el7.i686 : System and process monitoring utilities4Repo : base5Matched from:6Filename : /usr/bin/pmap78[root@666 ~]# rpm -ql procps-ng9/usr/bin/free10/usr/bin/pgrep11/usr/bin/pkill12/usr/bin/pmap13/usr/bin/ps14/usr/bin/pwdx15/usr/bin/skill16/usr/bin/slabtop17/usr/bin/snice18/usr/bin/tload19/usr/bin/top20/usr/bin/uptime21/usr/bin/vmstat22/usr/bin/w23/usr/bin/watch24/usr/lib64/libprocps.so.425/usr/lib64/libprocps.so.4.0.026/usr/sbin/sysctl27
这里第一列是内存的起始地址,第二列是mapping的地址大小,第三列是这段内存的访问权限,最后一列是mapping到的文件。这里的地址都是虚拟地址,大小也是虚拟地址大小。
这里的输出有很多的[ anon ]行,表示在磁盘上没有对应的文件,这些一般都是可执行文件或者动态库里的bss段。当然有对应文件的mapping也有可能是anonymous,比如文件的数据段。
xxxxxxxxxx20412# 通过以下示例可以看到nginx引用了哪些链接库、每个链接库占用了多少内存,以及总的内存占用。在最下方Total3# ps -ef | grep nginx4root 14224 1 0 Sep09 ? 00:00:00 nginx: master process /usr/local/nginx/nginx -c /usr/local/nginx/nginx.conf5root 29926 28136 0 19:50 pts/0 00:00:00 grep --color=auto nginxfr6[root@666 ~]# pmap 14224714224: nginx: master process /usr/local/nginx/nginx -c /usr/local/nginx/nginx.conf80000000000400000 4500K r-x-- nginx90000000000a64000 196K r---- nginx100000000000a95000 160K rw--- nginx110000000000abd000 148K rw--- [ anon ]120000000001a77000 4644K rw--- [ anon ]130000000001f00000 3704K rw--- [ anon ]1400007f1724340000 1024K rw-s- zero (deleted)1500007f1724440000 2500K r--s- GeoLite2-Country.mmdb1600007f17246b1000 128228K r--s- GeoIP2-City.mmdb1700007f172c4ea000 88K r-x-- libresolv-2.17.so18...1900007f172c702000 8K rw--- [ anon ]2000007f172c704000 24K r-x-- libnss_dns-2.17.so21...2200007f17348b5000 48K r-x-- libnss_files-2.17.so23...2400007f1734ac2000 24K rw--- [ anon ]2500007f1734ac8000 384K r-x-- libpcre.so.1.2.026...2700007f1734d2a000 8K r-x-- libXau.so.6.0.02800007f1734d2c000 2048K ----- libXau.so.6.0.02900007f1734f2c000 4K r---- libXau.so.6.0.03000007f1734f2d000 4K rw--- libXau.so.6.0.03100007f1734f2e000 176K r-x-- libgraphite2.so.3.0.132...3300007f173515c000 1104K r-x-- libglib-2.0.so.0.5600.13400007f1735270000 2044K ----- libglib-2.0.so.0.5600.13500007f173546f000 4K r---- libglib-2.0.so.0.5600.13600007f1735470000 4K rw--- libglib-2.0.so.0.5600.13700007f1735471000 4K rw--- [ anon ]3800007f1735472000 36K r-x-- libjbig.so.2.03900007f173547b000 2044K ----- libjbig.so.2.04000007f173567a000 4K r---- libjbig.so.2.04100007f173567b000 12K rw--- libjbig.so.2.04200007f173567e000 156K r-x-- libxcb.so.1.1.04300007f17356a5000 2044K ----- libxcb.so.1.1.04400007f17358a4000 4K r---- libxcb.so.1.1.04500007f17358a5000 4K rw--- libxcb.so.1.1.04600007f17358a6000 60K r-x-- libbz2.so.1.0.64700007f17358b5000 2044K ----- libbz2.so.1.0.64800007f1735ab4000 4K r---- libbz2.so.1.0.64900007f1735ab5000 4K rw--- libbz2.so.1.0.65000007f1735ab6000 16K r-x-- libuuid.so.1.3.05100007f1735aba000 2044K ----- libuuid.so.1.3.05200007f1735cb9000 4K r---- libuuid.so.1.3.05300007f1735cba000 4K rw--- libuuid.so.1.3.05400007f1735cbb000 156K r-x-- libexpat.so.1.6.05500007f1735ce2000 2048K ----- libexpat.so.1.6.05600007f1735ee2000 8K r---- libexpat.so.1.6.05700007f1735ee4000 4K rw--- libexpat.so.1.6.05800007f1735ee5000 108K r-x-- libfribidi.so.0.4.05900007f1735f00000 2044K ----- libfribidi.so.0.4.06000007f17360ff000 4K r---- libfribidi.so.0.4.06100007f1736100000 4K rw--- libfribidi.so.0.4.06200007f1736101000 624K r-x-- libharfbuzz.so.0.10705.06300007f173619d000 2044K ----- libharfbuzz.so.0.10705.06400007f173639c000 4K r---- libharfbuzz.so.0.10705.06500007f173639d000 4K rw--- libharfbuzz.so.0.10705.06600007f173639e000 84K r-x-- libgcc_s-4.8.5-20150702.so.16700007f17363b3000 2044K ----- libgcc_s-4.8.5-20150702.so.16800007f17365b2000 4K r---- libgcc_s-4.8.5-20150702.so.16900007f17365b3000 4K rw--- libgcc_s-4.8.5-20150702.so.17000007f17365b4000 932K r-x-- libstdc++.so.6.0.197100007f173669d000 2048K ----- libstdc++.so.6.0.197200007f173689d000 32K r---- libstdc++.so.6.0.197300007f17368a5000 8K rw--- libstdc++.so.6.0.197400007f17368a7000 84K rw--- [ anon ]7500007f17368bc000 436K r-x-- libwebp.so.7.0.57600007f1736929000 2044K ----- libwebp.so.7.0.57700007f1736b28000 4K r---- libwebp.so.7.0.57800007f1736b29000 4K rw--- libwebp.so.7.0.57900007f1736b2a000 8K rw--- [ anon ]8000007f1736b2c000 444K r-x-- libtiff.so.5.2.08100007f1736b9b000 2048K ----- libtiff.so.5.2.08200007f1736d9b000 4K r---- libtiff.so.5.2.08300007f1736d9c000 12K rw--- libtiff.so.5.2.08400007f1736d9f000 4K rw--- [ anon ]8500007f1736da0000 1248K r-x-- libX11.so.6.3.08600007f1736ed8000 2048K ----- libX11.so.6.3.08700007f17370d8000 4K r---- libX11.so.6.3.08800007f17370d9000 20K rw--- libX11.so.6.3.08900007f17370de000 68K r-x-- libXpm.so.4.11.09000007f17370ef000 2044K ----- libXpm.so.4.11.09100007f17372ee000 4K r---- libXpm.so.4.11.09200007f17372ef000 4K rw--- libXpm.so.4.11.09300007f17372f0000 268K r-x-- libjpeg.so.62.1.09400007f1737333000 2048K ----- libjpeg.so.62.1.09500007f1737533000 4K r---- libjpeg.so.62.1.09600007f1737534000 4K rw--- libjpeg.so.62.1.09700007f1737535000 64K rw--- [ anon ]9800007f1737545000 732K r-x-- libfreetype.so.6.14.09900007f17375fc000 2048K ----- libfreetype.so.6.14.010000007f17377fc000 28K r---- libfreetype.so.6.14.010100007f1737803000 4K rw--- libfreetype.so.6.14.010200007f1737804000 256K r-x-- libfontconfig.so.1.11.110300007f1737844000 2044K ----- libfontconfig.so.1.11.110400007f1737a43000 8K r---- libfontconfig.so.1.11.110500007f1737a45000 4K rw--- libfontconfig.so.1.11.110600007f1737a46000 16K r-x-- libraqm.so.0.700.010700007f1737a4a000 2044K ----- libraqm.so.0.700.010800007f1737c49000 4K r---- libraqm.so.0.700.010900007f1737c4a000 4K rw--- libraqm.so.0.700.011000007f1737c4b000 164K r-x-- libpng15.so.15.13.011100007f1737c74000 2048K ----- libpng15.so.15.13.011200007f1737e74000 4K r---- libpng15.so.15.13.011300007f1737e75000 4K rw--- libpng15.so.15.13.011400007f1737e76000 16K r-x-- libgpg-error.so.0.10.011500007f1737e7a000 2044K ----- libgpg-error.so.0.10.011600007f1738079000 4K r---- libgpg-error.so.0.10.011700007f173807a000 4K rw--- libgpg-error.so.0.10.011800007f173807b000 500K r-x-- libgcrypt.so.11.8.211900007f17380f8000 2044K ----- libgcrypt.so.11.8.212000007f17382f7000 4K r---- libgcrypt.so.11.8.212100007f17382f8000 12K rw--- libgcrypt.so.11.8.212200007f17382fb000 4K rw--- [ anon ]12300007f17382fc000 1028K r-x-- libm-2.17.so12400007f17383fd000 2044K ----- libm-2.17.so12500007f17385fc000 4K r---- libm-2.17.so12600007f17385fd000 4K rw--- libm-2.17.so12700007f17385fe000 148K r-x-- liblzma.so.5.2.212800007f1738623000 2044K ----- liblzma.so.5.2.212900007f1738822000 4K r---- liblzma.so.5.2.213000007f1738823000 4K rw--- liblzma.so.5.2.213100007f1738824000 84K r-x-- libz.so.1.2.713200007f1738839000 2044K ----- libz.so.1.2.713300007f1738a38000 4K r---- libz.so.1.2.713400007f1738a39000 4K rw--- libz.so.1.2.713500007f1738a3a000 8K r-x-- libfreebl3.so13600007f1738a3c000 2044K ----- libfreebl3.so13700007f1738c3b000 4K r---- libfreebl3.so13800007f1738c3c000 4K rw--- libfreebl3.so13900007f1738c3d000 1808K r-x-- libc-2.17.so14000007f1738e01000 2044K ----- libc-2.17.so14100007f1739000000 16K r---- libc-2.17.so14200007f1739004000 8K rw--- libc-2.17.so14300007f1739006000 20K rw--- [ anon ]14400007f173900b000 56K r-x-- libprofiler.so.0.4.1414500007f1739019000 2048K ----- libprofiler.so.0.4.1414600007f1739219000 4K r---- libprofiler.so.0.4.1414700007f173921a000 4K rw--- libprofiler.so.0.4.1414800007f173921b000 16K rw--- [ anon ]14900007f173921f000 256K r-x-- libgd.so.3.0.815000007f173925f000 2048K ----- libgd.so.3.0.815100007f173945f000 20K r---- libgd.so.3.0.815200007f1739464000 128K rw--- libgd.so.3.0.815300007f1739484000 16K rw--- [ anon ]15400007f1739488000 80K r-x-- libexslt.so.0.8.1715500007f173949c000 2044K ----- libexslt.so.0.8.1715600007f173969b000 4K r---- libexslt.so.0.8.1715700007f173969c000 4K rw--- libexslt.so.0.8.1715800007f173969d000 240K r-x-- libxslt.so.1.1.2815900007f17396d9000 2048K ----- libxslt.so.1.1.2816000007f17398d9000 4K r---- libxslt.so.1.1.2816100007f17398da000 4K rw--- libxslt.so.1.1.2816200007f17398db000 4K rw--- [ anon ]16300007f17398dc000 1404K r-x-- libxml2.so.2.9.116400007f1739a3b000 2044K ----- libxml2.so.2.9.116500007f1739c3a000 32K r---- libxml2.so.2.9.116600007f1739c42000 8K rw--- libxml2.so.2.9.116700007f1739c44000 8K rw--- [ anon ]16800007f1739c46000 48K r-x-- libsregex.so.0.0.116900007f1739c52000 2044K ----- libsregex.so.0.0.117000007f1739e51000 4K r---- libsregex.so.0.0.117100007f1739e52000 4K rw--- libsregex.so.0.0.117200007f1739e53000 20K r-x-- libmaxminddb.so.0.0.717300007f1739e58000 2044K ----- libmaxminddb.so.0.0.717400007f173a057000 4K r---- libmaxminddb.so.0.0.717500007f173a058000 4K rw--- libmaxminddb.so.0.0.717600007f173a059000 32K r-x-- libcrypt-2.17.so17700007f173a061000 2044K ----- libcrypt-2.17.so17800007f173a260000 4K r---- libcrypt-2.17.so17900007f173a261000 4K rw--- libcrypt-2.17.so18000007f173a262000 184K rw--- [ anon ]18100007f173a290000 92K r-x-- libpthread-2.17.so18200007f173a2a7000 2044K ----- libpthread-2.17.so18300007f173a4a6000 4K r---- libpthread-2.17.so18400007f173a4a7000 4K rw--- libpthread-2.17.so18500007f173a4a8000 16K rw--- [ anon ]18600007f173a4ac000 8K r-x-- libdl-2.17.so18700007f173a4ae000 2048K ----- libdl-2.17.so18800007f173a6ae000 4K r---- libdl-2.17.so18900007f173a6af000 4K rw--- libdl-2.17.so19000007f173a6b0000 136K r-x-- ld-2.17.so19100007f173a7b2000 1024K rw-s- zero (deleted)19200007f173a8b2000 80K rw--- [ anon ]19300007f173a8cf000 4K rw-s- zero (deleted)19400007f173a8d0000 4K rw--- [ anon ]19500007f173a8d1000 4K r---- ld-2.17.so19600007f173a8d2000 4K rw--- ld-2.17.so19700007f173a8d3000 4K rw--- [ anon ]19800007fffd249b000 132K rw--- [ stack ]19900007fffd2568000 16K r---- [ anon ]20000007fffd256c000 8K r-x-- [ anon ]201ffffffffff600000 4K r-x-- [ anon ]202total 246988K203[root@666 ~]#204
xxxxxxxxxx281#这里$$代表当前bash的进程ID,下面只显示了部分输出结果2dev@dev:~$ pmap $$32805: bash40000000000400000 976K r-x-- bash500000000006f3000 4K r---- bash600000000006f4000 36K rw--- bash700000000006fd000 24K rw--- [ anon ]80000000000be4000 1544K rw--- [ anon ]9......1000007f1fa0e9e000 2912K r---- locale-archive1100007f1fa1176000 1792K r-x-- libc-2.23.so1200007f1fa1336000 2044K ----- libc-2.23.so1300007f1fa1535000 16K r---- libc-2.23.so1400007f1fa1539000 8K rw--- libc-2.23.so1500007f1fa153b000 16K rw--- [ anon ]16......1700007f1fa196c000 152K r-x-- ld-2.23.so1800007f1fa1b7e000 28K r--s- gconv-modules.cache1900007f1fa1b85000 16K rw--- [ anon ]2000007f1fa1b8f000 8K rw--- [ anon ]2100007f1fa1b91000 4K r---- ld-2.23.so2200007f1fa1b92000 4K rw--- ld-2.23.so2300007f1fa1b93000 4K rw--- [ anon ]2400007ffde903a000 132K rw--- [ stack ]2500007ffde90e4000 8K r---- [ anon ]2600007ffde90e6000 8K r-x-- [ anon ]27ffffffffff600000 4K r-x-- [ anon ]28total 22464K
这里第一列是内存的起始地址,第二列是mapping的地址大小,第三列是这段内存的访问权限,最后一列是mapping到的文件。这里的地址都是虚拟地址,大小也是虚拟地址大小。
这里的输出有很多的[ anon ]行,表示在磁盘上没有对应的文件,这些一般都是可执行文件或者动态库里的bss段。当然有对应文件的mapping也有可能是anonymous,比如文件的数据段。关于程序的数据段和bss段的介绍请参考elf的相关资料。
上面可以看到bash、libc-2.23.so等文件出现了多行,但每行的权限不一样,这是因为每个动态库或者可执行文件里面都分很多段,有只能读和执行的代码段,有能读写的数据段,还有比如这一行“00007f1fa153b000 16K rw--- [ anon ]”,就是它上面一行libc-2.23.so的bss段。
[ stack ]表示进程用到的栈空间,而heap在这里看不到,因为pmap默认情况下不单独标记heap出来,由于heap是anonymous,所以从这里的大小可以推测出来,heap就是“0000000000be4000 1544K rw--- [ anon ]”。
其实从上面的结果根本看不出实际上每段占用了多少物理内存,要想看到RSS,需要使用-X参数,下面看看更详细的输出:
xxxxxxxxxx301dev@dev:~$ pmap -X $$22805: bash3Address Perm Offset Device Inode Size Rss Pss Referenced Anonymous Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked Mapping400400000 r-xp 00000000 fc:00 390914 976 888 526 888 0 0 0 0 0 0 bash5006f3000 r--p 000f3000 fc:00 390914 4 4 4 4 4 0 0 0 0 0 bash6006f4000 rw-p 000f4000 fc:00 390914 36 36 36 36 36 0 0 0 0 0 bash7006fd000 rw-p 00000000 00:00 0 24 24 24 24 24 0 0 0 0 0800be4000 rw-p 00000000 00:00 0 1544 1544 1544 1544 1544 0 0 0 0 0 [heap] 这里是heap标记9.....107f1fa0e9e000 r--p 00000000 fc:00 136340 2912 400 83 400 0 0 0 0 0 0 locale-archive117f1fa1176000 r-xp 00000000 fc:00 521726 1792 1512 54 1512 0 0 0 0 0 0 libc-2.23.so127f1fa1336000 ---p 001c0000 fc:00 521726 2044 0 0 0 0 0 0 0 0 0 libc-2.23.so137f1fa1535000 r--p 001bf000 fc:00 521726 16 16 16 16 16 0 0 0 0 0 libc-2.23.so147f1fa1539000 rw-p 001c3000 fc:00 521726 8 8 8 8 8 0 0 0 0 0 libc-2.23.so157f1fa153b000 rw-p 00000000 00:00 0 16 12 12 12 12 0 0 0 0 016......177f1fa196c000 r-xp 00000000 fc:00 521702 152 144 4 144 0 0 0 0 0 0 ld-2.23.so187f1fa1b7e000 r--s 00000000 fc:00 132738 28 28 9 28 0 0 0 0 0 0 gconv-modules.cache197f1fa1b85000 rw-p 00000000 00:00 0 16 16 16 16 16 0 0 0 0 0207f1fa1b8f000 rw-p 00000000 00:00 0 8 8 8 8 8 0 0 0 0 0217f1fa1b91000 r--p 00025000 fc:00 521702 4 4 4 4 4 0 0 0 0 0 ld-2.23.so227f1fa1b92000 rw-p 00026000 fc:00 521702 4 4 4 4 4 0 0 0 0 0 ld-2.23.so237f1fa1b93000 rw-p 00000000 00:00 0 4 4 4 4 4 0 0 0 0 0247ffde903a000 rw-p 00000000 00:00 0 136 24 24 24 24 0 0 0 0 0 [stack]257ffde90e4000 r--p 00000000 00:00 0 8 0 0 0 0 0 0 0 0 0 [vvar]267ffde90e6000 r-xp 00000000 00:00 0 8 4 0 4 0 0 0 0 0 0 [vdso]27ffffffffff600000 r-xp 00000000 00:00 0 4 0 0 0 0 0 0 0 0 0 [vsyscall]28===== ==== ==== ========== ========= ============== =============== ==== ======= ======2922468 5084 2578 5084 1764 0 0 0 0 0 KB30
xxxxxxxxxx1012[root@666 ~]# top -p$$3Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie4%Cpu0 : 1.0 us, 1.0 sy, 0.0 ni, 98.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st5KiB Mem : 63.5/2036924 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ]6KiB Swap: 0.0/2036924 [ ]78PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND928136 root 20 0 116656 4760 3216 S 0.0 0.2 0:00.01 /bin/bash -i10
https://tldp.org/LDP/tlk/mm/memory.html
在linux下,使用top,vmstat,free等命令查看系统或者进程的内存使用情况时,经常看到buff/cache memeory,swap,avail Mem等,他们都代表什么意思呢?这篇文章将来聊一聊Linux下的内存管理并解答这个问题。
讨论Linux下的内存管理其实就是讨论Linux下虚拟内存的实现方式,本人不是内核专家,所以这篇文章只会介绍一些概念性的东西,不会深入实现细节,有些地方描述的有可能不精确。
在早些时候,物理内存比较有限,人们希望程序可以使用的内存空间能超过实际物理内存,于是出现了虚拟内存的概念,不过随着时间的推移,虚拟内存的意义已经远远的超过了最初的想法。
虚拟内存是Linux管理内存的一种技术。它使得每个应用程序都认为自己拥有独立且连续的可用的内存空间(一段连续完整的地址空间),而实际上,它通常是被映射到多个物理内存段,还有部分暂时存储在外部磁盘存储器上,在需要时再加载到内存中来。
每个进程所能使用的虚拟地址大小和CPU位数有关,在32位的系统上,虚拟地址空间大小是4G,在64位系统上,是2^64=?(算不过来了)。而实际的物理内存可能远远小于虚拟地址空间的大小。
虚拟地址和进程息息相关,不同进程里的同一个虚拟地址指向的物理地址不一定一样,所以离开进程谈虚拟地址没有任何意义。
注意:网上很多文章将虚拟内存等同于交换空间,其实描述不够严谨,交换空间只是虚拟内存这张大蓝图中的一部分。
下面这张表很直观的表述了它们之间的关系
xxxxxxxxxx221 进程X 进程Y2+-------+ +-------+3| VPFN7 |--+ | VPFN7 |4+-------+ | 进程X的 进程Y的 +-------+5| VPFN6 | | Page Table Page Table +-| VPFN6 |6+-------+ | +------+ +------+ | +-------+7| VPFN5 | +----->| .... |---+ +-------| .... |<---+ | | VPFN5 |8+-------+ +------+ | +------+ | +------+ | | +-------+9| VPFN4 | +--->| .... |---+-+ | PFN4 | | | .... | | | | VPFN4 |10+-------+ | +------+ | | +------+ | +------+ | | +-------+11| VPFN3 |--+ | | .... | | | +--->| PFN3 |<---+ +----| .... |<---+--+ | VPFN3 |12+-------+ | | +------+ | | | +------+ | +------+ | +-------+13| VPFN2 | +-+--->| .... |---+-+-+ | PFN2 |<------+ | .... | | | VPFN2 |14+-------+ | +------+ | | +------+ +------+ | +-------+15| VPFN1 | | | +----->| FPN1 | +----| VPFN1 |16+-------+ | | +------+ +-------+17| VPFN0 |----+ +------->| PFN0 | | VPFN0 |18+-------+ +------+ +-------+19 虚拟内存 物理内存 虚拟内存20
21
22PFN(the page frame number): 页编号当进程执行一个程序时,需要先从先内存中读取该进程的指令,然后执行,获取指令时用到的就是虚拟地址,这个地址是程序链接时确定的(内核加载并初始化进程时会调整动态库的地址范围),为了获取到实际的数据,CPU需要将虚拟地址转换成物理地址,CPU转换地址时需要用到进程的page table,而page table里面的数据由操作系统维护。
注意:Linux内核代码访问内存时用的都是实际的物理地址,所以不存在虚拟地址到物理地址的转换,只有应用层程序才需要。
为了转换方便,Linux将虚拟内存和物理内存都拆分为固定大小的页,x86的系统一般内存页大小是4K,每个页都会分配一个唯一的编号,这就是页编号(PFN).
从上面的图中可以看出,虚拟内存和物理内存的page之间通过page table进行映射。进程X和Y的虚拟内存是相互独立的,且page table也是独立的,它们之间共享物理内存。进程可以随便访问自己的虚拟地址空间,而page table和物理内存由内核维护。当进程需要访问内存时,CPU会根据进程的page table将虚拟地址翻译成物理地址,然后进行访问。
注意:并不是每个虚拟地址空间的page都有对应的Page Table相关联,只有虚拟地址被分配给进程后,也即进程调用类似malloc函数之后,系统才会为相应的虚拟地址在Page Table中添加记录,如果进程访问一个没有和Page Table关联的虚拟地址,系统将会抛出SIGSEGV信号,导致进程退出,这也是为什么我们访问野指针时会经常出现segmentfault的原因。换句话说,虽然每个进程都有4G(32位系统)的虚拟地址空间,但只有向系统申请了的那些地址空间才能用,访问未分配的地址空间将会出segmentfault错误。Linux会将虚拟地址0不映射到任何地方,这样我们访问空指针就一定会报segmentfault错误。
page table可以简单的理解为一个memory mapping的链表(当然实际结构很复杂),里面的每个memory mapping都将一块虚拟地址映射到一个特定的资源(物理内存或者外部存储空间)。每个进程拥有自己的page table,和其它进程的page table没有关系。
每个memory mapping就是对一段虚拟内存的描述,包括虚拟地址的起始位置,长度,权限(比如这段内存里的数据是否可读、写、执行), 以及关联的资源(如物理内存page,swap空间上的page,磁盘上的文件内容等)。
当进程申请内存时,系统将返回虚拟内存地址,同时为相应的虚拟内存创建memory mapping并将它放入page table,但这时系统不一定会分配相应的物理内存,系统一般会在进程真正访问这段内存的时候才会分配物理内存并关联到相应的memory mapping,这就是所谓的延时分配/按需分配。
每个memory mapping都有一个标记,用来表示所关联的物理资源类型,一般分两大类,那就是anonymous和file backed,在这两大类中,又分了一些小类,比如anonymous下面有更具体的shared和copy on write类型, file backed下面有更具体的device backed类型。下面是每个类型所代表的意思:
这种类型表示memory mapping对应的物理资源存放在磁盘上的文件中,它所包含的信息包括文件的位置、offset、rwx权限等。
当进程第一次访问对应的虚拟page的时候,由于在memory mapping中找不到对应的物理内存,CPU会报page fault中断,然后操作系统就会处理这个中断并将文件的内容加载到物理内存中,然后更新memory mapping,这样下次CPU就能访问这块虚拟地址了。以这种方式加载到内存的数据一般都会放到page cache中,关于page cache会在后面介绍到.
一般程序的可执行文件,动态库都是以这种方式映射到进程的虚拟地址空间的。
和file backed类似,只是后端映射到了磁盘的物理地址,比如当物理内存被swap out后,将被标记为device backed。
程序自己用到的数据段和堆栈空间,以及通过mmap分配的共享内存,它们在磁盘上找不到对应的文件,所以这部分内存页被叫做anonymous page。anonymous page和file backed最大的差别是当内存吃紧时,系统会直接删除掉file backed对应的物理内存,因为下次需要的时候还能从磁盘加载到内存,但anonymous page不能被删除,只能被swap out。
不同进程的Page Table里面的多个memory mapping可以映射到相同的物理地址,通过虚拟地址(不同进程里的虚拟地址可能不一样)可以访问到相同的内容,当一个进程里面修改内存的内容后,在另一个进程中可以立即读取到。这种方式一般用来实现进程间高速的共享数据(如mmap)。当标记为shared的memory mapping被删除回收时,需要更新物理page上的引用计数,便于物理page的计数变0后被回收。
copy on write基于shared技术,当读这种类型的内存时,系统不需要做任何特殊的操作,而当要写这块内存时,系统将会生成一块新的内存并拷贝原来内存中的数据到新内存中,然后将新内存关联到相应的memory mapping,然后执行写操作。Linux下很多功能都依赖于copy on write技术来提高性能,比如fork等。
通过上面的介绍,我们可以简单的将内存的使用过程总结如下:
当然缺页中断不是每次都会发生,只有系统觉得有必要延迟分配内存的时候才用的着,也即很多时候在上面的第3步系统会分配真正的物理内存并和memory mapping关联。
操作系统只要实现了虚拟内存和物理内存之间的映射关系,就能正常工作了,但要使内存访问更高效,还有很多东西需要考虑,在这里我们可以看看跟内存有关的一些其它概念以及它们的作用。
MMU是CPU的一个用来将进程的虚拟地址转换成物理地址的模块,简单点说,这个模块的输入是进程的page table和虚拟地址,输出是物理地址。将虚拟地址转换成物理地址的速度直接影响着系统的速度,所以CPU包含了这个模块用来加速。
上面介绍到,MMU的输入是page table,而page table又存在内存里面,跟CPU的cache相比,内存的速度很慢,所以为了进一步加快虚拟地址到物理地址的转换速度,Linux发明了TLB,它存在于CPU的L1 cache里面,用来缓存已经找到的虚拟地址到物理地址的映射,这样下次转换前先查一下TLB,如果已经在里面了就不需要调用MMU了.
由于实际情况下物理内存要比虚拟内存少很多,所以操作系统必须很小心的分配物理内存,以使内存的使用率达到最大化。一个节约物理内存的办法就是只加载当前正在使用的虚拟page对应的数据到内存。比如,一个很大的数据库程序,如果你只是用了查询操作,那么负责插入删除等部分的代码段就没必要加载到内存中,这样就能节约很多物理内存,这种方法就叫做物理内存页按需分配,也可以称作延时加载。
其实现原理很简单,就是当CPU访问一个虚拟内存页的时候,如果这个虚拟内存页对应的数据还没加载到物理内存中,则CPU就会通知操作系统发生了page fault,然后由操作系统负责将数据加载进物理内存。由于将数据加载进内存比较耗时,所以CPU不会等在那里,而是去调度其它进程,当它下次再调度到该进程时,数据已经在物理内存上了。
Linux主要使用这种方式来加载可执行文件和动态库,当程序被内核开始调度执行时,内核将进程的可执行文件和动态库映射到进程的虚拟地址空间,并只加载马上要用到的那小部分数据到物理内存中,其它的部分只有当CPU访问到它们时才去加载。
当一个进程需要加载数据到物理内存中,但实际的物理内存已经被用完时,操作系统需要回收一些物理内存中的page以满足当前进程的需要。
对于file backed的内存数据,即物理内存里面的数据来自于磁盘上的文件,那么内核将直接将该部分数据从内存中移除掉来释放出更多的内存,当下次有进程需要访问这部分数据时,再将它从磁盘上加载到内存中来。但是,如果这部分数据被修改过且没被写入文件,那这部分数据就变成了脏数据,脏数据不能被直接删掉,只能被移动到交换空间上去。(可执行文件和动态库文件不会被修改,但通过mmap+private的方式映射到内存的磁盘文件有可能被修改,这种方式映射的内存比较特殊,没修改之前是file backed,修改后但没有写回磁盘之前就变成了anonymous的)
对于anonymous的内存数据,在磁盘上没有对应的文件,这部分数据不能直接被删除,而是被系统移到交换空间上去。交换空间就是磁盘上预留的一块特殊空间,被系统用来临时存放内存中不常被访问的数据,当下次有进程需要访问交换空间上的数据时,系统再将数据加载到内存中。由于交换空间在磁盘上,所以访问速度要比内存慢很多,频繁的读写交换空间会带来性能问题。
关于swap空间的详细介绍请参考Linux交换空间
有了虚拟内存之后,进程间共享内存变得特别的方便。进程所有的内存访问都通过虚拟地址来实现,而每个进程都有自己的page tables。当两个进程共享一块物理内存时,只要将物理内存的页号映射到两个进程的page table中就可以了,这样两个进程就可以通过不同的虚拟地址来访问同一块物理内存。
从上面的那个图中可以看出,进程X和进程Y共享了物理内存页PFN3,在进程X中,PFN3被映射到了VPFN3,而在进程Y中,PFN3被映射到了VPFN1,但两个进程通过不同的虚拟地址访问到的物理内存是同一块。
page table里面的每条虚拟内存到物理内存的映射记录(memory mapping)都包含一份控制信息,当进程要访问一块虚拟内存时,系统可以根据这份控制信息来检查当前的操作是否是合法的。
为什么需要做这个检查呢?比如有些内存里面放的是程序的可执行代码,那么就不应该去修改它;有些内存里面存放的是程序运行时用到的数据,那么这部分内存只能被读写,不应该被执行;有些内存里面存放的是内核的代码,那么在用户态就不应该去执行它;有了这些检查之后会大大增强系统的安全性。
由于CPU的cache有限,所以TLB里面缓存的数据也有限,而采用了huge page后,由于每页的内存变大(比如由原来的4K变成了4M),虽然TLB里面的纪录数没变,但这些纪录所能覆盖的地址空间变大,相当于同样大小的TLB里面能缓存的映射范围变大,从而减少了调用MMU的次数,加快了虚拟地址到物理地址的转换速度。
为了提高系统性能,Linux使用了一些跟内存管理相关的cache,并且尽量将空闲的内存用于这些cache。这些cache都是系统全局共享的:
从上面的定义可以看出,page cache和buffer cache有重叠的地方,不过实际情况是buffer cache只缓存page cache不缓存的那部分内容,比如磁盘上文件的元数据。所以一般情况下和page cache相比,Buffer Cache的大小基本可以忽略不计。
当然,使用cache也有一些不好的地方,比如需要时间和空间去维护cache,cache一旦出错,整个系统就挂了。
有了上面介绍的知识,再来看看我们刚开始提出来的问题,以top命令的输出为例:
xxxxxxxxxx21KiB Mem : 500192 total, 349264 free, 36328 used, 114600 buff/cache2KiB Swap: 524284 total, 524284 free, 0 used. 433732 avail MemKiB Mem代表物理内存,KiB Swap代表交换空间,它们的单位都是KiB。
total、used和free没什么好介绍的,就是总共多少,然后用了多少,还剩多少。
buff/cached代表了buff和cache总共用了多少,buff代表buffer cache占了多少空间,由于它主要用来缓存磁盘上文件的元数据,所以一般都比较小,跟cache比可以忽略不计;cache代表page cache和其它一些占用空间比较小且大小比较固定的cache的总和,基本上cache就约等于page cache,page cache的准确值可以通过查看/proc/meminf中的Cached得到。由于page cache是用来缓存磁盘上文件内容的,所以占有空间很大,Linux一般会尽可能多的将空闲物理内存用于page cache。
avail Mem表示可用于进程下一次分配的物理内存数量,这个大小一般比free大一点,因为除了free的空间外,系统还能立即释放出一些空间来。
那么怎么判断当前内存使用情况出现了异常呢?有下面几点供参考:
查看全部内存信息 cat /proc/meminfo
查看进程地址空间信息
cat /proc/进程ip/maps
proc查看进程状态
cat /proc/进程ip/status
使用pmap -d选项
是一个进程获取信息时,获取指向自身进程id的指针,/proc/pid/,而无须查询。
xxxxxxxxxx561[root@666 ~]# cat /proc/14224/status2Name: nginxfrontend3Umask: 00004State: S (sleeping)5Tgid: 142246Ngid: 07Pid: 142248PPid: 19TracerPid: 010Uid: 0 0 0 011Gid: 0 0 0 012FDSize: 6413Groups:14NStgid: 1422415NSpid: 1422416NSpgid: 1422417NSsid: 1422418VmPeak: 379908 kB19VmSize: 246984 kB20VmLck: 0 kB21VmPin: 0 kB22VmHWM: 16672 kB23VmRSS: 15544 kB24RssAnon: 8556 kB25RssFile: 6976 kB26RssShmem: 12 kB27VmData: 9556 kB28VmStk: 132 kB29VmExe: 4500 kB30VmLib: 13608 kB31VmPTE: 264 kB32VmSwap: 0 kB33HugetlbPages: 0 kB34CoreDumping: 035THP_enabled: 136Threads: 137SigQ: 1/785938SigPnd: 000000000000000039ShdPnd: 000000000000000040SigBlk: 000000000000000041SigIgn: 000000004000100042SigCgt: 000000019c016a0743CapInh: 000000000000000044CapPrm: 000000ffffffffff45CapEff: 000000ffffffffff46CapBnd: 000000ffffffffff47CapAmb: 000000000000000048NoNewPrivs: 049Seccomp: 050Speculation_Store_Bypass: thread vulnerable51Cpus_allowed: 152Cpus_allowed_list: 053Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,0000000154Mems_allowed_list: 055voluntary_ctxt_switches: 856nonvoluntary_ctxt_switches: 3
[root@localhost 1]# cat /proc/4668/status Name: gam_server State: S (sleeping) SleepAVG: 88% Tgid: 31999 Pid: 31999 PPid: 1 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 256 Groups: 0 1 2 3 4 6 10 VmSize: 2136 kB VmLck: 0 kB VmRSS: 920 kB VmData: 148 kB VmStk: 88 kB VmExe: 44 kB VmLib: 1820 kB VmPTE: 20 kB Threads: 1 SigQ: 1/2047 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000000001006 SigCgt: 0000000210000800 CapInh: 0000000000000000 CapPrm: 00000000fffffeff CapEff: 00000000fffffeff
** cat /proc/4743/statm **611450 185001 883 18 0 593431 0
- size :- total program size (611450 X 4096/1024 = 2445800kB = 2388M)
- resident :- resident set size (185001 X 4096/1024 = 740004kB = 722M)
- share :- shared pages (883 X 4096 = 3532)
- trs :- text (code) (18 X 4096/1024 = 72kB = VmExe )
- drs :- data/stack
- lrs :- library (593431 X 4096/1024 = 2373724kB = VmData +VmStk)
- dt :- dirty pages
*补充 by wylhistory:*
*对于信号那几个段:*
*SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000000001006 SigCgt: 0000000210000800*
*其中前32位分别表示普通的信号,比如:*
*SigBlk: 0000000000002000 这里表示SIGALRM已经被阻塞了,*
*因为SIGALRM的值就是14;*
*而里面的Siglgn:1006就表示:*
*类型为2,3,13的信号被忽略了,*
*分别是:SIGINT,SIGQUIT,SIGPIPE,*
*记得从1开始而不是0,因为信号没有类型为0的信号;*
*后32位的一般不用管为实时信号;*
#分析实例
检查mysqld进程情况
ipcns,mntns,netns,pidns,userns,和utsns每种namespace均有对应的唯一的nid,相互之间不同。
xxxxxxxxxx101[root@666 ~]# yum provides lsns2Loaded plugins: fastestmirror, getpagespeed3Loading mirror speeds from cached hostfile4util-linux-2.23.2-65.el7.i686 : A collection of basic system utilities5Repo : base6Matched from:7Filename : /usr/bin/lsns8910
lsns -- this NEW COMMAND lists information about all currently accessible namespaces or about the given namespace.
引 https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.28/v2.28-ReleaseNotes
xxxxxxxxxx231[root@666 ~]# lsns2NS TYPE NPROCS PID USER COMMAND34026531836 pid 152 1 root /usr/lib/systemd/systemd --system --deserialize 1744026531837 user 153 1 root /usr/lib/systemd/systemd --system --deserialize 1754026531838 uts 152 1 root /usr/lib/systemd/systemd --system --deserialize 1764026531839 ipc 152 1 root /usr/lib/systemd/systemd --system --deserialize 1774026531840 mnt 96 1 root /usr/lib/systemd/systemd --system --deserialize 1784026531860 mnt 1 14 root kdevtmpfs94026531992 net 152 1 root /usr/lib/systemd/systemd --system --deserialize 17104026532211 mnt 1 554 chrony /usr/sbin/chronyd114026532212 mnt 2 577 root /usr/sbin/NetworkManager --no-daemon124026532216 mnt 52 20093 root php-fpm: master process (/etc/php-fpm.conf)134026532224 mnt 1 31192 root /usr/sbin/sshd -D -e144026532225 uts 1 31192 root /usr/sbin/sshd -D -e154026532226 ipc 1 31192 root /usr/sbin/sshd -D -e164026532227 pid 1 31192 root /usr/sbin/sshd -D -e174026532229 net 1 31192 root /usr/sbin/sshd -D -e18[root@666 ~]#1920# NS namespace ID21# TYPE Namespace Type22# NPROCS Namespace Process ID23#
xxxxxxxxxx321# 查看进程的命名空间id23[root@666 ~]# ls -alh /proc/self/ns4total 05dr-x--x--x 2 root root 0 Sep 17 09:19 .6dr-xr-xr-x 9 root root 0 Sep 17 09:19 ..7lrwxrwxrwx 1 root root 0 Sep 17 09:19 cgroup -> cgroup:[4026531835]8lrwxrwxrwx 1 root root 0 Sep 17 09:19 ipc -> ipc:[4026531839]9lrwxrwxrwx 1 root root 0 Sep 17 09:19 mnt -> mnt:[4026531840]10lrwxrwxrwx 1 root root 0 Sep 17 09:19 net -> net:[4026531992]11lrwxrwxrwx 1 root root 0 Sep 17 09:19 pid -> pid:[4026531836]12lrwxrwxrwx 1 root root 0 Sep 17 09:19 pid_for_children -> pid:[4026531836]13lrwxrwxrwx 1 root root 0 Sep 17 09:19 time -> time:[4026531834]14lrwxrwxrwx 1 root root 0 Sep 17 09:19 time_for_children -> time:[4026531834]15lrwxrwxrwx 1 root root 0 Sep 17 09:19 user -> user:[4026531837]16lrwxrwxrwx 1 root root 0 Sep 17 09:19 uts -> uts:[4026531838]17[root@666 ~]# ls -alh /proc/$$/ns18total 019dr-x--x--x 2 root root 0 Sep 17 09:11 .20dr-xr-xr-x 9 root root 0 Sep 17 09:11 ..21lrwxrwxrwx 1 root root 0 Sep 17 09:20 cgroup -> cgroup:[4026531835]22lrwxrwxrwx 1 root root 0 Sep 17 09:20 ipc -> ipc:[4026531839]23lrwxrwxrwx 1 root root 0 Sep 17 09:20 mnt -> mnt:[4026531840]24lrwxrwxrwx 1 root root 0 Sep 17 09:11 net -> net:[4026531992]25lrwxrwxrwx 1 root root 0 Sep 17 09:20 pid -> pid:[4026531836]26lrwxrwxrwx 1 root root 0 Sep 17 09:20 pid_for_children -> pid:[4026531836]27lrwxrwxrwx 1 root root 0 Sep 17 09:20 time -> time:[4026531834]28lrwxrwxrwx 1 root root 0 Sep 17 09:20 time_for_children -> time:[4026531834]29lrwxrwxrwx 1 root root 0 Sep 17 09:20 user -> user:[4026531837]30lrwxrwxrwx 1 root root 0 Sep 17 09:20 uts -> uts:[4026531838]31[root@666 ~]#32
ps现在具有用于与过程相关联的不同类型的命名空间的输出选择:ipcns,mntns,netns,pidns,userns,和utsns。对于这个问题,相关的是PID名称空间或pidns。
因此,如果您想找出PID命名空间ID,例如pid 459:
xxxxxxxxxx21# ps -h -o pidns -p 45924026532661
xxxxxxxxxx11121 Executable programs or shell commands32 System calls (functions provided by the kernel)43 Library calls (functions within program libraries)54 Special files (usually found in /dev)65 File formats and conventions eg /etc/passwd76 Games87 Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7)98 System administration commands (usually only for root)109 Kernel routines [Non standard]11
unshare - disassociate parts of the process execution context
unshare() allows a process (or thread) to disassociate parts of its execution context that are currently being shared with other processes (or threads). Part of the execution context, such as the mount namespace, is shared implicitly when a new process is created using fork(2) or vfork(2), while other parts, such as virtual memory, may be shared by explicit request when creating a process or thread using clone(2).
unshare()函数方法用于对已分配与其它进程共享的执行上下文进行解除关联。上下文包括,通过fork,vfork创建的mount 命名空间,及通过明确要求的虚拟内存。
https://man7.org/linux/man-pages/man2/unshare.2.html
fork - create a child process
派生--创建一个子进程。
fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.
通过复制调用进程来创建一个新的进程。
新的进程一般称作子进程。
调用进程一般称作父进程。