一点背景
malloc()
不会说谎,您的内核虚拟内存子系统会说谎,这是大多数现代操作系统上的常见做法。当你使用malloc()
,真正发生的事情是这样的:
libc 的实现malloc()
检查其内部状态,并尝试使用各种策略来优化您的请求(例如尝试使用预分配的块、分配比提前请求更多的内存......)。这意味着该实现将影响性能并稍微改变内核请求的内存量,但这在检查“大数字”时并不真正相关,就像您在测试中所做的那样。
如果预分配的内存块中没有空间(请记住,内存块通常非常小,大约为 128KB 到 1MB),它将向内核请求更多内存。实际的系统调用因内核而异(mmap()
, vm_allocate()
...)但其目的基本相同。
内核的 VM 子系统将处理该请求,如果它发现该请求是“可接受的”(稍后会详细介绍该主题),它将在请求任务的内存映射中创建一个新条目(我使用的是 UNIX 术语) ,其中任务是一个进程及其所有状态和线程),并将所述映射条目的起始值返回给malloc()
.
malloc()
将记录新分配的内存块,并将适当的答案返回给您的程序。
好的,现在你的程序已经成功了已分配内存一些记忆,但事实是物理内存的单页(x86 中为 4KB)尚未实际分配给您的请求(嗯,这是一个过于简单化的问题,因为一些页面可能被用来存储有关内存池状态的信息,但它更容易说明这一点)。
那么,当您最近尝试访问此内容时会发生什么已分配内存记忆?分段错误。令人惊讶的是,这是一个相对鲜为人知的事实,但您的系统正在生成分段错误每时每刻。然后,您的程序被中断,内核接管控制权,检查地址错误是否对应于有效的映射条目,获取一个或多个物理页并将它们链接到任务的映射。
如果您的程序尝试访问不在任务中的映射条目内的地址,则内核将无法解决该错误,并将向其发送信号(或非 UNIX 系统的等效机制)以指出这个问题。如果程序本身不处理该信号,它将被臭名昭著的命令杀死分段故障 error.
So 调用时没有分配物理内存malloc()
,但是当你实际访问该内存时。这允许操作系统执行一些漂亮的技巧,例如磁盘paging, 气球 and 过度投入.
这样,当您询问特定进程使用了多少内存时,您需要查看两个不同的数字:
虚拟尺寸:已请求的内存量,即使实际上并未使用。
居民规模:它真正使用的内存,由物理页支持。
过量使用多少就足够了?
在计算中,资源管理是一个复杂的问题。您有各种各样的策略,从最严格的基于功能的系统,到 Linux 等更宽松的内核行为(带有memory_overcommit == 0
),这基本上允许您请求内存达到任务允许的最大映射大小(这是取决于架构的限制)。
在中间,您有像 Solaris 这样的操作系统(在您的文章中提到),它将任务的虚拟内存量限制为 (physical pages + swap disk pages
)。但不要被您引用的文章所迷惑,这并不总是一个好主意。如果您运行的 Samba 或 Apache 服务器同时运行数百到数千个独立进程(这会因碎片而导致大量虚拟内存浪费),则必须配置大量交换磁盘,否则您的系统将耗尽虚拟内存,但仍有大量可用 RAM。
但为什么内存过量使用在 ARM 上的工作方式不同呢?
事实并非如此。至少不应该如此,但 ARM 供应商有一种疯狂的倾向,会对他们随系统分发的内核进行任意更改。
在您的测试用例中,x86 机器按预期工作。当你以小块的形式分配内存时,你有vm.overcommit_memory
设置为 0,您可以填充所有虚拟空间,即 3GB 线上的某个位置,因为您在 32 位机器上运行它(如果您在 64 位上尝试此操作,循环将运行直到 n= =N)。显然,当您尝试使用该内存时,内核会检测到物理内存变得稀缺,并激活 OOM 杀手对策。
在ARM上应该是一样的。由于事实并非如此,我想到了两种可能性:
overcommit_memory
采用 NEVER (2) 策略,可能是因为有人在内核上强制采用这种方式。
您已达到任务允许的最大地图大小。
与 ARM 上的每次运行一样,您会得到不同的值malloc阶段,我会放弃第二个选项。确保overcommit_memory
已启用(值 0)并重新运行测试。如果您有权访问这些内核源代码,请查看它们以确保内核尊重这一点sysctl(正如我所说,一些 ARM 供应商喜欢对他们的内核做一些令人讨厌的事情)。
作为参考,我在 QEMU 下模拟 vertilepb 和 Efika MX (iMX.515) 上运行了 demo3。第一个停了内存分配3 GB 标记,正如 32 位机器上所预期的那样,而另一个则更早,为 2 GB。这可能会让人感到惊讶,但如果你看一下它的内核配置(https://github.com/genesi/linux-legacy/blob/master/arch/arm/configs/mx51_efikamx_defconfig https://github.com/genesi/linux-legacy/blob/master/arch/arm/configs/mx51_efikamx_defconfig),你会看到这个:
CONFIG_VMSPLIT_2G=y
# CONFIG_VMSPLIT_1G is not set
CONFIG_PAGE_OFFSET=0x80000000
内核配置为 2GB/2GB 分割,因此系统的行为符合预期。