Linux | 堆内存管理

image

from pixiv

进程的地址空间

如何查看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

image

在状态机(进程)状态上增加/删除/修改一段可访问的内存:

image

实验分析

#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(sudo strace -p $PID)和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;
}
# main thread before malloc
41981:   ./test2
000062d6816fa000      4K r---- test2
000062d6816fb000      4K r-x-- test2
000062d6816fc000      4K r---- test2
000062d6816fd000      4K r---- test2
000062d6816fe000      4K rw--- test2
000062d6b5e8e000    132K rw---   [ anon ]
00007d6087800000    160K r---- libc.so.6
00007d6087828000   1620K r-x-- libc.so.6
00007d60879bd000    352K r---- libc.so.6
00007d6087a15000      4K ----- libc.so.6
00007d6087a16000     16K r---- libc.so.6
00007d6087a1a000      8K rw--- libc.so.6
00007d6087a1c000     52K rw---   [ anon ]
00007d6087aaf000     12K rw---   [ anon ]
00007d6087ac6000      8K rw---   [ anon ]
00007d6087ac8000      8K r---- ld-linux-x86-64.so.2
00007d6087aca000    168K r-x-- ld-linux-x86-64.so.2
00007d6087af4000     44K r---- ld-linux-x86-64.so.2
00007d6087b00000      8K r---- ld-linux-x86-64.so.2
00007d6087b02000      8K rw--- ld-linux-x86-64.so.2
00007ffc168e3000    136K rw---   [ stack ]
00007ffc1697e000     16K r----   [ anon ]
00007ffc16982000      8K r-x--   [ anon ]
ffffffffff600000      4K --x--   [ anon ]
 total             2784K

# main thread after malloc
41981:   ./test2
000062d6816fa000      4K r---- test2
000062d6816fb000      4K r-x-- test2
000062d6816fc000      4K r---- test2
000062d6816fd000      4K r---- test2
000062d6816fe000      4K rw--- test2
000062d6b5e8e000    132K rw---   [ anon ]
00007d6087800000    160K r---- libc.so.6
00007d6087828000   1620K r-x-- libc.so.6
00007d60879bd000    352K r---- libc.so.6
00007d6087a15000      4K ----- libc.so.6
00007d6087a16000     16K r---- libc.so.6
00007d6087a1a000      8K rw--- libc.so.6
00007d6087a1c000     52K rw---   [ anon ]
00007d6087aaf000     12K rw---   [ anon ]
00007d6087ac6000      8K rw---   [ anon ]
00007d6087ac8000      8K r---- ld-linux-x86-64.so.2
00007d6087aca000    168K r-x-- ld-linux-x86-64.so.2
00007d6087af4000     44K r---- ld-linux-x86-64.so.2
00007d6087b00000      8K r---- ld-linux-x86-64.so.2
00007d6087b02000      8K rw--- ld-linux-x86-64.so.2
00007ffc168e3000    136K rw---   [ stack ]
00007ffc1697e000     16K r----   [ anon ]
00007ffc16982000      8K r-x--   [ anon ]
ffffffffff600000      4K --x--   [ anon ]
 total             2784K

这里主进程只进行了小额malloc分配, 在malloc前后实际上pmap的输出结果是一样的。

我们初始就有132K的堆,这 132KB 的堆空间叫做arena,此时因为是主线程分配的,所以叫做 main arena(每个 arena 中含有多个 chunk,这些 chunk 以链表的形式加以组织)。

由于 132KB 比 1000 bytes 大很多,所以主线程后续再申请堆空间的话,就会先从这 132KB 的剩余部分中申请,直到用完或不够用的时候,再通过增加 program break location 的方式来增加 main arena 的大小。

使用strace跟踪这段代码,也会发现并没有sbrkmmap之类的系统调用发生。


