1

I'm learning about OSs memory management and just learned about virtual memory and how it can be implemented using demand paging.

I made this simple program:

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

int main(void){
  int x=0;
  scanf("%d",&x);
  if(x==1){
    int *a=malloc(1073741824*sizeof(int));
    while(1){
      for(size_t i=0;i<1073741824;i++){
        a[i]=1;
      }
    }
  }
  else if(x==2){
    int *b=malloc(1073741824*sizeof(int));
    while(1);
  }
  return 0;
}

It has 2 paths:

  1. One allocated an array of 4 gb and keeps changing its values.

  2. The other allocated an array of 4 gb, but doesn't change its values.

As I expected, after running the first option, the memory of the program increases to about 4 gb, but the second one doesn't change. I'm guessing this is due to the memory not being accessed, so its pages are "swapped out" to a backing store, until they're needed again.

Is my understanding correct?

  • Probably it's just your compiler optimizing your code. The allocation of the unused array is fully removed. – Roberto Caboni Aug 28 '21 at 14:56
  • @RobertoCaboni that actually also makes sense. I was trying to find a real example of demand paging though, and it the case was that the compiler just optimized it, then I don't know how an example can be found –  Aug 28 '21 at 14:59
  • If you step through the allocated memory in a loop, with ```sleep()``` delays, you will probably see the process footprint grow as each page is "touched' (mapped) for the first time. – sj95126 Aug 28 '21 at 15:03

2 Answers2

0

If you enable code optimizations the generated code will not call malloc at all.

.LC0:
        .string "%d"
main:
        sub     rsp, 24
        mov     edi, OFFSET FLAT:.LC0
        xor     eax, eax
        lea     rsi, [rsp+12]
        mov     DWORD PTR [rsp+12], 0
        call    __isoc99_scanf
        mov     eax, DWORD PTR [rsp+12]
        cmp     eax, 1
        je      .L4
        cmp     eax, 2
        je      .L6
        xor     eax, eax
        add     rsp, 24
        ret
.L4:
        mov     eax, 1073741824
.L3:
        sub     rax, 1
        jne     .L3
        jmp     .L4
.L6:
        jmp     .L6

To prevent it make pointer volatile:

int main(void){
  int x=0;
  scanf("%d",&x);
  if(x==1){
    int * volatile a=malloc(1073741824*sizeof(int));
    while(1){
      for(size_t i=0;i<1073741824;i++){
        a[i]=1;
      }
    }
  }
  else if(x==2){
    int * volatile b=malloc(1073741824*sizeof(int));
    while(1);
  }
  return 0;
}

Now it should malloc in both cases:

.LC0:
        .string "%d"
main:
        sub     rsp, 24
        mov     edi, OFFSET FLAT:.LC0
        xor     eax, eax
        lea     rsi, [rsp+4]
        mov     DWORD PTR [rsp+4], 0
        call    __isoc99_scanf
        mov     eax, DWORD PTR [rsp+4]
        cmp     eax, 1
        je      .L10
        cmp     eax, 2
        je      .L11
        xor     eax, eax
        add     rsp, 24
        ret
.L10:
        mov     edi, 1
        sal     rdi, 32
        call    malloc
        mov     QWORD PTR [rsp+8], rax
.L4:
        mov     eax, 1073741824
.L3:
        mov     rdx, QWORD PTR [rsp+8]
        sub     rax, 1
        jne     .L3
        jmp     .L4
.L11:
        mov     edi, 1
        sal     rdi, 32
        call    malloc
        mov     QWORD PTR [rsp+8], rax
.L6:
        jmp     .L6
0___________
  • 60,014
  • 4
  • 34
  • 74
0

Demand paging is when you access pages of memory that aren't in Linux's page cache. For example, if you use memory mapping you can access a file on disk as if it were in RAM. When you dereference a memory mapped pointer Linux first checks the page cache for the data; if the page isn't in the cache then it page faults and loads the missing pages from the disk. Your program doesn't see all the on-demand disk access going on behind the scenes.

Demand paging isn't relevant with a fresh malloc() call since there's nothing on disk. What you're seeing is called overcommit. Linux doesn't allocate memory when malloc() is called; it lies and always returns a pointer whether it can satisfy the request or not. The memory is only actually allocated when (if) you access it. If you ask for 4GB of memory but don't do anything with it nothing's actually allocated.

Unsurprisingly, lying doesn't always work out. Linux might not actually be able to satisfy a program's memory needs, but the program has no way of knowing because malloc() didn't return NULL. It's like an airline that overbooks its flights. The airline gives you a ticket, but when you show up at the airport they tell you they ran out of seats and kick you off the flight. You try to access the memory you allocated and Linux panics, invokes the OOM killer, and starts killing processes to free up memory. Who does it kill? Maybe your process. Maybe you're spared and others die. Suffice it to say, overcommit is a controversial feature.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578