14. The Memory Subsystem

Part of 22C:60, Computer Organization Notes
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

Background

Up to this point, we have assumed a very simple model of main memory, where the address output by the processor goes straight to the memory without modification, and where the data path to and from memory is not interrupted by anything. An abstract view of this naive model is given in this figure.

A naive model of memory
Naive model of memory

There are two problems with this model. The first is one of speed! A typical processor of the year 2000 could execute about 1 billion instructions per second, while typical memory of that year could handle only about 20 million memory references per second. Central processors are much faster than memory!

Processors designed with vacuum tubes in 1950 or those designed with integrated circuits in 1965 or those designed as microprocessors in 1980 were generally about the same speed as main memory. On such processors, the naive model was no problem. By 1970, however, transistorized supercomputers had central processors that were significantly faster than the main memory, and by 1980, the difference had increased, although it took decades for the speed difference to reach today's extreme.

Our solution to this problem is to use what is called a cache memory between the central processor and the main memory. Cache memory takes advantage of the fact that, with most memory technologies, we have had a choice between building large but slow memories or small but fast memories. This was known as far back as 1946, when Berks, Goldstine and VonNeumann proposed the use of a memory hierarchy, with a few fast registers in the central processor at the top of the hierarchy, a large main memory in the middle, and a library of archival data, stored off-line, at the very bottom.

A cache memory sits between the central processor and the main memory. During any particular memory cycle, the cache checks the memory address issued by the processor. If this address matches the address of the data in one of the few memory locations held in the cache, the cache finishes the memory cycle very quickly; this is called a cache hit. If the address does not match, the cache cannot help and the request goes to main memory; memory; this is called a cache miss. All cache systems implement some policy to determine which memory locations are held in the fast memory inside the cache and when this set will change. This is called the cache replacement policy.

Adding a cache to the naive view
Memory with a cache

A cache constantly monitors the data and address busses that connect the processor to the main memory. This requires that this bus be designed to allow such eavesdropping, and it requires that the memory be designed to allow a cache to preempt it, forcing the premature completion of a memory cycle that would otherwise take much longer.

Without cache memory, the applications programmer would have to explicitly transfer data between the large main memory and the small but fast cache. The cache automates this. It is natural to ask, can we automate the transfer of data between other levels in the memory hierarchy? The answer to this question is a firm yes. In 1962, Ferranti Ltd., working with Manchester University solved this problem with the introduction of the Atlas computer, the first computer to incorporate what was later called a paged memory management unit and to support virtual memory.

By 1970, several commercial systems supported virtual memory and IBM was scrambling to support it on their big computers. By 1985, several microcomputer-based virtual memory systems were being sold, and by 2000, virtual memory technology was universally available on all but the smallest computers.

Put simply, virtual memory is present in any system where the memory addresses issued by the program are transformed, by any mechanism, to produce the memory addresses actually seen by the memory hardware. The mechanism that performs this transformation has come to be called a memory management unit, although it has gone under other names in older systems.

Adding a memory management unit
A Memory Management Unit between the CPU and Memory

Memory management units take the address issued by the central processor and replace it with some other address, allowing those parts of memory that the current program is using to be stored in a relatively small main memory, while unneeded parts of memory are stored on another slower memory such as a disk. So long as the program never uses the other memory, it runs at full speed. When it tries to address a word that is not currently in main memory, the memory management unit forces a trap. The trap handler in the operating system responds by bringing the required data into main memory. To do this, it typically has to move other data out of main memory to make room. This activity is called a page fault.

With our simple memory model, we spoke simply of the address of a word. Adding a memory management unit forces us to distinguish between two different kinds of address. The address output by the CPU that as input to the memory management unit is the virtual address, while the output of the memory management unit is the physical address. The memory management unit translates virtual addresses to physical addresses.

Some authors, primarily British, refer to a virtual address as a name while they refer to a physical address as simply an address. They describe application programs as operating in terms of the (numeric) names of variables, while the memory holds these variables in specific addresses. In these terms, the memory management translates names to addresses, much as a village postman might have done in the era when letters were addressed to people and it was up to the postman to know where they lived.

