---
title: "virtual memory"
source: https://www.jemoka.com/posts/kbhvirtual_memory/
---

We are trying to share a resource: memory; memory allows multiple processes to use a share pool of memory.
key goals multitasking: multiple processes should be able to use memory transparency: no process need to know that memory is shared; each process should be able to run regardless of the number/locations of processes isolation: processes shouldn&rsquo;t be able to corrupt other processes&rsquo; memory efficiency: shouldn&rsquo;t be degraded by sharing virtual memory The operating system will translate virtual addresses (which are 0 based for every program, which isn&rsquo;t a problem) to physical addresses in memory.
the OS doesn&rsquo;t need to map all virtual addresses unless its needed (i.e. if the program is asking for it) worst case: we can kick out unused memory into disk, and load it back when needed This is an example of virtualization.
OS memory Whenever a process makes a syscall, OS will be handed virtual memory addresses. How do we resolve it?
Solution: *every process reserves some virtual memory for the OS&mdash;all of these virtual addresses maps to the SAME PHYSICAL REGION for the OS.
Then, the page map will have a kernel bit which marks this virtual region no read and no write.
dynamic address translation The system will die if we try to do virtual mapping to physical mapping.
So we have a Memory Management Unit (MMU) to do:
How does an MMU work?
base and bound This is basically load-time relocation, but with virtual memory.
assign a location in physical memory, call the base; during translation, we just add every virtual address by the base we can cap the virtual address space for each process by a bound, we can raise a bus error/segfault if it goes above the highest allowable The bound is a virtual address (the first invalid address in the virtual world), whereas the base is a physical address. This is both stored in the process control block.
last possible address: is (bound - 1)+base
translation compare virtual address to bound, trap and raise if &gt;= bound then, return virtual address + base importantly, we can arbitrary adjust base and bound.
tradeoff good news
inexpensive: just doing addition doesn&rsquo;t require additional space: (just two addresses) separation: virtualization. bad news
one contiguous region: need to allocate free spcae fragmentation: because of the above growing can only happens upwards with bounds (and its kind of useless)&mdash;we can&rsquo;t move the stack up in virtual space, and we can&rsquo;t give more space downwards, because that would cause negative addresses no read only memory (we&rsquo;ll want to limit access to code segment, for instance) multiple segments Let&rsquo;s break up multiple virtual address space into segments, and map each of those segments separately. EACH SEGMENT will have its own base and bound. So, you will store each struct in a map: [segment number: [segment base, segment bound, read only or not]].
translation look up what segment a virtual address is in (we can do this by making the top couple bits of the virtual address the segment number, and the next bits as the offset into the segment) get that segment&rsquo;s info compare that address&rsquo; offset to that segment&rsquo;s bound, if its &gt;= limit, trap otherwise, to go the base of that segment and fetch data tradeoff features
you can recycle segments: if you have two instances of a program running, we can actually share read-only segments (such as code). you can not map the middle: because stack and data segments are independent, we can not map the hole in the middle until more data is asked you can grow things: if you run out of continuous space, you can grow the segment by either just doing it or by moving it and growing it (and indeed we now can move the stack down as the stack is addressed as the highest address) drawbacks
growing can only happens upwards with bounds&mdash;now that we can move the heap independently, growing the heap makes sense now; however, growing the STACK still is impossible because growing the stack would entail moving the base address in order to go downwards variable length segments&mdash;extrernal fragmentation! small number of segments&mdash;the [segment, offset] deign divides virtual addresses, so you have to decide segment number exogenously paging So let&rsquo;s instead allocate memory in pages. Instead of variable-length segments that can GROW in base and bound and multiple segments, let&rsquo;s force a specific size of memory in each chunk.
virtual address: virtual page number + offset physical address: physical page number + offset we map each page independently, and keep the offset. If a page is unused, internal fragmentation but not too bad. The stack can now grow downwards: because if it reaches into lower page numbers we can just map that page somewhere too.
To store page mappings, in a seperate storage location, we store a page map/page table: its an array of tuples, where the index is the virtual page number, and each entry has [(physical page, writable)].
Notice that page continuity isn&rsquo;t a problem: the upper digits just count up, and the lower digits tells you offset in that chunk:
0x0000 - 0x0fff 0x1000 - 0x1fff 0x2000 - 0x2fff where, the first digit tells you the page number
0x0 - 0x0 0x1 - 0x1 0x2 - 0x2 and the rest is the offset.
And everything is contiunous, and automatically paged.
For instance, typically page sizes are 4kb
Page Size Offset Number Digits 4096 bytes (16^3) 3 then the rest of the address would just be the page number.
Intel&rsquo;s implementation Virtual Addresses
Unused (16 bits) Virtual page number (36 bits) Offset (12 bits) Physical Addresses
Page number (40 bits) Offset (12 bits) translation chop off page number and offset translate the page number concat the two together internal fragmentation why not something simpler? single-tasking memory very bad idea:
ASSUME that there is only one process. Stack grows down, data grows up, and code sits at the bottom.
tradeoff no isolation: even in this case, nothing is stopping the program from accessing memory in the OS reserve segment; which is bad. no multitasking: because, well, we have one program fragmentation: little bits of space all over the place load-time relocation separate processes.
When program is compiled, it assumes that its initial address is 0x0; so, at load time, we have to go into the code segment when the program is set up and increment all of its memory addresses up.
tradeoff no isolation: nothing is stopping the program from accessing memory in otherbody&rsquo;s segments must decide the memory usage of a program ahead of time + cannot grow if needs more memory (we can&rsquo;t move because the addresses would be in stack) external fragmentation (normal alloc problems)
