TinyOS in C#
This was the final project for my CST352-Operating Systems class at OIT (Oregon Institute of Technology). The requirements, exactly as they were given to me by the teacher, are in originalassignment.md. The goal of this project was to write a small virtual operating system for an abstract machine that provides a number of basic OS-like services like:
This is a cute, fun, interesting and completely useless thing. So, don't get your hopes up that you'll get actual work done with it.
It's neat however, as it is a study on how I solved a particular problem (this assignment) given a 10 week semester. I was the only student to use C#, and I finished it in 4 weeks, leaving 6 weeks to chill and watch the other students using Java and C++ do their thing.
It's also ironic because I used a high-level OO language like C# to deal with a minute concept like an "Operating System" the might have 256 bytes (bytes, not Kbytes) of memory.
During this process I exercised a good and interesting chunk of the C# language and the .NET Framework. There are some very silly things like the OS's implementation of virtual memory swapping out memory pages as XML. I might take an array of 4 bytes and make an XML file to help them. I hope the irony isn't lost on you. I also commented all the C# with XML and built an ndoc MSDN style help file. So, you might just use this as a learning tool and a pile of sample code.
One Note: I'm an ok programmer, but understand that I whipped this out in several 3am coding sessions, as well as during lectures in class. This is NOT fabulous code, and should be looked upon as interesting, not gospel. I'm sure I've done some very clever things in it, and for each clever thing, there's n stupid things. I'll leave the repair of these stupid things as an exercise to the reader.
Of course, you'll need the .NET Framework to run this, but to really have fun debugging it and stepping through the code you'll need VS.NET. Go read the code and final_project.doc and enjoy!
I started out with some basic OO constructs, a static CPU object, an OS object, objects for Processes, Instructions, etc. A lot of this information is in the Help (CHM) File. Basically it's setup like this:
The OS is made up, the CPU is made up, the opCodes are made up. This is an exercise to learn about Operating System concepts, and it's not meant to look or act like any specific OS or system.
Here's an example program, specifically the "idle" loop. The idle loop is a process that never ends, it just prints out "20" over and over. I use it to keep the clock running when all the other processes are sleeping.
6 r4, $0 ;move 0 into r4 26 r4 ;lower our priority TO 0 6 r1, $20 ;move 20 into r1 11 r1 ;print the number 20 6 r2, $-19 ;back up the ip 19 bytes 13 r2 ;loop forever (jump back 19)Programs consist of:
Opcode, [optional param], [other optional param] ;optional comment
So, if we look at:
6 r4, $0 ;move 0 into r4
We see that it's operation 6, which is Movi or "Move Immediate." It moves a constant value into one of our 10 registers. The first param is r4, indicating register 4. The second param is $0. The "$" indicates a constant. So we are the value 0 into register #4. Just like x86 assembly – only not at all.
So if you look at the comments for this app you can see that it loops forever.
OS membytes [files]For example:
OS 1568 prog1.txt prog2.txt prog3.txt prog1.txt idle.txtThis command line would run the OS with 1568 bytes of virtual (addressable) memory and start up 5 processes. Note that Prog1.txt is specified twice. That means it will have two separate and independent running processes. We also specified the forever running idle.txt. Use CTRL-C to break. The OS has operations for shared memory, locks, and events, so you can setup rudimentary inter-module communication.
There are 13 other sample apps you can play with. The OS is most interesting when you run it with multiple apps. You can mess with OS.config to setup the amount of memory available to each process, the whole system's physical memory, memory page size etc.
It's interesting from an OS theory point of view if you think about the kinds of experiments you can do like lowering physical memory to something ridiculous like 32 bytes. That will increase page faults and virtual memory swapping. So, the OS can be tuned with the config file.
It is certainly possible to give the OS a bad config, like more physical memory than addressable, or a page size that is the same size as physical memory. But, use common sense and play. I've setup it up with reasonable defaults.
Here's a sample OS.config file. The Config has to be in the same directory as the OS.exe. Take a look at the included OS.config, as it has additional XML comments explaining each option.
Of note are the Dumpxxx options. With all of these options set to false, the OS only outputs the bare minimum debug information. With them on (I like to have the all on) you'll be able to see the results of each instruction and how they affect registers, physical memory, etc. You'll see physical memory pages swap to disk, memory fragmentation, the instruction pointer increment, and processes take turns on the CPU.
Be sure to turn up the size of screen buffer (usually under "Layout" in the Properties Dialog of your command prompt) to something like 3000 or 9999. This way you'll be able to scroll back and look at the details of your run.
OS 512 prog1.txt > output.txtYou might also try something like this to create a log file, or modify the source to include logging!
These are also printed and explain in the Help File as well as Final_Project.doc. When writing a program you use the value, not the text name of the opcode. So, you'd say
2 r1, $1and NOT
addi r1, $1.
| Opcode | Value(decimal) | Format | | --- | --- | --- | | Incr | 1 | incr r1(increment value of register 1 by 1 ). | | Addi | 2 | addi r1,$1 is the same as incr r1 | | addr | 3 | Addr r1, r2( r1 <= r1 + r2 ). | | Pushr | 4 | Pushr rx (pushes contents of register x onto stack. Decrements sp by 4 ). | | Pushi | 5 | Pushi $x . pushes the constant x onto stack. Sp is decremented by 4 after push. | | Movi | 6 | Movi rx, $y. rx <= y | | Movr | 7 | Movr rx, ry ; rx <= ry | | Movmr | 8 | Movmr rx, ry ; rx <= [ry] | | Movrm | 9 | Movrm rx,ry; [rx] <= ry | | Movmm | 10 | Movmm rx, ry [rx] <= [ry] | | Printr | 11 | Printr r1 ; displays contents of register 1 | | Printm | 12 | Printm r1; displays contents of memory whose address is in register 1. | | Jmp | 13 | Jmp r1; control transfers to the instruction whose address is r1 bytes relative to the current instruction. R1 may be negative. | | Cmpi | 14 | Cmpi rx, $y; subtract y from register rx. If rx < y, set sign flag. If rx > y, clear sign flag. If rx == y , set zero flag. | | Cmpr | 15 | The same as cmpi except now both operands are registers. | | Jlt | 16 | Jlt rx; if the sign flag is set, jump to the instruction whose offset is rx bytes from the current instruction. | | Jgt | 17 | Jgt rx; if the sign flag is clear, jump to the instruction whose offset is rx bytes from the current instruction | | Je | 18 | Je rx; if the zero flag is clear, jump to the instruction whose offset is rx bytes from the current instruction. | | Call | 19 | Call r1; call the procedure at offset r1 bytes from the current instruction. The address of the next instruction to execute after a return is pushed on the stack. | | Callm | 20 | Callm r1; call the procedure at offset [r1] bytes from the current instruction. The address of the next instruction to execute after a return is pushed on the stack. | | Ret | 21 | Pop the return address from the stack and transfer control to this instruction. | | Alloc | 22 | Alloc r1, r2; allocate memory of size equal to r1 bytes and return the address of the new memory in r2. If failed, r2 is cleared to 0. | | AcquireLock | 23 | AcquireLock r1; Acquire the operating system lock whose # is provided in the register r1. If the lock is invalid, the instruction is a no-op. | | ReleaseLock | 24 | Releaselock r1; release the operating system lock whose # is provided in the register r1; if the lock is not held by the current process, the instruction is a no-op. | | Sleep | 25 | Sleep r1; Sleep the # of clock cycles as indicated in r1. Another process or the idle process must be scheduled at this point. If the time to sleep is 0, the process sleeps infinitely. | | SetPriority | 26 | SetPriority r1; Set the priority of the current process to the value in register r1; See priorities discussion in Operating system design | | Exit | 27 | Exit. This opcode is executed by a process to exit and be unloaded. Another process or the idle process must now be scheduled. | | FreeMemory | 28 | FreeMemory r1; Free the memory allocated whose address is in r1. | | MapSharedMem | 29 | MapSharedMem r1, r2; Map the shared memory region identified by r1 and return the start address in r2. | | SignalEvent | 30 | SignalEvent r1; Signal the event indicated by the value in register r1. | | WaitEvent | 31 | WaitEvent r1; Wait for the event in register r1 to be triggered. This results in context-switches happening. | | Input | 32 | Input r1; read the next 32-bit value into register r1. | | MemoryClear | 33 | MemoryClear r1, r2; set the bytes starting at address r1 of length r2 bytes to zero. | | TerminateProcess | 34 | TerminateProcess r1; terminate the process whose id is in the register r1. | | Popr | 35 | pop the contents at the top of the stack into register rx which is the operand. Stack pointer is decremented by 4. | | popm | 36 | pop the contents at the top of the stack into the memory operand whose address is in the register which is the operand. Stack pointer is decremented by 4. |