2

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

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

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

4

3 回答 3

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现场,相信我)
  • 在某些时候,一旦操作系统认为它已经很长时间没有被使用,附加的内存也可能被分离。它将被添加到空闲内存池中或/和用于其他目的。在这种情况下,它的内容可能会被推送到后备存储(磁盘)

`

于 2013-08-19T22:12:49.730 回答
2

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

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

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

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

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

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,它作为对 libcmalloc()函数的调用。

现在我可以做

>>> 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。一个空指针。

现在做

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 杀手杀死。

于 2013-08-19T21:30:28.567 回答
1

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

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

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

于 2013-08-19T21:25:48.350 回答