Memory management units were invented to automate data transfers between levels in the memory hierarchy, but they have a second use. The memory management unit can be used to completely isolate different programs from each other. Prior to the advent of virtual memory, an erroneous or malicious program could corrupt data anywhere in memory. With a memory management unit, we can arrange things so that programs can only touch the parts of memory they are authorized to use.

Typical modern computers contain multiple caches plus a memory management unit. An instruction cache or I cache holds only instructions, speeding the fetch phase of the fetch-execute cycle. A data cache or D cache holds only operands, and serves to speed the execute phase of the fetch-execute cycle. Sometimes, instead of separate I and D caches, these are combined into a single on-chip cache, the L1 cache. In addition, it is common to find an L2 cache that is outside the processor chip, and there are systems that use an L3 cache; sometimes each memory chip has a separate L3 cache. The cache levels L1 and L2 are counted moving from the processor toward memory. Thus, a complete system might be even more complex than that shown here:

A complete system
A complete system with I cache, D cache, MMU and L2 cache

In the above diagram, the central processor has two separate interfaces to memory, each with its own cache. Most modern processors are pipelined, so the next instruction is fetched while its predecessor is executed. If the execute phase of one instruction involves a memory reference, for example for a load or store instruction, the processor can usually do this in parallel with the instruction fetch. So long as one or the other cache has a hit, the two memory operations will take place in parallel. Only when both caches have a miss will the bus arbitration logic force the two memory references to take place in sequence, finishing the load or store first before allowing the next instruction fetch.

Exercises

a) In the last figure above, identify the various caches according to type (I cache, D cache, L1 cache, L2 cache, L3 cache, etc). There may be multiple caches of one kind and none of some other kind. Also note that multiple names may be applicable to some of the caches.

b) In the last figure above, if the mapping performed by the memory management unit is changed, the contents of some of the caches must be erased. Which ones? Why?

c) Suppose input/output devices are connected to the memory buss. Explain why the presence of cache memory causes problems when a program tries to read a device registers. Hint: Consider the effect of a cache on polling loop testing a device-ready bit.

Building a Simple Cache

How can a cache be built from a small, fast random-access memory and some auxiliary logic? If our memory address space allows a 32-bit word of memory to be selected by a 30 bit address, this means that main memory could be up to a gigaword, 230 words. Consider accessing this through a cache holding 32 kilowords, 215 words. This might be the L2 cache on a modern computer. Such a small memory can be much faster than main memory. If one memory cycle on main memory takes 50 nanoseconds, our L2 cache might have a 5 nanosecond cycle. The internal L1 caches, the I cache and D cache, might have cycle times of 0.5 nanoseconds in the same system.

One way to describe the function of a cache is in algorithmic terms. We can describe the cache function in algorithmic terms, modeling each memory reference as a call to a cache function by the central processor. The cache access routines begin by searching the cache. If they find the desired data in the cache, they use it, while if they do not, they update the cache by accessing main memory, as described below:

Algorithmic description of cache function, part 1
unsigned int address_table[cachesize];
unsigned int value_table[cachesize];

unsigned int cache_read( unsigned int address )              
{
    int i;
    for (i = 0; i < cachesize; i++) {
        if (address_table[i] == address) { /* hit */
            return value_table[i]
        }
    }
    /* miss */
    i = replacement_policy( 0, cachesize );
    address_table[i] = address;
    value_table[i] = RAM_read( address );
    return value_table[i];
}

Alorithmic description of cache function, part 2
void cache_write( unsigned int address, unsigned int value )
{
    int i;
    for (i = 0; i < cachesize; i++) {
        if (address_table[i] == address) { /* hit */
	    value_table[i] = value;
            RAM_write( address, value );
            return;
        }
    }
    /* miss */
    i = replacement_policy( 0, cachesize );
    address_table[i] = address;
    value_table[i] = value;
    RAM_write( address, value );
    return;
}

The above algorithmic description of a cache is correct, up to a point. This is what a cache does, with two important caveats: First, the replacement policy that determines the location in the cache gets used is completely unspecified. How this policy operates has a big impact on performance. Second, the algorithmic description uses a sequential search to find cache hits, while all modern caches perform this search in parallel so it is much faster.

