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

How "linux process address space" is stored?

从书上看到当一个进程启动时,它的私有进程地址space被创建

假设它是从 0x0 到 0xMAX

而 space 的一部分是堆,我们写了一个 for 循环来继续 malloc (1k date) 直到它 returns false。它分配了 3GB 的日期。

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

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

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

这里有一些误解。首先,进程地址dpace和分配给进程的内存是有区别的

其次,地址space内的有效地址范围不太可能是线性的。它最有可能在内存的几个断开连接的区域中。

如果您在循环中执行 malloc,则会导致 (a) 导致将有效页面添加到进程中 (b) 导致在 malloc 返回的内存之外分配开销内存。

第三,在一个进程的开始,有页表。 (忽略克隆)这些表在进程开始时没有引用任何内容。您的进程必须导致页面分配并分配给表。

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

void main {}

用gcc编译这个程序后,我们得到一个executable文件(ELF格式)a.out。如果我们通过 运行ning 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 加载程序。它负责在内存中加载任何 executable 并 运行 它。如果我们查看生成的 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 确保所有库都在那里,它就会分配内存来加载进程。通常一个 executable 有几个段,为简单起见,我们可以将它们简化为文本(代码)、bss(未初始化数据)、数据(初始化和静态数据)。当进程加载到内存中时,加载程序会保留保存所有这些部分所需的内存量,并将进程所依赖的所有共享库映射到进程的虚拟 space 中。可以参考linux查看某个进程的maps列表:

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(程序映射 table),用于在虚拟地址和物理地址之间进行转换。一般来说,一个进程内存有如下布局:

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

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

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?

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

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

这可能是一个很长的答案。我希望我没有错过很多细节,它可以帮助您理解 linux.

中进程内存的剖析