Os Unit Iii
Os Unit Iii
MEMORY MANAGEMENT
Background
Logical versus Physical Address Space
Swapping
Contiguous Allocation
Paging
Segmentation
Segmentation with Paging
Background
Program must be brought into memory and placed within a process for
it to be executed.
User programs go through several steps before being executed.
Swapping
A process can be swapped temporarily out of memory to a backing
store, and then brought back into memory for continued execution.
Backing store - fast disk large enough to accommodate copies of all
memory images for all users; must provide direct access to these
memory images.
Major part of swap time is transfer time; total transfer time is directly
proportional to the amount of memory swapped.
Modified versions of swapping are found on many systems, e.g., UNIX
and Windows 95.
Schematic view of swapping
Contiguous Allocation
Main memory usually into two partitions:
o Resident operating system, often held in low memory with
interrupt vector.
o User processes then held in high memory.
Single-partition allocation
o Relocation-register scheme used to protect user processes from
each other, and from changing operating-system code and data.
o Relocation register contains value of smallest physical address;
limit register contains range of logical addresses - each logical
address must be less than the limit register.
Multiple-partition allocation
o Hole - block of available memory; holes of various size are
scattered throughout memory.
o When a process arrives, it is allocated memory from a hole large
enough to accommodate it.
Example
o allocated partitions
o free partitions (holes)
Dynamic storage-allocation problem - how to satisfy a request of size n
from a list of free holes.
o First-fit: Allocate the first hole that is big enough.
o Best-fit: Allocate the smallest hole that is big enough; must
search entire list, unless ordered by size. Produces the smallest
leftover hole.
o Worst-fit: Allocate the largest hole; must also search entire list.
Produces the largest leftover hole.
First-fit and best-fit better than worst-fit in terms of speed and storage
utilization.
External fragmentation - total memory space exists to satisfy a request,
but it is not contiguous.
Internal fragmentation - allocated memory may be slightly larger than
requested memory; difference between these two numbers is memory
internal to a partition, but not being used.
Reduce external fragmentation by compaction.
o Shuffle memory contents to place all free memory together in one
large block.
o Compaction is possible only if relocation is dynamic, and is done
at execution time.
o I/O problem
Latch job in memory while it is involved in I/O.
Do I/O only into OS buffers.
EAT = (m + e) a+ (2m + e) (1 - a) = 2m + e - ma
Decreases memory needed to store each page table, but increases time
needed to search the table when a page reference occurs.
Use hash table to limit the search to one - or at most a few - page-table
entries.
Shared pages
One copy of read-only (reentrant) code shared among processes (i.e.,
text editors, compilers, window systems).
Segmentation - memory-management scheme that
supports user view of memory.
A program is a collection of segments. A segment is a logical unit such
as:
code
local variables
global variables
stack
Example
Logical address consists of a two tuple:
<segment-number, offset>
Sharing
shared segments
same segment number
Protection
With each entry in segment table associate:
validation bit = 0 -> illegal segment
read/write/execute privileges
Allocation
first fit/best fit
external fragmentation
Protection bits associated with segments; code sharing occurs at
segment level.
Since segments vary in length, memory allocation is a dynamic storage-
allocation problem.
Virtual memory also allows the sharing of files and memory by multiple
processes, with several benefits:
o System libraries can be shared by mapping them into the virtual
address space of more than one process.
o Processes can also share virtual memory by mapping the same
block of memory to more than one process.
o Process pages can be shared during a fork( ) system call,
eliminating the need to copy all of the pages of the original
( parent ) process.
Demand Paging
The basic idea behind demand paging is that when a process is swapped in, its pages
are not swapped in all at once. Rather they are swapped in only when the process needs
them. ( on demand. ) This is termed a lazy swapper, although a pager is a more accurate
term.
The basic idea behind paging is that when a process is swapped in, the pager only loads
into memory those pages that it expects the process to need ( right away. )
Pages that are not loaded into memory are marked as invalid in the page table, using the
invalid bit. ( The rest of the page table entry may either be blank or contain information
about where to find the swapped-out page on the hard drive. )
If the process only ever accesses pages that are loaded in memory ( memory
resident pages ), then the process runs exactly as if all the pages were loaded in to
memory.
Figure 9.5 - Page table when some pages are not in main memory.
On the other hand, if a page is needed that was not originally loaded up, then a page
fault trap is generated, which must be handled in a series of steps:
1. The memory address requested is first checked, to make sure it was a valid
memory request.
2. If the reference was invalid, the process is terminated. Otherwise, the page must
be paged in.
3. A free frame is located, possibly from a free-frame list.
4. A disk operation is scheduled to bring in the necessary page from disk. ( This will
usually block the process on an I/O wait, allowing some other process to use the
CPU in the meantime. )
5. When the I/O operation is complete, the process's page table is updated with the
new frame number, and the invalid bit is changed to indicate that this is now a
valid page reference.
6. The instruction that caused the page fault must now be restarted from the
beginning, ( as soon as this process gets another turn on the CPU. )
Figure 9.6 - Steps in handling a page fault
In an extreme case, NO pages are swapped in for a process until they are requested by
page faults. This is known as pure demand paging.
In theory each instruction could generate multiple page faults. In practice this is very rare,
due to locality of reference, covered in section 9.6.1.
The hardware necessary to support virtual memory is the same as for paging and
swapping: A page table and secondary memory. ( Swap space, whose allocation is
discussed in chapter 12. )
A crucial part of the process is that the instruction must be restarted from scratch once
the desired page has been made available in memory. For most simple instructions this
is not a major difficulty. However there are some architectures that allow a single
instruction to modify a fairly large block of data, ( which may span a page boundary ),
and if some of the data gets modified before the page fault occurs, this could cause
problems. One solution is to access both ends of the block before executing the
instruction, guaranteeing that the necessary pages get paged in before the instruction
begins.
Obviously there is some slowdown and performance hit whenever a page fault occurs
and the system has to go get it from memory, but just how big a hit is it exactly?
There are many steps that occur when servicing a page fault ( see book for full details ),
and some of the steps are optional or variable. But just for the sake of discussion,
suppose that a normal memory access requires 200 nanoseconds, and that servicing a
page fault takes 8 milliseconds. ( 8,000,000 nanoseconds, or 40,000 times a normal
memory access. ) With a page fault rate of p, ( on a scale from 0 to 1 ), the effective
access time is now:
( 1 - p ) * ( 200 ) + p * 8000000
= 200 + 7,999,800 * p
which clearly depends heavily on p! Even if only one access in 1000 causes a page fault,
the effective access time drops from 200 nanoseconds to 8.2 microseconds, a
slowdown of a factor of 40 times. In order to keep the slowdown less than 10%, the
page fault rate must be less than 0.0000025, or one in 399,990 accesses.
A subtlety is that swap space is faster to access than the regular file system, because it
does not have to go through the whole directory structure. For this reason some
systems will transfer an entire process from the file system to swap space before
starting up the process, so that future paging all occurs from the ( relatively ) faster
swap space.
Some systems use demand paging directly from the file system for binary code ( which
never changes and hence does not have to be stored on a page operation ), and to
reserve the swap space for data segments that must be stored. This approach is used
by both Solaris and BSD Unix.
9.3 Copy-on-Write
The idea behind a copy-on-write fork is that the pages for a parent process do not have
to be actually copied for the child until one or the other of the processes changes the
page. They can be simply shared between the two processes in the meantime, with a bit
set that the page needs to be copied if it ever gets written to. This is a reasonable
approach, since the child process usually issues an exec( ) system call immediately after
the fork.
Figure 9.7 - Before process 1 modifies page C.
Obviously only pages that can be modified even need to be labeled as copy-on-write.
Code segments can simply be shared.
Pages used to satisfy copy-on-write duplications are typically allocated using zero-fill-on
-demand, meaning that their previous contents are zeroed out before the copy proceeds.
Some systems provide an alternative to the fork( ) system call called a virtual memory
fork, vfork( ). In this case the parent is suspended, and the child uses the parent's
memory pages. This is very fast for process creation, but requires that the child not
modify any of the shared memory pages before performing the exec( ) system call. ( In
essence this addresses the question of which process executes first after a call to fork,
the parent or the child. With vfork, the parent is suspended, allowing the child to execute
first until it calls exec( ), sharing pages with the parent in the meantime.
In order to make the most use of virtual memory, we load several processes into
memory at the same time. Since we only load the pages that are actually needed by each
process at any given time, there is room to load many more processes than if we had to
load in the entire process.
However memory is also needed for other purposes ( such as I/O buffering ), and what
happens if some process suddenly decides it needs more pages and there aren't any
free frames available? There are several possible solutions to consider:
1. Adjust the memory used by I/O buffering, etc., to free up some frames for user
processes. The decision of how to allocate memory for I/O versus user
processes is a complex one, yielding different policies on different systems.
( Some allocate a fixed amount for I/O, and others let the I/O system contend for
memory along with everything else. )
2. Put the process requesting more pages into a wait queue until some free frames
become available.
3. Swap some process out of memory completely, freeing up its page frames.
4. Find some page in memory that isn't being used right now, and swap that page
only out to disk, freeing up a frame that can be allocated to the process
requesting it. This is known as page replacement, and is the most common
solution. There are many different algorithms for page replacement, which is the
subject of the remainder of this section.
Figure 9.9 - Ned for page replacement.
Note that step 3c adds an extra disk write to the page-fault handling,
effectively doubling the time required to process a page fault. This can
be alleviated somewhat by assigning a modify bit, or dirty bit to each
page, indicating whether or not it has been changed since it was last
loaded in from disk. If the dirty bit has not been set, then the page is
unchanged, and does not need to be written out to disk. Otherwise the
page write is required. It should come as no surprise that many page
replacement strategies specifically look for pages that do not have their
dirty bit set, and preferentially select clean pages as victim pages. It
should also be obvious that unmodifiable code pages never get their
dirty bits set.
There are two major requirements to implement a successful demand
paging system. We must develop a frame-allocation algorithm and
a page-replacement algorithm. The former centers around how many
frames are allocated to each process ( and to other needs ), and the
latter deals with how to select a page for replacement when there are
no free frames available.
The overall goal in selecting and tuning these algorithms is to generate
the fewest number of overall page faults. Because disk access is so
slow relative to memory access, even slight improvements to these
algorithms can yield large improvements in overall system performance.
Algorithms are evaluated using a given string of memory accesses
known as a reference string, which can be generated in one of ( at least )
three common ways:
1. Randomly generated, either evenly distributed or with some
distribution curve based on observed system behavior. This is the
fastest and easiest approach, but may not reflect real
performance well, as it ignores locality of reference.
2. Specifically designed sequences. These are useful for illustrating
the properties of comparative algorithms in published papers and
textbooks, ( and also for homework and exam problems. :-) )
3. Recorded memory references from a live system. This may be the
best approach, but the amount of data collected can be enormous,
on the order of a million addresses per second. The volume of
collected data can be reduced by making two important
observations:
1. Only the page number that was accessed is relevant. The
offset within that page does not affect paging operations.
2. Successive accesses within the same page can be treated
as a single page request, because all requests after the first
are guaranteed to be page hits. ( Since there are no
intervening requests for other pages that could remove this
page from the page table. )
The prediction behind LRU, the Least Recently Used, algorithm is that
the page that has not been used in the longest time is the one that will
not be used again in the near future. ( Note the distinction between FIFO
and LRU: The former looks at the oldest load time, and the latter looks
at the oldest use time. )
Some view LRU as analogous to OPT, except looking backwards in time
instead of forwards. ( OPT has the interesting property that for any
reference string S and its reverse R, OPT will generate the same number
of page faults for S and for R. It turns out that LRU has this same
property. )
Figure 9.15 illustrates LRU for our sample string, yielding 12 page faults,
( as compared to 15 for FIFO and 9 for OPT. )
Figure 9.15 - LRU page-replacement algorithm.
Finer grain is possible by storing the most recent 8 reference bits for
each page in an 8-bit byte in the page table entry, which is interpreted as
an unsigned int.
o At periodic intervals ( clock interrupts ), the OS takes over, and
right-shifts each of the reference bytes by one bit.
o The high-order ( leftmost ) bit is then filled in with the current
value of the reference bit, and the reference bits are cleared.
o At any given time, the page with the smallest value for the
reference byte is the LRU page.
Obviously the specific number of bits used and the frequency with
which the reference byte is updated are adjustable, and are tuned to
give the fastest performance on a given hardware platform.
The enhanced second chance algorithm looks at the reference bit and
the modify bit ( dirty bit ) as an ordered page, and classifies pages into
one of four classes:
1. ( 0, 0 ) - Neither recently used nor modified.
2. ( 0, 1 ) - Not recently used, but modified.
3. ( 1, 0 ) - Recently used, but clean.
4. ( 1, 1 ) - Recently used and modified.
This algorithm searches the page table in a circular fashion ( in as many
as four passes ), looking for the first page it can find in the lowest
numbered category. I.e. it first makes a pass looking for a ( 0, 0 ), and
then if it can't find one, it makes another pass looking for a ( 0, 1 ), etc.
The main difference between this algorithm and the previous one is the
preference for replacing clean pages if possible.
We said earlier that there were two important tasks in virtual memory
management: a page-replacement strategy and a frame-allocation strategy.
This section covers the second part of that pair.
9.6 Thrashing
The working set model is based on the concept of locality, and defines a working set
window, of length delta. Whatever pages are included in the most recent delta page
references are said to be in the processes working set window, and comprise its current
working set, as illustrated in Figure 9.20:
The selection of delta is critical to the success of the working set model - If it is too
small then it does not encompass all of the pages of the current locality, and if it is too
large, then it encompasses pages that are no longer being frequently accessed.
The total demand, D, is the sum of the sizes of the working sets for all processes. If D
exceeds the total number of available frames, then at least one process is thrashing,
because there are not enough frames available to satisfy its minimum working set. If D
is significantly less than the currently available frames, then additional processes can be
launched.
The hard part of the working-set model is keeping track of what pages are in the current
working set, since every reference adds one to the set and removes one older page. An
approximation can be made using reference bits and a timer that goes off after a set
interval of memory references:
o For example, suppose that we set the timer to go off after every 5000 references
( by any process ), and we can store two additional historical reference bits in
addition to the current reference bit.
o Every time the timer goes off, the current reference bit is copied to one of the two
historical bits, and then cleared.
o If any of the three bits is set, then that page was referenced within the last 15,000
references, and is considered to be in that processes reference set.
o Finer resolution can be achieved with more historical bits and a more frequent
timer, at the expense of greater overhead.
A more direct approach is to recognize that what we really want to control is the page-
fault rate, and to allocate frames based on this directly measurable value. If the page-
fault rate exceeds a certain upper bound then that process needs more frames, and if it
is below a given lower bound, then it can afford to give up some of its frames to other
processes.
( I suppose a page-replacement strategy could be devised that would select victim
frames based on the process with the lowest current page-fault frequency. )
Figure 9.21 - Page-fault frequency.
Note that there is a direct relationship between the page-fault rate and the working-set,
as a process moves from one locality to another:
Rather than accessing data files directly via the file system with every file access, data
files can be paged into memory the same as process files, resulting in much faster
accesses ( except of course when page-faults occur. ) This is known as memory-
mapping a file.
Basically a file is mapped to an address range within a process's virtual address space,
and then paged in as needed using the ordinary demand paging system.
Note that file writes are made to the memory page frames, and are not immediately
written out to disk. ( This is the purpose of the "flush( )" system call, which may also be
needed for stdout in some cases. See the timekiller program for an example of this. )
This is also why it is important to "close( )" a file when one is done writing to it - So that
the data can be safely flushed out to disk and so that the memory frames can be freed
up for other purposes.
Some systems provide special system calls to memory map files and use direct disk
access otherwise. Other systems map the file to process address space if the special
system calls are used and map the file to kernel address space otherwise, but do
memory mapping in either case.
File sharing is made possible by mapping the same file to the address space of more
than one process, as shown in Figure 9.23 below. Copy-on-write is supported, and
mutual exclusion techniques ( chapter 6 ) may be needed to avoid synchronization
problems.
Figure 9.22 Memory-mapped files.
Figure 9.25
All access to devices is done by writing into ( or reading from ) the device's registers.
Normally this is done via special I/O instructions.
For certain devices it makes sense to simply map the device's registers to addresses in
the process's virtual address space, making device I/O as fast and simple as any other
memory access. Video controller cards are a classic example of this.
Serial and parallel devices can also use memory mapped I/O, mapping the device
registers to specific memory addresses known as I/O Ports, e.g. 0xF8. Transferring a
series of bytes must be done one at a time, moving only as fast as the I/O device is
prepared to process the data, through one of two mechanisms:
o Programmed I/O ( PIO ), also known as polling. The CPU periodically checks the
control bit on the device, to see if it is ready to handle another byte of data.
o Interrupt Driven. The device generates an interrupt when it either has another
byte of data to deliver or is ready to receive another byte.