Memory Mapping

CS 321 2007 Lecture, Dr. Lawlor

(You should really read the Silberschatz book Chapter 8 and Chapter 8.7 for more details on the hardware implementation of this.  Also see Section 3.7 & 3.8 of the Intel x86 system programming guide.  Finally, check out this 2006 lecture note for pictures of memory mapping actually in progress.)

So your program's memory doesn't actually correspond 1-to-1 with the system's physical RAM; there's one layer of indirection called the "page table" that maps program "virtual addresses" into real "physical addresses".

There's a silly problem that if every byte of memory had one (say 4-byte) entry in the page table, *most* of your memory would be used just to store the page table!  So all real machines break up virtual memory into fairly large "pages" (around 4KB to 4MB in size) that all get mapped to adjacent places in physical memory.  This cuts the size of the page table by the page size; so for a 4GB virtual address space, instead of needing an absurd 16GB for 4 billion 32-bit byte pointers, you need just 16MB for 4 million 32-bit page pointers.  You can cut the space required even further by paging the page table--split up the page table into pieces that are pointed to by an even bigger table.

For example, 32-bit x86 first looks up a page directory (1024 32-bit pagetable pointers), each of which points to a page table (1024 32-bit page pointers), and each of these page table entries gives the physical address for a 4KB block of memory, one page.  64-bit machines are even worse, since their virtual address space is so much bigger; x86-64 uses four layers of tables before you finally reach the page you need!

If the system's designers weren't careful, looking up each memory access via two or four tables would result in memory accesses being 2x to 4x slower!  Luckily, modern machines use a special "pagetable cache" called the "translation lookaside buffer" or TLB.  The TLB just stores the virtual-to-physical mappings for the most recently accessed pages--if most TLB accesses are cache hits, memory accesses will be fast.  When you access a new page not in the TLB (a TLB miss), the CPU (or on PowerPC, a software interrupt) has to walk the page table to fill the TLB before the access can happen.  This can be slow, so sometimes people will choose large page sizes just so the TLB's fixed number of entries covers more memory!  A typical TLB holds from 32 to 128 pagetable entries, which is only 128KB to a few hundred megs depending on the page size.

So here's what the CPU does for a typical memory access:
  1. Program asks for a byte at virtual address 0xf00dead. 
  2. That's part of the page starting at virtual address 0xf00d000 (4K == 4096 == 212, so page addresses always end in 12 zero bits, or 3 hex zeros).
  3. Luckily, the TLB contains the entry for the page at virtual address 0xf00d000.  The physical address for that page is 0xcafe000.
  4. So the physical address we need is 0xcafeead (we've stuck on the low 12 bits from the original request).
  5. We check for this physical address in our cache.  It's there, so we return the program that byte.
In step 3, if the TLB didn't contain that entry, we'd find the page directory (0xf0-----), then index the page table (0x--0d---) to find the physical address.  We'd also stick this mapping into the TLB.  If that entry in the page table isn't valid, we segfault.

A page table entry usually contains a bunch of access control bits indicating what operations are allowed by whom on that page.  For example, a page can be marked readonly to a particular process by just flipping a bit in that page's page table entry.

Bottom line: the pagetable is the cool CPU hardware support the OS needs in order to do crazy stuff with memory.  For example, we talked about how fork() keeps nominally-separate copies of the new program's memory using the fancy "copy-on-write" technique (Silberschatz Chapter 9.3).  Copy-on-write is used all over in modern machines.
 

UNIX Mapping

The UNIX system calls to manipulate the page table are:
Here's an example of how to call mmap, to get 1MB of readable, writeable memory.  The first argument is a "suggested address" where you want the memory to go; try putting some values in there and see what happens!
#include <sys/mman.h>

int foo(void) {
int len=1024*1024;
void *addr=mmap((void *)0,len, /* address and data size (bytes) */
PROT_READ+PROT_WRITE,MAP_ANONYMOUS+MAP_SHARED, /* access flags */
-1,0); /* <- file descriptor (none needed) and file offset */
if (addr==MAP_FAILED) {perror("mmap"); exit(1);}

int *buf=(int *)addr; /* <- make mmap'd region into an int pointer */
buf[3]=7;
buf[2]=buf[3];
printf("mmap returned %p, which seems readable and writable\n",addr);
munmap(addr,len);

return 0;
}
(executable NetRun link)

Windows Memory Mapping

The Windows calls to manipulate the page table are: