内存分配如何在极端情况下工作?

时间:2013-08-19 21:04:27

标签: c linux memory-management

我很担心内存分配(malloc / calloc)在linux / c中是如何工作的。假设我有一台16GB RAM的机器,我以root身份运行程序。它是64位机器,因此所有16GB都可以作为单个段进行寻址。我可以使用单个malloc调用分配所有这些(当然,减去操作系统的数量)吗?有很多malloc电话?

它是如何与“堆内存”和“虚拟内存”相关的?如果我分配一个小的内存块,它发生在堆内存中,那么我调整(放大)这个块,当我接近堆栈区域时会发生什么?

如果我想(几乎)将所有RAM分配给我的单个进程,我是否必须摆弄setrlimit RLIMIT_AS,即使它认为它以root身份运行?

3 个答案:

答案 0 :(得分:5)

在虚拟内存操作系统(如Linux)上,malloc()不会分配内存。它分配地址空间例如,编译并运行以下片段,并(在另一个终端中)运行top

#include <stdlib.h>
#include <unistd.h>

int main(void) {
  char *cp;
  cp = malloc( 16ULL * 1024 *1024 *1024);
  if (cp) pause();
  return 0;
}

在我的电脑上,TOP显示:

PID    USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND            
29026 plasser    20   0 16.0g  324  248 S    0  0.0   0:00.00 a.out 

这意味着:a.out有16GB的虚拟大小,并且只使用324(字节?KB?)驻留内存(可能只是二进制文件)

发生了什么事?

  • malloc()调用已要求操作系统扩展地址空间,添加16 GB
  • 操作系统已完成此请求,并为其设置页面表等等。
  • 这些页面表没有物理内存附加(尚未),可能除了表格本身
  • 一旦程序开始引用此地址空间,将附加页面(操作系统将其置于中)
  • (页面可能是/ dev / zero的COW,但这只是一个细节)
  • 但是,当程序确实引用地址时,操作系统必须分配内存并将附加添加到进程中。 (它将显示在RES字段中,相信我)
  • 在某些时刻,一旦操作系统认为它已经使用了很长时间,附加的内存也可以分离。它将被添加到可用内存池中或/和用于其他目的。在这种情况下,其内容可能会被推送到后备存储(磁盘)

`

答案 1 :(得分:2)

malloc()可以通过扩展堆或mmap()足够大的内存块来分配内存。它使用哪个取决于您请求的大小以及几个mallopt()选项。

如果它使用通常位于虚拟内存区域开头的“真实”堆部分,它可以长大到达下一个分配的内存块的位置,该内存块远离开始。 / p>

如果它使用mmap(),则取决于是否有可用的连续地址空间。

/proc/<PID>/maps是查找进程地址空间情况的一个很好的查询点。

我刚用我的64位服务器测试了Python和ctypes:

import ctypes
import ctypes.util
m=ctypes.CDLL(ctypes.util.find_library("c")).malloc
m.argtypes=(ctypes.c_uint64,)
m.restype = ctypes.c_uint64

现在我有m,它可以调用libc的malloc()函数。

现在我可以做到

>>> hex(m(2700000000))
'0x7f1345e3b010L'
>>> hex(m(2700000000))
'0x7f12a4f4f010L'
>>> hex(m(2700000000))
'0x7f1204063010L'
>>> hex(m(2700000000))
'0x7f1163177010L'
>>> hex(m(2700000000))
'0x7f10c228b010L'
>>> hex(m(2700000000))
'0x7f102139f010L'
>>> hex(m(2700000000))
'0x7f0f804b3010L'

但由于我不能做的任何理由

>>> hex(m(2900000000))
'0x0L'

因为它返回0 resp。一个NULL指针。

立即行动

print open("/proc/self/maps").read()

显示了所述文件的行;

中的某个地方
7f0ed8000000-7f0ed8021000 rw-p 00000000 00:00 0 
7f0ed8021000-7f0edc000000 ---p 00000000 00:00 0 
7f0edf5c7000-7f13e6d27000 rw-p 00000000 00:00 0 

我得到了我的分配。

但是,如果我改为

>>> hex(m(1))
'0x1f07320L'
>>> hex(m(1))
'0x1f0c0e0L'
>>> hex(m(1))
'0x1f02d50L'
>>> hex(m(1))
'0x1f02d70L'
>>> hex(m(1))
'0x1ef94f0L'
>>> hex(m(1))
'0x1eb7b20L'
>>> hex(m(1))
'0x1efb700L'
>>> hex(m(270))
'0x1f162a0L'
>>> hex(m(270))
'0x1f163c0L'
>>> hex(m(270))
'0x1f164e0L'
>>> hex(m(270))
'0x1f16600L'

内存地址来自

>>> print open("/proc/self/maps").read()
[...]
01d2e000-01f2b000 rw-p 00000000 00:00 0                                  [heap]
[...]

指定的堆。

请注意,虽然我可以分配这个内存,但使用它会造成伤害,因为我的进程可能会被着名的OOM杀手杀死。

答案 2 :(得分:0)

64位进程可以分配所有内存。除非系统为非root用户定义了ulimit设置,否则它甚至不需要是root用户。请尝试ulimit -v查看是否设置了限制。

在Linux默认设置下,进程可以请求几乎任何数量的内存,并且它将被授予。内存将在使用时实际分配,它将根据需要来自物理RAM或磁盘交换。

内存分配调整大小通常在C库中完成,方法是分配新的更大的大小并将旧数据复制到新的分配中。通常不会通过扩展现有分配来完成。选择内存分配不与其他分配(如程序堆栈)冲突。