之后子进程被创建了,在这个过程中strace捕捉到了:mmap(NULL, 8392704, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7d6086e00000

pmap输出为:

41981:   ./test2
000062d6816fa000      4K r---- test2
000062d6816fb000      4K r-x-- test2
000062d6816fc000      4K r---- test2
000062d6816fd000      4K r---- test2
000062d6816fe000      4K rw--- test2
000062d6b5e8e000    132K rw---   [ anon ]
00007d6086e00000      4K -----   [ anon ]
00007d6086e01000   8192K rw---   [ anon ]
00007d6087800000    160K r---- libc.so.6
00007d6087828000   1620K r-x-- libc.so.6
00007d60879bd000    352K r---- libc.so.6
00007d6087a15000      4K ----- libc.so.6
00007d6087a16000     16K r---- libc.so.6
00007d6087a1a000      8K rw--- libc.so.6
00007d6087a1c000     52K rw---   [ anon ]
00007d6087aaf000     12K rw---   [ anon ]
00007d6087ac6000      8K rw---   [ anon ]
00007d6087ac8000      8K r---- ld-linux-x86-64.so.2
00007d6087aca000    168K r-x-- ld-linux-x86-64.so.2
00007d6087af4000     44K r---- ld-linux-x86-64.so.2
00007d6087b00000      8K r---- ld-linux-x86-64.so.2
00007d6087b02000      8K rw--- ld-linux-x86-64.so.2
00007ffc168e3000    136K rw---   [ stack ]
00007ffc1697e000     16K r----   [ anon ]
00007ffc16982000      8K r-x--   [ anon ]
ffffffffff600000      4K --x--   [ anon ]
 total            10980K

注意这段内容:

00007d6086e00000      4K -----   [ anon ]
00007d6086e01000   8192K rw---   [ anon ]

地址在Memory Map Segment,且大小为\(8192 * 1024 + 4 * 1024 = 8392704\) ,这段空间正是子进程的空间!Linux 子线程的地址空间是由 mmap 创建的

在子进程malloc之后:

41981:   ./test2
000062d6816fa000      4K r---- test2
000062d6816fb000      4K r-x-- test2
000062d6816fc000      4K r---- test2
000062d6816fd000      4K r---- test2
000062d6816fe000      4K rw--- test2
000062d6b5e8e000    132K rw---   [ anon ]
00007d6080000000    132K rw---   [ anon ]
00007d6080021000  65404K -----   [ anon ]
00007d6086e00000      4K -----   [ anon ]
00007d6086e01000   8192K rw---   [ anon ]
00007d6087800000    160K r---- libc.so.6
00007d6087828000   1620K r-x-- libc.so.6
00007d60879bd000    352K r---- libc.so.6
00007d6087a15000      4K ----- libc.so.6
00007d6087a16000     16K r---- libc.so.6
00007d6087a1a000      8K rw--- libc.so.6
00007d6087a1c000     52K rw---   [ anon ]
00007d6087aaf000     12K rw---   [ anon ]
00007d6087ac6000      8K rw---   [ anon ]
00007d6087ac8000      8K r---- ld-linux-x86-64.so.2
00007d6087aca000    168K r-x-- ld-linux-x86-64.so.2
00007d6087af4000     44K r---- ld-linux-x86-64.so.2
00007d6087b00000      8K r---- ld-linux-x86-64.so.2
00007d6087b02000      8K rw--- ld-linux-x86-64.so.2
00007ffc168e3000    136K rw---   [ stack ]
00007ffc1697e000     16K r----   [ anon ]
00007ffc16982000      8K r-x--   [ anon ]
ffffffffff600000      4K --x--   [ anon ]
 total            76516K

变化主要在:

00007d6080000000    132K rw---   [ anon ]
00007d6080021000  65404K -----   [ anon ]
00007d6086e00000      4K -----   [ anon ]
00007d6086e01000   8192K rw---   [ anon ]
posted @ 2025-03-15 15:23  次林梦叶  阅读(58)  评论(0)    收藏  举报