进程的地址空间
- jyy 进程的地址空间
- Linux 堆内存管理深入分析
如何查看Linux进程的地址空间?
答:
- pmap
- /proc/$PID/maps
/proc
文件系统
动态内核信息: /proc 是一个虚拟文件系统,主要提供内核和正在运行的进程的信息。它不是存储在磁盘上的真实文件,而是在运行时动态生成的。
- 进程信息: 每个正在运行的进程在 /proc 中都有一个以其 PID 命名的目录,里面包含了该进程的状态、内存、文件描述符等详细信息。
- 系统参数: /proc 中包含许多系统运行时参数,例如 /proc/cpuinfo(CPU 信息)、/proc/meminfo(内存信息)、/proc/uptime(系统运行时间)等。
pmap 是一个用于显示进程内存映射情况的工具,pmap 的实现主要依赖于 Linux 内核提供的/proc 文件系统
包括读取读取/proc/<pid>/maps
或读取 /proc/<pid>/smaps
(包含更详细的内存统计,该文件提供了每个内存区域的内存使用情况,包括私有内存、共享内存、脏页、换页信息等。)
pmap $PID
输出案例
33498: ./test
000059cef6580000 4K r---- test
000059cef6581000 4K r-x-- test
000059cef6582000 4K r---- test
000059cef6583000 4K r---- test
000059cef6584000 4K rw--- test
000059cf32a73000 132K rw--- [ anon ]
0000734aa0e00000 102404K rw--- [ anon ]
0000734aa7400000 160K r---- libc.so.6
0000734aa7428000 1620K r-x-- libc.so.6
0000734aa75bd000 352K r---- libc.so.6
0000734aa7615000 4K ----- libc.so.6
0000734aa7616000 16K r---- libc.so.6
0000734aa761a000 8K rw--- libc.so.6
0000734aa761c000 52K rw--- [ anon ]
0000734aa7652000 12K rw--- [ anon ]
0000734aa7669000 8K rw--- [ anon ]
0000734aa766b000 8K r---- ld-linux-x86-64.so.2
0000734aa766d000 168K r-x-- ld-linux-x86-64.so.2
0000734aa7697000 44K r---- ld-linux-x86-64.so.2
0000734aa76a3000 8K r---- ld-linux-x86-64.so.2
0000734aa76a5000 8K rw--- ld-linux-x86-64.so.2
00007ffdb8f1e000 136K rw--- [ stack ]
00007ffdb8ffa000 16K r---- [ anon ]
00007ffdb8ffe000 8K r-x-- [ anon ]
ffffffffff600000 4K --x-- [ anon ]total 105188K
当然可以直接cat /proc/$PID/maps
,会看到一些更原始的输出:
59cef6580000-59cef6581000 r--p 00000000 08:03 1835085 /home/cilinmengye/tmp/test
59cef6581000-59cef6582000 r-xp 00001000 08:03 1835085 /home/cilinmengye/tmp/test
59cef6582000-59cef6583000 r--p 00002000 08:03 1835085 /home/cilinmengye/tmp/test
59cef6583000-59cef6584000 r--p 00002000 08:03 1835085 /home/cilinmengye/tmp/test
59cef6584000-59cef6585000 rw-p 00003000 08:03 1835085 /home/cilinmengye/tmp/test
59cf32a73000-59cf32a94000 rw-p 00000000 00:00 0 [heap]
734aa0e00000-734aa7201000 rw-p 00000000 00:00 0
734aa7400000-734aa7428000 r--p 00000000 08:03 395403 /usr/lib/x86_64-linux-gnu/libc.so.6
734aa7428000-734aa75bd000 r-xp 00028000 08:03 395403 /usr/lib/x86_64-linux-gnu/libc.so.6
734aa75bd000-734aa7615000 r--p 001bd000 08:03 395403 /usr/lib/x86_64-linux-gnu/libc.so.6
734aa7615000-734aa7616000 ---p 00215000 08:03 395403 /usr/lib/x86_64-linux-gnu/libc.so.6
734aa7616000-734aa761a000 r--p 00215000 08:03 395403 /usr/lib/x86_64-linux-gnu/libc.so.6
734aa761a000-734aa761c000 rw-p 00219000 08:03 395403 /usr/lib/x86_64-linux-gnu/libc.so.6
734aa761c000-734aa7629000 rw-p 00000000 00:00 0
734aa7652000-734aa7655000 rw-p 00000000 00:00 0
734aa7669000-734aa766b000 rw-p 00000000 00:00 0
734aa766b000-734aa766d000 r--p 00000000 08:03 395381 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
734aa766d000-734aa7697000 r-xp 00002000 08:03 395381 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
734aa7697000-734aa76a2000 r--p 0002c000 08:03 395381 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
734aa76a3000-734aa76a5000 r--p 00037000 08:03 395381 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
734aa76a5000-734aa76a7000 rw-p 00039000 08:03 395381 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffdb8f1e000-7ffdb8f40000 rw-p 00000000 00:00 0 [stack]
7ffdb8ffa000-7ffdb8ffe000 r--p 00000000 00:00 0 [vvar]
7ffdb8ffe000-7ffdb9000000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
每列从左到右的含义为:
- 地址范围
- 权限标识:权限部分有四个字符,典型的形式如 r--p、r-xp、rw-p:
- r:可读(readable)。
- w:可写(writable)。
- x:可执行(executable)。
- -:相应的权限缺失。
- p 表示私有(private,即写时复制),s 表示共享。
- 偏移量:紧接在权限后面的字段(例如 00000000、00001000)表示该段内存在对应映射文件中的偏移量(以 16 进制计)。
比如59cef6580000-59cef6581000
这段虚拟内存空间的内容来源于(或者说对应于映射文件)``/home/cilinmengye/tmp/test+偏移量
00000000`开始 - 设备号:类似 08:03 的字段表示设备的主设备号和次设备号。
- inode 号:例如 1835094 表示文件系统中该映射文件的 inode 编号。inode 为 0 表示这段映射不是由文件提供的(如匿名映射、堆、栈)。
- 映射文件或标签:
- 如果是某个文件(如 /home/cilinmengye/tmp/test2、/usr/lib/x86_64-linux-gnu/libc.so.6 等),说明该内存区域映射自该文件。
- [heap]、[stack]、[vdso]、[vvar]、[vsyscall] 等标识显示特殊用途的内存区域。
- [ anon ] 表示匿名映射,即不是直接从磁盘文件映射而来的内存区域。
如何管理Linux进制的地址空间
malloc()
/free()
库函数, 它们的作用是在用户程序的堆区中申请/释放一块内存区域. 堆区的使用情况是由libc来进行管理的, 但堆区的大小却需要通过系统调用向操作系统提出更改.
调整堆区大小是通过sbrk()
库函数来实现的, 它的原型是:
void* sbrk(intptr_t increment);
用于将用户程序的program break增长increment
字节, 其中increment
可为负数.
所谓program break, 就是用户程序的数据段(data segment)结束的位置. 我们知道可执行文件里面有代码段和数据段, 链接的时候ld
会默认添加一个名为_end
的符号, 来指示程序的数据段结束的位置. 用户程序开始运行的时候, program break会位于_end
所指示的位置, 意味着此时堆区的大小为0.
malloc()
被第一次调用的时候, 会通过sbrk(0)
来查询用户程序当前program break的位置, 之后就可以通过后续的sbrk()
调用来动态调整用户程序program break的位置了.
当前program break和和其初始值之间的区间就可以作为用户程序的堆区, 由malloc()
/free()
进行管理. 注意用户程序不应该直接使用sbrk()
, 否则将会扰乱malloc()
/free()
对堆区的管理记录.
mmap
现代 libc(例如 glibc)的 malloc 在分配大块内存时,会调用mmap
,而不是依赖于 sbrk 来扩展堆。此时分配到的内存区域会显示为匿名映射([ anon ]
),而不会标记为 “[ heap ]”, 此时分配的内存空间在Memory Map Segment
在状态机(进程)状态上增加/删除/修改一段可访问的内存:
实验分析
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {printf("Press Enter to allocate memory...\n");getchar();void *heap_mem = malloc(100 * 1024 * 1024); // 分配 100MB 内存if (!heap_mem) {perror("malloc failed");return 1;}printf("Allocated 100MB on heap. Press Enter to exit...\n");getchar();free(heap_mem);return 0;
}
运行上述代码,使用strace和pmap分析在malloc之前和之后进程的地址空间变化:
运行malloc
之前:
4414: ./test
0000564aa69b2000 4K r---- test
0000564aa69b3000 4K r-x-- test
0000564aa69b4000 4K r---- test
0000564aa69b5000 4K r---- test
0000564aa69b6000 4K rw--- test
0000564aaa120000 132K rw--- [ anon ]
000076a64e200000 160K r---- libc.so.6
000076a64e228000 1620K r-x-- libc.so.6
000076a64e3bd000 352K r---- libc.so.6
000076a64e415000 4K ----- libc.so.6
000076a64e416000 16K r---- libc.so.6
000076a64e41a000 8K rw--- libc.so.6
000076a64e41c000 52K rw--- [ anon ]
000076a64e4dd000 12K rw--- [ anon ]
000076a64e4f4000 8K rw--- [ anon ]
000076a64e4f6000 8K r---- ld-linux-x86-64.so.2
000076a64e4f8000 168K r-x-- ld-linux-x86-64.so.2
000076a64e522000 44K r---- ld-linux-x86-64.so.2
000076a64e52e000 8K r---- ld-linux-x86-64.so.2
000076a64e530000 8K rw--- ld-linux-x86-64.so.2
00007ffe93cac000 136K rw--- [ stack ]
00007ffe93dc1000 16K r---- [ anon ]
00007ffe93dc5000 8K r-x-- [ anon ]
ffffffffff600000 4K --x-- [ anon ]total 2784K
可以看到初始给我们分配了132K的堆,000076a64e200000~000076a64e530000
为Memory Map Segment
堆和.bss之间是有空隙的, 可见他们的地址分别为0000564aaa120000
, 0000564aa69b6000
运行malloc
之后:
# strace 输出
mmap(NULL, 104861696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7c0f54e00000
...
munmap(0x7c0f54e00000, 104861696) = 0
exit_group(0) = ?# pmap输出
0000564aa69b2000 4K r---- test
0000564aa69b3000 4K r-x-- test
0000564aa69b4000 4K r---- test
0000564aa69b5000 4K r---- test
0000564aa69b6000 4K rw--- test
0000564aaa120000 132K rw--- [ anon ]
000076a647c00000 102404K rw--- [ anon ]
000076a64e200000 160K r---- libc.so.6
000076a64e228000 1620K r-x-- libc.so.6
000076a64e3bd000 352K r---- libc.so.6
000076a64e415000 4K ----- libc.so.6
000076a64e416000 16K r---- libc.so.6
000076a64e41a000 8K rw--- libc.so.6
000076a64e41c000 52K rw--- [ anon ]
000076a64e4dd000 12K rw--- [ anon ]
000076a64e4f4000 8K rw--- [ anon ]
000076a64e4f6000 8K r---- ld-linux-x86-64.so.2
000076a64e4f8000 168K r-x-- ld-linux-x86-64.so.2
000076a64e522000 44K r---- ld-linux-x86-64.so.2
000076a64e52e000 8K r---- ld-linux-x86-64.so.2
000076a64e530000 8K rw--- ld-linux-x86-64.so.2
00007ffe93cac000 136K rw--- [ stack ]
00007ffe93dc1000 16K r---- [ anon ]
00007ffe93dc5000 8K r-x-- [ anon ]
ffffffffff600000 4K --x-- [ anon ]total 105188K
注意:malloc 在分配大块内存时,会调用mmap
,且分配的内存空间在Memory Map Segment。正如上述000076a647c00000 102404K rw--- [ anon ]
子进程的地址空间在哪?
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>void* threadFunc(void *arg)
{printf("Before malloc in thread 1\n");getchar();char* addr = (char*)malloc(1000);printf("After malloc and before free in thread 1\n");getchar();free(addr);printf("After free in thread 1\n");getchar();
}int main()
{pthread_t t1;void *s;int ret;char* addr;printf("Welcome to per thread arena example: %d\n", getpid());printf("Before malloc in main thread\n");getchar();addr = (char*) malloc(1000);printf("After malloc and before free in main thread\n");getchar();free(addr);printf("After free in main thread\n");getchar();ret = pthread_create(&t1, NULL, threadFunc, NULL);if (ret){printf("Thread creation error\n");return -1;}ret = pthread_join(t1, &s);if (ret){printf("THread join error\n");return -1;}return 0;
}