Operating Systems — Lecture 04
$ ps -e -o pid,vsz,rss,comm --sort -vsz | head -4
PID VSZ RSS COMMAND
32573 1544448496 879760 chrome
34405 1533182040 904016 Discord
253577 1528348464 346232 signal-desktop
1,544,448,496 KB is 1.44 TiB. The machine has 59 GB of RAM and 80 GB of swap.
for (unsigned int i = 0; i < num_gb; i++)
mmap(NULL, ONE_GB, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);$ ps -o pid,vsz,rss,cmd -p $(pidof allocate_gb)
PID VSZ RSS CMD
1195513 2752 1824 ./allocate_gb 10240 before
1195513 10737420992 1824 ./allocate_gb 10240 after
Ten thousand calls, a fraction of a second, and 1,824 KB resident before and after.
$ ./same_address $ ./same_address
pid 1209178: &value = 0x404040, pid 1209182: &value = 0x404040,
value = 1209178 value = 1209182
pid 1209178: code = 0x401253 pid 1209182: code = 0x401253
Same address, two values. Press Enter in either: its value is still its own.
An address is not a place in RAM. This lecture is about what it is instead.
A new area, or a larger one; an area removed, or a smaller one.
malloc()An allocator in libc; brk() to grow the heap, mmap() for a block of its own.
Kbytes RSS Dirty
after malloc(4 MB) 4100 4 4 rw--- [ anon ] a new area
after mmap(4 MB) 4096 0 0 rw--- [ anon ] a new area
after 1024 x malloc(4 KB) 4224 4108 4108 rw--- [ anon ] the heap, grown
after 1024 x mmap(4 KB) 3704 0 0 rw--- [ anon ] 1024 calls, 4 areas
A thousand mmap()s made four lines: adjacent areas with the same permissions are merged.
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. One has 4 MB in physical memory; the other has none.
The list of areas does not say which. Nothing in lecture 03 did.
0x404040 names a byte in a process; every process has a complete set of names of its own.main() at 0x401253 in both copies.struct zone {
unsigned long start;
unsigned long size;
unsigned int permissions;
struct zone *next, *prev;
};
struct zone *pvas_head;Extend a zone:
Or add one:
z_new = malloc(sizeof(struct zone));
z_new->start = start_address;
z_new->size = extra_space;
z_new->permissions = zone_perm;
add_zone_to_vas(pvas_head, z_new);That is all mmap() did ten thousand times. No byte was found, cleared or handed over.
./allocate_gb 140000 stops with ENOMEM near 128 TiB.| 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 | a set of addresses, a promise | the bytes |
| obtained by | reserving: mmap(), brk(), malloc() |
the kernel, page by page |
| used by | every program, through every address | nobody directly |
| process | VSZ |
RSS |
ratio |
|---|---|---|---|
./hello |
2,752 KB | 1,696 KB | 1.6 |
bash |
10,452 KB | 4,268 KB | 2.4 |
chrome |
1,544,448,496 KB | 879,760 KB | 1,755 |
VSZ: the sum of the zones. RSS: the physical memory held right now. The first is never smaller.
PID VSZ RSS CMD
1195603 2752 1696 ./reserve_mem before
1195603 6848 1696 ./reserve_mem after mmap(4 MB, rw)
1195603 10944 1696 ./reserve_mem after mmap(4 MB, r)
1195603 15040 1696 ./reserve_mem after mmap(4 MB, rwx)
1195603 10944 1696 ./reserve_mem after munmap()
1195603 6848 1696 ./reserve_mem after munmap()
1195603 2752 1696 ./reserve_mem after munmap()
VSZ moves by 4,096 KB a step. RSS is 1,696 KB seven times.
| step | done by | when | costs |
|---|---|---|---|
| reserve | mmap(), brk(), malloc(); the loader; the kernel for the stack |
at the call | an entry in a list |
| allocate | the kernel | at the first access to each page | a page, found and zeroed |
| map | the kernel | right after allocating | a page table entry |
Step 1 happens alone all the time. Steps 2 and 3 never happen apart.
VSZ RSS faults the zone, in pmap
before 2752 1696 65 --
after mmap(4 MB) 6848 1696 65 4096 0 0 rw--- [ anon ]
after writing to 512 pages 6848 3744 577 4096 2048 2048 rw--- [ anon ]
after munmap() 2752 1696 577 --
512 pages written, 2,048 KB resident, 512 page faults. The other 512 pages cost nothing.
$ ./page_faults
mmap(): 0 page faults
first write to every page: 1024 page faults
second write to every page: 0 page faults
Once mapped, the hardware translates on its own; the kernel is out of the picture.
first read of every page: 1024 page faults
then first write: 1024 page faults
Read a fresh page and the kernel maps it to one shared, read-only page of zeroes. Write to it and it faults again, and gets a page of its own: copy-on-write.
VSZ > RSS, resolvedRSS, counted once per process, existing onceThe browser: one terabyte zone, reserved on purpose, almost none of it touched.
For every virtual page touched: which physical page holds it.
That structure is the page table.
Virtual memory in pages, physical memory in frames, 4 KB each. Any page to any frame.
0x7cc0081e7abc = page 0x7cc0081e7 + offset 0xabc
Why 4 KB and not 4 MB: next lecture.
struct page_table_entry {
bool valid;
unsigned int frame_index;
unsigned int permissions;
unsigned int flags;
};
struct page_table_entry page_table[N];valid$ ./pagemap
after mmap(), before any access:
page 0: virtual 0x7cc0081e7000 index 0x7cc0081e7 present 0
page 1: virtual 0x7cc0081e8000 index 0x7cc0081e8 present 0
after writing to pages 0 and 2:
page 0: virtual 0x7cc0081e7000 index 0x7cc0081e7 present 1
page 1: virtual 0x7cc0081e8000 index 0x7cc0081e8 present 0
page 2: virtual 0x7cc0081e9000 index 0x7cc0081e9 present 1
/proc/self/pagemap: one 64-bit entry per page, at offset index * 8. The file is the array.
$ sudo ./pagemap
page 0: virtual 0x7e62419b6000 index 0x7e62419b6 present 1 frame 0x3bef35
page 1: virtual 0x7e62419b7000 index 0x7e62419b7 present 1 frame 0x6c7f90
page 2: virtual 0x7e62419b8000 index 0x7e62419b8 present 1 frame 0x8b48ec
page 3: virtual 0x7e62419b9000 index 0x7e62419b9 present 0 frame 0x0
Three consecutive pages; frames 16 GB, 29 GB and 37 GB into the RAM. Frame 0x6c7f90 is the zero page: page 1 was only read.
$ sudo ./shared_frame
pid 1209193: printf() is at 0x76398c263f90 -> frame 0xfac4bd
$ sudo ./shared_frame
pid 1209195: printf() is at 0x722fcc463f90 -> frame 0xfac4bd
One copy of libc’s code in memory. /proc/kpagecount: 229 page tables point at that frame.
0x404040 twice: two page tables, the same index, two frames.printf() twice: two page tables, two indices, one frame.The MMU raises a page fault for an entry that is not valid, or forbids the access. The kernel decides what it means:
SIGSEGV, SEGV_MAPERR or SEGV_ACCERRSame hardware event. The list of zones tells them apart.
Neither happens. Lecture 05: tables in levels, a cache of translations, and what the kernel does when the page is on disk.
malloc() over brk() and mmap()VSZ against RSS, and what the gap is made ofAll 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.
fork() and copy-on-write.mmap() as a buffer; 32,400 page faults per 63 MiB frame in the malloc() tuning bonus.SIGSEGV is an entry that said no; gdb and valgrind find the access that asked.