started discussing paging
key terms: physical address, logical/virtual address, MMU, page, frame, offset, address translation
Could just stick all processes into physical memory; allocate each process a block of addresses that it uses. When loading a program, use the linker to relocate all addresses so they are within the process's allocated space
no isolation: nothing prevents processes from stomping on each other (or kernel)
fragmentation: as processes come and go, we end up with holes between the allocated blocks of memory. If a new process appears, we may find a situation where there is enough space to satisfy the new process, but we can't because the space is not contiguous. This situation is referred to as external fragmentation.
processes know their locations: pointers within a process's address space are not relative, so the process cannot be relocated (which we're not doing in this strawman, but we may want to)
processes need to know how much space they need: to allocate slots for each process, we need to know how much space it might need. If we overestimate, we may have internal fragmentation: unused space within an allocated block which we cannot use because it has been allocated.
Suppose we add some simple hardware: a memory management unit (MMU) is a piece of hardware that sits between the processor and the bus. When the processor accesses an address (by loading or storing for example), the MMU transforms the address before sending the request on the bus.
Terminology: the MMU performs address translation by mapping the logical addresses (also called virtual addresses) to physical addresses.
Virtual addresses are used by programs. Pointers will be to virtual addresses; the linker will also map symbolic names to virtual addresses.
Physical addresses are indexes into RAM. Programs do not know which physical addresses store their data.
We can use a simple MMU to deal with some of the memory allocation problems discussed above. If we allocate a range of addresses to a process (lets say 0x1000 of them), we can use the MMU to translate 0x0 to the bottom of the address space, and 0xFFF to the top of the address space.
To do this, we can store two registers in the MMU: base and bounds. The MMU will take a virtual address, add the base register to find the physical address. It can also compare the physical address to the bounds register, and raise an exception if the address is out of range.
The base and bounds registers are set by the OS before context switching to the application.
For example, if P1 is given physical addresses 0x100000 -- 0x105000, then when P1 is scheduled, we set the base and bounds registers to 0x100000 and 0x105000 respectively. If process P1 accesses (virtual) address 0x25FC, the MMU will translate this to 0x1025FC. If P1 tries to access address 0x5500, then the MMU will translate this to 0x105500, but since this is bigger than the bounds register, the MMU will cause an exception, allowing the OS to crash the program.
Note that each process will have its own base and bounds; this information will be stored in the process's PCB (this is the "VM info" we alluded to earlier).
With this simple address translation, we have isolation, and processes do not know where they are located. We still have problems with both internal and external fragmentation.
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, we can swap processes out to disk.
If a new process arrives and there is insufficient space to allocate for it, we can take an existing process and swap it out: move the entire contents of its address space to disk. We can then reclaim its space to allocate to the new process. Later, when that process is scheduled, we can swap 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 process back into the same physical location that we swapped it out of. We simply need to update the base and bounds registers in its PCB.
Note however that swapping is extremely slow: any communication with disk is expensive; copying an entire address space is downright painful. But, if the user wants to run more processes than they have ram, there isn't much choice (but see paging below).
Paging solves many of the remaining problems discussed above. The idea behind paging is that we split a process's logical address space into many small chunks called pages. Pages are typically on the order of a few kilobytes.
In addition, we divide up the physical address space into frames. We are allowed to place any page into any frame.
The terminology can be useful as a guide to understanding what's going on. Think of pages as numbered pages in a three-ring binder. A process (binder) contains a large number of pages. On your desk (physical memory), you have a small number of picture frames. In order to read a page, you must take it out of the binder and place it in a frame.
The advantages of this scheme are many:
each process can have an entire 32-bit address space without requiring 2^32 contiguous bytes of physical memory to be allocated to it.
in fact, with clever management and a little luck, you can use physical memory to store just the pages that the process is actually using (see page replacement, next lecture)
moving pages to the backing store is much quicker than swapping out address spaces: 4k << 4GB. Note that moving pages is still very slow!
it is easy to implement shared memory between processes by just allocating the same page to both of them.
To accomplish paging, we need a more complicated MMU. The MMU must do the following to translate a virtual address:
For example, suppose the page size is 4kb (0x1000b), and the contents of physical memory are:
frame # | physical address | frame contents |
---|---|---|
0x0 | 0x0000 | Process 3, page 0x85 |
0x1 | 0x1000 | Process 2, page 0x02 |
0x2 | 0x2000 | Process 3, page 0x14 |
0x3 | 0x3000 | Process 1, page 0x01 |
Suppose process 3 accesses (virtual) address 0x14303. This address refers to the 0x303rd byte of the 0x14th page of process 3's address space (the offset is 0x303, and the page number is 0x14). The MMU will find that process 3's page 0x14 is mapped into frame 0x2 (which always starts at physical address 0x2000). Therefore, the physical address that it outputs is the 0x303rd byte of the 0x2nd frame, or 0x2303.
The mysterious part of this process is how the MMU knows which page is in which frame. This data is stored in memory in a lookup data structure, called the page table. We will discuss page tables in more detail tomorrow.