3

我写了一个简单的应用程序来测试内存消耗。在这个测试应用程序中,我创建了四个进程来持续消耗内存,除非进程退出,否则这些进程不会释放内存。

我预计这个测试应用程序会消耗最多的 RAM 内存并导致其他应用程序变慢或崩溃。但是结果和预想的不一样。下面是代码:

 #include <stdio.h>
 #include <unistd.h>
 #include <list>
 #include <vector>

 using namespace std;
 unsigned short calcrc(unsigned char *ptr, int count)
 {
     unsigned short crc;
     unsigned char i;

     //high cpu-consumption code 
     //implements the CRC algorithm
     //CRC is Cyclic Redundancy Code
 }


 void* ForkChild(void* param){
    vector<unsigned char*>  MemoryVector;
    pid_t PID = fork();
    if (PID > 0){
        const int TEN_MEGA = 10 * 10 * 1024 * 1024;
        unsigned char* buffer = NULL;
        while(1){
            buffer  = NULL;
            buffer = new unsigned char [TEN_MEGA];
            if (buffer){
                 try{
                    calcrc(buffer, TEN_MEGA);
                    MemoryVector.push_back(buffer);
                 } catch(...){
                    printf("An error was throwed, but caught by our app!\n");
                    delete [] buffer;
                    buffer = NULL;
                 }
            }
            else{
                 printf("no memory to allocate!\n");
                 try{
                     if (MemoryVector.size()){
                        buffer = MemoryVector[0];
                        calcrc(buffer, TEN_MEGA);
                        buffer = NULL;
                     } else {
                        printf("no memory ever allocated for this Process!\n");
                        continue;
                     }
                 } catch(...){
                    printf("An error was throwed -- branch 2," 
                           "but caught by our app!\n");
                    buffer = NULL;
                 }
             }
         }  //while(1)
    } else if (PID == 0){
    } else {
      perror("fork error");
    }   

    return NULL;
}


int main(){
int children = 4;
    while(--children >= 0){
    ForkChild(NULL);
    };

    while(1) sleep(1);
    printf("exiting main process\n");
    return 0;
 }
  1. 顶部命令

    PID  USER      PR  NI  VIRT  RES  SHR S  %CPU %MEM    TIME+  COMMAND           
    2775 steve     20   0 1503m  508  312 R  99.5  0.0   1:00.46 test              
    2777 steve     20   0 1503m  508  312 R  96.9  0.0   1:00.54 test              
    2774 steve     20   0 1503m  904  708 R  96.6  0.0   0:59.92 test              
    2776 steve     20   0 1503m  508  312 R  96.2  0.0   1:00.57 test
    

    虽然 CPU 很高,但内存百分比仍然为 0.0。怎么可能??

  2. 自由命令

                      free  shared    buffers     cached          
    Mem:           3083796       0      55996     428296
    

    可用内存超过 4G RAM 中的 3G。

有谁知道为什么这个测试应用程序不能按预期工作?

4

5 回答 5

6

Linux 使用乐观内存分配:在实际写入该页之前,它不会物理分配内存页。因此,您可以分配比可用内存更多的内存,而不会增加系统的内存消耗。

如果你想强制系统分配(提交)一个物理页面,那么你必须写入它。

以下行不发出任何write,因为它是 的默认初始化unsigned char,这是一个无操作:

buffer = new unsigned char [TEN_MEGA];

如果要强制提交,请使用零初始化:

buffer = new unsigned char [TEN_MEGA]();
于 2013-09-20T13:25:26.277 回答
3

要将评论变成答案:

  • Linux 不会为进程分配内存页面,直到它写入它们(写时复制)。
  • 此外,您不会在任何地方写入缓冲区,因为默认构造函数unsigned char 不执行任何初始化,并且new[]默认初始化所有项目。
于 2013-09-20T13:21:41.600 回答
1

fork()在父进程中返回 PID,在子进程中返回 0。您ForkChild所写的将执行父母的所有工作,而不是孩子。

并且标准new运算符永远不会返回 null;如果它无法分配内存,它会抛出(但由于过度使用它实际上不会在 Linux 中这样做)。这意味着buffer分配后的测试是没有意义的:它要么总是采用第一个分支,要么永远不会到达测试。如果要返回 null,则需要编写new (std::nothrow) .... 包括<new>它的工作。

于 2013-09-20T13:35:41.433 回答
0

但是你的程序实际上正在做你期望它做的事情。正如答案所指出的(@ Michael Foukarakis 的答案),未分配未使用的内存。在top程序的输出中,我注意到virt对于运行程序的每个进程,该列都有大量内存。谷歌搜索了一下,我看到了这是什么:

VIRT——它包括所有代码、Virtual Memory Size (KiB). The total amount of virtual memory used by the task. 数据和共享库以及已换出的页面和已映射 但未使用的页面。

如您所见,您的程序实际上确实为自己生成内存,但以页面的形式并存储为虚拟内存。我认为这是明智之举

此wiki 页面的片段

页面内存页面或虚拟页面——固定长度的连续虚拟内存块,它是以下数据的最小单位:

  • 操作系统为程序执行的内存分配;和
  • 在主存储器和任何其他辅助存储器(如硬盘驱动器)之间传输。

...因此,程序可以寻址比计算机中物理存在的更多(虚拟)RAM。虚拟内存是一种让用户产生使用一大块连续内存空间(甚至可能比实际内存更大)的错觉的方案,而实际上他们的大部分工作都在辅助存储(磁盘)上。根据需要将作业的固定大小块(页)或可变大小块读入主存储器。

资料来源

于 2013-09-20T13:46:39.617 回答
-1

如果你想吞噬大量内存:

int mb = 0;
char* buffer;
while (1) {
    buffer = malloc(1024*1024);
    memset(buffer, 0, 1024*1024);
    mb++;
}

在进行一些文件 I/O 时序测量时,我使用了类似的方法来确保文件缓冲区缓存为空。

正如其他答案已经提到的那样,您的代码在分配缓冲区后永远不会写入缓冲区。这里 memset 用于写入缓冲区。

于 2013-09-20T05:09:46.017 回答