为什么这个非常小的C程序会占用这么多内存?
上面的程序(使用gnu g++编译,没有额外的编译器标志)只会永远循环,似乎占用了比它应该使用的更多的内存(顶部的输出如下所示): 我明白为什么CPU使用率是100%,因为它在while循环中不断旋转。为什么VIRT的容量是4MB?为什么SHR的存储空间为728KB?我没有使用图书馆。最后,也是最重要的一点,为什么使用788KB只存储一个变量?剩余(4376-788)KB存储/使用在哪里/如何使用 为什么VIRT的容量是4MB?为什么SHR的存储空间为728kB?我没有使用图书馆 这是不准确的 让我们编译:为什么这个非常小的C程序会占用这么多内存?,c,memory,C,Memory,上面的程序(使用gnu g++编译,没有额外的编译器标志)只会永远循环,似乎占用了比它应该使用的更多的内存(顶部的输出如下所示): 我明白为什么CPU使用率是100%,因为它在while循环中不断旋转。为什么VIRT的容量是4MB?为什么SHR的存储空间为728KB?我没有使用图书馆。最后,也是最重要的一点,为什么使用788KB只存储一个变量?剩余(4376-788)KB存储/使用在哪里/如何使用 为什么VIRT的容量是4MB?为什么SHR的存储空间为728kB?我没有使用图书馆 这是不准确的
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9392 root 20 0 4376 788 728 R 100.0 0.0 2:32.65 a.out
(如果在pmap-x
输出中看到重复的行,则在旧版本中这是一个错误)
因此,它使用
-C标准库libc.so
-动态链接器ld.so
- 920kB是可执行文件(4kB)和共享库(带有
模式的页面)的可执行代码r-x--
- 12kB是堆栈(标记为
)[stack]
- 标记为
且带有[anon]
模式的页面是可执行库和共享库的rw---
部分的堆和0初始化数据,具有静态存储持续时间.bss
- 其余为只读和非0初始化数据,静态存储持续时间来自可执行库和共享库的
和.data
(.rodata
模式)部分r--
main
之前运行的代码,我必须将我的程序命名为“main function”\u start
。编辑如下:
$ cat tiny.s
.text
.globl _start
.type _start,@function
_start:
pause
jmp _start
.size _start, .-_start
.section .note.GNU-stack,"",@progbits
-nostlib
关闭大部分C库,-nostartfiles
关闭在main
之前运行的代码,-static
因此它甚至不会拉入动态链接器,-Wl,--build id=none
抑制使可执行文件在磁盘上显著增大的注释。这就是我们得到的:
$ gcc -nostdlib -nostartfiles -static -Wl,--build-id=none -o tiny tiny.s
这仍然分配156kB的地址空间和4kB的实际RAM。为了更详细地了解这个空间将要做什么,我们可以查看流程的/proc//maps
:(注意,您系统上的输出可能会略有不同)
有五种虚拟内存分配,每行的前两个数字分别是它们的起始地址和结束地址。可执行文件的第一个0x1000字节(4kB)已映射为只读,可执行文件的第二个4kB已映射为read execute。(是的,这意味着文件比它的内存映射要短。内核将用零来填补空白。)然后我们有了
0x7fff5c9d4000− 0x7fff5c9b3000=132kB分配给堆栈,12kB分配给“vvar”,4kB分配给“vdso”。8+132+12+4=156kB
这里可以看到一个有趣的事实:top
的RES只统计已提交到当前进程的页面。在本例中,这是堆栈分配的一页。来自可执行文件的8kB映射不被计算在内,因为它们是只读的、可共享的和可丢弃的——如果有许多进程运行同一个程序,它们将在物理RAM中共享相同的程序代码副本,并且如果内核需要将这些页从RAM中踢出,以便为其他内容腾出空间,它不必将它们写入交换文件。(顶部的手册页对RES的描述有所不同,但据我所知,这是错误的。)
“vvar”和“vdso”映射分别是由内核提供给Linux上所有用户空间进程的小数据和代码。它们用于一些低级技巧,比如在不将CPU切换到内核模式的情况下执行gettimeofday
。这就减少了几千个周期的开销,这对精确计时很重要。据我所知,没有办法关掉这些
您可以使用ulimit
命令减小堆栈分配的大小。例如,ulimit-s4
将其削减到绝对最小值4kB。如果我像这样运行我的程序
$ cat /proc/12514/maps
000000400000-000000401000 r--p 00000000 fd:01 26477890 /home/zack/tiny
000000401000-000000402000 r-xp 00001000 fd:01 26477890 /home/zack/tiny
7fff5c9b3000-7fff5c9d4000 rw-p 00000000 00:00 0 [stack]
7fff5c9fb000-7fff5c9fe000 r--p 00000000 00:00 0 [vvar]
7fff5c9fe000-7fff5c9ff000 r-xp 00000000 00:00 0 [vdso]
然后top
报告
$ (unset $(printenv | cut -d= -f1); ulimit -s 4; exec ./tiny)
而/proc/14293/maps
中的堆栈
行
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
14293 zack 20 0 28 4 0 R 100.0 0.0 0:06.02 tiny
但是如果没有初始的unset
命令(清除所有环境变量),程序在启动时崩溃:
7ffdf80e5000-7ffdf80e6000 rw-p 00000000 00:00 0 [stack]
这是因为内核在启动程序运行之前,会将一组数据写入堆栈分配中——命令行参数向量、所有环境变量和。如果我不清除环境变量,数据将占用超过4kB的空间,程序将崩溃。我打赌您不知道在execve
系统调用中可能触发segfault
1正确的二进制千字节,即:1kB≝ 1024字节。不要听任何人说不同的话,即使是国际度量衡局也不行。这不是一个真正的C问题,更像是一个平台/操作系统问题,但我猜这里有一个4MB的堆栈。你可能得到了整个(共享)C库。“我没有使用库。”这是一种错觉。你的p
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12154 zack 20 0 156 4 0 R 100.0 0.0 0:16.51 a.out
$ cat /proc/12514/maps
000000400000-000000401000 r--p 00000000 fd:01 26477890 /home/zack/tiny
000000401000-000000402000 r-xp 00001000 fd:01 26477890 /home/zack/tiny
7fff5c9b3000-7fff5c9d4000 rw-p 00000000 00:00 0 [stack]
7fff5c9fb000-7fff5c9fe000 r--p 00000000 00:00 0 [vvar]
7fff5c9fe000-7fff5c9ff000 r-xp 00000000 00:00 0 [vdso]
$ (unset $(printenv | cut -d= -f1); ulimit -s 4; exec ./tiny)
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
14293 zack 20 0 28 4 0 R 100.0 0.0 0:06.02 tiny
7ffdf80e5000-7ffdf80e6000 rw-p 00000000 00:00 0 [stack]
$ (ulimit -s 4; exec ./tiny)
Segmentation fault