jsos
college code for operating system fundamentals in js
git clone https://9o.is/git/jsos.git
commit e99a47ea16b09aa862f8fe0f8cdd57b7a900a045 parent 3e1d6ed4bae7c6ac1a0e752a7d5567316696d539 Author: Jul <jul@9o.is> Date: Wed, 7 Nov 2012 02:20:29 -0500 A working CPU =D Diffstat:
| M | globals.js | | | 6 | ++++-- |
| M | scripts/host/cpu.js | | | 235 | +++++++++++++++++++++++++++++++++++++++++++++++-------------------------------- |
| M | scripts/host/memory.js | | | 88 | +++++++++++++++++++++++++++++++++++++++++++++++-------------------------------- |
| M | scripts/os/kernel.js | | | 48 | +++++++++++++++++++++++++++++++++++++----------- |
| M | scripts/os/memoryManager.js | | | 54 | ++++++++++++++++++++++++++++++++++++++++-------------- |
| M | scripts/os/shell.js | | | 15 | +++++++++++---- |
| M | scripts/utils.js | | | 35 | +++++++++++++++++++++++++++++++++++ |
7 files changed, 318 insertions(+), 163 deletions(-)
diff --git a/globals.js b/globals.js @@ -76,7 +76,7 @@ var krnKeyboardDriver = null; // memory size var TOTAL_PAGES = 4; -var MAX_MEMORY_PROGRAM = 64; //bytes +var MAX_MEMORY_PROGRAM = 256; //bytes var TOTAL_MEMORY = TOTAL_PAGES * MAX_MEMORY_PROGRAM; //bytes // pcb states @@ -86,4 +86,6 @@ var PCB_STATE_RUNNING = "running"; var PCB_STATE_TERMINATED = "terminated"; // error codes -var ERROR_INVALID_OPERATOR = 1; +var _ERROR = "ERROR: "; +var _ERROR_INVALID_SYSTEM_CALL = _ERROR+"INVALID SYSTEM CALL"; +var _ERROR_INVALID_OPERATOR = _ERROR+"INVALID OPERATOR"; diff --git a/scripts/host/cpu.js b/scripts/host/cpu.js @@ -19,151 +19,194 @@ * Y register (Y), Z-ero flag (Z). */ function cpu() { - this.PC = '0'; - this.ACC = '0'; - this.X = '0'; - this.Y = '0'; + this.PC = 0; + this.ACC = 0; + this.X = 0; + this.Y = 0; this.Z = 0; this.isExecuting = false; - this.running = null; // process currently running + this.running = undefined; // process currently running this.init = function() { this.output(); } - // Executes a CPU cycle - // if process is not running, CPU checks in the ready queue - // if ready queue is empty, it sets isExecuting false. + /* Executes a CPU cycle. */ this.cycle = function() { krnTrace("CPU cycle"); - if(this.running == null && _KernelReadyQueue.isEmpty()) + + // if process is not running, and ready queue is empty + // stop cpu cycles + if(this.proccessTerminated() && _KernelReadyQueue.isEmpty()) this.isExecuting = false; - else if(this.running == null && _KernelReadyQueue.isNonEmpty()) { - this.running = _KernelReadyQueue.dequeue(); - this.running.setState(PCB_STATE_RUNNING); - this.PC = this.running.PC; + + // if ready is not empty, dequeue + else if(this.proccessTerminated()) { + this.setRunningProcess(_KernelReadyQueue.dequeue()); this.execute(); - } else this.execute(); + } + + // or continue executing the current process + else this.execute(); + // output current changes in the registry this.output(); this.running.updateRegisters( this.PC, this.ACC, this.X, this.Y, this.Z); } - // executes the byte in location PC - // assumes it's a operator. + /* + * Executes the byte in location PC. + */ this.execute = function() { this.isExecuting = true; - var operator = _Memory.getByte(parseInt(this.PC, 16)); - this.incrementPC(1); - - if(operator == undefined) return; - switch(operator.toUpperCase()) { - case 'A9': // LDA: Load the accumulator with a constant - var operand = this.immediateOperand(); - this.ACC = operand; - this.incrementPC(1); + var operator = _Memory.getByte(this.PC++); + + switch(operator) { + // LDA: Load the accumulator with a constant + case 0xA9: + this.ACC = this.immediateOperand(); break; - case 'AD': // LDA: Load the accumulator from memory - var operand = this.directOperand(); - this.ACC = (parseInt(this.ACC,16) + parseInt(operand,16)).toString(16); - this.incrementPC(2); + + // LDA: Load the accumulator from memory + case 0xAD: + this.ACC = this.directOperand(); break; - case '8D': // STA: Store the accumulator in memory - _Memory.setByte(this.ACC, this.PC); - this.incrementPC(2); + + // STA: Store the accumulator in memory + case 0x8D: + _Memory.setByte(this.ACC, this.immediateOperand()); + this.PC += 1; break; - case '6D': // ADC: Add with carry - var operand = this.directOperand(); - addToACC(operand); - this.incrementPC(2); + + // ADC: Add with carry + case 0x6D: + this.ACC += this.directOperand(); break; - case 'A2': // LDX: Load the X register with a constant - var operand = this.immediateOperand(); - this.X = operand; - this.incrementPC(1); + + // LDX: Load the X register with a constant + case 0xA2: + this.X = this.immediateOperand(); break; - case 'AE': // LDX: Load the X register from memory - var operand = this.directOperand(); - this.X = operand; - this.incrementPC(2); + + // LDX: Load the X register from memory + case 0xAE: + this.X = this.directOperand(); break; - case 'A0': // LDY: Load the Y register with a constant - var operand = this.immediateOperand(); - this.Y = operand; - this.incrementPC(1); + + // LDY: Load the Y register with a constant + case 0xA0: + this.Y = this.immediateOperand(); break; - case 'AC': // LDY: Load the Y register from memory - var operand = this.directOperand(); - this.Y = operand; - this.incrementPC(2); + + // LDY: Load the Y register from memory + case 0xAC: + this.Y = this.directOperand(); break; - case 'EA': // NOP: No operation + + // NOP: No operation + case 0xEA: break; - case '00': // BRK: Break (which is really a system call) - krnInterrupt(SYSCALL_IRQ, new Array('00')); + + // BRK: Break (Terminate) + case 0x00: + krnInterrupt(SYSCALL_IRQ, 0x00); break; - case 'EC': // CPX: Compare a byte in memory to the X reg Sets the Z (zero) flag if equal - var operand = this.directOperand(); - if(operand == this.X) this.Z = 1; - else this.Z = 0; - this.incrementPC(2); + + // CPX: Compare a byte in memory to the X reg + // Sets the Z (zero) flag if equal + case 0xEC: + this.Z = this.directOperand() == this.X ? 1 : 0; break; - case 'D0': // BNE: Branch X bytes if Z flag = 0 - if(this.Z != 0) this.incrementPC(1); - else { - var operand = this.immediateOperand(); - this.PC = operand; - } + + // BNE: Branch X bytes if Z flag = 0 + case 0xD0: + if(this.Z == 0) { + this.PC += this.immediateOperand()+1; + this.PC -= this.PC > 0xFF ? 0x100 : 0; + } + else + this.PC += 1; break; - case 'EE': // INC: Increment the value of a byte - var operand = this.directOperand(); - operand = (parseInt(operand,16) + 1).toString(16); - var location = _Memory.getByte(parseInt(this.PC, 16)); - _Memory.setByte(operand, location); - this.incrementPC(2); + + // INC: Increment the value of a byte + case 0xEE: + var location = _Memory.getByte(this.PC); + _Memory.setByte(this.directOperand() + 1, location); break; - case 'FF': // SYS: System Call - krnInterrupt(SYSCALL_IRQ); + + // SYS: System Call + case 0xFF: + krnInterrupt(SYSCALL_IRQ, 0xFF); break; - default: // unknown operator - krnInterrupt(SYSCALL_IRQ, new Array('FF', ERROR_INVALID_OPERATOR)); + + // unknown operator + default: + krnInterrupt(SYSCALL_IRQ, -1); break; } } - // Gets operand from memory in location PC + /* + * Gets operand from memory in location PC. + */ this.directOperand = function() { - var location = _Memory.getByte(parseInt(this.PC, 16)); - var value = _Memory.getByte(parseInt(location, 16)); + var location = _Memory.getByte(this.PC); + var value = _Memory.getByte(location); + this.PC += 2; return value; } - // Gets operand as a constant in location PC + /* + * Gets operand as a constant in location PC + */ this.immediateOperand = function() { - var value = _Memory.getByte(parseInt(this.PC, 16)); + var value = _Memory.getByte(this.PC); + this.PC += 1; return value; } - // Given decimal value, it adds that value to the PC which - // is a hexadecimal. - this.incrementPC = function(value) { - this.PC = (parseInt(this.PC, 16) + value).toString(16); + /* + * Sets a process as the currently executing process + * transfers PCB registers to the CPU. + */ + this.setRunningProcess = function(process) { + this.running = process; + process.setState(PCB_STATE_RUNNING); + this.PC = process.PC; + this.ACC = process.ACC; + this.X = process.X; + this.Y = process.Y; + this.Z = process.Z; } - // adds operand to accumulator - // Note: operand and accumulator are hexadecimal values - this.addToACC = function(operand) { - var value = parseInt(this.ACC,16) + parseInt(operand,16); - this.ACC = (value).toString(16); + /* + * Even though CPU may be in execution mode + * (or isExecuting is true), the process in running mode + * may not be in executing mode. + */ + this.proccessTerminated = function() { + return this.running == undefined || + this.running.state == PCB_STATE_TERMINATED; } - // displays register values in interface + /* + * Displays register values in interface. + */ this.output = function() { - document.getElementById("pc_register").innerHTML = _CPU.PC; - document.getElementById("acc_register").innerHTML = _CPU.ACC; - document.getElementById("x_register").innerHTML = _CPU.X; - document.getElementById("y_register").innerHTML = _CPU.Y; - document.getElementById("z_register").innerHTML = _CPU.Z; + document.getElementById("pc_register").innerHTML = + pad(this.PC.toString(16),4).toUpperCase(); + + document.getElementById("acc_register").innerHTML = + pad(this.ACC.toString(16),2).toUpperCase(); + + document.getElementById("x_register").innerHTML = + pad(this.X.toString(16),2).toUpperCase(); + + document.getElementById("y_register").innerHTML = + pad(this.Y.toString(16),2).toUpperCase(); + + document.getElementById("z_register").innerHTML = + this.Z.toString(16); } } diff --git a/scripts/host/memory.js b/scripts/host/memory.js @@ -14,69 +14,75 @@ ------------ */ /* - * The core memory has an array to save bytes of data + * The core memory has an array to save bytes of data. + * Bytes will be formatted as integers of 0-255, but + * outputs in hex format. */ - function memory(maxBytes) { + + /* The main container of bytes in memory. */ this.memory = new Array(); - // initialize the memory with zeros + /* + * Whether it should update the output when + * a byte is set in memory. + */ + var showOuput = true; + + /* Initializes the memory with zeros. */ this.init = function() { for(var i=0; i<maxBytes; i++) { - this.memory[i] = "00"; + this.memory[i] = 0; } this.output(); } - // allocate a byte in a location in memory - // and limit the memory to its size - this.allocateByte = function(byte, location) { - if(maxBytes < location) - krnTrapError("Stack overflow."); - this.memory[location] = byte; - } - - this.deallocateByte = function() { - this.memory.pop(); - } - - // allocates an array of bytes starting from base + /* + * Allocates an array of bytes starting from base. + */ this.allocateBytes = function(bytes, base) { + this.showOutput = false; + for(var i=0; i<bytes.length; i++) { - this.allocateByte(bytes[i], base+i); + this.setByte(bytes[i], base+i); } + + this.showOutput = true; this.output(); } - // gets byte in a location + /* + * Gets byte value in a location. + */ this.getByte = function(location) { - if(maxBytes < location) - krnTrapError("Stack overflow."); + if(!this.isValidLocation(location)) + krnTrapError("STACK OVERFLOW"); return this.memory[location]; } - // sets byte in a location + /* + * Allocate a byte in a location in memory. + */ this.setByte = function(byte, location) { - if(maxBytes < location) - krnTrapError("Stack out of bounds."); + if(!isByte(byte)) + krnTrapError("INVALID BYTE VALUE"); + if(!this.isValidLocation(location)) + krnTrapError("STACK OVERFLOW"); this.memory[location] = byte; + + if(showOuput) this.output(); } - // memory dump + /* Memory dump. */ this.dumpMemory = function() { return this.memory; } - // outputs memory dump to interface + /* + * Outputs memory dump to interface. + */ this.output = function() { - // padding for location hex ie. a8 to 00a8 - function pad(hex) { - while(hex.length < 4) hex = '0'+hex; - return hex; - } - - // extracts every byte and returns html table rows of // 8 columns function extract(dump) { var i=0; @@ -86,10 +92,12 @@ function memory(maxBytes) { i=0; html += '</tr>'; if(j < dump.length) - html += '<tr><th align="right">'+pad(j.toString(16))+'</th>'; + html += '<tr><th align="right">'+ + pad(j.toString(16),4).toUpperCase()+'</th>'; } i++; - html += '<td>'+dump[j]+'</td>'; + html += '<td>'+ + pad(dump[j].toString(16),2).toUpperCase()+'</td>'; } return html; } @@ -99,6 +107,14 @@ function memory(maxBytes) { html += '<th align="right">0000</th>'; html += extract(dump); html += '</tr>'; - document.getElementById("memoryDumpTable").innerHTML = html; + document. + getElementById("memoryDumpTable").innerHTML = html; + } + + /* + * Checks if the location address is valid. + */ + this.isValidLocation = function(location) { + return location > -1 && location < maxBytes; } } diff --git a/scripts/os/kernel.js b/scripts/os/kernel.js @@ -83,7 +83,7 @@ function krnOnCPUClockPulse() krnInterruptHandler(interrput.irq, interrput.params); } // If there are no interrupts then run a CPU cycle if there is anything being processed. - else if (_KernelReadyQueue.getSize() > 0 || _CPU.isExecuting) + else if (_CPU.isExecuting || _KernelReadyQueue.getSize() > 0) { _CPU.cycle(); } @@ -165,19 +165,45 @@ function krnTimerISR() // The built-in TIMER (not clock) Interrupt Service Rout // - ReadFile // - WriteFile // - CloseFile -function krnSystemCall(params) { - switch(params[0]) { - case '00': // a process has been terminated - krnTrace("Process "+_CPU.running.pid+" terminated successfully."); - _CPU.running.setState(PCB_STATE_TERMINATED); - _CPU.running = null; - case 'FF': // something bad happened. Terminate process and print error code - krnTrace("Error: "+params[1]+" - Process "+_CPU.running.pid+" terminated unsuccessfully."); +function krnSystemCall(operator) { + + switch(operator) { + + // a process has been terminated + case 0x00: + krnTrace("Process "+_CPU.running.pid+ + " terminated successfully."); _CPU.running.setState(PCB_STATE_TERMINATED); - _CPU.running = null; + _CPU.isExecuting = false; + _Console.advanceLine(); + _OsShell.putPrompt(); + break; + + // normal system call + case 0xFF: + if(_CPU.X == 1) + _StdOut.putText(_CPU.Y.toString()); + + // print the 00-terminated string stored at + // the address in the Y register. + else if(_CPU.X == 2) { + var ascii = hex2ASCII(_Memory.getByte(_CPU.Y)); + do { + _StdOut.putText(ascii); + ascii = hex2ASCII(_Memory.getByte(++_CPU.Y)); + } while (ascii != null) + } + break; + + // Error + case -1: + krnTrapError(_ERROR_INVALID_OPERATOR); break; + + // Invalid system call default: - krnTrapError("Invalid system call in X register: "+_CPU.x); + krnTrapError(_ERROR_INVALID_SYSTEM_CALL); + break; } } diff --git a/scripts/os/memoryManager.js b/scripts/os/memoryManager.js @@ -5,25 +5,51 @@ function MemoryManager() { this.processes = new Array(); // array of pcb's /* - * Loads a program (hex or bytes) into memory. + * Loads a hex program into memory. * @param hex an array of bytes */ - this.load = function(bytes) { - var pid = this.processes.length; + this.load = function(hex) { + var pid = this.getAvailablePage(); + if(pid < 0) return pid; + + var bytes = this.hex2Dec(hex); var base = pid * MAX_MEMORY_PROGRAM; var limit = (pid+1) * MAX_MEMORY_PROGRAM - 1; var process = new pcb(pid, PCB_STATE_RESIDENT, base, limit, base); - this.processes.push(process); + this.processes[pid] = process; _Memory.allocateBytes(bytes, base); - console.log("Loaded pid:"+pid+" base="+base+" limit="+limit); this.output(); return pid; }; - // outputs pcb attributes of each process in control of this - // OS's memory manager to the interface + /* + * Gets the next available page in memory. Returns -1 if + * there is no page available. + */ + this.getAvailablePage = function() { + for(var i=0; i<TOTAL_PAGES; i++) { + var p = this.processes[i]; + if(p == undefined || p.state == PCB_STATE_TERMINATED) + return i; + } + return -1; + } + + /* + * Converts an array of hexadecimals to an array of decimals. + */ + this.hex2Dec = function(hex) { + for(var i=0; i<hex.length; i++) + hex[i] = parseInt(hex[i], 16); + return hex; + } + + /* + * Outputs pcb attributes of each process in control of this + * OS's memory manager to the interface. + */ this.output = function() { var div = document.getElementById("processesTable"); var html = @@ -35,13 +61,13 @@ function MemoryManager() { html += '<tr>'; html += '<td>'+p.PID+'</td>'; html += '<td>'+p.state+'</td>'; - html += '<td>'+p.base.toString(16)+'</td>'; - html += '<td>'+p.limit.toString(16)+'</td>'; - html += '<td>'+p.PC.toString(16)+'</td>'; - html += '<td>'+p.ACC+'</td>'; - html += '<td>'+p.X+'</td>'; - html += '<td>'+p.Y+'</td>'; - html += '<td>'+p.Z+'</td>'; + html += '<td>'+pad(p.base.toString(16),4).toUpperCase()+'</td>'; + html += '<td>'+pad(p.limit.toString(16),4).toUpperCase()+'</td>'; + html += '<td>'+pad(p.PC.toString(16),4).toUpperCase()+'</td>'; + html += '<td>'+pad(p.ACC.toString(16),2).toUpperCase()+'</td>'; + html += '<td>'+pad(p.X.toString(16),2).toUpperCase()+'</td>'; + html += '<td>'+pad(p.Y.toString(16),2).toUpperCase()+'</td>'; + html += '<td>'+p.Z.toString(16)+'</td>'; html += '</tr>'; } div.innerHTML = html; diff --git a/scripts/os/shell.js b/scripts/os/shell.js @@ -466,8 +466,11 @@ function shellLoad(args) { if(bytes.length > 1) { if (bytes.length <= MAX_MEMORY_PROGRAM) { // pass the hex to kernel's memory manager - var pid = _KernelMemoryManager.load(bytes); - _StdOut.putText("Your program has been successfully loaded with PID: " + pid); + var pid = _KernelMemoryManager.load(bytes); + if(pid < 0) + _StdOut.putText("There is no memory available."); + else + _StdOut.putText("Your program has been successfully loaded with PID: " + pid); } else { _StdOut.putText("Program is too large. Must be less than "+MAX_MEMORY_PROGRAM+" bytes."); } @@ -489,8 +492,12 @@ function shellRun(args) { if(process == undefined) { _StdOut.putText("No such process."); } else { - _KernelReadyQueue.enqueue(process); - process.state = PCB_STATE_READY; + if(process.state != PCB_STATE_RESIDENT) + _StdOut.putText("Process cannot run."); + else { + _KernelReadyQueue.enqueue(process); + process.setState(PCB_STATE_READY); + } } } } diff --git a/scripts/utils.js b/scripts/utils.js @@ -45,3 +45,38 @@ function isValidHex(str) { } return true; } + +/* + * Padding string hex values + * ie. If size is 4 and hex string is 'a8' + * result will be '00a8'. + */ +function pad(hex, size) { + while(hex.length < size) + hex = '0'+hex; + return hex; +} + +/* + * Checks if the value is a valid byte or a value + * of 0 to 255. + */ +function isByte(value) { + return value > -1 && value < 256; +} + +/* + * Converts a number to an ASCII character. + */ +function hex2ASCII(num) { + if(num==0) return null; + var hex = num.toString(16); + var str = ''; + for (var i=0; i<hex.length; i+=2) + str += String. + fromCharCode(parseInt(hex.substr(i, 2), 16)); + return str; +} + + +