The easiest ways of searching the cache in parallel have strong effects on the replacement policy. For our 32K cache, consider what happens if we use 15 bits of our 30 bit main memory address to select a word in our small cache. This gives us no choice of replacement policy: For each particular word in main memory, there is exactly one word of cache that can be associated with it. At any instant, that word of the cache will be associated with just one of 32K different words in main memory, so we must store 15 bits of the memory address with that word to remember the particular word in main memory it mirrors.

As a result, each entry in our small cache must hold 15 bits of address along with the 32 bits of data. We call these extra bits the cache tag, and we declare a cache hit if they match the corresponding memory bits. The following diagram describes the resulting design, using the low bits of the address to select the cache line, that is, the word in the cache:

The data flow within a cache
The structure of a simple cache

The logic shown here has one output to the cache controller, hit, indicating that the tag of the addressed word in the cache's memory matches the contents of the address bus. The cache controller for this cache must produce 2 control signals, clock, to indicate when a new data word and its tag should be stored by the cache, and enable to control when a data word from the cache should be put on the data bus.

The job of the cache controller is to clock new data into the cache's internal memory whenever there is valid data on the data bus along with a valid address on the address bus, and, in response to a hit that occurs when there is a read request from the processor, to put the cache's data on the data bus, cutting the memory cycle short.

This cache design is called a write-through cache because it does nothing to accelerate write cycles. Write-back cahces are more complex, but their payoff is lower because most memory cycles issued by most processors are read operations. Write-back caches allow the CPU to continue as if a write to main memory had been completed as soon as the cache gets a copy of the value written. The cache is responsible for completing this write at a later time. As a result, the cache controller for a write-back cache must be able to initiate a write to memory independently of the CPU. The I cache that handles only instruction fetches need not support write cycles at all, and when a system has both L1 and l2 caches, it is fairly common for one of these to be a simple write-through cache.

A control system for this cache must connect with the bus control lines. Here, we will assume that there is a read line that is asserted when the processor attempts to read from memory, and an ack line asserted by memory in response. The read signal indicates that a valid address is already on the address lines, and the ack line indicates that the data from memory is valid. The cache itself will assert the ack line when there is a hit. If we ignore write cycles, for example, because the cache is serving a read-only memory or because the cache is being used as an I cache, no other control signals are involved, and the full cache controller looks like this:

A read-only cache controller
a read-only cache controller

Note here that both the cache controller and the memory can drive the ack signal. If neither drives this signal, it is false. We show this using a bus driver on the cache controller; this driver provides no output if it is not enabled, and provides a constant output of one if it is enabled.

Extending this cache controller to make a write-through cache is not difficult. The primary new requirement is that the cache should record the value written whenever there is a write to memory. To support write operations, we must extend our set of bus control lines by adding a write signal that goes true when a valid address and the data to be written are both present on the bus. Again, we will assume that the memory responds to this with an ack signal to acknowledge completion of the write.

A write-through cache controller
a write-through cache controller

The cache controller and bus interface for a write-back cache are more complex. Such a cache must sometimes initiate memory cycles independently of the CPU, so a write-back cache controller is really an independent but simple processor. Whenever the cache controller needs to write data back to RAM, it initiates its own memory cycles.

Exercises

d) The cache shown above will still work if the address lines are switched so that the most significant bits are used to select a word from the cache's memory and the least significant bits are stored as the tag. There would be no performance change if the addresses issued by the CPU were random. What is it about typical programs that makes this a bad choice?

e) The random access memory used in the cache shown here has a 15-bit memory address. How big is each word of this memory?

Inside the Memory Management Unit

In the simplest virtual memory systems, the virtual address space is broken into fixed size pages, while the physical memory is broken into equal-sized page frames. In such a paged virtual memory, any page used by the CPU can be stored in any frame of physical memory, and the memory management unit's job is to substitute the right frame number into the physical address when given a page number.

the data structure used for this substitution has been called the page table or the memory map. We can describe the function of the memory management unit using programming language notation as follows:

Algorithmic description of a simple MMU
constant unsigned int pagesize = (1 << bits_in_bytefield);

unsigned int physical_address( unsigned int virtual_address )
{
	unsigned int address_in_page = virtual_address & (pagesize - 1);
	unsigned int page_number = virtual_address >> bits_in_bytefield;
	unsigned int frame_number = page_table[ frame_number ];
	return (frame_number << bits_in_bytefield) | address_in_page;
}

