Linux内核逻辑地址空间组织

Linux内核逻辑地址空间组织

根据“编写伟大的代码”,几乎所有操作系统运行时内存都被组织成以下区域:

操作系统 |堆栈|堆|文字|静态|存储/BSS

[以增加地址方式]

用户空间进程为其不同类型的数据对象使用更高的内存区域。

内核空间进程也有不同类型的数据对象。这些对象是否共享用户空间内存区域(堆栈、堆等),或者它们在操作系统区域中是否有自己单独的子部分(堆、堆栈等)。如果是,它们的排列顺序是什么。谢谢,

答案1

关于顺序是错误的。操作系统位于内存顶部,在 32 位内核中通常位于 3 GB 标记( 0xC0000000 )之上,在 64 位内核中它是 0x8000000000000000 IIRC 的中间点。

栈和堆的位置是随机的。主程序中的 text/data/bss 段的顺序没有真正的规则,并且每个动态库都有自己的一组这些段,因此其中许多段分散在内存中。

当恐龙统治地球时(20多年前),程序地址空间是线性的(没有漏洞),顺序是文本、数据、bss、堆栈、堆。那时也没有动态库或线程。这一切都随着虚拟内存而改变。

内核进程完全包含在地址空间的内核部分内;用户部分被忽略。这允许内核加速内核线程之间的上下文切换,因为它不必更新页表,因为所有进程共享页表的相同内核部分。

答案2

“几乎所有操作系统”的情况并非如此。所表示的内存区域的类型相当典型,但没有理由说明它们应该采用任何特定的顺序,并且给定类型可以有多个内存区域。

在Linux 下,您可以通过进程ID查看进程的地址空间cat /proc/$pid/maps,例如查看正在运行的shell,或者查看进程自己的映射。命令$pidcat /proc/$$/mapscatcat /proc/self/mapscatpmap产生稍微好一点的输出。

08048000-08054000 r-xp 00000000 08:01 828061     /bin/cat
08054000-08055000 r--p 0000b000 08:01 828061     /bin/cat
08055000-08056000 rw-p 0000c000 08:01 828061     /bin/cat
08c7f000-08ca0000 rw-p 00000000 00:00 0          [heap]
b755a000-b7599000 r--p 00000000 08:01 273200     /usr/lib/locale/en_US.utf8/LC_CTYPE
b7599000-b759a000 rw-p 00000000 00:00 0 
b759a000-b76ed000 r-xp 00000000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76ed000-b76ee000 ---p 00153000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76ee000-b76f0000 r--p 00153000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76f0000-b76f1000 rw-p 00155000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76f1000-b76f4000 rw-p 00000000 00:00 0 
b770b000-b7712000 r--s 00000000 08:01 271618     /usr/lib/gconv/gconv-modules.cache
b7712000-b7714000 rw-p 00000000 00:00 0 
b7714000-b7715000 r-xp 00000000 00:00 0          [vdso]
b7715000-b7730000 r-xp 00000000 08:01 263049     /lib/ld-2.11.1.so
b7730000-b7731000 r--p 0001a000 08:01 263049     /lib/ld-2.11.1.so
b7731000-b7732000 rw-p 0001b000 08:01 263049     /lib/ld-2.11.1.so
bfbec000-bfc01000 rw-p 00000000 00:00 0          [stack]

您可以从可执行文件中看到代码和读写数据(文本和BSS),然后是堆,然后是内存映射文件,然后是更多的读写数据,然后是代码,只读数据和只读数据从共享库(再次是文本和 BSS)写入数据,更多读写数据,另一个共享库(更准确地说,动态链接器),最后是唯一线程的堆栈。

内核代码使用自己的地址范围。在许多平台上,Linux 将地址空间的上部部分用于内核,通常是上部 1GB。理想情况下,这个空间足以映射内核代码、内核数据、系统内存 (RAM) 和每个内存映射设备。在当今典型的 32 位 PC 上,这是不可能的,这需要进行只有内核黑客才感兴趣的扭曲。

当内核代码处理系统调用时,理想情况下(当上述扭曲未到位时)进程的内存映射到相同的地址。这允许进程将数据传递给内核,并且内核可以直接从指针读取。不过,这并不是一个很大的收获,因为无论如何都需要验证指针(这样进程就不能欺骗内核从进程不应该访问的内存中读取数据)。

Linux 内核空间内的内存区域相当复杂。有几种不同的内存池,主要区别不在于内存来自哪里,而在于与谁共享内存。如果您对它们感到好奇,请从LDD3

答案3

不是答案,而是需要更多空间的仅供参考。

我认为您的逻辑地址布局概念根本不正确。

您可以编译并运行该程序来查看用户态进程的地址:

#include <stdio.h>
long global_initialized = 119234;
long global_uninitialized;
extern int _end, _edata, _etext;
int
main(int ac, char **av)
{
        long local;

        printf("main at 0x%lx\n", main);
        printf("ac at   0x%lx\n", &ac);
        printf("av at   0x%lx\n", &av);
        printf("av has  0x%lx\n", av);
        printf("initialized global at 0x%lx\n", &global_initialized);
        printf("global at             0x%lx\n", &global_uninitialized);
        printf("local at              0x%lx\n", &local);
        printf("_end at               0x%lx\n", &_end);
        printf("_edata at             0x%lx\n", &_edata);
        printf("_etext at             0x%lx\n", &_etext);
        return 0;
}

我运行的 Red Hat Enterprise Server 有readelf,它可以用来表示内核将(逻辑上)加载可执行文件的位置:

readelf -S where

向我显示了许多与 的输出where给出的相同的寻址信息。

我认为readelf在 Linux 内核(/boot/vmlinuz 或类似的内核)上工作并不容易,并且我认为内核默认在其自己的地址空间中从 0x80000000 开始:它没有映射到用户态进程中,尽管使用了用户态堆栈顶部上方的地址位于 0x7fffffff(x86,32 位寻址)。

相关内容