在Linux中理解进程内存映射

5
我将尝试解释Linux进程内存布局的基本知识,并提供以下程序作为参考:

我正在尝试理解Linux进程内存布局的基础知识,我得到了这个程序:

#include <stdio.h> // standard io
#include <stdlib.h> // C standard library
#include <pthread.h> // threading
#include <unistd.h> // unix standard library
#include <sys/types.h> // system types for linux

// getchar basically is like "read"
// it prompts the user for input
// in this case, the input is thrown away
// which makes similar to a "pause" continuation primitive 
// but a pause that is resolved through user input, which we promptly throw away!
void * thread_func (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 () {

    char * addr;
    printf("Welcome to per thread arena example::%d\n", getpid());
    printf("Before malloc in the 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();

    // pointer to the thread 1
    pthread_t thread_1;
    // pthread_* functions return 0 upon succeeding, and other numbers upon failing
    int pthread_status;

    pthread_status = pthread_create(&thread_1, NULL, thread_func, NULL);

    if (pthread_status != 0) {
        printf("Thread creation error\n");
        return -1;
    }

    // returned status code from thread_1
    void * thread_1_status;

    pthread_status = pthread_join(thread_1, &thread_1_status);

    if (pthread_status != 0) {
        printf("Thread join error\n");
        return -1;
    }

    return 0;
}

当我启动程序时,/proc/<pid>/maps 中的内容如下:

00400000-00401000 r-xp 00000000 08:01 1323314                            /home/oscp/xg/c/memory_layout/a.out
00600000-00601000 r--p 00000000 08:01 1323314                            /home/oscp/xg/c/memory_layout/a.out
00601000-00602000 rw-p 00001000 08:01 1323314                            /home/oscp/xg/c/memory_layout/a.out
7fcc372d7000-7fcc37491000 r-xp 00000000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37491000-7fcc37691000 ---p 001ba000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37691000-7fcc37695000 r--p 001ba000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37695000-7fcc37697000 rw-p 001be000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37697000-7fcc3769c000 rw-p 00000000 00:00 0 
7fcc3769c000-7fcc376b5000 r-xp 00000000 08:01 1053877                    /lib/x86_64-linux-gnu/libpthread-2.19.so
7fcc376b5000-7fcc378b4000 ---p 00019000 08:01 1053877                    /lib/x86_64-linux-gnu/libpthread-2.19.so
7fcc378b4000-7fcc378b5000 r--p 00018000 08:01 1053877                    /lib/x86_64-linux-gnu/libpthread-2.19.so
7fcc378b5000-7fcc378b6000 rw-p 00019000 08:01 1053877                    /lib/x86_64-linux-gnu/libpthread-2.19.so
7fcc378b6000-7fcc378ba000 rw-p 00000000 00:00 0 
7fcc378ba000-7fcc378dd000 r-xp 00000000 08:01 1053733                    /lib/x86_64-linux-gnu/ld-2.19.so
7fcc37abe000-7fcc37ac1000 rw-p 00000000 00:00 0 
7fcc37ad8000-7fcc37adc000 rw-p 00000000 00:00 0 
7fcc37adc000-7fcc37add000 r--p 00022000 08:01 1053733                    /lib/x86_64-linux-gnu/ld-2.19.so
7fcc37add000-7fcc37ade000 rw-p 00023000 08:01 1053733                    /lib/x86_64-linux-gnu/ld-2.19.so
7fcc37ade000-7fcc37adf000 rw-p 00000000 00:00 0 
7ffdc1cff000-7ffdc1d20000 rw-p 00000000 00:00 0                          [stack]
7ffdc1dd8000-7ffdc1ddb000 r--p 00000000 00:00 0                          [vvar]
7ffdc1ddb000-7ffdc1ddd000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

这些内存区域的目的是什么?
7fcc37491000-7fcc37691000 ---p 001ba000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
...
7fcc37abe000-7fcc37ac1000 rw-p 00000000 00:00 0 
7fcc37ad8000-7fcc37adc000 rw-p 00000000 00:00 0 

然后我在运行程序后按下几次回车键。当它打印出 "Before malloc in thread 1" 后,内存布局如下:
00400000-00401000 r-xp 00000000 08:01 1323314                            /home/oscp/xg/c/memory_layout/a.out
00600000-00601000 r--p 00000000 08:01 1323314                            /home/oscp/xg/c/memory_layout/a.out
00601000-00602000 rw-p 00001000 08:01 1323314                            /home/oscp/xg/c/memory_layout/a.out
00632000-00653000 rw-p 00000000 00:00 0                                  [heap]
7fcc36ad6000-7fcc36ad7000 ---p 00000000 00:00 0 
7fcc36ad7000-7fcc372d7000 rw-p 00000000 00:00 0 
7fcc372d7000-7fcc37491000 r-xp 00000000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37491000-7fcc37691000 ---p 001ba000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37691000-7fcc37695000 r--p 001ba000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37695000-7fcc37697000 rw-p 001be000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37697000-7fcc3769c000 rw-p 00000000 00:00 0 
7fcc3769c000-7fcc376b5000 r-xp 00000000 08:01 1053877                    /lib/x86_64-linux-gnu/libpthread-2.19.so
7fcc376b5000-7fcc378b4000 ---p 00019000 08:01 1053877                    /lib/x86_64-linux-gnu/libpthread-2.19.so
7fcc378b4000-7fcc378b5000 r--p 00018000 08:01 1053877                    /lib/x86_64-linux-gnu/libpthread-2.19.so
7fcc378b5000-7fcc378b6000 rw-p 00019000 08:01 1053877                    /lib/x86_64-linux-gnu/libpthread-2.19.so
7fcc378b6000-7fcc378ba000 rw-p 00000000 00:00 0 
7fcc378ba000-7fcc378dd000 r-xp 00000000 08:01 1053733                    /lib/x86_64-linux-gnu/ld-2.19.so
7fcc37abe000-7fcc37ac1000 rw-p 00000000 00:00 0 
7fcc37ad8000-7fcc37adc000 rw-p 00000000 00:00 0 
7fcc37adc000-7fcc37add000 r--p 00022000 08:01 1053733                    /lib/x86_64-linux-gnu/ld-2.19.so
7fcc37add000-7fcc37ade000 rw-p 00023000 08:01 1053733                    /lib/x86_64-linux-gnu/ld-2.19.so
7fcc37ade000-7fcc37adf000 rw-p 00000000 00:00 0 
7ffdc1cff000-7ffdc1d20000 rw-p 00000000 00:00 0                          [stack]
7ffdc1dd8000-7ffdc1ddb000 r--p 00000000 00:00 0                          [vvar]
7ffdc1ddb000-7ffdc1ddd000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

这两个区域有什么目的?
7fcc36ad6000-7fcc36ad7000 ---p 00000000 00:00 0 
7fcc36ad7000-7fcc372d7000 rw-p 00000000 00:00 0 

在打印出“在线程1中进行malloc和free之后”的内容之后,它会创建另外两个区域:

7fcc30000000-7fcc30021000 rw-p 00000000 00:00 0 
7fcc30021000-7fcc34000000 ---p 00000000 00:00 0 

这两个区域的目的是什么?

没有权限的映射可能是某种“守卫”页面,和/或为未来增长保留虚拟地址空间(以防止mmap(NULL, ...)随机选择它)。 - Peter Cordes
1个回答

10
你的问题涉及到许多完全不同的事情,因此答案会很长。 第一个问题是指:
7fcc37491000-7fcc37691000 ---p 001ba000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so

7fcc372d7000-7fcc37491000 r-xp 00000000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37491000-7fcc37691000 ---p 001ba000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37691000-7fcc37695000 r--p 001ba000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so
7fcc37695000-7fcc37697000 rw-p 001be000 08:01 1053757                    /lib/x86_64-linux-gnu/libc-2.19.so

这个不可访问的内存区域是库相邻ELF段之间的间隙(该库应占用连续内存块)。---p保护模式禁止将此间隙用于偶发内存分配。如果在库加载进程时使用strace(1),您可能会看到类似以下内容:

mmap(NULL, 1848896, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3</usr/lib/libc-2.28.so>, 0) = 0x7f9673d8f000
mprotect(0x7f9673db1000, 1671168, PROT_NONE) = 0
mmap(0x7f9673db1000, 1355776, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3</usr/lib/libc-2.28.so>, 0x22000) = 0x7f9673db1000
mmap(0x7f9673efc000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3</usr/lib/libc-2.28.so>, 0x16d000) = 0x7f9673efc000
mmap(0x7f9673f49000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3</usr/lib/libc-2.28.so>, 0x1b9000) = 0x7f9673f49000

第一个mmap()将第一个ELF段映射到内存中,但保留了整个库的空间。这样做是为了允许内核自行选择库的位置。为了保护任何可能存在的段间隙,调用mprotect(..., PROT_NONE); 然后使用mmap()将所有剩余的段映射到内存中-这还将适当内存页面的保护模式从---p更改为所需的模式。您可以通过查看实际工作原理来轻松了解详情。如果您想验证此加载过程中如何形成---p间隙,还可以使用readelf(1)与库的二进制文件并执行一些十六进制算法,将段的位置和对齐进行比较,并将结果与strace输出进行对照。 第二个问题涉及以下匿名映射:
7fcc36ad6000-7fcc36ad7000 ---p 00000000 00:00 0 
7fcc36ad7000-7fcc372d7000 rw-p 00000000 00:00 0 

这看起来像是 线程1 的线程堆栈。第二个映射是堆栈本身(372d7000 - 36ad7000 = 800000 = 8 MiB,在许多发行版中都是默认的堆栈大小限制,这也是 pthread 的默认堆栈大小),而第一个映射是堆栈警戒页。这个带有 ---p 模式的页面保护堆栈不会溢出,并在溢出时触发段错误(因为写入此受保护页会导致溢出)。
注:在旧的Linux内核中,线程堆栈在 maps 文件中用 [stack:TID] 名称进行标注,但这个特性已被删除,所以我无法保证该映射确实是线程堆栈(虽然看起来像)。然而,您可以使用 straceclone() 系统调用的 child_stack 参数中找到精确的线程堆栈位置并将其与此映射进行比较。
继续说下去,第三个问题 是什么?
7fcc30000000-7fcc30021000 rw-p 00000000 00:00 0 
7fcc30021000-7fcc34000000 ---p 00000000 00:00 0 

好的,这是线程1中的malloc()用来分配您请求的内存的方式。简单地说,整个区域7fcc30000000-7fcc34000000是一个堆,从中进行分配。 从该堆分配的rw-p间隔7fcc30000000-7fcc30021000,将随着使用malloc()请求越来越多的内存而增长。 当此堆耗尽时,将使用mmap()请求新的堆。

正如您可能已经注意到的那样,我没有对您问题中的以下映射进行解释:

7fcc37abe000-7fcc37ac1000 rw-p 00000000 00:00 0 
7fcc37ad8000-7fcc37adc000 rw-p 00000000 00:00 0 

我无法快速识别这些人,并且不确定这些是普通的分配。可能需要进行单独的调查,因为这个话题已经太长了。


可能是TLS吧? - o11c
还有几个类似于这两个的小匿名映射,因此我无法证明它们每一个的目的,并说“这个绝对是TLS”。最好使用调试器单独检查每个映射以确保(使用“catch syscall mmap”,然后查看调用者)。 - Danila Kiver
关于第三个问题的答案,如果您在主线程中看到了malloc,您会发现malloc没有生成受保护的区域。也就是说,在打印“After malloc and before free in main thread”之前,请按“Enter”。为什么会这样? - drdot
1
这里的主线程使用了标记为[heap]的区域。您可以进行1000次100k块的malloc(),并且您将看到该区域正在增长。由于历史原因,此区域有点特殊,并且不是通过mmap()系统调用调整大小,而是通过brk()调整大小。您可能会对阅读一些关于glibc的malloc()如何工作的内容感兴趣,例如https://sourceware.org/glibc/wiki/MallocInternals。 - Danila Kiver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接