The simplest memory management units store the entire page table inside a very small, very fast memory that is part of the memory management unit itself. For example, the Digital Equipment Corporation PDP-11 had a virtual address that was 16 bits in length, and a physical address that was 18 bits in length. The first memory management unit sold with this machine had a structure based on the following outline:

The PDP-11 memory management unit, oversimplified
            Simplified PDP-11 MMU            

On the PDP-11, the top 3 bits of the 16-bit virtual address were used to select one of 8 entries in the page table inside the memory management unit. The simplest way to use each page table entry was to use that entry to provided the top 5 bits of the 18-bit physical address. This solved two problems: First, it quadrupled the amount of main memory that could be attached to the machine, from 64K bytes to 256K bytes, and second, by changing the page table with each change from one running application to another, it was possible to give each application a protected memory address space, thus controlling the damage that could be done by a malfunctioning application.

A possible PDP-11 virtual address layout
               
15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
page byte in page
               

The term page is generally used when talking about the view of memory seen by the program. Thus, each virtual address contains three bits used to specify the page being addressed, with the lower bits of the address specifying the byte or word within that page. In contrast, when speaking about physical addresses, we generally use the term page frame or just frame to refer to a region of memory exactly big enough to hold one page of a program's address space. As a result, the fields of a physical address are generally referred to as the frame field and the word-in-frame or byte-in-frame field.

A possible PDP-11 physical address layout
               
17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
frame byte in frame
               

Another detail shown in the diagram for the PDP-11 memory management unit is a trap-request output from the memory management unit to the central processor. The memory management unit will request a trap if the use being made of a virtual address does not match the permissions associated with the page being referenced. Each page-table entry contains not only the frame number associated with the given page, but also the permissions or access rights for the page. When the central processor issues a memory address, it also indicates the use it intends to make of that address, for example, whether the processor intends a read or a write operation. The permission field of the page table indicates, for each page, what uses are permitted, and the trap logic inside the memory management unit compares the use proposed with the permissions granted.

The operating system software is responsible for setting the access rights associated with each page. Pages containing variables must be marked read-write, while the pages containing instructions and constants are typically marked read-only. Some machines permit more subtle distinctions, for example, allowing pages that contain only instructions and no constants to be marked execute-only, pages containing only constants and not instructions to be marked read-only, and pages containing a mixture of instructions and constants to be marked read-execute. The closer the match between the access rights and the needs of the program, the sooner program malfunctions will be detected.

The figures given here do not show one detail: They do not hint at how the page table is loaded into the memory management unit. On the PDP-11, the 8 registers holding the page table in the memory management unit were accessed as input-output device registers.

Exercises

f) The PDP-11 had byte addressing and a 16-bit word. Therefore, the virtual address has a word-in-page field and a byte-in-word field. Redraw the figures for the PDP-11 address layouts to show this detail.

g) How many bytes were there in a PDP-11 page, as described above.

h) The real PDP-11 memory management unit did not divide the address space into fixed-size pages, but rather, into 8 variable-sized segments, where segments could be any number of 32-byte chunks in size, up to a maximum given by the size of the byte-in-segment field of the virtual address. Each segment could begin with any 32-byte chunk of physical memory, with the remainder of the segment occupying consecutive 32-bit chunks. Each page-table entry, therefore, contained the base address of a segment, in chunks, and the size of that segment, in chunks. Show the layout of the virtual and physical addresses.

i) Given the details given in the previous problem, how many bits are there in each segment-table entry? Give a possible layout for these bits.

j) Given the details given in the previous problems, plus the fact that the memory management unit used an adder to add the chunk-in-segment field of the virtual address to the base-chunk-address from the page table, give a diagram for the data flow through the memory management unit.

Using a Memory Management Unit

As mentioned above, the most elementary use of the memory management unit is to provide each application that shares a computer with a separate virtual address space. As a result, no application is can address any memory other than that specifically given to it by the operating system. Without a memory management unit, programming errors in an appliction can potentially damage other applications or the operating system itself, while with a memory management unit, the consequences of programming errors or mailicious programs are strictly limited. As such, memory management units are essential components secure and fault-tolerant computer systems.

