如何在Linux上检查进程的堆大小

bas*_*hrc 11 c linux memory crash heap

我正在写一些代码并且它一直在崩溃.在挖掘转储后,我意识到我超出了最大堆限制(如果我在malloc上添加了检查,生活会更容易).虽然我修复了,有没有办法增加我的堆大小?

PS:这是一个非常相似的问题,但回复对我来说并不清楚.

Ada*_*man 15

堆和内存管理是您的C库(可能是glibc)提供的工具.它会维护堆并在每次执行时返回内存块malloc().它不知道堆大小限制:每次请求的内存多于堆上可用的内存时,它只会向内核请求更多(使用sbrk()mmap()).

默认情况下,内核在询问时几乎总会给你更多内存.这意味着malloc()将始终返回有效地址.只有在您第一次引用已分配的页面时,内核才会真正为您找到一个页面.如果它发现它无法给你一个,它会运行一个OOM杀手,根据某种称为坏处的措施(包括你的进程及其子的虚拟内存大小,不错的关卡,整体运行时间等)选择一个受害者并发送一个SIGTERM.此内存管理技术称为overcommit,内核在/proc/sys/vm/overcommit_memory0或1 时使用.有关详细信息,请参阅内核文档中的overcommit-accounting.

通过将2写入/proc/sys/vm/overcommit_memory您可以禁用过度使用.如果你这样做,内核将在承诺之前实际检查它是否有内存.malloc()如果没有更多可用内存,这将导致返回NULL.

您还可以设置虚拟内存限制一个进程可以分配与setrlimit()RLIMIT_AS或与ulimit -v命令.无论上面描述的overcommit设置如何,如果进程尝试分配的内存超过限制,内核将拒绝它并malloc()返回NULL.请注意,与现代Linux内核(包括整个2.6.x系列)相比,驻留大小(setrlimit()使用RLIMIT_RSSulimit -m命令)的限制无效.

下面的会话是在内核2.6.32上运行的,具有4GB RAM和8GB交换.

$ cat bigmem.c
#include <stdlib.h>
#include <stdio.h>

int main() {
  int i = 0;
  for (; i < 13*1024; i++) {
    void* p = malloc(1024*1024);
    if (p == NULL) {
      fprintf(stderr, "malloc() returned NULL on %dth request\n", i);
      return 1;
    }
  }
  printf("Allocated it all\n");
  return 0;
}
$ cc -o bigmem bigmem.c
$ cat /proc/sys/vm/overcommit_memory
0
$ ./bigmem
Allocated it all
$ sudo bash -c "echo 2 > /proc/sys/vm/overcommit_memory"
$ cat /proc/sys/vm/overcommit_memory
2
$ ./bigmem
malloc() returned NULL on 8519th request
$ sudo bash -c "echo 0 > /proc/sys/vm/overcommit_memory"
$ cat /proc/sys/vm/overcommit_memory
0
$ ./bigmem
Allocated it all
$ ulimit -v $(( 1024*1024 ))
$ ./bigmem
malloc() returned NULL on 1026th request
$
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,交换或OOM终止可能永远不会发生,但如果进程实际上试图触摸分配的所有内存,则会发生显着变化.

要直接回答您的问题:除非您使用ulimit -v命令显式设置了虚拟内存限制,否则除了计算机的物理资源或地址空间的逻辑限制(在32位系统中相关)之外,没有堆大小限制.你的glibc将继续在堆上分配内存,并在堆增长时从内核请求越来越多.如果所有物理内存都耗尽,最终可能会导致交换错误.一旦交换空间耗尽,随机进程将被内核的OOM杀手杀死.

但请注意,由于缺少可用内存,碎片或达到配置的限制,内存分配可能会失败的原因多得多.glib的分配器使用的sbrk()mmap()调用都有自己的失败,例如,程序中断到达另一个已经分配的地址(例如共享内存或以前映射的页面mmap())或者超出了进程的最大内存映射数.


Red*_*met 10

堆通常与架构上的可寻址虚拟内存一样大.

您应该使用该ulimit -a命令检查系统当前限制并查找此行max memory size (kbytes, -m) 3008828,在我的OpenSuse 11.4 x86_64上使用~3.5 GiB ram的这一行表示我每个进程大约有3GB内存.

然后,您可以使用此简单程序真正测试您的系统,以检查每个进程的最大可用内存:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char* argv[]){
        size_t oneHundredMiB=100*1048576;
        size_t maxMemMiB=0;
        void *memPointer = NULL;
        do{
                if(memPointer != NULL){
                        printf("Max Tested Memory = %zi\n",maxMemMiB);
                        memset(memPointer,0,maxMemMiB);
                        free(memPointer);
                }
                maxMemMiB+=oneHundredMiB;
                memPointer=malloc(maxMemMiB);
        }while(memPointer != NULL);
        printf("Max Usable Memory aprox = %zi\n",maxMemMiB-oneHundredMiB);
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

这个程序以100MiB为增量获取内存,显示当前分配的内存,在其上分配0,然后释放内存.当系统无法提供更多内存时,返回NULL并显示最终可用的最大内存量.

需要注意的是,您的系统将在最后阶段开始大量交换内存.根据您的系统配置,内核可能会决定终止某些进程.我使用100 MiB增量,因此某些应用程序和系统有一些喘息空间.你应该关闭任何你不想崩溃的东西.

话虽如此.在我写的系统中,没有任何事情发生过.上面的程序报告几乎没有ulimit -a.不同之处在于它实际测试了内存,并通过memset()确认内存被给出和使用.

为了在具有256 MiB ram和400MiB交换的Ubuntu 10.04x86 VM上进行比较,ulimit报告 memory size (kbytes, -m) unlimited和我的小程序报告了524.288.000字节,这大致是组合的ram和swap,折扣其他软件和内核使用的ram.

编辑:正如Adam Zalcman写的那样,ulimit -m不再对更新的2.6及更高版本的内核感到荣幸,所以我站得更正了.但ulimit -v很荣幸.为了获得实际效果,您应该将-m替换为-v,并寻找virtual memory (kbytes, -v) 4515440.似乎我的suse盒子的-m值与我的小实用程序报告的值一致.您应该记住,这是由内核分配的虚拟内存,如果物理内存不足,则需要交换空间来弥补它.

如果您想知道有多少物理ram可用而不会干扰任何过程或系统,您可以使用

long total_available_ram =sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE) ;

这将排除缓存和缓冲区内存,因此该数字可能远小于实际可用内存.操作系统缓存可以很安静,并且它们的驱逐可以提供所需的额外内存,但这是由内核处理的.