home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
misc
/
volume44
/
toy_os
/
part01
next >
Wrap
Internet Message Format
|
1994-09-05
|
61KB
From: oleg@ponder.csci.unt.edu (Kiselyov Oleg)
Newsgroups: comp.sources.misc
Subject: v44i053: toy_os - yet another OS & hardware emulator, in full C++, Part01/04
Date: 5 Sep 1994 13:18:36 -0500
Organization: University of North Texas
Sender: kent@sparky.sterling.com
Approved: kent@sparky.sterling.com
Message-ID: <csm-v44i053=toy_os.131819@sparky.sterling.com>
Reply-To: oleg@ponder.csci.unt.edu, oleg@unt.edu
Summary: UNIX-like OS running on emulated hardware, in full C++ (cf nachos)
Keywords: Operating System, C++, hardware emulation, nachos
X-Md4-Signature: f79c19cdaeb98dd4979dd7768407678f
Submitted-by: oleg@ponder.csci.unt.edu (Kiselyov Oleg)
Posting-number: Volume 44, Issue 53
Archive-name: toy_os/part01
Environment: GNU C++ 2.2
Highlights: UNIX-like OS kernel with semaphores, virtual memory and
asynchronous I/O, hardware emulator, "bus" paradigm for both OS and
hardware (classes), extensibility to multi-processor architecture, C++
in full grace.
This is yet another UNIX-like (maybe not so toy) operating system,
which operates on the emulated hardware. It was an (extended) class
project, so I wasn't at liberty to choose architecture, the
instruction set, and system calls to implement.
The operating system supports a minimal set of multiprocessing system
calls like fork, wait, P and V semaphore operations, and I/O
initiation. Process scheduling is round-robin with fixed time
quants. A memory manager handles page faults and provides address
translation (for channel instructions), page locking/unlocking, page
reservation, etc. common memory services. Two different page
replacement strategies were implemented and compared: "swap out" and
"local LRU" (with working set quotas like those in VAX/VMS). I ran a
suite of test "jobs" and compared the number of page faults,
etc. There is a report as to how the two strategies fare (it wasn't
actually a part of the class project, I did it just for the heck of
it). The I/O is asynchronous, that is, an I/O processor (channel) runs
concurrently with the main CPU, there can be several active i/o
requests, and many more may be submitted simultaneously. There are
extensive recording facilities, which print the status of the system
and different units as the system runs. I have a few full traces of
system runs, but they're too big (and too monotonous) to include in
the submission, please mail me if interested. Sorry, it's only the
kernel stuff, there is no file system (it wasn't a project requirement -:)
The hardware looks suspiciously like IBM/370, though with pure page
virtual memory (not page-segmented), but with several "I/O channels"
that understand their own set of "channel commands" and run
asynchronously with the main CPU. See references below for history of
the project. The hardware emulator is made of classes Memory,
MemoryManagementUnit (to handle the virtual memory), CPU, IOBus,
IOChannel, and devices LinePrinter, CardReader (so quaint -:), and the
HardDisk. The entire project makes a great deal of use of a "bus
architecture" paradigm. Say, on the hardware side, there is a class
"Computer" that holds all units and gives necessary references from
one unit to another at the "construction time". This format makes it a
snap to have a computer with two CPUs "connected" to a single memory
bank, two CPUs with two memory banks, etc.
BTW, there is a class Context that holds all CPU registers and other
control info. The class CPU is based on Context, so is a class PCB
(Process Control Block). So, it makes saving/restoring of the CPU
context during a process switch look as a simple assignment.
As OS was designed, some (elementary) basic classes have
"precipitated". One of the fundamental classes is BasicQueue, that
provides _asynchronous_ access to a double-linked list of
QueueLink's. The class lets one do simple searches on id or key (two
int properties of QueueLink), add or delete elements, performing
locking where necessary: it's assumed that several threads may want to
operate a queue "simultaneously". Other OS classes, like PCB or
Semaphore, are based on QueueLink, so it lets one right upfront queue
PCBs and access them simultaneously.
Operating System classes are built upon the hardware classes,
moreover, they mirror the hardware classes. Say, MemoryManager is
based on Memory, CPUManager is based on the CPU (as well as on
ProcessTable, etc), IOChannelManager is based on the IOChannel. So,
the OS is considered and designed as an "extension" of the
hardware. The class OperatingSystem is based on the class Computer.
It holds all managers and provides for their mutual references, so
it's kind of "software bus". BTW, OperatingSystem is the *only* object
created (in the module bootstrap). All other components are
"physically" parts of the OperatingSystem object and know of each other
through the "bus".
Unlike nachos, I used C++ in its full, with very multiple
inheritance (sometimes to the point of breaking the compiler), static
classes, virtual classes, operator/function overloads, etc. That's was
the whole point actually, I bet (literally) that all specific C++
features are very useful in designing the OS. The code is _well_
commented! I tried to make the OS as fool-proof as possible, so as to
minimize the probability of a misbehaved/malicious process crashing
the OS or seriously affecting other processes.
The submission consists of a README file (you're reading it
now), and quite a few source code files. File OS.dr lists all of them
and tells briefly what they're for.
I did this project two years ago, so now I would've done a few
things differently. Besides, I spent only 3 weeks (though pretty tough
3 weeks) implementing the software and "hardware". I even repeated my
personal record of 2,000 lines of *working* C++ code per week.
Anyway, I guess I proved, at least to myself, that C++ in its full
grace is quite useful (and efficient) for the OS design. Actually, I
personally have always taken it for granted, but I was surprised to
come across some people thinking otherwise. As to the OS development,
well, I still have hankering for it. So if something in this project
turns out to be useful in any respect, but some changes/rework seems
necessary, well, I can do it (not overnight, of course, but I'll try -:)
References & Credits: The code for vm_unt was originally written in
Modula (called vm_537) at University of Wisconsin-Madison by Raphael
Finkel. The program is adopted into C by Cui-Qing Yang at University
of North Texas. However, I don't use even a shred of this code, my
implementation is made completely from scratch. Yet it was mandatory
to implement the same system call format and functionality as in that
system. Sorry, but I had no choice in that.
The OS code is using a C thread library: "The Toy Operating System" by
Robert S. Fabry, Computer Science Division, Dept. of Electrical
Engineering and Computer Sciences, University of California, Berkeley
Well, I myself was thinking something along these lines, and even
started to implement my own threads. But then I came across this
library and gave up on mine (hey, it was a class project, time was
kind of tight). Anyway, if somebody wants to try VM UNT and can't get
hold of this threads library, I solemnly promise to implement my ideas
then (and I'll do it in C++).
I'm not a frequent reader of this newsgroup, please mail me at
oleg@ponder.csci.unt.edu or oleg@unt.edu should you want to comment.
---------
#! /bin/sh
# This is a shell archive. Remove anything before this line, then feed it
# into a shell via "sh file" or similar. To overwrite existing files,
# type "sh file -c".
# Contents: README c++l cpu.cc cpu_manager.cc mem_manager.cc
# Wrapped by kent@sparky on Mon Sep 5 13:12:54 1994
PATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin:/usr/lbin:$PATH ; export PATH
echo If this archive is complete, you will see the following message:
echo ' "shar: End of archive 1 (of 4)."'
if test -f 'README' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(6680 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
XHighlights: UNIX-like OS kernel with semaphores, virtual memory and
Xasynchronous I/O, hardware emulator, "bus" paradigm for both OS and
Xhardware (classes), extensibility to multi-processor architecture, C++
Xin full grace.
X
XThis is yet another UNIX-like (maybe not so toy) operating system,
Xwhich operates on the emulated hardware. It was an (extended) class
Xproject, so I wasn't at liberty to choose architecture, the
Xinstruction set, and system calls to implement.
X
XThe operating system supports a minimal set of multiprocessing system
Xcalls like fork, wait, P and V semaphore operations, and I/O
Xinitiation. Process scheduling is round-robin with fixed time
Xquants. A memory manager handles page faults and provides address
Xtranslation (for channel instructions), page locking/unlocking, page
Xreservation, etc. common memory services. Two different page
Xreplacement strategies were implemented and compared: "swap out" and
X"local LRU" (with working set quotas like those in VAX/VMS). I ran a
Xsuite of test "jobs" and compared the number of page faults,
Xetc. There is a report as to how the two strategies fare (it wasn't
Xactually a part of the class project, I did it just for the heck of
Xit). The I/O is asynchronous, that is, an I/O processor (channel) runs
Xconcurrently with the main CPU, there can be several active i/o
Xrequests, and many more may be submitted simultaneously. There are
Xextensive recording facilities, which print the status of the system
Xand different units as the system runs. I have a few full traces of
Xsystem runs, but they're too big (and too monotonous) to include in
Xthe submission, please mail me if interested. Sorry, it's only the
Xkernel stuff, there is no file system (it wasn't a project requirement
X-:)
X
XThe hardware looks suspiciously like IBM/370, though with pure page
Xvirtual memory (not page-segmented), but with several "I/O channels"
Xthat understand their own set of "channel commands" and run
Xasynchronously with the main CPU. See references below for history of
Xthe project. The hardware emulator is made of classes Memory,
XMemoryManagementUnit (to handle the virtual memory), CPU, IOBus,
XIOChannel, and devices LinePrinter, CardReader (so quaint -:), and the
XHardDisk. The entire project makes a great deal of use of a "bus
Xarchitecture" paradigm. Say, on the hardware side, there is a class
X"Computer" that holds all units and gives necessary references from
Xone unit to another at the "construction time". This format makes it a
Xsnap to have a computer with two CPUs "connected" to a single memory
Xbank, two CPUs with two memory banks, etc.
X
XBTW, there is a class Context that holds all CPU registers and other
Xcontrol info. The class CPU is based on Context, so is a class PCB
X(Process Control Block). So, it makes saving/restoring of the CPU
Xcontext during a process switch look as a simple assignment.
X
XAs OS was designed, some (elementary) basic classes have
X"precipitated". One of the fundamental classes is BasicQueue, that
Xprovides _asynchronous_ access to a double-linked list of
XQueueLink's. The class lets one do simple searches on id or key (two
Xint properties of QueueLink), add or delete elements, performing
Xlocking where necessary: it's assumed that several threads may want to
Xoperate a queue "simultaneously". Other OS classes, like PCB or
XSemaphore, are based on QueueLink, so it lets one right upfront queue
XPCBs and access them simultaneously.
X
XOperating System classes are built upon the hardware classes,
Xmoreover, they mirror the hardware classes. Say, MemoryManager is
Xbased on Memory, CPUManager is based on the CPU (as well as on
XProcessTable, etc), IOChannelManager is based on the IOChannel. So,
Xthe OS is considered and designed as an "extension" of the
Xhardware. The class OperatingSystem is based on the class Computer.
XIt holds all managers and provides for their mutual references, so
Xit's kind of "software bus". BTW, OperatingSystem is the *only* object
Xcreated (in the module bootstrap). All other components are
X"physically" parts of the OperatingSystem object and know of each other
Xthrough the "bus".
X
X Unlike nachos, I used C++ in its full, with very multiple
Xinheritance (sometimes to the point of breaking the compiler), static
Xclasses, virtual classes, operator/function overloads, etc. That's was
Xthe whole point actually, I bet (literally) that all specific C++
Xfeatures are very useful in designing the OS. The code is _well_
Xcommented! I tried to make the OS as fool-proof as possible, so as to
Xminimize the probability of a misbehaved/malicious process crashing
Xthe OS or seriously affecting other processes.
X
X The submission consists of a README file (you're reading it
Xnow), and quite a few source code files. File OS.dr lists all of them
Xand tells briefly what they're for.
X
X I did this project two years ago, so now I would've done a few
Xthings differently. Besides, I spent only 3 weeks (though pretty tough
X3 weeks) implementing the software and "hardware". I even repeated my
Xpersonal record of 2,000 lines of *working* C++ code per week.
XAnyway, I guess I proved, at least to myself, that C++ in its full
Xgrace is quite useful (and efficient) for the OS design. Actually, I
Xpersonally have always taken it for granted, but I was surprised to
Xcome across some people thinking otherwise. As to the OS development,
Xwell, I still have hankering for it. So if something in this project
Xturns out to be useful in any respect, but some changes/rework seems
Xnecessary, well, I can do it (not overnight, of course, but I'll try
X-:)
X
XReferences & Credits: The code for vm_unt was originally written in
XModula (called vm_537) at University of Wisconsin-Madison by Raphael
XFinkel. The program is adopted into C by Cui-Qing Yang at University
Xof North Texas. However, I don't use even a shred of this code, my
Ximplementation is made completely from scratch. Yet it was mandatory
Xto implement the same system call format and functionality as in that
Xsystem. Sorry, but I had no choice in that.
X
XThe OS code is using a C thread library: "The Toy Operating System" by
XRobert S. Fabry, Computer Science Division, Dept. of Electrical
XEngineering and Computer Sciences, University of California, Berkeley
XWell, I myself was thinking something along these lines, and even
Xstarted to implement my own threads. But then I came across this
Xlibrary and gave up on mine (hey, it was a class project, time was
Xkind of tight). Anyway, if somebody wants to try VM UNT and can't get
Xhold of this threads library, I solemnly promise to implement my ideas
Xthen (and I'll do it in C++).
X
XShould you want to comment, please mail me at oleg@ponder.csci.unt.edu
Xor oleg@unt.edu. I'd appreciate that!
END_OF_FILE
if test 6680 -ne `wc -c <'README'`; then
echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'c++l' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'c++l'\"
else
echo shar: Extracting \"'c++l'\" \(313 characters\)
sed "s/^X//" >'c++l' <<'END_OF_FILE'
X#!/bin/csh
X# GNU C++ linking
X/usr/local/bin/gcc -O -pipe -W -Wall -Wpointer-arith -Wenum-clash -Woverloaded-virtual \
X-Wstrict-prototypes -Wmissing-prototypes \
X-finline-functions -fforce-mem -funsigned-char \
X-fforce-addr -fomit-frame-pointer \
X-L{$HOME}/croot/c++serv \
X$* -lserv -liostream -liberty -lg++ -lm
END_OF_FILE
if test 313 -ne `wc -c <'c++l'`; then
echo shar: \"'c++l'\" unpacked with wrong size!
fi
# end of 'c++l'
fi
if test -f 'cpu.cc' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'cpu.cc'\"
else
echo shar: Extracting \"'cpu.cc'\" \(7459 characters\)
sed "s/^X//" >'cpu.cc' <<'END_OF_FILE'
X// This may look like C code, but it is really -*- C++ -*-
X/*
X ************************************************************************
X *
X * UNT Virtual Machine
X * Central Processing Unit
X *
X * The present file implemements a central processing unit, i.e. emulates
X * all the operations the "regular" CPU would perform.
X * Once started, the CPU runs until it gets stopped or trap occures. In
X * the latter case, 'trap_signal' is raised so the trap handler can
X * wake up and handle the trap. The trap handler is not a part of CPU,
X * though, and runs as a separate thread within the "hardware".
X *
X ************************************************************************
X */
X
X#pragma implementation
X#include "hardware.h"
X#include "myenv.h"
X#include <std.h>
X
X/*
X *------------------------------------------------------------------------
X * Initialization
X */
X
Xint _ebx_save; // newproc() spoils %ebx on the
X // second return. So we have to
X // save it.
X
XCentralProcessingUnit::CentralProcessingUnit(Memory& _memory)
X : MemoryManagementUnit(_memory),
X stopped("CPU operation",0),
X trap_signal("CPU trap",0)
X{
X pc = 0;
X clock = -1;
X trap_code = NONE;
X
X// message("\ntrap_signal ptr %x, val =%x",&trap_signal,*(int *)&trap_signal);
X asm("movl %ebx,__ebx_save"); // Saving %ebx. It's a shame we need
X // to resort to such a kludge!
X if( !newproc("CPU process",1) )
X { // This is a main CPU process
X for(;;)
X {
X stopped--; // Wait until can run again
X if( execute_instruction() )
X stopped++; // If everything was fine, keep going
X else
X trap_signal++;
X }
X }
X
X asm("movl __ebx_save,%ebx"); // Restore %ebx. I wish I didn't
X // have to do this!
X// message("\ntrap_signal ptr %x, val =%x",&trap_signal,*(int *)&trap_signal);
X}
X
X/*
X *------------------------------------------------------------------------
X * Starting and stopping the CPU
X */
X
Xvoid CentralProcessingUnit::start(void)
X{
X if( !is_running() )
X single_message("CPU: has been stopped, starting..."),
X stopped++;
X else
X single_message("CPU: already running");
X}
X
Xvoid CentralProcessingUnit::stop(void)
X{
X if( is_running() )
X single_message("CPU: has been running, stopping..."),
X stopped--;
X else
X single_message("CPU: already stopped");
X}
X
X/*
X *------------------------------------------------------------------------
X * Emulates a single CPU operation
X * Fetch an instruction at current pc and execute it
X * Returns 0 if trap or other interrupt has occurred that makes continuation
X * useless. Otherwise returns 1.
X * Note, that pc always points to the instruction following the one
X * which has been executed or which has caused the interrupt.
X *
X */
X
Xint CentralProcessingUnit::execute_instruction(void)
X{
X trap_code = NONE;
X
X if( clock == 0 ) // Handle the clock and generate
X { // interrupt if necessary
X trap_code = CLOCKINT; // (Clock is to turn negative)
X --clock;
X return 0;
X }
X else if( clock > 0 )
X --clock;
X
X inst_length = 0; // Fetch and examine the
X pc++; inst_length++; // 1st word of the instruction
X if( *(Word *)&iword1 = fetch(pc-1), got_fault() )
X {
X trap_code = MEMORY;
X pc -= inst_length; // Set pc to repeat the
X return 0; // operation after recovery
X }
X opcode = (OpCodes)iword1.opcode;
X Word& reg_a = (*this)[iword1.areg];
X Word reg_b = (*this)[iword1.breg];
X EA = 0;
X if( opcode >= TWO_WORD )
X { // Fetch the second word of instruction
X ++pc, ++inst_length;
X if( *(Word *)&iword2 = fetch(pc-1), got_fault() )
X {
X trap_code = MEMORY;
X pc -= inst_length; // Set pc to repeat the
X return 0; // operation after recovery
X }
X EA = iword2.ea;
X // Fetch the indirect address
X if( iword2.indirect && (EA = get(EA), got_fault() ) )
X {
X trap_code = MEMORY;
X pc -= inst_length; // Set pc to repeat the
X return 0; // operation after recovery
X }
X if( iword1.breg != 0 ) // Add the index to the address
X EA += reg_b;
X }
X
X // Print the trace info
X begin_printing();
X message("\nCPU: Executing instruction");
X message("\n PC Clock Opcode Areg Breg R(Areg) R(Breg) SvcOp Ind EA"
X " C(EA)"
X "\n%4ob %3d %2d %2d %2d %06o %06o",
X pc-inst_length,clock,opcode,iword1.areg,iword1.breg,
X reg_a,reg_b);
X if( opcode >= TWO_WORD )
X message(" %2d %1d %4ob %06o",iword2.svcop,iword2.indirect,EA,
X get(EA)), clear_error(); // Clear possible error due to get()
X message("\n");
X end_printing();
X
X // Interpret the instruction
X switch( opcode )
X {
X case HALT:
X trap_code = ILLEGOP;
X return 0;
X
X case DUMP:
X Context::dump();
X dump_phys_memory(reg_a,reg_b);
X break;
X
X case LOADR:
X reg_a = reg_b;
X break;
X
X case ADD:
X reg_a += reg_b;
X break;
X
X case SUBT:
X reg_a -= reg_b;
X break;
X
X case MULT:
X reg_a *= reg_b;
X break;
X
X case DIV:
X if( reg_b != 0 )
X reg_a /= reg_b;
X break;
X
X case AND:
X reg_a &= reg_b;
X break;
X
X case OR:
X reg_a |= reg_b;
X break;
X
X case XOR:
X reg_a ^= reg_b;
X break;
X
X case SHIFTL:
X reg_a <<= reg_b;
X break;
X
X case SHIFTR:
X reg_a >>= reg_b;
X break;
X
X case NOP:
X break;
X
X case IOPR:
X trap_code = ILLEGOP;
X return 0;
X
X case LOAD:
X register Word ea_cont = get(EA);
X if( got_fault() )
X {
X trap_code = MEMORY;
X pc -= inst_length; // Set pc to repeat the
X return 0; // operation after recovery
X }
X reg_a = ea_cont;
X break;
X
X case LOADI:
X reg_a = EA;
X break;
X
X case STORE:
X if( put(EA,reg_a), got_fault() )
X {
X trap_code = MEMORY;
X pc -= inst_length; // Set pc to repeat the
X return 0; // operation after recovery
X }
X break;
X
X case BR:
X pc = EA;
X break;
X
X case BRNEG:
X if( reg_a < 0 )
X pc = EA;
X break;
X
X case BRZERO:
X if( reg_a == 0 )
X pc = EA;
X break;
X
X case BRPOS:
X if( reg_a > 0 )
X pc = EA;
X break;
X
X case BRSUBR:
X reg_a = pc;
X pc = EA;
X break;
X
X case READ:
X case WRITE:
X trap_code = ILLEGOP;
X return 0;
X
X case SVC:
X svcop = iword2.svcop;
X trap_code = SVCCALL;
X return 0;
X
X default:
X trap_code = ILLEGOP;
X return 0;
X }
X return 1;
X}
X
X
X/*
X *------------------------------------------------------------------------
X * Dump the context
X */
X
Xvoid CentralProcessingUnit::dump(void) const
X{
X Context::dump();
X MemoryManagementUnit::dump();
X}
X
Xvoid Context::dump(void) const
X{
X register int i;
X message("\n\t\t\tContext Dump\n");
X message("\n R0 R1 R2 R3 R4 R5 R6 R7\n");
X for(i=0; i<=7; i++)
X message(" %06o",Registers[i]);
X message("\n\n R8 R9 R10 R11 R12 R13 R14 R15\n");
X for(i=8; i<=15; i++)
X message(" %06o",Registers[i]);
X message("\n\nPC = %06o\n\n",pc);
X}
X
X/*
X *------------------------------------------------------------------------
X * Processor context functions
X */
X
X // Clean up the context at the very beginning
XContext::Context(void)
X{
X memset(Registers,0,sizeof(Registers));
X pc = 0;
X}
X
X // Get ref to a given register
XWord& Context::operator [] (const int reg_number)
X{
X assure( reg_number >= 0 && reg_number <= 15, "Bad register no" );
X return Registers[reg_number];
X}
X
X // Copy the context from another one
Xvoid Context::copy_context(const Context& a_context)
X{
X memcpy(this,&a_context,sizeof(Context));
X}
X
X
END_OF_FILE
if test 7459 -ne `wc -c <'cpu.cc'`; then
echo shar: \"'cpu.cc'\" unpacked with wrong size!
fi
# end of 'cpu.cc'
fi
if test -f 'cpu_manager.cc' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'cpu_manager.cc'\"
else
echo shar: Extracting \"'cpu_manager.cc'\" \(18467 characters\)
sed "s/^X//" >'cpu_manager.cc' <<'END_OF_FILE'
X// This may look like C code, but it is really -*- C++ -*-
X/*
X ************************************************************************
X *
X * UNT Virtual Machine
X *
X * This is the core of the operating system
X *
X * The functions defined in the present file are executed within the
X * TRAP-handler thread that gets control if CPU raised the trap signal.
X * While CPU is stopped during the trap handling, the trap thread
X * handles traps and system requests (SVC traps) and performs all the
X * high level process scheduling.
X *
X ************************************************************************
X */
X
X#pragma implementation "cpu_manager.h"
X#include "cpu_manager.h"
X#include "io_manager.h"
X#include "myenv.h"
X
X/*
X *------------------------------------------------------------------------
X * Initializing the operating system
X */
X
XCPUManager::CPUManager(CentralProcessingUnit& _CPU,
X MemoryManager& _mem_manager,IOManager& _io_manager)
X : CPU(_CPU),
X memory_manager(_mem_manager),
X io_manager(_io_manager),
X Halt("Computer halt",0),
X ProcessTable(20)
X{
X semas = new SemaphoreTable(20);
X running_pcb = 0;
X if( !newproc("TRAP handling",1) )
X { // This is a CPU trap handler
X for(;;)
X {
X CPU.wait_for_trap(); // Wait for the trap
X if( trap_handler(CPU.q_trap_code()) == DISPATCH )
X dispatch();
X CPU.start();
X }
X }
X}
X
X
X // Load a program from the drum and run it
Xvoid CPUManager::commence(void)
X{
X int no_programs = memory_manager.q_no_programs();
X console("There are %d programs on the drum to run",no_programs);
X register int i;
X for(i=0; i<no_programs; i++)
X {
X PCB& pcb = (*this)[new_pid()];
X pcb.brand_new();
X pcb.pc = memory_manager.load_program(pcb,i);
X readyPCBs.append(pcb);
X }
X dispatch();
X CPU.start();
X}
X
X/*
X *------------------------------------------------------------------------
X * TRAP handler
X */
X
XHANDLER_STATUS
XCPUManager::trap_handler(const CentralProcessingUnit::TRAPCODE trap_code)
X{
X save_context(); // Save the CPU context anyway
X switch(trap_code)
X {
X case CentralProcessingUnit::ILLEGOP:
X if( CPU.q_opcode() == HALT )
X console("Current process has terminated normally (by HALT)");
X else if( CPU.q_opcode() == READ || CPU.q_opcode() == WRITE )
X return initiate_IO(CPU.q_opcode(),CPU.q_EA());
X else
X console("Illegal operation (dec code %d)",CPU.q_opcode());
X return terminate();
X
X case CentralProcessingUnit::SVCCALL:
X return svc_handler((SVCCODE)CPU.q_svcop());
X
X case CentralProcessingUnit::CLOCKINT:
X single_message("Clock Interrupt");
X if( readyPCBs.is_empty() )
X return RESUME; // We've got only a single process
X readyPCBs.append(*running_pcb);
X return DISPATCH; // Else pick up smth else
X
X case CentralProcessingUnit::MEMORY:
X return mfault_handler(CPU.what_fault());
X
X default:
X _error("FATAL: Trap handler has been entered with illegal "
X "trap code %d",trap_code);
X }
X return DISPATCH;
X}
X
X/*
X *------------------------------------------------------------------------
X * SVC Handler
X */
X
XHANDLER_STATUS CPUManager::svc_handler(const SVCCODE svc_code)
X{
X single_message("SVC interrupt %d",svc_code);
X switch(svc_code)
X {
X case DUMP_STATUS: // Dump the OS status
X dump();
X return RESUME;
X
X case FORK: // Fork a process.
X // reg_a := PID of a kid or parent
X return fork(CPU.q_RegA(),CPU.q_EA()); // EA = start address of a kid
X
X case WAIT_KIDS: // Wait for kid processes to terminate
X return wait_for_kids();
X
X case SEMINIT: // Create a semaphor for a process
X return create_semaphore(CPU.q_RegA(),CPU.q_EA());
X
X case SEM_P: // P-operation on semaphore
X return semaphore_p((SID)CPU[CPU.q_RegA()]);
X
X case SEM_V: // V-operation on semaphore
X return semaphore_v((SID)CPU[CPU.q_RegA()]);
X
X case KILL: // Try to kill the process
X return shoot((PID)CPU[CPU.q_RegA()]);
X
X default:
X console("Illegal SVC call %d, program is being tossed out",
X svc_code);
X return terminate();
X }
X return DISPATCH;
X}
X
X/*
X *------------------------------------------------------------------------
X * Elementary process scheduling functions
X */
X
X // Prepare the process for running
X // on the CPU
Xvoid CPUManager::prepare_for_running(void)
X{
X assert( running_pcb != 0 );
X assert( !CPU.is_running() );
X (*running_pcb).load_context(CPU);
X CPU.clock = Time_quant;
X}
X
X // Save the status of the currently
X // running process as the process is
X // going to lose the CPU control
Xvoid CPUManager::save_context(void)
X{
X assert( running_pcb != 0 );
X assert( !CPU.is_running() );
X (*running_pcb).save_context(CPU);
X}
X
X // Terminate the currently running process
XHANDLER_STATUS CPUManager::terminate(void)
X{
X console("Terminating the process PID %d",running_pcb->id);
X (*running_pcb).dump();
X kill(*running_pcb);
X return DISPATCH; // Pick up a new process to run
X}
X
X // Pick up a new process to run and make
X // it current
Xvoid CPUManager::dispatch(void)
X{
X if( q_all_free() ) // Check if all processes are trough
X Halt++;
X running_pcb = &(*this)[readyPCBs.get_from_head()->id]; // Implies waiting
X single_message("Dispatchung process %d...",running_pcb->id);
X prepare_for_running();
X}
X
X/*
X *------------------------------------------------------------------------
X * Dump whatever goes on in the system
X */
X
Xvoid CPUManager::dump(void)
X{
X begin_printing();
X message("\n%s\n\n\t\t\tOperating System Status\n",_Minuses);
X message("\nCurrent process\n");
X (*running_pcb).dump();
X ProcessTable::dump();
X (*semas).dump();
X memory_manager.dump_status();
X io_manager.dump();
X end_printing();
X}
X
X/*
X *------------------------------------------------------------------------
X * Making new processes
X */
X
X // Create a child process at EA of
X // the currently running process
X // Put PID of the son into the parent rega,
X // and PID of the parent into the son rega
XHANDLER_STATUS CPUManager::fork(const int rega,Address EA)
X{
X assert( running_pcb != 0 );
X if( running_pcb -> lchild != NIL_pid && running_pcb -> rchild != NIL_pid )
X {
X console("ABEND: attempt to create the 3d child");
X return terminate();
X }
X
X PID son_id = new_pid();
X if( son_id == NIL_pid )
X {
X console("ABEND: can't create a process - too many are running");
X return terminate();
X }
X
X PCB& son_pcb = (*this)[son_id];
X PCB& dad_pcb = *running_pcb;
X single_message("Creating a child %d for a parent %d",son_pcb.id,dad_pcb.id);
X
X son_pcb.fork_from_dad(dad_pcb);
X son_pcb[rega] = dad_pcb.id;
X son_pcb.pc = EA;
X
X dad_pcb[rega] = son_pcb.id;
X
X if( !memory_manager.fork_son((MMContext&)son_pcb,(const MMContext&)dad_pcb) )
X { // If some problem occurred during
X kill(son_pcb); // memory allocation for the son
X return RESUME;
X }
X
X readyPCBs.append(dad_pcb);
X readyPCBs.append(son_pcb);
X return DISPATCH;
X}
X
X // Wait until all the kids of the
X // running process are through.
X // Return RESUME if the running process
X // has got no kids.
XHANDLER_STATUS CPUManager::wait_for_kids(void)
X{
X assert( running_pcb != 0 );
X if( running_pcb->lchild == NIL_pid && running_pcb->rchild == NIL_pid )
X return RESUME; // No kids
X running_pcb->status = PCB::Wait_for_kids; // The PCB remains unqueued
X return DISPATCH;
X}
X
X // Try to kill the process
XHANDLER_STATUS CPUManager::shoot(const PID pid)
X{
X single_message("An attempt to shoot a process %d",pid);
X if( pid == running_pcb->id )
X {
X console("The current process %d shot himself",pid);
X return terminate();
X }
X
X if( pid == NIL_pid )
X {
X console("An attempt to kill a nonexistent process");
X return terminate();
X }
X
X if( pid != running_pcb->lchild && pid != running_pcb->rchild )
X {
X console("Process %d has no right to shoot %d",running_pcb->id,pid);
X return terminate();
X }
X
X PCB& victim = (*this)[pid];
X if( victim.status == PCB::Doing_io )
X victim.status = PCB::Shall_die;
X
X kill(victim);
X return RESUME;
X}
X
X/*
X *------------------------------------------------------------------------
X * Kill the process
X * The program performs the clean-up and releases all the resources
X * that process occupied.
X * - Kids are terminated
X * - Parent is notified and turned ready if has been waiting
X * - Owned semaphores are destroyed and all the processes being
X * waiting on it are terminated
X * - the process is purged of all semaphore waiting lists if
X * it has been waiting on P operation
X * - dispose of the memory of the deseased process
X */
X
Xvoid CPUManager::kill(PCB& pcb)
X{
X assert(pcb.status != PCB::Dead);
X
X readyPCBs.purge_id(pcb.id); // Purge from the ready queue
X // (if any)
X if( pcb.lchild != NIL_pid )
X kill((*this)[pcb.lchild]);
X
X if( pcb.rchild != NIL_pid )
X kill((*this)[pcb.rchild]);
X
X if( pcb.parent != NIL_pid )
X {
X PCB& dad_pcb = (*this)[pcb.parent];
X if( dad_pcb.lchild == pcb.id )
X dad_pcb.lchild = NIL_pid;
X else if( dad_pcb.rchild == pcb.id )
X dad_pcb.rchild = NIL_pid;
X else
X _error("FATAL: parent doesn't know of his son");
X
X // If dad has been waiting for kids, it may go
X if( dad_pcb.lchild == NIL_pid && dad_pcb.rchild == NIL_pid &&
X dad_pcb.status == PCB::Wait_for_kids )
X dad_pcb.status = PCB::Ok,
X readyPCBs.append(dad_pcb);
X pcb.parent = NIL_pid;
X }
X
X // Release all owned semaphores
X SID sid;
X while( (sid = (*semas).owned_by(pcb.id)) != NIL_sid )
X {
X (*semas).dispose(sid);
X }
X
X if( pcb.status == PCB::Wait_on_sema )
X (*semas).purge(pcb.id);
X
X memory_manager.dispose((MMContext&)pcb);
X dispose(pcb.id);
X}
X
X/*
X *------------------------------------------------------------------------
X * Processes and Semaphores
X */
X // Create a new semaphore with initial value EA
X // Put SID in rega
X // Return RESUME if everything is fine
XHANDLER_STATUS CPUManager::create_semaphore(const int rega,Address EA)
X{
X assert( running_pcb != 0 );
X
X SID semid = (*semas).new_semaphore(EA,running_pcb->id);
X if( semid == NIL_sid )
X {
X console("ABEND: Can't create a semaphore - too many are in use");
X return terminate();
X }
X
X single_message("Semaphore %d (in_value %d) has been allocated for PID %d",
X semid,EA,running_pcb->id);
X
X CPU[rega] = semid;
X return RESUME;
X}
X
X // Check to see that the Sema is valid
X // to perform P or V operation on.
X // It should be active and belong to
X // the running process or its ancestor
XSema * CPUManager::valid_semaphore(const SID sid)
X{
X if( !(*semas).is_active(sid) )
X return 0;
X Sema& sem = (*semas)[sid];
X PID owner = sem.q_owner(); // Check ownership
X PID id = running_pcb->id; // through the chain of
X for(; id != NIL_pid; id = (*this)[id].parent) // ancestorship
X if( id == owner )
X return &sem;
X return 0;
X}
X
X // Semaphore P-operation
X // Return RESUME if the process is to
X // be resumed
XHANDLER_STATUS CPUManager::semaphore_p(const SID sid)
X{
X single_message("\nP-operation on semaphore %d",sid);
X assert( running_pcb != 0 );
X Sema * semp;
X if( (semp = valid_semaphore(sid)) == 0 )
X {
X console("Semaphore %d may not be used by process %d",sid,
X running_pcb->id);
X return terminate();
X }
X
X if( !(*semp).p(*running_pcb) )
X { // Suspend the current process
X single_message("Process %d should wait on semaphore %d",
X running_pcb->id,sid);
X running_pcb->status = PCB::Wait_on_sema;
X return DISPATCH;
X }
X
X return RESUME;
X}
X
X
X // Semaphore V-operation
X // Return RESUME if the process is to
X // be resumed
XHANDLER_STATUS CPUManager::semaphore_v(const SID sid)
X{
X single_message("V-operation on semaphore %d",sid);
X assert( running_pcb != 0 );
X Sema * semp;
X if( (semp = valid_semaphore(sid)) == 0 )
X {
X console("Semaphore %d may not be used by process %d",sid,
X running_pcb->id);
X return terminate();
X }
X
X PID pid_to_wake;
X if( (pid_to_wake = (*semp).v()) != NIL_pid )
X { // Wake up pid_to_wake
X single_message("Process %d is being woken up",pid_to_wake);
X PCB& pcb_to_wake = (*this)[pid_to_wake];
X pcb_to_wake.status = PCB::Ok;
X readyPCBs.append(pcb_to_wake);
X }
X
X return RESUME;
X}
X
X/*
X *------------------------------------------------------------------------
X * Preliminary Memory Fault Handling
X * Advance memory handling is done by the MemoryManager
X */
X
XHANDLER_STATUS CPUManager::mfault_handler
X (const MemoryManagementUnit::MemoryFault code)
X{
X MMUContext::VirtualMemoryAddr culprit_VA = CPU.q_current_VA();
X
X switch(code)
X {
X case MemoryManagementUnit::OutofRange:
X console("ABEND: Virtual address %o is out of range",culprit_VA);
X return terminate();
X
X case MemoryManagementUnit::WrongPageTable:
X console("ABEND: Page table has wrong format for VA %o",culprit_VA);
X return terminate();
X
X case MemoryManagementUnit::WrongVPageNo:
X console("ABEND: Wrong virtual page number in VA %o",culprit_VA);
X return terminate();
X
X case MemoryManagementUnit::PageInvalidated:
X break; // Try to handle that
X
X case MemoryManagementUnit::NoAccess:
X console("ABEND: Access to the virtual page denied, VA %o",culprit_VA);
X return terminate();
X
X case MemoryManagementUnit::ReadOnly:
X console("ABEND: May not write to a read-only page, VA %o",culprit_VA);
X return terminate();
X
X case MemoryManagementUnit::ExecOnly:
X console("ABEND: EXEConly page may only be fetched, VA %o",culprit_VA);
X return terminate();
X
X default:
X _error("FATAL: unknown memory fault %d, cannot handle",code);
X }
X
X single_message("Missing the page at VA %6o",*(Address*)&culprit_VA);
X
X // We've access to the page not in
X // the physical memory. Try to bring
X // it to there
X enum {Begin, Blocked, Ready} swap_status = Begin;
X for(;;)
X switch(memory_manager.load_the_page((MMContext&)*running_pcb,culprit_VA))
X {
X case MemoryManager::Ok:
X CPU.clear_error();
X (*running_pcb).load_context(CPU);
X return RESUME;
X
X case MemoryManager::PhysMemoryExhausted:
X message("\nMemory exhausted: ");
X PID victim_pid;
X switch(swap_status)
X {
X case Begin:
X start_scan();
X swap_status = Blocked;
X
X case Blocked:
X if( (victim_pid = next_pcb(ProcessTable::Blocked))
X != NIL_pid )
X {
X PCB& pcb = (*this)[victim_pid];
X message("Blocked PID %d is to be swapped out",victim_pid);
X memory_manager.swap_out((MMContext&)pcb);
X continue;
X }
X start_scan();
X swap_status = Ready;
X
X case Ready:
X if( (victim_pid = next_pcb(ProcessTable::Ready))
X != NIL_pid )
X {
X if( victim_pid == running_pcb->id )
X continue;
X PCB& pcb = (*this)[victim_pid];
X message("Ready PID %d is to be swapped out",victim_pid);
X memory_manager.swap_out((MMContext&)pcb);
X continue;
X }
X console("We've tried hard enough, but there is no "
X "free physical memory");
X return terminate();
X }
X break;
X
X case MemoryManager::LethalFault:
X return terminate();
X }
X}
X
X/*
X *------------------------------------------------------------------------
X * The following utilities help handle the parameter block
X * the process has prepared in the memory
X * They operate in the current process virtual memory. Page table is
X * assumed to be loaded.
X * The program return FALSE if some problem occured. Usually it is the
X * reason for process termination.
X */
X
X // Read a word from the (virtual)
X // address space of a current process
Xint CPUManager::read_word(const Address va, Word& dest)
X{
X if( dest = CPU.get(va), CPU.got_fault() )
X if( mfault_handler(CPU.what_fault()) != RESUME )
X return 0; // Incorrectable memory fault
X
X if( dest = CPU.get(va), CPU.got_fault() )
X return 0; // Second fault - also bad
X return 1;
X}
X
X // Write a word to the (virtual)
X // address space of a current process
Xint CPUManager::write_word(const Address va, Word src)
X{
X if( CPU.put(va,src), CPU.got_fault() )
X if( mfault_handler(CPU.what_fault()) != RESUME )
X return 0; // Incorrectable memory fault
X
X if( CPU.put(va,src), CPU.got_fault() )
X return 0; // Second fault - also bad
X return 1;
X}
X // Lock the page of the current process
X // at the specified VA
Xint CPUManager::lock_page(Address va)
X{
X // First, try to read this address
X // and load the page if necessary
X if( CPU.get(va), CPU.got_fault() )
X if( CPU.what_fault() != MemoryManagementUnit::PageInvalidated ||
X mfault_handler(CPU.what_fault()) != RESUME )
X return 0; // Incorrectable memory fault
X return memory_manager.lock_loaded_page((MMContext&)*running_pcb,va);
X}
X
X // Unlock the page of a given process
X // at the specified VA
Xvoid CPUManager::unlock_page(const PID pid,Address va)
X{
X memory_manager.unlock_page((MMContext&)(*this)[pid],va);
X}
X
X // Translate the virtual address to physical
X // one using the translation tables of the
X // running process
X // running process. The page has to be loaded
X // Returns 0 if fails
XAddress CPUManager::translate_va(Address va)
X{
X return memory_manager.translate_va((MMContext&)*running_pcb,va);
X}
X
X // Read/write a word from/to the
X // PHYSICAL address space
X // Error is fatal
Xvoid CPUManager::read_word_phys_memory(const Address ra, Word& dest)
X{
X Memory::MemoryAnswer ans = CPU.memory[ra];
X if( ans.out_of_range )
X _error("CPU reading phys memory failed at %o",ra);
X dest = ans.contents;
X}
X
Xvoid CPUManager::write_word_phys_memory(const Address ra, Word src)
X{
X Memory::MemoryAnswer ans = CPU.memory[ra];
X if( ans.out_of_range )
X _error("CPU writing phys memory failed at %o",ra);
X ans.contents = src;
X}
X
X/*
X *------------------------------------------------------------------------
X * Initiate the Input/Output
X * The following program performs only preliminary I/O initiation
X */
X
XHANDLER_STATUS CPUManager::initiate_IO(const OpCodes opcode,Address IO_params)
X{
X single_message("Processing I/O parameters");
X if( !io_manager.submit_request(running_pcb->id,opcode,IO_params) )
X return terminate();
X running_pcb->status = PCB::Doing_io;
X return DISPATCH;
X}
X
X // Post the completion of the I/O request
Xvoid CPUManager::io_request_completed(const PID pid)
X{
X PCB& pcb = (*this)[pid];
X assert( pcb.status == PCB::Doing_io );
X pcb.status = PCB::Ok;
X readyPCBs.append(pcb);
X}
END_OF_FILE
if test 18467 -ne `wc -c <'cpu_manager.cc'`; then
echo shar: \"'cpu_manager.cc'\" unpacked with wrong size!
fi
# end of 'cpu_manager.cc'
fi
if test -f 'mem_manager.cc' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'mem_manager.cc'\"
else
echo shar: Extracting \"'mem_manager.cc'\" \(16274 characters\)
sed "s/^X//" >'mem_manager.cc' <<'END_OF_FILE'
X// This may look like C code, but it is really -*- C++ -*-
X/*
X ************************************************************************
X *
X * UNT Virtual Machine
X *
X * High Level Virtual Memory Manager
X *
X * This is a high-level extension of the memory unit, and uses paging to
X * manage memory requests of processes.
X * Page selection is on demand, that's the page isn't loaded until it is
X * missed by the CPU to complete the execution of an instruction.
X *
X * The present version supports the working set concept and an approximate
X * LRU page replacement strategy. Working set of a process is a set of
X * all pages that are currently referenced (mapped, i.e. loaded into
X * the physical memory) by the process. In other words, it is the set of
X * pages the process is working with. Working set quota limits the number
X * of the pages the process is allowed to have mapped. If the working set
X * is filled up to the quota, yet the process needs (demands) one more page,
X * one page of the working set should be unreferenced (and swapped out if
X * it has been modified) before the request to map new page is served.
X * To decide which page to unmap, an approximate LRU page replacement
X * strategy is used.
X * It means, the algorithm will try to find the least recently used page
X * of the working set and push it out. To figure out which page has been
X * used least recently, the memory context of the process keeps a time-since-
X * last-access count of the page usage. When the process gets control
X * of the CPU and its page table gets loaded into the hardware, was_ref
X * bit of every page is dropped. When the CPU accesses memory, the memory
X * hardware sets the was_ref bit of the accessed page. When the process
X * loses control of the CPU (because it runs into the blocking condition,
X * or because it exhausted its time slice), the time-since-last-access
X * count for all the pages that haven't been accessed during that
X * process run is incremented. Therefore, the larger the count, the
X * least recently the page has been accessed (used). Note, since
X * process switchings out occure non-uniformly (the process loses
X * the CPU control not only because of the time click, but also because
X * of blocking), the LRU count is only approximately correct. Yet,
X * interrupts in the real system occur quite regularly, so this
X * approximation seems fairly accurate.
X *
X ************************************************************************
X */
X
X#pragma implementation
X#include "mem_manager.h"
X
X#include "myenv.h"
X#include <std.h>
X
XMemoryManager::MemoryManager(Memory& _physical_memory)
X : physical_memory(_physical_memory),
X virtual_space(_physical_memory,(Memory::hiaddr+1)/Memory::pagesize,
X Memory::no_drum_sectors)
X{
X no_programs = 0;
X}
X
X
X // Read the drum header and tell the
X // number of programs on the drum
Xint MemoryManager::q_no_programs(void)
X{
X virtual_space.declare_page_private(0);
X physical_memory.drum_read(0,PageFrameTable::sys_page_frame);
X const Address base_address = PageFrameTable::sys_page_frame*Memory::pagesize;
X register Address curr_address = base_address;
X no_programs = physical_memory[curr_address++].contents;
X assert( no_programs > 0 && no_programs <= max_no_programs );
X register int i;
X for(i=0; i<no_programs; i++)
X prog_descriptors[i].pc = physical_memory[curr_address++].contents,
X prog_descriptors[i].mem_map = physical_memory[curr_address++].contents;
X
X return no_programs;
X}
X
X
Xvoid MemoryManager::dump_status(void) const
X{
X virtual_space.dump_status();
X}
X
X/*
X *------------------------------------------------------------------------
X * Servicing memory requests of a process
X */
X
X // Initializing all areas
XMMContext::MMContext(void)
X{
X register int i;
X for(i=0; i<process_virtual_space; i++)
X vm_map[i] = NIL_vpn;
X memset(page_table,0,sizeof(page_table));
X memset(time_since_last_ref,0,sizeof(time_since_last_ref));
X no_mapped_pages = 0;
X no_page_faults = 0;
X}
X
X // Given vm_map, map of the process virtual
X // space, construct the page table
Xvoid MMContext::build_page_table(const VirtualPageTable& virtual_space)
X{
X register int i;
X for(i=0; i<process_virtual_space; i++)
X {
X PageTableEntry& pte = page_table[i];
X pte.valid = pte.was_ref = pte.was_written = 0;
X if( vm_map[i] == NIL_vpn ) // The page hasn't been allocated
X pte.any_access = pte.read_only = pte.exec_only = 0;
X else
X {
X pte.any_access = 1; // The page has been allocated
X const VirtualPage& vp = virtual_space[vm_map[i]];
X pte.exec_only = vp.q_data_page() ? 0 : 1;
X if( vp.page_frame != NIL_pfn)
X pte.phys_page_no = vp.page_frame,// The page is mapped
X pte.valid = 1;
X }
X }
X}
X
X
X // Load the context to the MMU
X // Note, the referenced bit for every page
X // in the page table is cleared to
X // find out which pages are going to be
X // accessed during the time the process
X // is running on CPU
Xvoid MMContext::load_MMU(MemoryManagementUnit& mmu)
X{
X memcpy((MMUContext *)&mmu,this,sizeof(MMUContext));
X // Load the page table
X const Address base_address = PageFrameTable::sys_page_frame*Memory::pagesize;
X assert( page_table_len <= process_virtual_space );
X assert( page_table_len <= Memory::pagesize );
X
X register int i;
X for(i=0; i<page_table_len; i++)
X {
X PageTableEntry& pte = page_table[i];
X pte.was_ref = 0; // Clear the ref bit
X *(PageTableEntry *)&(mmu.memory[base_address+i].contents) = pte;
X }
X mmu.clear_error();
X}
X
X // Save the MMU context
X // Was_written/was_referenced fields in the
X // page_table might have been changed. This
X // needs to be saved. The referenced bit
X // is used to find out which pages have been
X // actually accessed, and to increment counter
X // for the pages that haven't been used
Xvoid MMContext::save_MMU(const MemoryManagementUnit& mmu)
X{
X // Save the page table
X const Address base_address = PageFrameTable::sys_page_frame*Memory::pagesize;
X assert( page_table_len <= process_virtual_space );
X assert( page_table_len <= Memory::pagesize );
X
X register int i;
X for(i=0; i<page_table_len; i++)
X {
X const PageTableEntry& pte =
X *(PageTableEntry *)&(mmu.memory[base_address+i].contents);
X if( pte.valid && !pte.was_ref )
X time_since_last_ref[i]++;
X page_table[i] = pte;
X }
X}
X
X // Dump whatever's in there
Xvoid MMContext::dump(void) const
X{
X if( !enabled_address_translation )
X {
X message("\nVirtual memory is disabled for this process\n");
X return;
X }
X
X message("\nProcess virtual space is %d pages long",page_table_len);
X message("\nWorking set size %d, working set quota %d\n",no_mapped_pages,
X working_set_quota);
X message("\nAddress range\tAccess\tMapped to Drum sector"
X "\tWritten\tReferenced LRU time\n");
X register int i;
X for(i=0; i<page_table_len; i++)
X {
X const PageTableEntry& pte = page_table[i];
X if( !(pte.any_access || pte.read_only || pte.exec_only) )
X continue; // Page isn't allocated
X message("%06o-%06o \t %s\t",Memory::pagesize*i,Memory::pagesize*(i+1)-1,
X pte.exec_only ? "Exec" : pte.read_only ? "Read" : "Any");
X if( pte.valid )
X message("%06o ",pte.phys_page_no*Memory::pagesize);
X else
X message("Not Mapped ");
X message("\t%d\t %s\t %s",vm_map[i],pte.was_written ? "Y" : "N",
X pte.was_ref ? "Y" : "N");
X if( pte.valid )
X message(" %d\n",time_since_last_ref[i]);
X else
X message("\n");
X }
X message("\nProcess has %d page faults\n\n",no_page_faults);
X}
X
X // Load program and return starting PC
XAddress MemoryManager::load_program
X (MMContext& context,const unsigned int prog_no)
X{
X assure(prog_no < (unsigned)no_programs,
X "FATAL: an attempt to load nonexistent program");
X ProgDescr& prog_descr = prog_descriptors[prog_no];
X // Read the memory map from the drum
X virtual_space.declare_page_private((VPN)prog_descr.mem_map);
X physical_memory.drum_read((VPN)prog_descr.mem_map,
X PageFrameTable::sys_page_frame);
X // Set up the vm_map and page_table
X const Address base_address = PageFrameTable::sys_page_frame*Memory::pagesize;
X register int i;
X for(i=0; i<MMContext::process_virtual_space; i++)
X {
X assert( i < Memory::pagesize );
X int drum_sector_no = physical_memory[base_address+i].contents;
X if( drum_sector_no == -1 )
X context.vm_map[i] = NIL_vpn;
X else
X {
X virtual_space.declare_page_private((VPN)drum_sector_no);
X if( !is_text_segment(i) )
X virtual_space[(VPN)drum_sector_no].declare_data_page();
X context.vm_map[i] = drum_sector_no;
X }
X }
X
X context.build_page_table(virtual_space);
X context.no_page_faults = 0;
X context.no_mapped_pages = 0;
X memset(context.time_since_last_ref,0,sizeof(context.time_since_last_ref));
X
X context.page_table_addr = PageFrameTable::sys_page_frame*Memory::pagesize;
X context.page_table_len = MMContext::process_virtual_space;
X context.enabled_address_translation = 1;
X
X return prog_descr.pc;
X}
X
X // Take care of son's virtual space
X // Return FALSE if we've got some problem
Xint MemoryManager::fork_son
X (MMContext& son_context,const MMContext& dad_context)
X{
X // Check is everything's in real memory
X if( !dad_context.enabled_address_translation )
X {
X son_context.enabled_address_translation = 0;
X return 1;
X }
X // Set up the vm_map
X register int i;
X son_context.no_mapped_pages = 0;
X for(i=0; i<MMContext::process_virtual_space; i++)
X {
X VPN dad_vpn = dad_context.vm_map[i];
X if( dad_vpn == NIL_vpn )
X {
X son_context.vm_map[i] = NIL_vpn;
X continue;
X }
X
X const VirtualPage& dad_vp = virtual_space[dad_vpn];
X if( !dad_vp.q_data_page() )
X { // Code text page, to be shared
X virtual_space.allocate(dad_vpn);
X son_context.vm_map[i] = dad_vpn;
X if( dad_vp.is_mapped() ) // Son would also refer to the
X virtual_space.reference(dad_vpn), // dad's mapped pages
X son_context.no_mapped_pages++;
X continue;
X }
X
X VPN son_vpn = virtual_space.allocate();
X if( son_vpn == NIL_vpn )
X {
X console("No virtual space for the son process. It has to be killed");
X return 0;
X }
X
X son_context.vm_map[i] = son_vpn;
X virtual_space[son_vpn].declare_data_page();
X virtual_space.copy(son_vpn,dad_vpn);
X }
X
X son_context.build_page_table(virtual_space);
X son_context.no_page_faults = 0;
X memset(son_context.time_since_last_ref,0,
X sizeof(son_context.time_since_last_ref));
X
X son_context.page_table_addr =
X PageFrameTable::sys_page_frame*Memory::pagesize;
X son_context.page_table_len = MMContext::process_virtual_space;
X son_context.enabled_address_translation = 1;
X
X return 1;
X}
X
X // Serve the request to bring a virtual page
X // into the real memory
XMemoryManager::MMAnswer MemoryManager::load_the_page
X (MMContext& context,MMUContext::VirtualMemoryAddr culprit_VA)
X{
X unsigned int page_no = culprit_VA.page_no;
X assert( page_no < (unsigned)context.page_table_len );
X VPN vpn = context.vm_map[page_no];
X
X if( context.no_mapped_pages >= context.working_set_quota )
X replace_LRU_page(context);
X
X PFN pfn = virtual_space.reference(vpn);
X if( pfn == NIL_pfn )
X return PhysMemoryExhausted;
X context.no_mapped_pages++;
X VirtualPage& vp = virtual_space[context.vm_map[page_no]];
X
X if( !vp.is_shared_mapped() )
X context.no_page_faults++; // The page has been physically loaded
X else // The page might have been loaded by
X if( vp.q_data_page() ) // siblings (if it's shared)
X _error("Non-shared data page %d turns out to be loaded into frame %d",
X vp.id,vp.page_frame);
X
X MMUContext::PageTableEntry& pte = context.page_table[page_no];
X pte.phys_page_no = vp.page_frame;
X pte.valid = 1;
X context.time_since_last_ref[page_no] = 0;
X return Ok;
X}
X
X // Release the virtual memory
X // occupied by the process
Xvoid MemoryManager::dispose(MMContext& context)
X{
X if( !context.enabled_address_translation )
X return; // No virtual memory was occupied
X register int i;
X for(i=0; i<MMContext::process_virtual_space; i++)
X {
X VPN vpn = context.vm_map[i];
X if( vpn == NIL_vpn )
X continue;
X MMUContext::PageTableEntry& pte = context.page_table[i];
X if( pte.valid )
X virtual_space.unreference(vpn,0),
X context.no_mapped_pages--;
X virtual_space.dispose(vpn);
X context.vm_map[i] = NIL_vpn;
X }
X assert(context.no_mapped_pages == 0);
X memset(context.time_since_last_ref,0,sizeof(context.time_since_last_ref));
X context.no_page_faults = 0;
X context.page_table_addr = 0;
X context.page_table_len = 0;
X context.enabled_address_translation = 0;
X}
X
X // Swap out a victim - forcibly deprive it
X // of mapped pages
X // Return 0 if no page frame was released
X // Swapping goals were not achieved
Xint MemoryManager::swap_out(MMContext& context)
X{
X message("\nSwapping out...");
X if( !context.enabled_address_translation )
X return 0; // No virtual memory was occupied
X register int i;
X int swapped_something = 0;
X for(i=0; i<MMContext::process_virtual_space; i++)
X {
X VPN vpn = context.vm_map[i];
X if( vpn == NIL_vpn )
X continue;
X VirtualPage& vp = virtual_space[vpn];
X MMUContext::PageTableEntry& pte = context.page_table[i];
X if( !pte.valid ) // The page wasn't accessed by us yet
X continue; // (if it was allocated at all)
X assert( pte.phys_page_no == vp.page_frame );
X virtual_space.unreference(vpn,pte.was_written);
X context.no_mapped_pages--;
X pte.valid = 0;
X pte.was_ref = pte.was_written = 0;
X swapped_something = 1;
X }
X assert(context.no_mapped_pages == 0);
X return swapped_something;
X}
X
X // Run the LRU page replacement strategy
X // to find out the least referenced page
X // and push it out
Xvoid MemoryManager::replace_LRU_page(MMContext& context)
X{
X int max_counter = -1;
X int page_victim = -1;
X register int i;
X for(i=0; i<MMContext::process_virtual_space; i++)
X if(context.page_table[i].valid &&
X context.time_since_last_ref[i] > max_counter)
X max_counter = context.time_since_last_ref[i],
X page_victim = i;
X
X assert( page_victim >= 0 );
X VPN vpn = context.vm_map[page_victim];
X message("\nFound LRU page %d, time since last used %d",vpn,max_counter);
X
X MMUContext::PageTableEntry& pte = context.page_table[page_victim];
X assert( pte.valid );
X virtual_space.unreference(vpn,pte.was_written);
X context.no_mapped_pages--;
X pte.valid = 0;
X pte.was_ref = pte.was_written = 0;
X}
X
X // Lock the page that contains the
X // specified virtual address. The page
X // should be already mapped
X // Return FALSE if failed
Xint MemoryManager::lock_loaded_page(MMContext& context,const Address addr)
X{
X if( !context.enabled_address_translation )
X return 0; // Virtual memory is disabled
X
X MMUContext::VirtualMemoryAddr va = *(MMUContext::VirtualMemoryAddr*)&addr;
X unsigned int page_no = va.page_no;
X assert( page_no < (unsigned)context.page_table_len );
X VPN vpn = context.vm_map[page_no];
X
X virtual_space.lock(vpn);
X return 1;
X}
X
X // Unlock the page that contains the
X // specified virtual address. The page
X // should be already mapped & locked
Xvoid MemoryManager::unlock_page(MMContext& context,const Address addr)
X{
X assert( context.enabled_address_translation );
X
X MMUContext::VirtualMemoryAddr va = *(MMUContext::VirtualMemoryAddr*)&addr;
X unsigned int page_no = va.page_no;
X assert( page_no < (unsigned)context.page_table_len );
X VPN vpn = context.vm_map[page_no];
X
X virtual_space.unlock(vpn);
X}
X
X // Translate the virtual address to physical
X // one. The page has to be loaded.
X // Returns 0 if fails
XAddress MemoryManager::translate_va(MMContext& context,const Address addr)
X{
X if( !context.enabled_address_translation )
X return addr; // Virtual memory is disabled
X
X MMUContext::VirtualMemoryAddr va = *(MMUContext::VirtualMemoryAddr*)&addr;
X unsigned int page_no = va.page_no;
X if( page_no >= (unsigned)context.page_table_len )
X return 0; // Out of range
X const MMUContext::PageTableEntry& pte = context.page_table[page_no];
X if( !pte.any_access )
X return 0; // No access
X if( !pte.valid )
X return 0; // Not mapped
X va.page_no = pte.phys_page_no;
X
X return *(Address *)&va;
X}
END_OF_FILE
if test 16274 -ne `wc -c <'mem_manager.cc'`; then
echo shar: \"'mem_manager.cc'\" unpacked with wrong size!
fi
# end of 'mem_manager.cc'
fi
echo shar: End of archive 1 \(of 4\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 2 3 4 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have unpacked all 4 archives.
rm -f ark[1-9]isdone
else
echo You still must unpack the following archives:
echo " " ${MISSING}
fi
exit 0
exit 0 # Just in case...