Once each application has been given its own address space, a number of things become possible. Each application can be given a virtual address space that appears entirely private, as if each application were running on a different computer, or the address spaces can be arranged to selectively share memory. It is the possibility of selective sharing of information that makes a single computer running multiple applications more interesting than a number of separate computers isolated from each other. To share a page between two virtual address spaces, we need to edit the page table entries for that page in each address space so that they refers to the same page frame. The contents of that frame will then be visible to both address spaces, as is the case with frames 5 and 6 shown in the figure below.

Two address spaces with shared and private pages
      Shared memory diagram      

An appropriate memory management unit can also allow a small machine to address a large main memory. For example, the maximum possible memory on the original PDP-8 sold by Digital Equipment Corporation in 1965 was 4K 12-bit words. As customers developed applications needing more memory, DEC offered an optional memory management unit that allowed the PDP-8 to address 32K 12-bit words.

The first version of the DEC PDP-11 sold in 1970 supported a maximum memory of 48K 8-bit bytes; the remainder of the 64k address space was used for memory mapped input/output. It is noteworthy that the 16-bit PDP-11 was intended to replace the 12-bit PDP-8, and 48K 8-bit bytes have the same number of bits as 32K 12-bit words. Again, as customers developed applications needing more memory, DEC offered models with a memory management unit supporting a larger physical memory.

Intel's 8080 microprocessor, introduced in the mid 1970s, allowed a maximum memory of 64k bytes. Intel made several changes when they moved from the 8080 to the 8086, but one of the most important was a rudimentary memory management unit allowing this second-generation 16-bit microprocessor to address more than 64K of memory.

The final reason for using a memory management unit is actually the first one that was commercially exploited. A memory management unit plus an appropriate trap handler plus a large slow memory such as a disk can be used to create a virtual address space bigger than the main memory. At any particular time, most of the pages of the virtual address space are stored on disk, while only those pages currently being used are stored in page frames in main memory. When the program attempts to address a page that is not in main memory, the memory management unit forces a trap, called a page fault. This scheme is called demand-paged virtual memory because pages of the application program are transferred between main memory and disk on demand instead of being transferred in anticipation of demand.

The structure of a page-fault trap handler is typically discussed, in some detail, in elementary operating systems courses. In short, the page-fault trap handler must find a free frame in main memory, typically by evicting some page from main memory to disk, and then it must read the page needed by the application from disk into this free frame. Having done this, it can update the page table to reflect the new locations of each of these pages and then return to the application program.

This use of virtual memory requires that the trap hardware have one very specific property: When the memory management unit requests a trap, the saved processor state must be the state immediately prior to fetching the instruction that caused the trap. Without this, the return from trap would not properly retry the instruction that caused the trap. If the hardware does not automatically save this state, it must be possible for the software to undo any side effects of a partially executed instruction. This can be difficult!

Consider the question "how many times was the program counter incremented before the trap occurred?" On the Hawk, the answer depends on the opcode, and without knowing how many times the program counter was incremented, we cannot know which memory location contains the opcode of the instruction that caused the trap. On the Hawk, the hardware solves this problem for us by setting the TPC register to the address of the first halfword of the instruction that caused the trap.

There is a second problem. If all the page frames in RAM are currently in use, which page frame should be empitied so it can hold the page the user needed? This is determined by the trap handler's page replacement policy. As with cache replacement, the particular policy adopted can have a big effect on performance. The worst case is to replace the page that the program is about to use, perhaps on the very next instruction, while the best case is to replace that page that the program will not use for the longest possible time into the future.

Lacking a crystal ball for predicting the future, realistic page replacement policies must estimate the likelihood that pages will be needed in the future by looking at the history of use of each page. The best practical policy is to replace that page that was least recently used. This policy, called LRU replacement, requires the memory management unit to maintain records of the most recent use of each page frame in main memory. Few memory management units do this, so most real operating systems rely on approximations of LRU replacement.

Exercises

k) The Hawk memory management unit records the address of the instruction that caused the trap into the TPC register. Suppose, instead, that it recorded the PC register at the instant of the trap. To allow demand-paged virtual memory under this new scheme, we can reserve a new field of the processor status word to hold how many times the program counter was incremented by the current instruction. First, how big would this field be on the Hawk, and second, what would the trap handler do with the value of this field.

l) The Hawk uses memory-mapped input-output. How can the memory-management unit be used to prevent an application program from directly accessing any input-output devices?

