There are a few necessary parts to a disk file system.
1. You need some way to give disk files namesOther features would be nice (and most operating systems offer them), but they are not in this minimal toy:
2. You need to be able to create new (named) files programmatically.
3. You should be able to open and close files.
4. You should be able to read and write concurrently from different open files.
5. The system should extend naturally to other non-disk devices.
6. You should be able to extend a file by re-opening it and writing additional data, after it has been created and closed.
7. You should be able to create and open more than one file at a time (we can read more than one file, but write only one).
8. Hierarchical directories are nice.
9. A disk should be able to hold thousands of files, not just 7 or 23.
The first 8 bytes of the first sector is a signature for quality control purposes, "HardDisk". Then there are four 2-byte numbers:
+0 Signature "HardDisk" or "DiskFlop"
+8 Reserved for a boot sector number (not currently
used)
+10 The next unused sector number. On a properly formatted
empty disk, this would be 4.
+12 The number of files on this volume (= the number
of entries in the directory).
+14 The maximum number of files this directory can hold
(7).
Each file entry in the directory has this format:
+0 Starting sector for this file
+2 File length in bytes (max 65534)
+4 File name (null filled)
MiniOS keeps in memory a 4-entry table of currently open files, each with four numbers:
+0 The file number in the directory (1-7), or else
a negative device number
+2 The next sector to be read or written
+4 The byte offset in the current sector, 0-255;
this is the next byte to be read or written.
+6 The remaining file size unread (initially the
size from the directory, or FFFF if creating a new file)
In this listing, the DefaultTable is probably most confusing.
It (implicitly) and the PageTable are both defined to be int[32],
where each integer holds two bytes. The first byte is most significant
(SandBox is defined to be "Big-Endian" so that the lowest address is at
the big end of the number, that is numbers read left-to-right, English
style), so it is multiplied times 256 to place it 8 bits to the left. In
the stack and data part of the table, all the entries are offset by
-32640, which is hex 0x8080. Since there are two one-byte
entries in each int, each byte has its own write-enable bit (the 8 in 0x8080).
Note that the final page is the I/O registers, so it is writable, while
the code pages immediately below it are not. Some of the entries are also
swapped with the next row, as a way of proving that it really is working.
A better proof is the March 25 assignment demo.
// Mini OS, Semaphores Demo and MMU Test added -- 2004 March 9Rev. 2004 March 9import Files;
import Semaphores;
import Hardware;
import Dangerous;// ==============================================================
// Global data, used as indicated..// Semaphore Demo data..
final int Qsize = 4;
int[32] DemoFrame; // stack frame for second process
char[Qsize] queue; // the buffer
int fx=0, tx=0; // fill and take buffer indexes
Semaphore fill, take; // their semaphores// MMU Test data..
int[32] PageTable;
final int[] DefaultTable = { 0*256-32640+1, 2*256-32640+3,
04*256-32640+05, 22*256-32640+23, 24*256-32640+25, 10*256-32640+11,
12*256-32640+13, 14*256-32640+15, 16*256-32640+17, 18*256-32640+19,
20*256-32640+21, 06*256-32640+07, 08*256-32640+09, 26*256-32640+27,
28*256-32640+29, 30*256-32640+31, 96*256+97, 98*256+99,
100*256+101, 102*256+103, 104*256+105, 106*256+107,
108*256+109, 110*256+111, 112*256+113, 114*256+115,
116*256+117, 118*256+119, 120*256+121, 122*256+123,
124*256+125, 126*256+255 };// Interrupt handler process stack frames..
int[16] MMUFrame; // stack frame for MMU interrupt
int[16] ClockFrame; // stack frame for Clock interrupt
int[16] DiskFrame; // stack frame for Disk interrupt
int[16] KeyIntFrame; // stack frame for KeyIn interrupt
int[16] PrintFrame; // stack frame for Print interrupt
int[16] Sema4Frame; // stack frame for Signal/Wait interrupts
int[80] SysCallFrame; // stack frame for SysCall interrupt;
// it's extra big because the SysCall handler calls lots of
// nested subroutines, which might collide with the next frame// Data used by the file system..
int[16] ActiveFiles; // the 4 files you can have open, 4 ints each
final int Directory = 1024; // start of disk dir
// watch for possible collision between this and main() stack
final int BuffersAt = Directory+256; // 256-byte buffers for files 1-4// Memory allocator data..
int HeapTop = 32766; // allocates down from here..
final int HeapBottom = BuffersAt+1024; // .. to here// Keyboard and Printer data..
int KeyFillPtr = BuffersAt, KeyTakePtr = BuffersAt,
KeyShowPtr = BuffersAt; // initialize them to empty buffer
int GotRetns = 0; // the number of CRs in KeyIn buffer
boolean PrintReady = true; // =false between '\r' and interrupt// Timer data..
int theTicks; // used by clock interrupt for Delay// Process manager data..
int ReadyList = 0; // list of waiting processes
int CurProcess = 0; // whatever is currently running
int[8] MainPIB; // to make main() look like another process
int[] AppPIB, AppCode; // dynamically allocated for demo// User-level file data..
FileId Kin, Cout; // KeyIn and ScreenOut, always open
String KeyString; // used by ReadStr() to buffer a line
final int[] MyName = { ORD('M')*256+ORD('i'), ORD('n')*256+ORD('i'),
ORD('O')*256+ORD('S') }; // "MiniOS", the name of this file// Video data, used within the system code..
int Vrow = 1, Vcol = 2; // current display V and H
final int TextColor = DoText+white;
final int TextBgnd = FillRect+minRed+minGreen+minBlue;
// dark gray fillfinal int[] FontBits = { 0, 0, 0, 0, 0, 0, 0, 190, 0, 0, // sp
0, 14, 0, 14, 0, 40, 124, 40, 124, 40,
76, 146, 511, 146, 100, 66, 37, 146, 328, 132,
96, 150, 137, 118, 208, 0, 0, 14, 0, 0,
0, 124, 130, 257, 0, 0, 257, 130, 124, 0,
8, 42, 28, 42, 8, 16, 16, 124, 16, 16,
0, 512, 448, 192, 0, 16, 16, 16, 16, 16,
0, 192, 192, 0, 0, 0, 384, 96, 24, 6,
56, 68, 130, 68, 56, 0, 4, 254, 0, 0, // 0
132, 194, 162, 146, 140, 68, 130, 146, 146, 108,
48, 40, 36, 254, 32, 78, 138, 138, 138, 114,
120, 148, 146, 146, 96, 2, 2, 226, 26, 6,
108, 146, 146, 146, 108, 12, 146, 146, 82, 60,
0, 204, 204, 0, 0, 0, 716, 460, 0, 0,
0, 16, 40, 68, 130, 40, 40, 40, 40, 40,
0, 130, 68, 40, 16, 4, 2, 162, 18, 12,
120, 132, 306, 330, 60, 224, 56, 38, 56, 224, // @A
130, 254, 146, 146, 108, 124, 130, 130, 130, 68,
130, 254, 130, 68, 56, 254, 146, 146, 130, 130,
254, 18, 18, 18, 2, 124, 130, 130, 146, 244,
254, 16, 16, 16, 254, 0, 130, 254, 130, 0,
96, 128, 128, 128, 126, 254, 16, 40, 68, 130,
254, 128, 128, 128, 128, 254, 4, 24, 4, 254,
254, 4, 24, 32, 254, 124, 130, 130, 130, 124,
254, 18, 18, 18, 12, 124, 130, 130, 386, 636, // P
254, 18, 50, 82, 140, 76, 146, 146, 146, 100,
2, 2, 254, 2, 2, 126, 128, 128, 128, 126,
14, 48, 192, 48, 14, 30, 224, 24, 224, 30,
198, 40, 16, 40, 198, 6, 8, 240, 8, 6,
194, 162, 146, 138, 134, 0, 0, 511, 257, 0,
0, 6, 24, 96, 384, 0, 257, 511, 0, 0,
4, 2, 1, 2, 4, 128, 128, 128, 128, 128,
0, 2, 4, 0, 0, 64, 168, 168, 168, 240, // `a
254, 136, 136, 136, 112, 112, 136, 136, 136, 136,
112, 136, 136, 136, 254, 112, 168, 168, 168, 48,
16, 252, 18, 2, 4, 112, 648, 648, 648, 504,
254, 8, 8, 8, 240, 0, 136, 250, 128, 0,
512, 520, 506, 0, 0, 254, 16, 48, 72, 128,
0, 2, 254, 0, 0, 248, 8, 240, 8, 240,
248, 16, 8, 8, 240, 112, 136, 136, 136, 112,
1016, 136, 136, 136, 112, 112, 136, 136, 136, 1016, // p
248, 16, 8, 8, 16, 144, 168, 168, 168, 72,
0, 8, 126, 136, 128, 120, 128, 128, 128, 248,
8, 48, 192, 48, 8, 56, 192, 48, 192, 56,
136, 80, 32, 80, 136, 536, 608, 384, 96, 24,
136, 200, 168, 152, 136, 0, 16, 238, 257, 0,
0, 0, 511, 0, 0, 0, 257, 238, 16, 0,
4, 2, 2, 4, 2 };// ==============================================================
// Video driver subroutines, for use by the system only..// This sets a simple rectangular clipping region
// for concurrent programs sharing the screen by tiling..
void Clipper(int T, int L, int B, int R) {
Video.top = T; // set clip region for this process
Video.left = L;
Video.bottom = B;
Video.right = R;
Video.datum = ClipRect;} // ~Clipper// Erase next line, used when an output string has \r in it..
void ScreenLn() { // start new line, and clear it..
Vcol = 2;
Vrow = Vrow+12;
if (Vrow>469) Vrow = 1;
Video.left = 0;
Video.right = 640;
Video.top = Vrow-1;
Video.bottom = Vrow+12;
Video.datum = TextBgnd;} // ~ScreenLn// Erase previous char, used when an output string has \b in it..
void ScreenBS() {
if (Vcol<8) return; // nothing to back up over
Video.right = Vcol;
Vcol = Vcol-6;
Video.left = Vcol;
Video.top = Vrow;
Video.bottom = Vrow+12;
Video.datum = TextBgnd;} // ~ScreenBS// Display one character..
void ScreenChar(char ch) { // display one character on current line
if (ORD(ch)==13) ScreenLn();
if (ORD(ch)==8) ScreenBS();
if (ch<' ' || ch>'~' || Vcol>633) return; // not printable
Video.address = ORD(ch)*10-320+&FontBits; // 5 ints x 2 bytes =10
Video.left = Vcol;
Video.right = Vcol+5;
Vcol = Vcol+6;
Video.top = Vrow;
Video.bottom = Vrow+12;
Video.datum = TextColor;} // ~ScreenChar// File-oriented write to screen, called by SysCall(7)
// when the file type is -1 (Screen/KeyIn); it assumes the data
// is like String arrays, 1 char per 2-byte array element..
void WriteScr(int nby, int DataPtr) {
while (nby>0) {
ScreenChar(CHR(PEEK(DataPtr)));
DataPtr = DataPtr+2;
nby = nby-2;}} // ~WriteScr// File-oriented read from keyin, called by SysCall(6)
// when the file type is -1 (Screen/KeyIn); it packs data in
// 1 char per 2-bytes, like String arrays, omitting final return.
// Special-case: if buffer has only one element (2 bytes),
// then a single return character is returned in it.
int ReadLine(int nby, int DataPtr) {
// we want nby bytes (but not more than 254) in array DataPtr
int got = 0; // the for-loop fill index
int ch; // temp to hold current char
boolean sho; // =true if need to display this char
if (nby>254) nby = 254; // request is too big; trim it
if (((KeyFillPtr-KeyTakePtr)&256)+KeyFillPtr-KeyTakePtr<nby
&& GotRetns==0) { // not enough chars to satisfy..
while (KeyShowPtr != KeyFillPtr) { // echo what we have so far..
ch = mmPEEK(KeyShowPtr);
KeyShowPtr = ((KeyShowPtr+2)&254)+BuffersAt;
ScreenChar(CHR(ch));}
return -1;}
while (got<nby) { // OK, we can do it this time..
ch = mmPEEK(KeyTakePtr); // get a character from buffer
sho = KeyShowPtr == KeyTakePtr; // false while working on the
// keystrokes that were already echoed, then true after that.
KeyTakePtr = ((KeyTakePtr+2)&254)+BuffersAt; // advance to next
if (sho) { // once it's caught up, keep it up..
ScreenChar(CHR(ch));
KeyShowPtr = KeyTakePtr;}
if (ch==13) { // keep returns count current..
GotRetns = GotRetns-1;
if (nby>2) break;} // exit if got end-of-line return
else if (ch==8) if (nby>2) if (got>0) { // backspace..
DataPtr = DataPtr-2; // back up over the previous character
got = got-2;
continue;} // don't insert it into buffer
mmPOKE(DataPtr,ch); // '\r' isn't included unless asking for 1 byte
DataPtr = DataPtr+2;
got = got+2;} // '\r' will be last char in 1-byte request
return got;} // ~ReadLine// ==============================================================
// Disk file I/O, for use by the system only..// Read or write one sector: flop =true if floppy; wrt =true to write;
// get/put nby bytes at sector number sec, memory buffer at dPtr;
// return the actual number of bytes transferred.
// Not interrupt-driven, it just busy-waits until completion.
// Called from various places inside the disk driver..
int RWsector(boolean flop, boolean wrt, int sec, int nby, int dPtr) {
int timo = 65533; // hopefully long enough to wait
if (flop && Enables.datum & FlopInBit ==0) return 0; // floppy not in
if (flop) sec = ~sec; // hardware wants these negatives..
if (wrt) nby = -nby;
Disk.sector = sec;
Disk.address = dPtr;
Disk.count = nby;
while (Enables.datum&DiskPendBit == 0 && timo !=0) // wait until done..
timo = timo - timo/timo; // a little slower than just timo-1
Enables.datum = IntOnBit; // turn DiskPendBit back off
nby = nby-Disk.count;
if (wrt) nby = -nby;
return nby;} // ~RWsector// Write nby bytes at address DataPtr to file index filix;
// called by WriteFile from SysCall(7) for disk files.
// Does not properly map data through MMU!
int WrtDskFile(boolean flop, int filix, int nby, int DataPtr) {
int sect = ActiveFiles[filix+1]; // get current file position
int offs = ActiveFiles[filix+2];
int BufPtr = filix*64+BuffersAt;
int eofAt = ActiveFiles[filix+3]; // how many bytes left in this file
int got = 0; // the # of bytes transferred
if (nby>eofAt) if (eofAt>=0) nby = eofAt; // can't write past eof
if (ActiveFiles[filix]+4==0) { // newly opened flop, or was reading..
ActiveFiles[filix] = -5; // note this is 'write'
if (offs>0) sect = sect-1;} // gotta rewrite this sector
if (offs>0) { // partial buffer to fill..
while (offs<256 && nby>0) { // transfer 2 bytes at a time
POKE(BufPtr+offs, PEEK(DataPtr));
offs = offs+2;
DataPtr = DataPtr+2;
nby = nby-2;
got = got+2;
eofAt = eofAt-2;}
offs = offs & 255; // =0 when buffer is full
if (offs==0) { // full: write sector out
offs = RWsector(flop, true, sect, 256, BufPtr);
sect = sect+1;
offs = 0;}}
// ASSERT: offs==0 || nby==0
if (nby>255) { // write some whole sectors, directly from user buffer..
offs = RWsector(flop, true, sect, nby&(-256), DataPtr);
nby = nby-offs;
got = got+offs;
DataPtr = DataPtr+offs;
eofAt = eofAt-offs;
sect = offs/256+sect;
offs = 0;}
if (nby>0) { // buffer the rest (less than one sector)..
offs = RWsector(flop, false, sect, 256, BufPtr);
if (offs<256) nby = 0; // oops, something went wrong, kill it
offs = 0;
while (nby>0) {
POKE(BufPtr+offs, PEEK(DataPtr));
offs = offs+2;
DataPtr = DataPtr+2;
nby = nby-2;
got = got+2;
eofAt = eofAt-2;}
sect = sect+1;}
ActiveFiles[filix+1] = sect; // update file position info
ActiveFiles[filix+2] = offs;
ActiveFiles[filix+3] = eofAt;
// ScreenChar('`');
return got;} // ~WrtDskFile// Read nby bytes to address DataPtr from file index filix;
// called by ReadFile from SysCall(6)..
// Does not properly map addresses through MMU!
int RdDiskFile(boolean flop, int filix, int nby, int DataPtr) {
int sect = ActiveFiles[filix+1]; // get current file position
int offs = ActiveFiles[filix+2];
int BufPtr = filix*64+BuffersAt;
int eofAt = ActiveFiles[filix+3]; // how many bytes left in this file
int got = 0; // the # of bytes transferred
if (nby>eofAt) if (eofAt>=0) nby = eofAt; // can't read past eof
if (offs>0) { // partial sector in buffer..
while (offs<256 && nby>0) { // transfer 2 bytes at a time
POKE(DataPtr, PEEK(BufPtr+offs));
offs = offs+2;
DataPtr = DataPtr+2;
nby = nby-2;
got = got+2;
eofAt = eofAt-2;}
offs = offs & 255; // =0 when buffer is empty
ActiveFiles[filix+2] = offs; // update file position info
ActiveFiles[filix+3] = eofAt;
if (offs==0) { // empty, advance sector pointer too
sect = sect+1;
ActiveFiles[filix+1] = sect;}}
if (nby>255) { // get some whole sectors, directly to user buffer..
offs = RWsector(flop, false, sect, nby&(-256), DataPtr);
nby = nby-offs;
got = got+offs;
DataPtr = DataPtr+offs;
eofAt = eofAt-offs;
sect = offs/256+sect;
ActiveFiles[filix+1] = sect;}
if (nby>0) { // buffer the rest (less than one sector)..
offs = RWsector(flop, false, sect, 256, BufPtr);
if (offs<nby) nby = offs; // maybe early eof? (shouldn't happen)
offs = 0;
while (nby>0) {
POKE(DataPtr, PEEK(BufPtr+offs));
offs = offs+2;
DataPtr = DataPtr+2;
eofAt = eofAt-2;
if (eofAt+1==0) eofAt = 0; // don't cross 0
nby = nby-2;
got = got+2;}
ActiveFiles[filix+2] = offs; // update file position info
ActiveFiles[filix+3] = eofAt;
ActiveFiles[filix+1] = sect+1;}
return got;} // ~RdDiskFile// Look for an unused file index, called by Open only..
int FindAvailFile() {
if (ActiveFiles[4]==0) return 4;
if (ActiveFiles[8]==0) return 8;
if (ActiveFiles[12]==0) return 12;
return 0;} // ~FindAvailFile// 2 functions to pack and unpack bytes,
// between char[] arrays (2 bytes for each character)
// and disk file format (byte for byte)
//
// These are called by the file manager to convert between String
// file names and the packed form of the directory.
// You could use them elsewhere, but they take integer pointers,
// which requires using the (unsafe) unary-& operator.// Pack nby bytes from char[] array frm to int[] array too
// bytes are 1/word in char[], 2/word in int[]
void PackBytes(int nby, int frm, int too) {
int temp;
while (nby>0) {
temp = mmPEEK(frm)*256;
nby = nby-2;
frm = frm+2;
if (nby>=0) temp = temp + (mmPEEK(frm)&255);
mmPOKE(too,temp);
too = too+2;
frm = frm+2;}} // ~PackBytes// Unpack nby bytes from int[] array frm to char[] array too
// bytes are 1/word in char[], 2/word in int[]
void unPackBy(int nby, int frm, int too) {
int temp;
while (nby>0) {
mmPOKE(too,mmPEEK(frm-1)&255);
mmPOKE(too+2,mmPEEK(frm)&255);
nby = nby-2;
frm = frm+2;
too = too+4;}} // ~unPackBy// Compare the String name at NamePtr to dirName (in the directory),
// return true if they are equal (not case-sensitive);
// NamePtr points to 1st byte of user String,
// dirName points to a name in directory, max 27 chars;
// Called by OpenFile to find an existing file with this name..
boolean EqualName(int dirName, int NamePtr) {
int ix = mmPEEK(NamePtr-2)/2; // get String length
int chd, chn;
if (ix>27) return false; // NamePtr is too long, cannot be equal
if ((mmPEEK(dirName+3+ix)&255)!=0) return false; // too short
while (ix>0) {
ix = ix-1;
chd = mmPEEK(dirName+3+ix)&255;
if (chd<91) if (chd>64) chd = chd+32;
chn = mmPEEK(NamePtr+ix+ix);
if (chn<91) if (chn>64) chn = chn+32;
if (chd != chn) return false;} // unequal
return true;} // ~EqualName// Store file type fity into file index fid,
// then set start sector and eof from directory entry # fino;
// return a FileID value.
// Called by OpenFile() on its way back to SysCall(4)
int SetActiveFile(int fid, int fino, int fity) {
if (fity>0) ActiveFiles[fid] = fity;
ActiveFiles[fid+1] = mmPEEK(Directory+fino*32);
ActiveFiles[fid+2] = 0;
ActiveFiles[fid+3] = mmPEEK(Directory+fino*32+2);
return fid/4+1;} // ~SetActiveFile// Open any known named file or file type
int OpenFile(int NamePtr) { // called by SysCall(4)
char two = CHR(mmPEEK(NamePtr+2)&223); // 2nd char of name, if needed
int fi = FindAvailFile(); // might not be needed, but cheap call
int ix = -1; // = default device name, KeyIn/ScreenOut
int nx;
if (mmPEEK(NamePtr)==ORD('.')) { // device name..
if (two == 'K') fi = 0; // KeyIn/ScreenOut are always FileId =1
else if (two == 'S') fi = 0;
else if (fi==0) return 0; // FindAvailFile() failed, can't open
else if (two == 'P') ix = -2; // printer
else if (two == 'F') ix = -4; // floppy
else if (two == 'H') ix = -3; // hard-sectored hard disk
else return 0; // dunno; can't open it
ActiveFiles[fi] = ix; // set the file type
if (ix+4 == 0) { // floppy, read the directory..
ix = RWsector(true, false, 0, 256, Directory);
// you should verify file name, etc...
return SetActiveFile(fi,1,0);} // we just open file # 1
else if (ix+3 == 0) { // hard disk, absolute sector..
ActiveFiles[fi+1] = 0; // sector 0
ActiveFiles[fi+2] = 0;
ActiveFiles[fi+3] = -1;} // no size limit
return fi/4+1;}
else { // named HD file..
if (mmPEEK(NamePtr-2)>54) return 0; // name too long
if (RWsector(false, false, 0, 256, Directory)<256)
return 0; // read failed
ix = mmPEEK(Directory+12); // # files on this disk
while (ix>0) // try each file..
if (EqualName(Directory+ix*32,NamePtr))
return SetActiveFile(fi,ix,ix); // found it
else ix = ix-1;
ix = mmPEEK(Directory+12)+1; // not there, add another file
if (ix>mmPEEK(Directory+14)) return 0; // oops, too many
nx = mmPEEK(NamePtr-2)/2; // length of file name, in chars
PackBytes(nx, NamePtr, Directory+ix*32+4); // insert name
mmPOKE(Directory+ix*32+4+nx,0); // null terminated
mmPOKE(Directory+ix*32,mmPEEK(Directory+10)); // set start to next sec
mmPOKE(Directory+ix*32+2,-1); // new file, assume 65K
if (RWsector(false, true, 0, 256, Directory)==256) // rewrite dir
return SetActiveFile(fi,ix,ix);} // fail if dir rewrite fails
return 0;} // ~OpenFile// Close the file indexed by FileId theFile..
void CloseFile(int theFile) { // called by SysCall(5)
int fi = theFile*4-4; // point to the indexed ActiveFiles
int BufPtr = fi*64+BuffersAt; // point to its buffer, in case needed
int ix;
if (fi<0) return; // not an open file (null FileId)
if (theFile>4) return; // not an open file (bogus FileId)
ix = ActiveFiles[fi]; // the file # on disk, or device #
if (ix>0) { // close hard disk file..
ActiveFiles[fi] = 0;
if (RWsector(false, false, 0, 256, Directory)<256) return;
if (mmPEEK(Directory+12)>=ix) return; // not closing new file
if (ActiveFiles[fi+3]+1==0) return; // didn't write anything
mmPOKE(Directory+12,ix); // update # files
mmPOKE(Directory+ix*32+2,~ActiveFiles[fi+3]); // actual file size
ix = ActiveFiles[fi+1]; // this is next sector to write
if (ActiveFiles[fi+2]>0) // rewrite partial buffer
if (RWsector(false, true, ix, 256, BufPtr)==256)
ix = ix+1; // successful write, update sector #
mmPOKE(Directory+10,ix); // that is sector for next file to open
ix = RWsector(false, true, 0, 256, Directory); // rewrite dir
return;}
else if (ix+5==0) if (ActiveFiles[fi+2]>0) // writing to floppy..
ix = RWsector(true, true, ActiveFiles[fi+1], 256, BufPtr);
if (fi>0) ActiveFiles[fi] = 0; // leave K/S open
} // ~CloseFile// Read a block into array DataPtr from open FileId theFile..
int ReadFile(int theFile, int DataPtr) { // called by SysCall(6)
int fi = theFile*4-4; // point to the indexed ActiveFiles
int who, len;
if (fi<0) return 0; // not an open file (null FileId)
if (theFile>4) return 0; // not an open file (bogus FileId)
who = ActiveFiles[fi];
if (who==0) return 0; // not an open file (stale fileId)
if (DataPtr==0) return 0; // no array to read into
len = mmPEEK(DataPtr-2); // the size of the array
if (who+1==0) return ReadLine(len, DataPtr); // Keyboard input
if (who+4==0) return RdDiskFile(true, fi, len, DataPtr); // Floppy in
if (who>0) { // named HD file
if (ActiveFiles[fi+3]+1==0) return 0; // new file, nothing to read
who = -3;}
if (who+3==0 || who>0) // Hard disk sector or file input..
return RdDiskFile(false, fi, len, DataPtr);
return 0; // ignore the rest
} // ~ReadFile// Write a block from array DataPtr to open FileId theFile..
int WriteFile(int theFile, int DataPtr) { // called by SysCall(7)
int fi = theFile*4-4; // point to the indexed ActiveFiles
int who, len;
if (fi<0) return 0; // not an open file (null FileId)
if (theFile>4) return 0; // not an open file (bogus FileId)
who = ActiveFiles[fi];
if (who==0) return 0; // not an open file (stale fileId)
if (DataPtr==0) return 0; // no array to write from
len = mmPEEK(DataPtr-2); // the size of the array
if (who+1==0) { // Screen output..
WriteScr(len, DataPtr);
return len;}
if (who != -2) // Floppy/hard disk output..
return WrtDskFile(who<-3, fi, len, DataPtr);
return 0; // ignore the rest
} // ~WriteFile// ==============================================================
// Process management subroutine,
// moves CurProcess to Blok2, and ReadyList to CurProcess
int SwapOut(int SemSP, int Blok2) {
int going = CurProcess;
int comin = ReadyList;
if (Blok2==0) Blok2 = &ReadyList; // Blok2=0 just swaps
if (comin !=0) if (going !=0) { // can't do it if nothing there
ReadyList = mmPEEK(comin); // unlinked top of ReadyList
CurProcess = comin; // and make it current
mmPOKE(going+6,SemSP); // save current process's SP in its PIB
mmPOKE(going, mmPEEK(Blok2)); // link it to rest of its list
mmPOKE(Blok2,going); // move (formerly) CurProcess to its list
return mmPEEK(comin+6);} // exit to it on caller's CoCall
return SemSP;} // ~SwapOut// ==============================================================
// Interrupt drivers..
// These are called ONCE to start up their respective processes.
// After that, they do all their work when interrupts hit..void DoSema4() {
// These 2 interrupts happen for Signal and Wait respectively
int SemSP = 0; // maps to Sema4Frame[3]
int aPtr, temp;
Sema4Frame[6] = &Sema4Frame[9]; // initial SP
Sema4Frame[8] = &Sema4Frame[0]; // initial FP
Sema4Frame[0] = mmPEEK((&SemSP)-6); // copy my globals ptr
Sema4Frame[1] = 0; // halt if it returns (it shouldn't)
SemSP = FORK(&Sema4Frame[6]); // returns twice:
if (SemSP==0) { // returned in caller's process..
aPtr = &Sema4Frame[3]; // set 2 interrupt vector items..
iVector[6] = aPtr;
iVector[7] = aPtr;
return;}
// otherwise returned in new process (first)..
mmPOKE(SemSP-6,0); // clear caller's return value
while (true) { // this is the interrupt handler loop..
COCALL(&SemSP); // wait for interrupt
aPtr = SignalWait.address; // the semaphore (not on stack)
if (mmPEEK(mmPEEK(SemSP)-2)&1 ==0) { // caller's opcode, =0 if Signal
temp = ReadyList; // get previous ready list,
ReadyList = mmPEEK(aPtr); // replace it with process from sem,
mmPOKE(aPtr,mmPEEK(ReadyList)); // (delete top of sem queue)
mmPOKE(ReadyList, temp);} // ..link ready list to previous
else SemSP = SwapOut(SemSP,aPtr);} // otherwise it's a Wait
} // ~DoSema4void DoDisk() {
// This interrupt is used for switching context at the end of
// disk I/O, if you switched out to make use of the wait time.
// Right now it's set up but unused.
int SemSP = 0; // maps to DiskFrame[3]
int unused;
int temp;
DiskFrame[6] = &DiskFrame[9]; // initial SP
DiskFrame[8] = &DiskFrame[0]; // initial FP
DiskFrame[0] = mmPEEK((&SemSP)-6); // copy my globals ptr
DiskFrame[1] = 0; // halt if it returns (it shouldn't)
SemSP = FORK(&DiskFrame[6]); // returns twice:
if (SemSP==0) { // returned in caller's process..
iVector[2] = &DiskFrame[3]; // set interrupt vector
return;}
// otherwise returned in new process (first)..
mmPOKE(SemSP-6,0); // clear caller's return value
while (true) { // this is the interrupt handler loop..
COCALL(&SemSP); // wait for interrupt
// YOUR CODE GOES HERE
} // do nothing
} // ~DoDiskvoid DoKeyInts() {
// This interrupt grabs a keystroke when it is available,
// and saves it in a buffer for when the program needs it.
int SemSP = 0; // maps to KeyIntFrame[3]
int theKey;
int temp;
KeyIntFrame[6] = &KeyIntFrame[9]; // initial SP
KeyIntFrame[8] = &KeyIntFrame[0]; // initial FP
KeyIntFrame[0] = mmPEEK((&SemSP)-6); // copy my globals ptr
KeyIntFrame[1] = 0; // halt if it returns
SemSP = FORK(&KeyIntFrame[6]); // create a new process (2 returns)
if (SemSP==0) { // final return in initial process
iVector[3] = &KeyIntFrame[3]; // set interrupt vector
return;}
// otherwise returned in new process (first)..
mmPOKE(SemSP-6,0); // clear caller's return value
while (true) { // this is the interrupt handler loop..
COCALL(&SemSP); // wait for interrupt
theKey = KeyIn.datum & 127; // got a keystroke, save it..
mmPOKE(KeyFillPtr, theKey); // ..in the keyboard buffer
if (theKey==13) GotRetns = GotRetns+1; // count returns (=lines)
KeyFillPtr = ((KeyFillPtr+2)&254)+BuffersAt; // advance ptr
if (KeyFillPtr==KeyTakePtr) { // buffer o'flow, discard oldest
if (mmPEEK(KeyTakePtr)==13) GotRetns = GotRetns-1;
KeyTakePtr = ((KeyTakePtr+2)&254)+BuffersAt;
if (KeyFillPtr==KeyShowPtr) // discard oldest show also..
KeyShowPtr = KeyTakePtr;}
} // ~while
} // ~DoKeyIntsvoid DoPrint() {
// This interrupt happens after line output completion
// Right now it's set up but unused.
int SemSP = 0; // maps to PrintFrame[3]
int unused;
int temp;
PrintFrame[6] = &PrintFrame[9]; // initial SP
PrintFrame[8] = &PrintFrame[0]; // initial FP
PrintFrame[0] = mmPEEK((&SemSP)-6); // copy my globals ptr
PrintFrame[1] = 0; // halt if it returns (it shouldn't)
SemSP = FORK(&PrintFrame[6]); // returns twice:
if (SemSP==0) { // returned in caller's process..
iVector[4] = &PrintFrame[3]; // set interrupt vector
return;}
// otherwise returned in new process (first)..
mmPOKE(SemSP-6,0); // clear caller's return value
while (true) { // this is the interrupt handler loop..
COCALL(&SemSP); // wait for interrupt
PrintReady = true;} // do nothing
} // ~DoPrintvoid DoMemMgr() {
// This interrupt happens on a memory manager page fault
// Right now ...
int SemSP = 0; // maps to MMUFrame[3]
int unused;
int temp;
MMUFrame[6] = &MMUFrame[9]; // initial SP
MMUFrame[8] = &MMUFrame[0]; // initial FP
MMUFrame[0] = mmPEEK((&SemSP)-6); // copy my globals ptr
MMUFrame[1] = 0; // halt if it returns (it shouldn't)
SemSP = FORK(&MMUFrame[6]); // returns twice:
if (SemSP==0) { // returned in caller's process..
iVector[0] = &MMUFrame[3]; // set interrupt vector
return;}
// otherwise returned in new process (first)..
mmPOKE(SemSP-6,0); // clear caller's return value
while (true) { // this is the interrupt handler loop..
COCALL(&SemSP); // wait for interrupt} // do something..
} // ~DoMemMgrvoid DoClock() {
// This interrupt happens 8x each second, for timing purposes;
// we use it to maintain theTicks.
int SemSP = 0; // maps to ClockFrame[3]
int prior; // holds previous Date.seconds reading, updated
int temp;
ClockFrame[6] = &ClockFrame[9]; // initial SP
ClockFrame[8] = &ClockFrame[0]; // initial FP
ClockFrame[0] = mmPEEK((&SemSP)-6); // copy my globals ptr
ClockFrame[1] = 0; // halt if it returns (it shouldn't)
SemSP = FORK(&ClockFrame[6]); // returns twice:
if (SemSP==0) { // returned in caller's process..
iVector[1] = &ClockFrame[3]; // set interrupt vector
return;}
// otherwise returned in new process (first)..
mmPOKE(SemSP-6,0); // clear caller's return value
while (true) { // this is the interrupt handler loop..
COCALL(&SemSP); // wait for interrupt
temp = Date.seconds;
if (temp != prior) { // ticks management, got a new second..
prior = temp;
theTicks = prior*8;}
else theTicks = theTicks+1;
// YOUR CODE GOES HERE
} } // ~DoClockvoid DoSysCall() {
// this interrupt happens when the program executes SysCall
int SemSP = 0; // maps to SysCallFrame[3]
int aPtr;
int temp;
SysCallFrame[6] = &SysCallFrame[9]; // initial SP
SysCallFrame[8] = &SysCallFrame[0]; // initial FP
SysCallFrame[0] = mmPEEK((&SemSP)-6); // copy my globals ptr
SysCallFrame[1] = 0; // halt if it returns
SemSP = FORK(&SysCallFrame[6]); // create a new process (2 returns)
if (SemSP==0) { // final return, into initial process
iVector[5] = &SysCallFrame[3]; // ready, set interrupt vector
return;}
// otherwise returned in new process (first)..
mmPOKE(SemSP-6,0); // clear caller's return value
while (true) { // this is the interrupt handler loop..
iVector[5] = &SysCallFrame[3]; // restore double-fault crasher
COCALL(&SemSP); // wait for interrupt
iVector[5] = 0; // so it crashes "Semaphor=0" on programming error
temp = mmPEEK(SemSP-8); // get a parameter (the last pushed)
switch(mmPEEK(SemSP-6)) { // get opcode
case 0: // exit from program
SemSP = SwapOut(SemSP,0); // Batch: let main take care of it
break;
case 1: // array bounds check
temp = mmPEEK(0xFABC); // non-existing memory, to crash
mmPOKE(SemSP,0); // clear caller's PC
break;
case 2: // memory allocator
temp = temp+4; // # bytes wanted
if (HeapBottom+temp<HeapTop) { // do we have enough available?
aPtr = HeapTop-temp; // yup, allocate it..
HeapTop = aPtr;
temp = temp-4;
mmPOKE(aPtr+2,temp); // record the size
mmPOKE(aPtr,0); // initially zero references
aPtr = aPtr+4;} // the front of the data proper
else aPtr = 0; // otherwise return null
mmPOKE(SemSP-8,aPtr); // return pointer, or null
break;
case 3: // memory de-allocator request
if (temp == 0) continue; // ignore null (initial assignment)
temp = temp-4; // the pointer's ref count is here
if (mmPEEK(temp) & -2 == 0) {
mmPOKE(temp,0); // take count down to 0 for viewing
} // dispose it (You get to write this part)
else if (mmPEEK(temp) & -16 == 0) { // not permanent..
mmPOKE(temp,mmPEEK(temp)-1);} // decrement reference count
break; // caller pops the pointer
case 4: // open file
mmPOKE(SemSP-8, OpenFile(temp)); // OpenFile does the work
break;
case 5: // close file
CloseFile(temp); // CloseFile does the work
break;
case 6: // read file
temp = ReadFile(mmPEEK(SemSP-10), temp); // ReadFile does it
if (temp<0) // the device is busy, so..
mmPOKE(SemSP,mmPEEK(SemSP)-1); // back up PC to try again
// better: block this process until it's unbusy,
// but we don't have processes implemented yet
else mmPOKE(SemSP-10, temp); // the return value (how many bytes)
break;
case 7: // write file
temp = WriteFile(mmPEEK(SemSP-10), temp);
if (temp<0) mmPOKE(SemSP,mmPEEK(SemSP)-1); // back up PC
else mmPOKE(SemSP-10, temp);
break;
case 8: // seek file
// this is left to the student as an exercise
break;
case 9: // get file info
mmPOKE(SemSP-8, 0); // info? what info? return 0
break;
case 13: // wait for event (yield to other process)
SemSP = SwapOut(SemSP,0);
break;
case 14: // ticks delay
if (temp<=0) { // asking for a delay (>0 is target time)
temp = theTicks-temp;
mmPOKE(SemSP-8,temp);}
if (temp>theTicks)
mmPOKE(SemSP,mmPEEK(SemSP)-1); // back up PC to wait some more
break;
}} // ~while ~switch // otherwise do nothing
} // ~DoSysCall// ==============================================================
// User-level system code..// Look to see if HD is formatted, and if not, build directory
void HDformat(boolean forced) { // if forced=true, build it regardless
int ix;
int[128] Buff; // one sector, for the 7-file directory.
FileId HD = Open(".H"); // all I/O is at user level!
ix = Read(HD,Buff); // reads sector 0 into Buff
Close(HD);
if (!forced) if (ix==256) if (Buff[0]==ORD('H')*256+ORD('a'))
if (Buff[1]==ORD('r')*256+ORD('d'))
if (Buff[2]==ORD('D')*256+ORD('i'))
if (Buff[3]==ORD('s')*256+ORD('k')) return; // it seems OK
ix = 128; // no, set up my 1-sector directory..
while (ix>0) {ix = ix-1; Buff[ix] = 0;} // clear the buffer,
Buff[0] = ORD('H')*256+ORD('a'); // then insert signature,
Buff[1] = ORD('r')*256+ORD('d');
Buff[2] = ORD('D')*256+ORD('i');
Buff[3] = ORD('s')*256+ORD('k');
Buff[4] = 0; // no boot sector
Buff[5] = 4; // next unused sector
Buff[6] = 0; // no files yet
Buff[7] = 7; // directory size (could be 31 in 4 sectors)
HD = Open(".H"); // OK, rewrite the directory
ix = Write(HD,Buff); // no error checking!
Close(HD);} // ~HDformat// Read one line (up to \r) from keyboard, return it as String
String ReadStr() { // return a pointer to an input line
int ix = 0, nx = 0;
String theStr;
int theLen;
theLen = Read(Kin, KeyString)/2; // get a line
theStr = new char[theLen]; // allocate as many bytes as we got
while (ix<theLen) { // copy them over to new String array..
theStr[ix] = KeyString[ix];
ix = ix+1;}
return theStr;} // ~ReadStr// Display output String,
// a cleaner interface than needing to deal with returned int..
void PrintStr(String str) {int ix = Write(Cout, str);}// Display output character..
void PrintChar(char ch) { // uses SysCall, for safety
int nx;
char[1] cc; cc[0] = ch;
nx = Write(Cout, cc);}// PrintNum, a string-free number display (uses PrintChar)
void PrintNum(int theNum) {
if (theNum==32768) { // special-case, it has no positive..
PrintChar('-');
PrintChar('3');
theNum = 2768;}
else if (theNum<0) { // negative number, show the sign
PrintChar('-');
theNum = -theNum;}
if (theNum>9) { // recursive call to do leftmost digits first
PrintNum(theNum/10);
theNum = theNum%10;}
PrintChar(CHR(theNum+48));} // ~PrintNum// Halt if stack overrun. Called by main() or a deep subroutine
// called from main(), in case I counted wrong in space allocation..
void BombCheck() {
int here; // a local address to test
if (&here>Directory-100) { // oops, this is a crasher...
PrintStr("** Stack Collision **");
here = SysOp0(0);}} // ~BombCheck// MMU experimentation..
void MMUtest() {
int ix = 32;
while (ix>0) {
ix = ix-1;
PageTable[ix] = DefaultTable[ix];}
MMU.address = &PageTable;} // ~MMUtest// This is the startup code that get the OS running.
// Call it once, before anything else..
void MiniOS() {
BombCheck(); // verify that we have enough stack space to do this
POKE(Directory-2,256); // make it look like array
POKE(Directory-4,-16);
DoSysCall(); // initialize each type of interrupt..
DoKeyInts();
DoDisk();
DoPrint();
DoClock();
DoSema4();
DoMemMgr();
MainPIB[0] = 0; // create a (fake) PIB for current process..
MainPIB[4] = 0;
CurProcess = &MainPIB;
Enables.datum = IntOnBit; // enables interrupts (reset turned 'em off)
HDformat(false); // check for hard drive & initialize
Kin = Open(".K"); // open default KeyIn & ScreenOut files..
Cout = Open(".S");
KeyString = new char[120]; // used by ReadStr()
do PrintChar('\r'); while (Vrow>2); // clear screen (interrupt-safe)
} // ~MiniOS// Some String operators...
// Return true if str1==str2 (not case sensitive)..
boolean StrEqual(String str1, String str2) {
int ix = LENGTH(str2);
char ch1, ch2;
if (LENGTH(str1) != ix) return false;
while (ix>0) {
ix = ix-1;
ch1 = str1[ix];
if (ch1>='A') if (ch1<='Z') ch1 = CHR(ORD(ch1)+32);
ch2 = str2[ix];
if (ch2>='A') if (ch2<='Z') ch2 = CHR(ORD(ch2)+32);
if (ch1 != ch2) return false;}
return true;} // ~StrEqual// Parse out word wdno from aStr..
String GetWord(String aStr, int wdno) {
String res;
boolean inwd = false;
int here = 0, bgn = -1, end = -1;
while (here<LENGTH(aStr)) {
if ((aStr[here]>' ') != inwd) {
inwd = !inwd;
if (inwd) {
if (wdno==0) bgn = here;
wdno = wdno-1;}
else if (wdno<0) break;}
here = here+1;}
if (wdno<0) end = here;
if (bgn<end) {
res = new char[end-bgn];
here=0;
while (bgn<end) {
res[here] = aStr[bgn];
here = here+1;
bgn = bgn+1;}
return res;}
else return "";} // ~GetWord// Some date extraction routines..
// These extract date parts from ints; they do not access the I/Oint YearOf(int datehours) {return (datehours>>1)/4383;}
// returns 4 for 2004
int HourOf(int datehours) {return (datehours-24000)%24;}
int JulianOf(int datehours) {return (datehours-YearOf(datehours)*8766)/24;}
int MonthDayOf(int datehours) { // computes both month and day..
final int[] mons = {31,28,31,30,31,30,31,31,30,31,30,31};
int yr = YearOf(datehours); // so we know if it's leapyear
int dy = JulianOf(datehours);
int mo = 0;
while (dy>=mons[mo]) { // count off the months..
dy = dy-mons[mo];
mo = mo+1;
if (mo==2) dy = dy-1;}
return mo*100+dy+101;} // ~MonthDayOf
int MonthOf(int datehours) {return MonthDayOf(datehours)/100;}
int DayOf(int datehours) {return MonthDayOf(datehours)%100;}// This cheats a little, by reaching directly into the directory,
// to extract the name of the recently-opened floppy disk file (fno=0),
// or the name of HD file #fno.
String GetFileName(int fno) { // return String name of the file #fno
int ix = 0;
String theStr;
char[28] theName;
int[] DirAry; // will point to Directory
int theLen = 27;
FileId fi;
if (fno>0) { // HD file name..
POKE(&DirAry,Directory);
fi = Open(".H"); // opens the HD directory
ix = Read(fi,DirAry);
fno = fno-1;}
else fi = Open(".F"); // opens the floppy directory
unPackBy(28, Directory+fno*32+36, &theName); // get name, padded by nulls
while (theLen>=0) {
if (theName[theLen]>' ') break;
theLen = theLen-1;}
theStr = new char[theLen+1]; // allocate as many bytes as we got
ix = 0;
while (ix<=theLen) { // copy them over to new String array..
theStr[ix] = theName[ix];
ix = ix+1;}
Close(fi);
return theStr;} // ~GetFileName// Returns true if current floppy is "MiniOS"
// Used to skip user interaction after flop is changed,
// and to force DH reformat if not.
boolean MyFlopName() {
int nx = LENGTH(MyName); // = "MiniOS" packed as int[]
int ix = 0;
FileId fi = Open(".F"); // opens the floppy directory
while (ix<nx) { // compare it to "MiniOS"
if (MyName[ix] != PEEK(Directory+36+ix*2)) nx = -1; // unequal (exit)
ix = ix+1;}
Close(fi);
return nx>0;} // ~MyFlopName// If current floppy is "MiniOS", asks for new;
// returns true if current floppy is still "MiniOS"
boolean FlopSwap() {
if (MyFlopName()) {
PrintStr("Mount floppy, then click\r");
while (Mouse.datum>=0) ;
while (Mouse.datum<0) ;}
return MyFlopName();} // ~FlopSwap// Show HD directory..
void ShowDir() {
String str;
int fno = 0;
while (true) {
fno = fno+1;
str = GetFileName(fno);
if (LENGTH(str)==0) return;
PrintStr("\r");
PrintNum(fno);
PrintStr(" ");
PrintNum(PEEK(Directory+fno*32));
PrintStr(" ");
PrintNum(PEEK(Directory+fno*32+2));
PrintStr(" ");
PrintStr(str);}} // ~ShowDir// Copy one (program) file from floppy to hard disk..
void CopyFlop() {
String str;
FileId fi, fo;
int[128] buffer;
int nby, totby = 0;
BombCheck();
if (FlopSwap()) HDformat(true); // erase hard drive
str = GetFileName(0); // the name on disk will be same as on floppy
PrintStr("Copying file '");
PrintStr(str);
PrintStr("' ");
fi = Open(".F"); // open floppy source
fo = Open(str); // open destination file, same name
nby = (Read(fi, buffer)+1)/2; // read a block
totby = buffer[0]; // get the byte count
PrintNum(totby);
PrintStr(" bytes ");
while (totby>0 && nby>0) {
while (nby<128) {buffer[nby] = 0; nby = nby+1;} // fill out partial
if (nby*2 > Write(fo, buffer)) { // write it
PrintStr("Write Error ");
break;}
PrintChar('.'); // one dot every block
totby = totby-nby*2; // count bytes down
nby = (Read(fi, buffer)+1)/2;} // read next block
Close(fi);
Close(fo);
PrintStr(" Done.\r");} // ~CopyFlop// Load a program file, start it up..
void LoadProgram(String prog) {
String str;
int[2] data; // to read just the program size
FileId fi;
int CodeBase, Stack, StakIx, PIB;
int nx, ix = 0;
BombCheck();
fi = Open(prog);
nx = Read(fi,data)/2;
Close(fi);
ix = data[0];
PrintStr("Reading "); PrintNum(ix); PrintStr(" bytes...");
if (nx<2 || ix<30 || ix>16000) {
if (ix>16000) PrintStr("Program is too big");
else PrintStr("Could not open code file");
return;}
AppCode = new int[(ix+1)/2]; // allocate a block big enough for code
AppPIB = new int[1024]; // probably big enough for stack and globals
if (AppCode==null || AppPIB==null) {
PrintStr("Could not allocate memory");
return;}
CodeBase = PEEK(&AppCode)+2; // 1st 2 bytes is file size, then CB
fi = Open(prog); // restarts at front of file
nx = Read(fi,AppCode); // read it from the floppy
Close(fi);
PrintStr(" Got "); PrintNum(nx);
if (nx<ix) return; // didn't get the whole program
PIB = PEEK(&AppPIB); // got it..
POKE(PIB-4,-16); // make these blocks permanent..
POKE(CodeBase-6,-16);
StakIx = 8; // allow 16 bytes for Process Info Block
Stack = PIB+StakIx*2; // the rest is user space
AppPIB[StakIx-1] = 0x2009; // SysCall(0) for quit
AppPIB[StakIx+1] = Stack-2; // return address is that stall
AppPIB[StakIx] = Stack; // globals ptr, =FP
AppPIB[StakIx+2] = CodeBase;
AppPIB[StakIx+3] = Stack; // initial FP
AppPIB[StakIx+4] = AppCode[1]+CodeBase; // initial PC
AppPIB[3] = Stack+8; // initial SP, where Process Mgr can find it
AppPIB[4] = 1;
AppPIB[0] = ReadyList; // link to front of ReadyList
ReadyList = PIB;
PrintStr(" Success!\r");} // ~LoadProgram// Start new thread...
void TakenShow() { // a thread to take chars from queue & display them
int temp; // maps to DemoFrame[7] (allow 4 ints for PIB)
DemoFrame[10] = &DemoFrame[13]; // initial SP
DemoFrame[12] = &DemoFrame[4]; // initial FP
DemoFrame[4] = PEEK((&temp)-6); // copy my globals ptr
DemoFrame[5] = 0; // halt if it returns (it shouldn't)
temp = FORK(&DemoFrame[10]); // returns twice:
if (temp==0) return; // returned in caller's process
// otherwise returned in new process (first)..
POKE(temp-6,0); // clear caller's return value
POKE(CurProcess+6,temp); // save caller's SP in its PIB
POKE(CurProcess,ReadyList); // move caller to ready list..
ReadyList = CurProcess;
CurProcess = &DemoFrame; // make me current
while (true) { // OK, process the data..
Wait(take);
PrintChar(queue[tx]);
Signal(fill);
tx = (tx+1)%Qsize;}
} // ~TakenShowvoid DoSemDemo() { // Demonstrate Semaphores..
int ix = Qsize;
char[1] GetKey;
while (ix>0) { // initialize this semaphore; take is already OK
Signal(fill);
ix = ix-1;}
TakenShow(); // go fork off a thread to do the other end
while (true) { // OK, process the data..
Wait(fill);
ix = Read(Kin, GetKey); // get a keystroke
ix = ORD(GetKey[0]);
queue[fx] = CHR(ix^((ix>>1)&32)); // [capitalize letters]
Signal(take);
fx = (fx+1)%Qsize;}
} // ~DoSemDemo// The main program..
void main() {
int here = 0;
String str, prog;
MiniOS(); // start up operating system
while (true) {
here = 0;
PrintStr("\rQuit, Copy (Flop->HD), <filename>\r> ");
str = ReadStr();
if (StrEqual(str,"Quit")) break;
else if (StrEqual(str,"Demo")) DoSemDemo();
while (true) {
prog = GetWord(str,here);
here = here+1;
if (LENGTH(prog)==0) break;
if (StrEqual(prog,"Copy")) CopyFlop();
else if (StrEqual(prog,"Dir")) ShowDir();
else if (StrEqual(prog,"MMUtest")) MMUtest();
else {
LoadProgram(prog); // load it and start it up as a process
if (SysOp0(13)==0) ;}}} // wait for termination
PrintStr("\rBye!");} // ~main