C语言静态链接下内存使用异常问题排查
实验背景
我正在学习静态链接与动态链接,了解到静态链接相比动态链接会占用更多内存与磁盘空间,磁盘占用的差异已通过可执行文件大小验证,但在验证内存占用时出现异常,因此设计了如下实验:
实验步骤
- 创建名为mylib.c的C文件
//mylib.c #include <unistd.h> // 该函数用于生成庞大的.text段 void many_code() { asm volatile ( "movabs $0x1122334455667788, %%rax \n\t" "movabs $0x1122334455667788, %%rax \n\t" "movabs $0x1122334455667788, %%rax \n\t" // 该行重复一百万次 ... ::: "rax" ); sleep(-1); }
编译目标文件
执行命令:gcc -c mylib.c -o mylib.o创建静态库
执行命令:ar -r libmy.a mylib.o创建名为use_st_lib.c的C文件
// use_st_lib.c extern void many_code(); int main() { many_code(); return 0; }
生成静态链接可执行文件
执行命令:gcc use_st_lib.c -static libmy.a -o use_st_lib_0生成第二个静态链接可执行文件
重复步骤5,生成use_st_lib_1
执行ls -hl的结果:
➜ static git:(master) ✗ ls -hl total 91M -rw-r--r-- 1 root root 9.6M May 29 09:37 libmy.a -rw-r--r-- 1 root root 51M May 27 23:33 mylib.c -rw-r--r-- 1 root root 9.6M May 29 09:37 mylib.o -rwxr-xr-x 1 root root 11M May 29 10:11 use_st_lib_0 -rwxr-xr-x 1 root root 11M May 29 10:12 use_st_lib_1 -rw-r--r-- 1 root root 76 May 27 22:10 use_st_lib.c
- 监控内存使用
在另一个终端执行top命令,设置刷新时间1.0s,过滤COMMAND=use_st_lib,初始输出:
top - 10:21:59 up 15 days, 17:40, 4 users, load average: 0.00, 0.01, 0.05 Tasks: 79 total, 1 running, 78 sleeping, 0 stopped, 0 zombie %Cpu(s): 1.0 us, 1.0 sy, 0.0 ni, 98.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 1756.5 total, 793.8 free, 133.2 used, 829.5 buff/cache MiB Swap: 0.0 total, 0.0 free, 0.0 used. 1469.8 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
- 启动第一个进程
执行./use_st_lib_0 &后台启动进程,此时top输出:
top - 10:31:06 up 15 days, 17:49, 4 users, load average: 0.07, 0.06, 0.06 Tasks: 80 total, 1 running, 79 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 1756.5 total, 793.6 free, 133.1 used, 829.8 buff/cache MiB Swap: 0.0 total, 0.0 free, 0.0 used. 1469.9 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 31239 root 25 5 10.6m 9.5m 9.5m S 0.0 0.5 0:00.00 use_st_lib_0
- 启动第二个进程
执行./use_st_lib_1 &后台启动第二个进程,此时top输出:
top - 10:32:02 up 15 days, 17:50, 4 users, load average: 0.03, 0.05, 0.05 Tasks: 81 total, 1 running, 80 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 1.0 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 1756.5 total, 793.3 free, 133.4 used, 829.9 buff/cache MiB Swap: 0.0 total, 0.0 free, 0.0 used. 1469.6 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 31309 root 25 5 10.6m 9.5m 9.5m S 0.0 0.5 0:00.00 use_st_lib_1 31239 root 25 5 10.6m 9.5m 9.5m S 0.0 0.5 0:00.00 use_st_lib_0
疑问列表
- 为何静态链接的程序在top命令中显示有SHR(共享内存)占用?我原本预期该数值为0。
- 启动use_st_lib_0后,RES(驻留内存)显示占用9.5m,但top顶部的总内存使用量未如预期从133.2变为123.7。
- 启动use_st_lib_1后,top顶部的总内存使用量仍几乎无变化,与预期从123.7变为114.2不符。
- 我的实验步骤存在哪些错误,或是对静态链接内存占用的理解有偏差?
问题解答
1. 静态链接程序为何有SHR占用?
静态链接的可执行文件并非完全没有共享内存。Linux内核会共享程序中只读且可执行的段(比如.text段),即使是静态链接的程序,只要多个进程的.text段内容完全一致(比如你生成的use_st_lib_0和use_st_lib_1),内核就会通过页表映射让它们共享物理内存页,避免重复加载。此外,静态链接的程序也可能依赖内核提供的vDSO(虚拟动态共享对象,用于快速系统调用),这部分也会被计入SHR。
2. RES显示占用9.5m但总内存使用无明显变化?
top顶部的used内存统计包含buff/cache,而进程的RES是实际驻留在物理内存中的字节数,但其中可能有部分属于共享页(比如.text段),这部分内存会被多个进程重复统计,但实际物理内存只占用一份。另外,你的进程启动后调用sleep(-1)进入休眠,内核可能会将部分不常用的内存页置换到缓存区,而不是立即从free内存中扣除,导致top顶部的used数值变化不明显。可以查看/proc/<pid>/smaps文件,其中的Private_Dirty字段才是进程独占的物理内存,更能反映进程实际占用的非共享内存。
3. 启动第二个进程后总内存使用无变化?
因为两个静态链接的程序.text段完全一致,内核会让它们共享这部分物理内存,不需要重新加载一份到内存中。只有进程的私有数据段(比如.data、.bss)才会占用新的物理内存,但你的程序几乎没有私有数据,所以总内存使用量几乎不变。这也解释了为什么两个进程的SHR都等于RES——它们的所有驻留内存都是共享的只读段。
4. 实验步骤与理解偏差
- 理解偏差:静态链接的程序并非所有内存都不共享,只读可执行段会被内核自动共享,这是Linux内存管理的优化机制,目的是减少物理内存浪费。你之前认为静态链接程序完全不共享内存是错误的。
- 实验设计问题:你的程序中几乎没有私有可写数据段(.data/.bss),所有大内存都是只读的.text段,导致多个进程可以完全共享这部分内存,无法体现静态链接在私有内存占用上的劣势。如果要验证静态链接的内存占用问题,应该设计包含大量私有可写数据的实验,比如在静态库中定义一个大的全局数组并初始化,这样每个进程都会拥有自己的副本,此时总内存使用量才会随着进程数量增加而线性增长。
内容的提问来源于stack exchange,提问作者Michael Coleman