m) If the page table itself is stored in main memory, how can we use the memory management unit to prevent application programs from changing the page table? Failure to do this would prevent virtual memory from being used to achieve any security goals.

The Hawk Memory Management Unit

The simple memory management unit structure illustrated in the previous section has one big problem: It does not scale well. When the virtual address is only 16 bits, with only 8 pages per address space, it is easy to build a very fast random access memory with 8 entries and incorporate this in the memory management unit. Machines with 256 pages in the virtual address space, using an 8-bit page field, have been built using this basic approach, but we cannot simply scale this up for use on a 32-bit machine. Consider the layout of the bits in a Hawk virtual address, a layout that is common on many modern 32-bit processors.
 

The Hawk virtual address format
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
page word in page byte

Storing the page-table for this virtual address format inside the memory management unit would require that the small fast memory there contain 220 entries, about a million. Furthermore, loading a new page table into this memory would require updating all of these million entries, something we cannot afford to do each time the machine switches from one application to another.

The solution to this problem involves the use of cache technology. Instead of storing the entire page table within the memory management unit, we only store that part of the page table relevant to the current program. For historical reasons, this special purpose cache has come to be called a translation lookaside buffer, sometimes abbreviated TLB. So long as the program only references page table entries that are already held in the translation lookaside buffer, the processor runs at full speed. When the program references a new page, however, we have what is called a TLB fault.

Remarkably, the memory management units built in the 1960s and 1970s generally handled TLB faults entirely in hardware, stopping the execution of the program while the memory management unit reached into a page table in main memory to update the contents of the translation lookaside buffer. This requires a memory management unit controller with direct memory access and significant, although limited function processing power. It was only in the 1980s that the designers of what came to be called reduced-instruction-set-complexity architectures or RISC architectures discovered that it was frequently quite reasonable to let software handle TLB faults.

Before we delve into the details of the Hawk memory management unit, it is important to note that the memory management unit of a computer may be turned off. When the computer is first started, the memory management unit is off, and it is up to the software to turn it on after the memory maps are set up and the appropriate trap service routines are installed to catch traps the memory management unit may request. In the case of the Hawk, the most significant bit of the level field of the processor status word, bit 31, determines whether or not the memory management unit is enabled. If this bit is one, the memory management unit is active, while if it is zero, the memory management unit is turned off and virtual addresses remain unchanged when they are presented to memory as physical addresses.

Each field in the Hawk translation lookaside buffer consists of 45 bits, although we will always view this as two 32-bit words with a number of unused bits. Using terminology from cache memory, the first word of each entry holds the tag field, a 20-bit page number. The second word holds the data associated with this, a 20-bit frame number plus 5 bits giving access rights used to protect that frame.

The Hawk TLB entry format
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
page number
frame number C R W X V

If the page number issued by the central processing unit does not match the page number of any entry in the translation lookaside buffer, the memory management unit will request a trap. If the page number issued by the central processing unit matches some entry, the corresponding frame number is substituted into the address in order to create the physical address. The physical address is not used until the permissions from the matching entry are checked against the use the processor is proposing. If the translation lookaside buffer is marked as invalid, or if the proposed operation on memory is not permitted, the memory management unit forces the central processing unit to trap, aborting the execution of the current instruction.

Access control bits of one TLB entry
       
        bitpurpose        
C-- cache enabled
R-- load permitted
W-- store permitted
X-- instruction fetch permitted
V-- entry valid
       

Note that each translation lookaside buffer entry has one special bit, the cache enable bit C that determines whether results from any cache memory will be accepted. This bit should be set, by the operating system software, for all pages in memory that are stored in normal random access memory, but it should be reset for pages of the address space that reference device registers. When a program attempts to read from a device register, it wants the actual contents of that register and not something remembered from the past by a cache sitting between the central processor and the device.

Conceptually, we may describe the behavior of the Hawk MMU by an algorithm, but as with the algorithms we've used to describe the behavior of caches, the Hawk algorithm uses a linear search where the real MMU would use a parallel compare operation. Also note that the size of the Hawk translation lookaside buffer, TLB_size in the following, is hidden from the programmer. All the programmer needs to know is that the translation lookaside buffer holds at least two entries and probably more, and that, each time the MMU requests a trap, it must be given the TLB entry it needs.

