Linux用掉的物理内存哪里去了

本文详细分析了Linux系统中内存的总体使用情况,包括已使用的物理内存、Slab、PageTables、内存文件系统、共享内存、用户进程的数据段和文本段。通过逐项统计并对比分析,揭示了内存消耗的热点,并提供了优化建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    内存是计算机系统的重要资源,在嵌入式系统中尤为明显。上层应用的程序员会接触到内存的使用,最常见的是堆栈。堆和栈仅仅是内存使用中的一部分,还有许多平常不太多见的部分占用着物理内存。在系统优化维护中,需要关注内存资源的整体使用情况以及大致的划分。在Linux系统中,通过free命令得到的Used内存被消耗在哪里了。下面我们逐步分析,Used内存被谁Used。为Linux系统维护和优化提供正确的指导。下面以我们的一个系统环境为例,分解统计过程。

总使用内存=Slab+PageTables+内存文件系统+SHM+用户进程的数据段+用户进程的文本段

(1)获取已使用物理内存值

~ # echo 3 > /proc/sys/vm/drop_caches
cat /proc/meminfo
~ # cat /proc/meminfo
MemTotal:        1763344 kB
MemFree:          946048 kB

执行之前先清理一下缓存,避免buffer和cache带来的误差。因为buffer和cache中并不全是正在使用的,没在使用的部分需要释放。

总使用内存=MemTotal-MemFree=817296kB

(2)获取Slab,PageTables的值

同(1),在cat /proc/meminfo中可以获取到它们的值。

Slab:              35616 kB
PageTables:         2672 kB
(3)获取内存文件系统占用的空间大小

在统计中,我们把共享内存部分独立统计,实际上它体现在内存文件系统的/dev/shm中。这里需要注意,不要重复统计。另外,挂载上来的外设空间也需要去除掉。

du -s 	/
du -s 	/RAMDISK
du -s 	/datadisk0
du -s 	/etc
du -s 	/init
du -s 	/lib32
du -s 	/linuxrc
du -s 	/mnt
du -s 	/proc
du -s 	/sbin
du -s 	/sysdisk0
du -s 	/usr
du -s 	/version
du -s 	/bin
du -s 	/dev
du -s 	/dev/shm
du -s 	/home
du -s 	/lib
du -s 	/lib64
du -s 	/lxImage
du -s 	/null
du -s 	/root
du -s 	/sys
du -s 	/tmp
du -s 	/var
结果如下:

675764	/            根目录
0	/RAMDISK     
168	/datadisk0   挂载第二块flash: /dev/ubi1_0
416	/etc
0	/init
18240	/lib32
0	/linuxrc
0	/mnt
0	/proc
15808	/sbin
494620	/sysdisk0    挂载第一块flash: /dev/ubi0_0
18912	/usr
0	/version
6096	/bin
94144	/dev         其中包括了挂载的tmpfs,对应/dev/shm共享内存
94128	/dev/shm     共享内存
209648	/home        重定向的目录/sysdisk0/home
5216	/lib
20512	/lib64
1568	/lxImage     BSP工具包
0	/null
16	/root
0	/sys
0	/tmp
48	/var
统计方法是:内存文件系统占用大小 = / - /datadisk0 - sysdisk0 - /dev/shm = 86848kB

(4)统计共享内存的大小

在上一步中已经有了统计结果:

94128	/dev/shm     共享内存

(5)统计用户进程的数据段占用内存大小

通过统计所有用户进程的smaps中的rss来获取用户进程数据段实际使用的物理内存大小。这里面不包括共享内存部分。因此统计的是smaps中的私有rss数据段。

用一个脚本来执行这段计算,得到的结果如下:

325,344
41,584
42,800
18,928
9,120
9,344
9,568
4,288
5,808
5,616
4,272
3,536
3,184
3,344
3,168
3,040
2,944
2,544
2,448
总和为500880kB

(6)统计用户进程的文本段占用内存大小

许多系统都会使用到动态库,动态库的大小通过rss统计不准确。好在较新的linux中提供了pss的统计值,看看pss和rss的对比区别。pss中把共享那部分按照引用次数平均分配到各进程中,这样统计出来就不会有重复了,呵呵!赞

RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS- Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
RSS is the total memory actually held in RAM for a process.RSS can be misleading, because it reports the total all of the shared libraries that the process uses, even though a shared library is only loaded into memory once regardless of how many processes use it. RSS is not an accurate representation of the memory usage for a single process.
PSS differs from RSS in that it reports the proportional size of its shared libraries, i.e. if three processes all use a shared library that has 30 pages, that library will only contribute 10 pages to the PSS that is reported for each of the three processes. PSS is a very useful number because when the PSS for all processes in the system are summed together, that is a good representation for the total memory usage in the system. When a process is killed, the shared libraries that contributed to its PSS will be proportionally distributed to the PSS totals for the remaining processes still using that library. In this way PSS can be slightly misleading, because when a process is killed, PSS does not accurately represent the memory returned to the overall system.

我们通过脚本对smaps中的文本段pss进行统计,结果如下:

6,501
10,027
10,782
14,614
4,975
7,779
2,914
1,347
5,584
4,072
1,376
988
715
663
748
952
1,387
526
438
总和为76388kB

汇总+思考:

汇总:35616 + 2672 + 86848 + 94128 + 500880 + 76388 = 796532. ( ⊙ o ⊙ )咦!? 咋比总使用内存817296kB还少了20764kB。想想是为什么呢?可以对比ps列出的下面的进程列表,分析一下以上统计中遗漏了什么,作为一个思考题  :)

/ # ps
PID   USER     TIME   COMMAND
    1 root       0:02 init
    2 root       0:00 [kthreadd]
    3 root       0:00 [migration/0]
    4 root       0:00 [ksoftirqd/0]
    5 root       0:00 [watchdog/0]
    6 root       0:00 [migration/1]
    7 root       0:00 [ksoftirqd/1]
    8 root       0:00 [watchdog/1]
    9 root       0:00 [migration/2]
   10 root       0:00 [ksoftirqd/2]
   11 root       0:00 [watchdog/2]
   12 root       0:00 [migration/3]
   13 root       0:00 [ksoftirqd/3]
   14 root       0:00 [watchdog/3]
   16 root       0:02 [events/0]
   17 root       0:00 [events/1]
   18 root       0:00 [events/2]
   19 root       0:00 [events/3]
   20 root       0:00 [rt_events/0]
   21 root       0:00 [rt_events/1]
   22 root       0:00 [rt_events/2]
   23 root       0:00 [rt_events/3]
   24 root       0:00 [khelper]
   27 root       0:00 [async/mgr]
  103 root       0:00 [sync_supers]
  105 root       0:00 [bdi-default]
  107 root       0:00 [kblockd/0]
  108 root       0:00 [kblockd/1]
  109 root       0:00 [kblockd/2]
  110 root       0:00 [kblockd/3]
  119 root       0:00 [khubd]
  165 root       0:00 [rpciod/0]
  166 root       0:00 [rpciod/1]
  167 root       0:00 [rpciod/2]
  168 root       0:00 [rpciod/3]
  179 root       0:00 [cerrhelper]
  180 root       0:00 [ecc_bg_thread]
  181 root       0:00 [nmi_daemon/0]
  182 root       0:01 [nmi_daemon/1]
  183 root       0:01 [nmi_daemon/2]
  184 root       0:01 [nmi_daemon/3]
  189 root       0:00 [khungtaskd]
  190 root       0:00 [kswapd0]
  191 root       0:00 [aio/0]
  192 root       0:00 [aio/1]
  193 root       0:00 [aio/2]
  194 root       0:00 [aio/3]
  195 root       0:00 [nfsiod]
  196 root       0:00 [crypto/0]
  197 root       0:00 [crypto/1]
  198 root       0:00 [crypto/2]
  199 root       0:00 [crypto/3]
  789 root       0:00 [mtdblockd]
  835 root       0:00 {rcS} /bin/sh /etc/init.d/rcS
  840 root       0:00 /sbin/udevd --daemon
 1476 root       0:00 telnetd
 1481 root       0:00 /sbin/udevd --daemon
 1520 root       0:00 /sbin/udevd --daemon
 1524 root       0:10 [ubi_bgt0d]
 1528 root       0:01 [ubifs_bgt0_0]
 1532 root       0:00 [ubi_bgt1d]
 1536 root       0:00 [ubifs_bgt1_0]
 1541 root       0:00 syslogd -l 8
 1546 root       0:00 {bspAppS.sh} /bin/sh /etc/init.d/bspAppS.sh
 1549 root       0:00 {admin.sh} /bin/sh /etc/init.d/admin.sh
 1569 daemon     0:00 portmap
 1571 root       0:00 rpc.mountd -d all
 1573 root       0:00 [lockd]
 1574 root       0:00 [nfsd4]
 1575 root       0:00 [nfsd]
 1576 root       7:27 ******  
 1641 root       0:56 ******
 1642 root       1:35 ******
 2047 root       0:40 ******
 2048 root       0:14 ******
 2049 root       0:18 ******
 2050 root       0:09 ******
 2051 root       0:34 ******
 2052 root       0:17 ******
 2053 root       2:26 ******
 2102 root       5:57 ******
 2138 root       4:02 ******
 2150 root     5295:2 ******
 2169 root       0:16 ******
 2170 root       0:41 ******
 2171 root       0:44 ******
 2250 root       0:12 ******
 2302 root      29:45 ******
 2303 root       0:14 ******
 2379 zte        0:00 -sh
 2380 root       0:00 sh
 2553 root       0:01 sh
 2775 root       0:00 ps
