Lecture 11: page tables

Swapping (Paging in and out)

It may seem like we can only run as many processes as we have physical memory to store, but in fact if we have a backing store (i.e. a disk), we can move pages that aren't currently being used to disk.

If a new page is needed and there is insufficient space to allocate for it, we can take an existing page and page it out: move the contents of the page to disk. We can then reuse the frame for a new page. Later, when the page is needed again, we can page it in: move it from the disk back into a region of physical memory.

Because processes do not have any information about their physical addresses, we don't have to swap a page back into the same physical location that we paged it out of. We simply need to update the page table to point to the current frame.

Note however that paging is extremely slow; communicating with disk is several orders of magnitude more expensive than communicating with main memory. But, if the user wants to run more processes than they have ram, there isn't much choice.

Note on terminology: most people use the terms "paging" and "swapping" interchangably. Originally, swapping referred to move an entire process's address space to disk (as opposed to a single page) but this is a silly thing to do.

Segmentation

It can be useful to mark different regions of a process's address space with different read/write/execute privileges. For example, a process is typically divided into a kernel area, a heap area, a stack area, a code area, and so forth. These large areas are called segments.

It makes sense to read and write in your heap, but not to jump there; conversely it makes sense to jump into your code section, but not to write it. Any access to unallocated space is an error.

The TLB can help us enforce these conventions. Each TLB entry has additional read, write, and execute bits. While translating an address, the TLB will also check whether the type of access is valid for the corresponding page. If not, it can raise an exception, and the OS can handle it appropriately. This is the source of your favorite C error: a segmentation fault occurs whenever you access a "bad" pointer: a pointer to a page of memory that hasn't been allocated with the corresponding permissions.

Abusing TLB permission bits

TLB permission bits give the OS a way to be interrupted when certain pages are accessed in certain ways (by clearing the corresponding permission bit). This can be used for various things other then protecting segments:

Single level page table

At the end of last lecture, we introduced the notion of paging: divide a large virtual address space into many small pages, which can be independently swapped into and out of frames in physical memory.

To do so, we need to keep a data structure (the page table) for each process mapping page numbers to frame numbers.

The simplest method is to put these into an array: the ith entry in the array gives the frame number in which the ith page is stored.

Size of the page table

note: these numbers are typical, but not worth memorizing: the process by which they are derived is more important.

The page table needs one entry per page. Assuming a 4GB (2^32 byte) virtual and physical address space and a page size of 4kB (2^12 bytes), we see that the the 2^32 byte address space must be split into 2^20 pages.

This means the page table must have 2^20 entries.

How large are the entries?

This gives a total of 25 bits per entry. The math is much easier if we round to bytes: each entry is 4 bytes.

Thus the total size of the page table is 2^20 entries * 2^2 bytes/entry = 2^22 bytes = 4MB.

Hierarchical paging / paged page table

4MB of contiguous space per process is a lot. Moreover, if the process is only using a small part of its address space, we will only need to access a small part of the page table.

Just as with the address space, we can solve these problems by paging the page table itself. For convenience, we can make the pages of the page table (POPTs) the same size as the pages of the process's address space. This allows us to use the same set of frames to store either process data or POPTs.

In our example, each POPT holds 2^12 bytes / 4 bytes per entry = 2^10 entries. Since there are 2^20 total entries in the page table, there must be 2^10 POPTs.

Just like we needed a page table when we split up the address space into pages, we will need a second level page table to tell us where the POPTs are stored. In our example, this table must contain 2^10 entries (one for each POPT), each of which is 4 bytes (it contains a 20 bit frame pointer and additional VDRWX bits). Thus the total size of the top-level page table is 2^10 entries * 2^2 bytes per entry = 2^12 bytes = 4kb. This fits in one page, so there is no reason to split it further.

With different numbers, we could have a very large top-level page table. If so, we could repeat this process by paging the top-level page table (thus introducing another layer of page table).