1

I have a need to allocate all memory available to a process, in order to implement a test of a system service. The test (among others) requires exhausting all available resources, attempting the call, and checking for a specific outcome.

In order to do this, I wrote a loop that reallocates a block of memory until, realloc returns null, then using the last good allocation, then cutting the difference between the last successful quantity and the last unsuccessful quantity until the unsuccessful quantity is 1 byte larger than the last successful quantity, guaranteeing that all available memory is consumed.

The code I wrote is as follows (debug prints also included)

#include <stdio.h>
#include <malloc.h>
int main(void)
{
    char*       X;
    char*       lastgood = NULL;
    char*       toalloc = NULL;
    unsigned int    top = 1;    
    unsigned int    bottom = 1; 
    unsigned int    middle;     
    do              
    {               
        bottom = top;
        lastgood = toalloc;     
        top = bottom*2;                 
        printf("lastgood = %p\ntoalloc = %p\n", lastgood, toalloc); 
        if (lastgood != NULL)           
            printf("*lastgood = %i\n", *lastgood);      
        toalloc = realloc(toalloc, top);    
        printf("lastgood = %p\ntoalloc = %p\n", lastgood, toalloc); 
        if (toalloc == NULL && lastgood != NULL)        
            printf("*lastgood = %i\n", *lastgood);  //segfault happens here 
    }while(toalloc != NULL);                    
    do                          
    {                           
        if (toalloc != NULL) lastgood = toalloc;        
        else toalloc = lastgood;                
        middle = bottom+(top - bottom)/2;           
        toalloc = realloc(toalloc, middle);         
        if (toalloc == NULL) top = middle;          
        else bottom = middle;               
    }while(top - bottom > 1);               
    if (toalloc != NULL) lastgood = toalloc;                
        X = lastgood;
//make a call that attempts to get more memory
    free(X);
}

According to realloc's manpage, realloc does not destroy the previous address if it returns null. Even so, this code results in a segfault when it tries to print lastgood after toalloc receives NULL from realloc. Why is this happening, and is there a better way to just grab the exact quantity of unallocated memory?

I am running it on glibc, on ubuntu with kernel 3.11.x

user2149140
  • 143
  • 7
  • As a sanity check, `realloc` is succeeding at least once, right? – Dennis Meng Jan 03 '14 at 23:15
  • yes. It's producing the following output on the last few cycles: ii = 0x7f3be8806010 jj = 0x7f3b68805010 ii = 0x7f3b68805010 jj = 0x7f3b68805010 *ii = 0 ii = 0x7f3b68805010 jj = (nil) Segmentation fault (core dumped) – user2149140 Jan 03 '14 at 23:16
  • Linux over-commits memory allocation. To verify what `realloc` returns is actually _usable_, you need to, uhm, use it. I don't know about other OSs. – Joseph Quinsey Jan 03 '14 at 23:21
  • I am using it. Every cycle. As per my previous comment, it's actually being used successfully without a segfault. The segfault only happens when realloc FAILS. See the output in the comment! – user2149140 Jan 03 '14 at 23:25
  • 1
    How much is the value of `top` when `realloc()` fails? – mcleod_ideafix Jan 03 '14 at 23:42
  • Ah, that'd be why it's returning null. `top` is hitting the upper bound for an unsigned int, and then on the last *2 it's getting set 0, the memory is getting deallocated explicitly (with the NULL return). Unfortunately this leaves me scratching my head as to how to prevent over-allocation of memory, as well as knowing the system being tested will explicitly fail the test when running on top of linux, because it has no way of knowing it's out of resources because linux won't just tell it it's OOM, so I'm back to **is there a better way to just grab the exact quantity of unallocated memory?** – user2149140 Jan 03 '14 at 23:53
  • `malloc()` does not consume memory, but may only _allocate_ it. "all available memory is consumed". In some systems, to use memory, code needs to do something with it (write data). – chux - Reinstate Monica Jan 03 '14 at 23:54
  • @chux: Even that may not be enough - e.g. computer with 32 GiB of RAM running a 32-bit OS where any single application can only use 2 or 3 GiB – Brendan Jan 04 '14 at 00:09

3 Answers3

6

You are not checking the value of top for overflow. This is what happens with its value:

2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432
67108864
134217728
268435456
536870912
1073741824
2147483648
0

Just before the last realloc(), the new value of top is 0 again (actually 2^32 but that doesn't fit in 32 bits), which seems to cause the memory block to actually deallocate.


Trying to allocate the maximum contiguous block is not a good idea. The memory map seen by the user process has some block already allocated for shared libraries, and the actual code and data of the current process. Unless you want to know the maximum contiguous memory block you can allocate, the way to go is to allocate as much as you can in a single block. When you have reached that, do the same with a different pointer, and keep doing that until you really run out of memory. Take into account that in 64-bit systems, you don't get all available memory in just one malloc()/realloc(). As I've just seen, malloc() in 64-bit systems allocate up to 4GB of memory in one call, even when you can issue several mallocs() and still succeed on each of them.

A visual of a user process memory map as seen in a 32-bit Linux system is described in an answer I gave a few days ago: Is kernel space mapped into user space on Linux x86?

I've come up with this program, to "eat" all the memory it can:

#include <stdio.h>
#include <malloc.h>

typedef struct slist
{
  char *p;
  struct slist *next;
} TList;

int main(void)
{
  size_t nbytes;
  size_t totalbytes = 0;
  int i = 0;
  TList *list = NULL, *node;

  node = malloc (sizeof *node);
  while (node)
  {
    node->next = list;
    list = node;
    nbytes = -1; /* can I actually do this? */ 
    node->p  = malloc(nbytes);  
    while (nbytes && !node->p)
    {
      nbytes/=2;
      node->p = malloc(nbytes);
    }
    totalbytes += nbytes + sizeof *node;
    if (nbytes==0)
      break;
    i++;
    printf ("%8d", i);
  }
  printf ("\nBlocks allocated: %d. Memory used: %f GB\n", 
                 i, totalbytes/(1024*1048576.0));
  return 0;
}

The execution yields these values in a 32-bit Linux system:

 1       2       3       4       5       6       7       8       9      10
11      12      13      14      15      16      17      18      19      20
21      22      23      24      25      26      27      28      29      30
31      32      33      34      35      36      37      38      39      40
41      42      43      44      45      46      47      48      49      50
51      52      53
Blocks allocated: 53. Memory used: 2.998220 GB

Very close to the 3GB limit on 32-bit Linux systems. On a 64-bit Linux system, I've reached 30000 blocks of 4GB each, and still counting. I don't really know if Linux can allocate that much memory or it's my mistake. According to this, the maximum virtual address space is 128TB (that would be 32768 4GB blocks)


UPDATE: in fact, so it is. I've left running this program on a 64-bit box, and after 110074 succesuflly allocated blocks, the grand total of memory allocated has been 131071.578884 GB . Each malloc() has been able to allocate as much as 4 GB per operation, but when reached 115256 GB, it has begun to allocate up to 2 GB, then when it reached 123164 GB allocated, it begun to allocate up to 1GB per malloc(). The progression asyntotically tends to 131072 GB, but it actually stops a little earlier, 131071.578884 GB because the very process, its data and shared libraries, uses a few KB of memory.

enter image description here

Community
  • 1
  • 1
mcleod_ideafix
  • 11,128
  • 2
  • 24
  • 32
  • Well, that answers why it's failing, however this leaves me with a new problem: is there a better way to just grab the exact quantity of unallocated memory from the system? – user2149140 Jan 03 '14 at 23:55
  • For that last bit, quoting the man page for realloc: `if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr).` – Dennis Meng Jan 04 '14 at 00:01
  • @user2149140: Which "exact quantity of unallocated memory" do you mean? Do you mean virtual memory allocated from the OS by the heap but still free in the heap (which can increase when `malloc()` is called), or "virtual address space size - total virtual space used" (which has nothing to do with heap or actual RAM), or "total RAM installed - actual RAM currently in use", or....? – Brendan Jan 04 '14 at 00:07
1

I think getrlimit() is the answer to your new question. Here is the man page http://linux.die.net/man/2/getrlimit

You are probably interested in RLIMIT_STACK. Please check the man page.

alvits
  • 6,550
  • 1
  • 28
  • 28
0

first part is replaced with the following.

char *p;
size_t size = SIZE_MAX;//SIZE_MAX : #include <stdint.h>
do{
    p = malloc(size);
    size >>= 1;
}while(p==NULL);
BLUEPIXY
  • 39,699
  • 7
  • 33
  • 70