linux - "linux process address space"是如何存储的?

标签 linux process malloc virtual-memory

从书上看到当一个进程启动时,它的私有(private)进程地址空间被创建

假设它是从 0x0 到 0xMAX

还有一部分空间是堆,我们写了一个for循环继续malloc(1k date)直到返回false。它分配了 3GB 的数据。

那么问题来了,如果一开始就分配了0x0到0xMAX,那说明0x0到0xMAX从一开始就大于3GB(因为有stack,control...)?

如果一开始一个进程可以占用超过3GB,那一定是我理解错了。

谁能解释一下这个 0x0 - 0xMAX 是如何存储在乞讨中的?

最佳答案

通常,将可执行文件加载到内存是由 linux 加载程序 ld 驱动的。例如,如果我创建一个非常简单的 C 程序,可以说:

void main {}

用gcc编译这个程序后,我们得到一个可执行文件(ELF格式)a.out。如果我们通过运行 ldd 来分析这个非常简单的程序的依赖关系,我们会发现:

linux-gate.so.1 =>  (0x00545000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x00ccb000)
/lib/ld-linux.so.2 (0x00594000)

第一个linux-gate.so被内核暴露出来进行系统调用。 ld-linux.so 实际上是 linux 加载器。它负责在内存中加载任何可执行文件并运行它。如果我们查看生成的 a.out(例如使用 hexedit 工具),我们可以看到它的 header 包含对 ld-linux 所在位置的引用:

 .ELF........................4...8.......
 4. ...(.........4...4...4... ... .......
 ........T...T...T.......................
 ........................................
 ........................(...(...(.......
 ................h...h...h...D...D.......
 ....P.td............4...4...........Q.td
 ............................R.td........
 ..................../lib/ld-linux.so.2..
 ............GNU.........................
 ....GNU....F*QLk$,.....)..Yl............

一旦您启动该进程,ld-linux 加载程序首先检查您需要(依赖)哪些共享库以及它们是否可用。如果你依赖一些不可用的共享库,ld-linux 将不会加载进程(ld-linux 会查看你的 LD_LIBRARY_PATH env 变量,/etc/ld.so.cache 文件,最后在默认路径中:/lib 和/usr/lib(man ld-linux 了解更多信息)。

一旦 ld-linux 确保所有的库都在那里,它就会分配内存来加载进程。通常一个可执行文件有几个段,为简单起见,我们可以将它们简化为文本(代码)、bss(未初始化数据)、数据(初始化和静态数据)。当进程被加载到内存中时,加载程序会保留保存所有这些部分所需的内存量,并将进程所依赖的所有共享库映射到进程的虚拟空间中。可以查看linux中某个进程的映射表列表:

cat /proc/pid_of_process/maps

如果我运行上面简单程序的修改版本(通过添加对 usleep 的调用以获取进程 pid)并检查其映射,我们得到以下内容(_ 只是为了隐藏真实路径,其中我的家出现了):

003a5000-003a6000 r-xp 00000000 00:00 0          [vdso]
0075a000-008fd000 r-xp 00000000 08:03 2137894    /lib/i386-linux-gnu/libc-2.15.so
008fd000-008ff000 r--p 001a3000 08:03 2137894    /lib/i386-linux-gnu/libc-2.15.so
008ff000-00900000 rw-p 001a5000 08:03 2137894    /lib/i386-linux-gnu/libc-2.15.so
00900000-00903000 rw-p 00000000 00:00 0 
00e4a000-00e6a000 r-xp 00000000 08:03 2137906    /lib/i386-linux-gnu/ld-2.15.so
00e6a000-00e6b000 r--p 0001f000 08:03 2137906    /lib/i386-linux-gnu/ld-2.15.so
00e6b000-00e6c000 rw-p 00020000 08:03 2137906    /lib/i386-linux-gnu/ld-2.15.so
08048000-08049000 r-xp 00000000 08:05 3589145    /______________/test/a.out
08049000-0804a000 r--p 00000000 08:05 3589145    /______________/test/a.out
0804a000-0804b000 rw-p 00001000 08:05 3589145    /______________/test/a.out
b771f000-b7720000 rw-p 00000000 00:00 0 
b7745000-b7747000 rw-p 00000000 00:00 0 
bf884000-bf8a5000 rw-p 00000000 00:00 0          [stack]

这实际上是进程的虚拟内存映射。这些页面被映射到物理内存,每个进程都有自己的 PMT(程序映射表),用于在虚拟地址和物理地址之间进行转换。通常,进程内存具有以下布局:

(来自 http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/)

enter image description here

因此,考虑到这些信息并回到您最初的问题,

So, question, if 0x0 to 0xMAX is allocated in the beginning, it means 0x0 to 0xMAX is larger than 3GB (since there are stack, control...) since the beginning?

答案是没有这样的保留。加载程序保留运行进程所需的物理内存。之后,根据进程需求(动态内存分配)及其行为,其堆和堆栈区域可能会增大或缩小。每次进程需要访问一些实际不存在于物理内存中的内存(虚拟)时,都会发出页面错误,并将该页面从磁盘加载到物理内存中的保留位置。有时为了做到这一点,内核必须将属于另一个进程的一些页面换出到磁盘。物理内存是一种有限的资源,操作系统必须正确处理它才能提供所有正在运行的进程。

通过这种策略,linux 内核能够运行多个进程,其中每个进程通常在物理内存中拥有 4GB(32 位系统)的虚拟内存(特别是在过去)。通常,即使您动态保留内存(例如使用 malloc),调用也会成功,但实际上您还没有保留此物理内存。一旦它尝试使用它(通过读取或写入此内存),您的进程就会得到它。

这可能是一个很长的答案。我希望我没有错过很多细节,希望它能帮助您理解 Linux 中进程内存的剖析。

关于linux - "linux process address space"是如何存储的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32746996/

相关文章:

linux - 在 bash 中对 find 命令的多个结果进行操作

c# - WPF + 在 TextBlock 中显示 bat 文件的输出

c - 我怎么知道数组中的元素是否存在?

c - 不确定是否为结构分配内存

linux - 在 Jenkins 执行 RSYNC

linux - Perf Tool Linux - 无调用图

windows - 如何在matlab中杀死孤立的winword.exe

c - 从进程 ID 获取进程名称 (win32)

c - 访问结构数组会出现段错误

c++ - OpenGL 窗口显示背景窗口中的内容而不是空白窗口