Algorithmic description of the Hawk memory management unit
constant unsigned int bits_in_pagesize_field = 12;
constant unsigned int pagesize = 1 << bits_in_bytefield; /* 4096 */

unsigned int physical_address( unsigned int virtual_address )
{
    int i;
    unsigned int address_in_page = virtual_address & (pagesize - 1);
    unsigned int page_number = virtual_address >> bits_in_bytefield;

    for (i = 0; i < TLB_size; i++) {
	if (TLB[i].page_number == page_number) { /* hit */
            unsigned int frame_number = TLB[i].frame_number;
            return (frame_number << bits_in_pagefield) | address_in_page;
	}
    }
    /* miss */
    raise PAGE_FAULT;
}

When the memory management unit requests a trap, it is reported to the operating system as a bus trap, which is to say, control is transferred to location 1016 and the memory management unit is turned off because the level field of the processor status word is set to zero. The trap service routine for the bus trap can then determine the cause of the trap by examining the following resources:

the old level field of the processor status word
Bit 27 of the processor status word will be set if the memory management unit was turned on at the time of the trap. We will use the following definition when testing this bit:
        MMUON   =       27

the trap memory address register, TMA read by CPUGET, set by trap or CPUSET
When a trap occurs, the page number and word-in-page field of the virtual address that caused the trap are encoded here, along with a 2-bit indication of the condition that caused the trap in the least significant two bits that would usually hold byte in page.

The trap handler may read this using the CPUGET instruction, and the CPUSET instruction may be used to change this register. The least significant bits take the following values:

        TMANOTV =       0       ; invalid page, unimplemented memory
        TMANOTX =       1       ; page not executable
        TMANOTW =       2       ; page not writable
        TMANOTR =       3       ; page not readable

the memory management unit data register, MMUDATA
The trap handler may read MMUDATA using the CPUGET instruction; the value returned will be the frame number and permission bits from the translation lookaside buffer entry that matched the virtual address in the TMA register, with the least significant bits reporting the access control bits of the TLB entry. This register is only defined if the MMU is present. It is always zero if the MMU is absent.

The trap handler may write this register using the CPUSET instruction, doing so places the contents of TMA and MMUDATA in some entry of the translation lookaside buffer. The software has no control over what entry is used, but there is a guarantee that the MMU will never contain more than one TLB entry for any particular page number. Each memory management unit will implement some workable replacement policy. The following definitions will be used to reference the fields of this register:

        MMUBITV =       0       ; valid bit
        MMUBITX =       1       ; executable bit
        MMUBITW =       2       ; writable bit
        MMUBITR =       3       ; readable bit
        MMUBITC =       4       ; cacheable bit
        MMUSHIFT=       12      ; shift to align page frame number

After a bus trap, If the cause field from the TMA register is zero (TMANOTV), indicating an invalid memory address, but the valid bit (MMUBITV) in the MMUDATA register is set, then the physical address referred to nonexistant memory.

The design of the Hawk memory management unit allows a trap handler written for a small memory management with a crude replacement policy to continue working if the memory management unit is upgraded to one with a big translation lookaside buffer and a sophisticated replacement policy. The trap handler never needs to know size of the translation lookaside buffer or the replacement policy being used. All that matters from the software perspective is that there are enough entries in the translation lookaside buffer to allow program execution, and that the memory management unit hardware will not use the worst possible replacement policy.

The worst possible replacement policy would be to replace the most recently used entry in the translation lookaside buffer. In most cases, this entry will be the one that is most likely to be used next, since consecutive instructions are frequently executed from the same page. Either random replacement or first-in, first-out replacement would be better, but the best possible TLB replacement policy is the same policy that works best with cache memory and demand paged virtual memory. The best possible TLB replacement policy would be ro replace the least recently used translation lookaside buffer entry, but as with cache and virtual memory systems, typical hardware will only approximate this.
 

Exercises

n) On the Hawk, access to the page table used by the memory management unit is through the CPUSET and CPUGET instructions. Why is it imortant to prevent applications programs from accessing these instructions?

o) Look at the algorithmic descriptions given for the cache-write function and for the Hawk memory management unit and write a corresponding algorithmic description for the operation of writing the MMUDATA register. As with the cache-write function, you can assume a function called replacement-policy that handles that issue.

 

