CS 537
Lecture Notes Part 7a
More About Paging
Previous Paging
Next Segmentation
Contents
Paging Details
Real-world hardware CPUs have all sorts of “features” that make life
hard for people trying to write page-fault handlers in operating systems.
Among the practical issues are the following.
Page Size
How big should a page be?
This is really a hardware design question, but since it depends on
OS considerations, we will discuss it here.
If pages are too large, lots of space will be wasted by internal fragmentation:
A process only needs a few bytes, but must take a full page.
As a rough estimate, about half of the last page of a process will be
wasted on the average.
Actually, the average waste will be somewhat larger, if the typical
process is small compared to the size of a page.
For example, if a page is 8K bytes and the typical process is only 1K,
7/8 of the space will be wasted.
Also, the relative amount of waste as a percentage of the space used
depends on the size of a typical process.
All these considerations imply that as typical processes get bigger and
bigger, internal fragmentation becomes less and less of a problem.
On the other hand, with smaller pages it takes more page table
entries to describe a given process, leading to space overhead
for the page tables, but more importantly time overhead for any operation
that manipulates them.
In particular, it adds to the time needed to switch form one process to
another.
The details depend on how page tables are organized.
For example, if the page tables are in registers, those registers have
to be reloaded.
A TLB will need more entries to cover the same size “working set,”
making it more expensive and require more time to re-load the TLB when
changing processes.
In short, all current trends point to larger and larger pages in the future.
If space overhead is the only consideration, it can be shown
that the optimal size of a page is sqrt(2se),
where s is the size of an average process and e is
the size of a page-table entry. This calculation is based on balancing
the space wasted by internal fragmentation against the space used for
page tables.
This formula should be taken with a big grain of salt however, because
it overlooks the time overhead incurred by smaller pages.
Restarting the instruction
After the OS has brought in the missing page and fixed up the page table,
it should restart the process in such a way as to cause it to re-try
the offending instruction.
Unfortunately, that may not be easy to do, for a variety of reasons.
- Variable-length instructions
-
Some CPU architectures have instructions with varying numbers of
arguments.
For example the Motorola 68000 has a move instruction with two
arguments (source and target of the move).
It can cause faults for three different reasons:
the instruction itself or either of the two operands.
The fault handler has to determine which reference faulted.
On some computers, the OS has to figure that out by interpreting the
instruction and in effect simulating the hardware.
The 68000 made it easier for the OS by updating the PC as it goes,
so the PC will be pointing at the word immediate following the part
of the instruction that caused the fault.
On the other hand, this makes it harder to restart the instruction:
How can the OS figure out where the instruction started, so that it
can back the PC up to retry?
- Side effects
-
Some computers have addressing modes that automatically increment
or decrement index registers as a side effect, making it easy to
simulate in one step the effect of the C statement *p++ = *q++;.
Unfortunately, if an instruction faults part-way through, it may be
difficult to figure out which registers have been modified so that they
can be restored to their original state.
Some computers also have instructions such as “move characters,” which work
on variable-length data fields, updating a pointer or count register.
If an operand crosses a page boundary, the instruction may fault part-way
through, leaving a pointer or counter register modified.
Fortunately, most CPU designers know enough about operating systems
to understand these problems and add hardware features to allow the
OS to recover.
Either they undo the effects of the instruction before faulting, or
they dump enough information into registers somewhere that the OS can
undo them.
The original 68000 did neither of these and so paging was not possible
on the 68000.
It wasn't that the designers were ignorant of OS issues, it was just that
there was not enough room on the chip to add the features.
However, one clever manufacturer built a box with two 68000 CPUs and an MMU
chip.
The first CPU ran “user” code.
When the MMU detected a page fault, instead of interrupting the first CPU,
it delayed responding to it and interrupted the second CPU.
The second CPU would run all the OS code necessary to respond to the
fault and then cause the MMU to retry the storage access.
This time, the access would succeed and return the desired result to
the first CPU, which never realized there was a problem.
Locking Pages
There are a variety of cases in which the OS must prevent certain
page frames from being chosen by the page-replacement algorithm.
For example, suppose the OS has chosen a particular frame to service
a page fault and sent a request to the disk scheduler to read in the
page.
The request may take a long time to service, so the OS will allow other
processes to run in the meantime.
It must be careful, however, that a fault by another process does not
choose the same page frame!
A similar problem involves I/O.
When a process requests an I/O operation it gives the virtual address
of the buffer the data is supposed to be read into or written out of.
Since DMA devices generally do not know anything about virtual memory,
the OS translates the buffer address into a physical memory location (a
frame number and offset) before starting the I/O device.
It would be very embarrassing if the frame were chosen by the page-replacement
algorithm before the I/O operation completes.
Both of these problems can be avoided by marking the frame a ineligible for
replacement.
We usually say that the page in that frame is “pinned” in memory.
An alternative way of avoid the I/O problem is to do the I/O operation into
or out of pages that belong to the OS kernel (and are not subject
to replacement) and copying between these pages and user pages.
Missing Reference Bits
At least one popular computer, the Digital Equipment Corp. VAX computer,
did not have any REF bits in its MMU.
Some people at the University of California at Berkeley came up with a
clever way of simulating the REF bits in software.
Whenever the OS cleared the simulated REF bit for a page, it
mark the hardware page-table entry for the page as invalid.
When the process first referenced the page, it would cause a page fault.
The OS would note that the page really was in memory, so the fault handler
could return without doing any I/O operations, but the fault would give
the OS the chance to turn the simulated REF bit on and mark the page as
valid, so subsequent references to the page would not cause page faults.
Although the software simulated hardware with a real real REF bit, the
net result was that there was a rather high cost to clearing the simulated
REF bit. The people at Berkeley therefore developed a version of the CLOCK
algorithm that allowed them to clear the REF bit infrequently.
Fault Handling
Overall, the core of the OS kernel looks something like this:
// This is the procedure that gets called when an interrupt occurs
// on some computers, there is a different handler for each "kind"
// of interrupt.
void handler() {
save_process_state(current_PCB);
// Some state (such as the PC) is automatically saved by the HW.
// This code copies that info to the PCB and possibly saves some
// more state.
switch (what_caused_the_trap) {
case PAGE_FAULT:
f = choose_frame();
if (is_dirty(f))
schedule_write_request(f); // to clean the frame
else
schedule_read_request(f); // to read in requested page
record_state(current_PCB);
// to indicate what this process is up to
make_unrunnable(current_PCB);
current_PCB = select_some_other_ready_process();
break;
case IO_COMPLETION:
p = process_that_requested_the_IO();
switch (reason_for_the_IO) {
case PAGE_CLEANING:
schedule_read_request(f); to read in requested page
break;
case BRING_IN_NEW_PAGE:
case EXPLICIT_IO_REQUEST:
make_runnable(p);
break;
}
case IO_REQUEST:
schedule_io_request();
record_state(current_PCB);
// to indicate what this process is up to
make_unrunnable(current_PCB);
current_PCB = select_some_other_ready_process();
break;
case OTHER_OS_REQUEST:
perform_request();
break;
}
// At this point, the current_PCB is pointing to a process that
// is ready to run. It may or may not be the process that was
// running when the interrupt occurred.
restore_state(current_PCB);
return_from_interrupt(current_PCB);
// This hardware instruction restores the PC (and possibly other
// hardware state) and allows the indicated process to continue.
}
Previous Paging
Next Segmentation
Contents
solomon@cs.wisc.edu
Tue Jan 16 14:33:41 CST 2007
Copyright © 1996-2007 by Marvin Solomon. All rights reserved.