注:******这些是我的业务进程的名字,因为是敏感信息,所以用******代替。

总结:

通过以上的统计分析,能够总体上观察到已经使用的物理内存哪里去了。从而找到内存消耗的热点,进而找到优化点。以上的每一步都可以进一步细化统计分析,进一步减小包围圈,定位到具体的内存占用者。呵呵,再交流。

### Linux 物理内存管理机制及分配原理 #### 三级标题:物理内存的划分与管理 在 Linux 系统中,物理内存被划分为大小为 4KB 的页面(Page),这些页面被称为页框(Page Frame)[^2]。这种设计使得内存的分配和释放都基于页面进行操作,从而提高了内存使用的效率。 为了更好地管理和利用有限的物理内存资源,Linux 提供了一种复杂的内存管理系统。该系统不仅负责将物理内存映射到进程的虚拟地址空间,还涉及到了诸如分页、交换以及缓存等技术的应用。具体来说,在用户态下运行的应用程序无法直接访问物理地址,而是通过虚拟地址来间接完成这一过程[^3]。 #### 三级标题:虚拟地址与物理地址之间的转换 由于硬件层面的原因,现代计算机架构普遍采用了 MMU (Memory Management Unit) 来支持操作系统中的虚拟存储概念。当一个应用程序试图读取某个特定位置的数据时,它实际上给出的是一个逻辑上的偏移量——即所谓的“虚拟地址”。随后经过一系列计算之后才会得到真正的目标所在之处——也就是我们所说的“物理地址”。 在这个过程中最重要的一步就是由 CPU 和其配套组件共同协作完成从虚拟地址向对应的实际物理地址变换的任务。这通常涉及到查找多级页表结构并最终定位所需数据所在的精确位置[^1]。 #### 三级标题:内存分配的具体流程 对于像 `malloc` 这样的库函数而言,它们的工作方式依赖于底层的操作系统的配合。以最常见的 GNU C Library 实现为例: - 当请求较小块连续区域时(小于等于128字节),会尝试从未初始化堆部分或者已存在空闲链表里寻找合适片段满足需求; - 如果上述方法均不可行,则调用系统调用 brk 或者 mmap 请求更多可用资源扩展当前进程可支配范围;在此期间可能还会伴随着零填充处理步骤确保新获取来的这部分干净无污染。 整个分配过程中还需要考虑诸多因素比如对齐约束条件等等细节问题才能保证高效稳定运作的同时兼顾安全性方面的要求。 ```c #include <stdlib.h> void* my_malloc(size_t size){ void *ptr; ptr = sbrk(0); /* Get current program break */ if ((sbrk(size)) == (void*) -1){/* Try to increment the program break by 'size' bytes*/ return NULL; /* If failed, return null pointer */ } memset(ptr, 0 ,size); /* Zero out allocated memory */ return ptr; /* Return new address of heap */ } ``` 以上是一个简化版的手动实现 malloc 函数的例子,展示了如何使用系统调用来增加进程的堆大小,并返回一块新的内存给调用方。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值