A Simple Hawk Memory Management Unit Trap Handler

The simplest trap handler for the Hawk memory management unit makes it appear as if the MMU translated addresses using a page table stored in RAM. More complex handlers are used on real systems in order to provide the illusion of a large physical memory by moving pages of data back and forth between disk and main memory. These more complex handlers are the subject of extended discussions in operating systems courses, but they are outside the scope of this work.

We will assume that the page table begins at address PAGETABLE, and we will assume that each page table entry is one word, in the format of the Hawk's MMU data register. The number of entries in the page table could potentially be 220, but it may be considerably smaller. Therefore, the first word in the PAGETABLE structure is the size of the table, in words, while the remainder is the actual table.

Our trap handler begins as outlined in the previous chapter, with code to save the trap registers. We will not repeat that code here, but will simply assume that, all registers have been saved in the register save area pointed to by R2 as the body of our handler begins.

The first job the handler must do is examine the cause of the trap. If the MMU was turned on, if it reported an invalid address, and if the TLB entry used was indeed invalid, we can try to update the MMU's translation lookaside buffer from the page table. If not, then there is no point attempting to update the TLB; we must handle this memory reference as an error.

Hawk code to detect MMU misses
; see if the bus trap involves might be caused by an MMU miss
        LOAD    R3,R2,PSWSAVE
        BITTST  R3,MMUON        ; test high bit of old level
        BBR     NOTMIS          ;   if MMU off, not our problem!
        CPUGET  R3,TMA
        TRUNC   R3,2            ; test the 2 low bits of TMA
        BZR     NOTMIS          ;   if nonzero, not a TLB miss
        CPUGET  R3,MMUDATA
	BITTST  R3,MMUBITV	; test the valid bit of TLB entry
        BBS     NOTMIS          ;   if nonzero, TLB entry valid,
                                ;   but it pointed to a bad frame

The label NOTMIS used above, should probably raise an exception on return from trap, using the logic given in the previous chapter. That code is not given here.

The next step, once we have determined that an MMU miss might be involved, is to fetch the page table entry that needs to be stored in the TLB. To do this, we need to use the contents of the TMA register to index into the page table, after checking that the address is within bounds.

Hawk code to fetch one page table entry
; get and check the page number field of the memory address
        CPUGET  R3,TMA
        SRU     R3,MMUSHIFT     ; convert address to pagenum
        LIL     R4,PAGETABLE
        LOADS   R5,R4           ; get size of pagetable, in words
        CMP     R3,R5           ; check if it is within bounds
        BGTU    NOTMIS          ;   if out of bounds, not our problem!
; get page table entry
	ADDSI   R4,4            ; array starts after the table size
        ADDSL   R3,R4,2         ; compute address in page table
        LOADS   R3,R3           ; get page table entry

It is possible that the page table entry is invalid, in which case, we should handle this as an exception instead of updating the TLB. Thus, the final part of our handler begins with a validity check. The actual TLB update is done by setting the MMU data register. A side effect of this store operation takes the page field of the TMA register to build the full TLB entry.
Hawk code to finish the TLB update
; update TLB if page table entry is valid
        BITTST  R3,0            ; test valid bit in page table entry
        BBR     NOTMIS          ;   if not valid, don't update TLB
        CPUSET  R3,MMUDATA
	JUMP    TRAPEXIT        ; return from trap

The above code uses the TRAPEXIT code defined in the previous chapter. In a real system, we would probably write streamlined code for both the bus trap entry and exit code in order to make TLB updates in the minimum possible time. The above trap handler code only registers R2 through R5, so our streamlined trap handler does not need to save or restore registers R6 and up.

If the code for NOTMIS referenced above tries to raise an exception in the code that was running, we have one issue we must deal with: What if the exception handler code itself was the cause of the trap? Simple-minded trap handlers sometimes throw the system into a tight loop in this case, but a well-written trap handler will prevent this. The details of how this is done depend on details we have not explored.

Exercises

p) Assemble the pieces of a Hawk bus-trap trap handler from the previous chapter and this into a complete handler.

q) Optimize the handler you created for the problem above. How many instructions are in the original, and how many in optimized version?