从书中读到,当进程启动时,会创建其私有进程地址空间
我们说它是从0x0到0xMAX
空间的一部分是堆,我们写了一个for循环来连接malloc(1k日期),直到它返回false。它分配了3GB日期。
所以,问一下,如果在开头分配0x0到0xMAX,那么从开始就意味着0x0到0xMAX大于3GB(因为有堆栈,控制......)?
如果一个进程在开始时可能超过3GB,那么我必须理解它是错误的。
任何人都可以解释这个0x0 - 0xMAX是如何存储在乞讨中的吗?
答案 0 :(得分:2)
通常,可执行文件加载到内存是由linux loader 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工具),我们可以看到它的头部包含对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/)
因此,记住这些信息并回到原来的问题,
所以,问一下,如果在开头分配0x0到0xMAX,那么从开始就意味着0x0到0xMAX大于3GB(因为有堆栈,控制......)?
答案是没有这样的保留。加载程序保留所需的物理内存以运行该进程。之后,根据进程需求(动态内存分配)及其行为,其堆和堆栈区域可能会增长和缩小。每次进程需要访问实际上不存在于物理内存中的某些内存(虚拟)时,就会发出页面错误,并且此页面将从磁盘加载到物理内存中的保留位置。有时为了做到这一点,内核必须将属于另一个进程的一些页面交换到磁盘。物理内存是操作系统必须正确处理的有限资源,以便提供所有正在运行的进程。
通过这种策略,linux内核能够运行多个进程,其中每个进程通常具有4GB(32位系统)的虚拟内存,其物理内存小于(特别是过去)。一般情况下,即使您动态保留内存(例如使用malloc),调用也会成功,但实际上您还没有保留此物理内存。一旦尝试使用它(通过读取或写入此内存),您的进程将获得它。
这可能是一个很长的答案。我希望我没有错过很多细节,它可以帮助你理解linux中进程内存的解剖结构。
答案 1 :(得分:0)
这里有一些误解。首先,进程地址空间和分配给进程的内存之间存在差异。
其次,地址空间内的有效地址范围不太可能是线性的。它最有可能出现在几个断开连接的内存区域。
如果在循环中执行了malloc,则导致(a)将有效页面添加到进程(b),除了malloc返回的内容之外,还会分配开销内存。
第三,在流程开始时,有页表。 (忽略克隆)这些表在过程开始时没有任何内容。您的流程必须分配页面并将其分配给表。