Virtual Memory

Operating Systems — Lecture 04

Three things that cannot be

$ ps -e -o pid,vsz,rss,comm --sort -vsz | head -2
    PID    VSZ   RSS COMMAND
  32573 1544448496 879760 chrome          1.44 TiB, on 59 GB of RAM

$ ps -o vsz,rss -p $(pidof allocate_gb)
       2752  1824                         before 10,240 x mmap(1 GB)
10737420992  1824                         after: 10 TiB, same RSS

pid 1209178: &value = 0x404040, value = 1209178
pid 1209182: &value = 0x404040, value = 1209182

If an address were a place in RAM, none of these would be possible.

The story

What we know: a list of areas

after 1024 x malloc(4 KB)   4224    4108    4108 rw---   [ anon ]
after mmap(4 MB)            4096       0       0 rw---   [ anon ]

Both added 4 MB to the list. The list does not say which is in RAM.

One list per process, and it is only a list

struct zone { unsigned long start, size; unsigned int permissions; struct zone *next, *prev; };

z->size += extra_space;                      /* extend a zone  */
add_zone_to_vas(pvas_head, z_new);           /* or add one     */
  • 0x404040 is a name inside a process; the kernel keeps the spaces apart.
  • Allocating adds to the list. Nothing else happens. We call it reserving.
  • “Virtual”: a promise about addresses, not a set of bytes.

Two kinds of memory

virtual physical
belongs to a process, one space each the machine, one of it
how large what the process reserves (up to 128 TiB) what is installed
what it is addresses, a promise the bytes
used by every program, through every address nobody directly

VSZ and RSS

    PID    VSZ   RSS CMD
1195603   2752  1696 ./reserve_mem      before
1195603   6848  1696 ./reserve_mem      after mmap(4 MB)
1195603  10944  1696 ./reserve_mem      after mmap(4 MB)
1195603  15040  1696 ./reserve_mem      after mmap(4 MB)
1195603   2752  1696 ./reserve_mem      after 3 x munmap()
VSZ RSS
./hello 2,752 KB 1,696 KB
chrome 1,544,448,496 KB 879,760 KB

What a virtual size is made of

Reserve, allocate, map

step who when
reserve mmap(), brk(), malloc(), the loader at the call
allocate the kernel at the first access to each page
map the kernel right after, same page

Pay on first touch

Touch half, pay for half

                              VSZ   RSS  faults
after mmap(4 MB)             6848  1696      65
after writing to 512 pages   6848  3744     577      +2048 KB, +512 faults

$ ./page_faults
mmap():                          0 page faults
first write to every page:    1024 page faults
second write to every page:      0 page faults

Once per page, and never again while the page stays in memory.

Pages and frames

0x7cc0081e7abc  =  page 0x7cc0081e7  +  offset 0xabc

4 KB each. Any page to any frame. Keep the offset, swap the page number for the frame number.

The page table

struct page_table_entry { bool valid; unsigned int frame_index; unsigned int permissions; };
struct page_table_entry page_table[N];     /* index: the virtual page number; one per process */

Translated on every access

$ sudo ./pagemap
  page 0: virtual 0x7e62419b6000  index 0x7e62419b6  present 1  frame 0x3bef35
  page 2: virtual 0x7e62419b8000  index 0x7e62419b8  present 1  frame 0x8b48ec
  page 3: virtual 0x7e62419b9000  index 0x7e62419b9  present 0  frame 0x0
$ sudo ./shared_frame
pid 1209193: printf() at 0x76398c263f90 -> frame 0xfac4bd
pid 1209195: printf() at 0x722fcc463f90 -> frame 0xfac4bd

All addresses are virtual.

Each process has a space of its own, a list of zones; allocating only adds to the list.

Physical memory arrives at the first touch of each page, through a page fault, once.

The page table, one per process, maps pages to frames, and the hardware reads it on every access.