Answers:
订购是错误的。操作系统位于内存顶部,通常在32位内核中位于3 GB标记(0xC0000000)之上,而在64位内核中,它是0IRC8000000000 IIRC的中点。
堆栈和堆的位置是随机的。关于主程序中text / data / bss段的顺序没有真正的规则,每个动态库都有它们自己的一组,因此其中许多分散在整个内存中。
回到20年前的恐龙统治地球时,程序的地址空间是线性的(没有孔),顺序是文本,数据,bss,堆栈,堆。那时也没有动态库或线程。这一切都随着虚拟内存而改变。
内核进程完全包含在地址空间的内核部分内。用户部分将被忽略。由于所有进程共享页表的相同内核部分,因此不必更新页表,这使内核可以加快内核线程之间的上下文切换。
对于“几乎所有操作系统”而言,情况并非如此。所表示的存储区域的类型是相当典型的,但是没有理由为什么它们应该以任何特定顺序排列,并且给定类型可以有多个存储区域。
在Linux下,你可以看看一个进程的地址空间cat /proc/$pid/maps
,其中$pid
是进程ID,例如cat /proc/$$/maps
看你正在运行的外壳cat
从,或cat /proc/self/maps
看cat
过程自身的映射。该命令pmap
产生更好的输出。
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开始。
不是答案,而是需要更多空间的FYI。
我认为您的逻辑地址布局概念根本不正确。
您可以编译并运行此程序,以查看地址的用户态进程:
#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位寻址)。
ac
和av
自动变量local
在每次调用时可能具有不同的地址。大多数现代Linux内核都具有“地址空间布局随机化”功能,从而使利用缓冲区溢出更加困难。
where.c
在Ubuntu 11.04上使用gcc where.c -o where
; 报告“主要位于0x80483c4”。尝试了readelf -S where
一下,并报告说“ [13] .text PROGBITS 08048310 ...”,哪一个看起来正确?虽然我也得到“ ac at 0xbfb035a0”和“ local at 0xbfb0358c”,但该地址范围(0xbf ...)似乎没有报告readelf -S
。