Up to now, you haven't needed the code for the operating system. We've just been building and testing a machine that could support an OS. This is the point at which we make the transition.
image
and image.map
. The former is an OS disk image file,
including the OS and some test applications.
The latter is a text file telling you what block numbers the various
apps are at.
Try running the image in Cebollita:
./cebrun.sh imageNow hit the Boot button, and then Run, to start execution. Once it has booted and you get the shell prompt, type a block number (from
image.map
) to run
a program. A negative number exits the shell (which results in
the machine shutting down).
Because we're getting perilously close to what a real machine and system is at this point, you do not simply use the SMOK memory component to magically read the OS disk image into itself. Instead, you use SMOK's boot capability, which requires you to attach a virtual disk (i.e., the OS image file you generated) to the Block Controller device inside the memory map container. (See the documentation on SMOK's Block Controller for more information.)
Having done that, you can boot your machine -- you no longer just Run it, you have to boot. There is a (different) toobar button for that: a power on toolbar button in SMOK, and a Boot button (Cebollita GUI). (If you use the Cebollita button, it causes the BIOS program to run (i.e., loads the boot loader into memory), but doesn't start execution. That gives you a chance to single step, if you want. You can hit Run if you just want to execute away.)
When you boot your machine, the SMOK Block Controller will load the boot
loader (whose source is .../cebollita/apps/os/bootloader.s
)
into memory at address 0.
If you've booted using the SMOK power on button,
when once the boot loader is in memory your machine starts executing (so
it should have its PC initialized to 0).
The boot loader loads the rest of the OS into memory and then
transfers control to it.
Once the OS has initialized itself it starts the first process, a shell.
The shell is just a regular old user application
(.../cebollita/apps/os/shell.c
).
The OS transfers control to it, and it starts running.
This is a very primitive system -- it has no file system at all. (It has a disk, but it doesn't have any notion of files, just raw disk space.) All the shell can do for you is start other programs.
The shell sits in a loop.
At the top of the loop it prints a prompt asking you for a disk block number.
To identify the program you want to run, you have to give the disk block number at which it is
located. The block number corresponding to each program on your disk is printed out when you
create the disk image, so those are the numbers you need to supply.
They are also dumped to the file .../cebollita/apps/image.map
when
you build the image
file
(see the first section of this writeup).
cebsim --bootable --smok ceb-final.smok imagewill use attach a Cebollita front-end GUI to a SMOK machine, but will not run the Cebollita software simulator.
cebsim --bootable --sync ceb-final.smok imagewill use both attach the Cebollita GUI and run the Cebollita software simulation in lock step with your SMOK implementation.Either way, hit the boot button on the Cebollita window, then run. Type input in the SMOK character controller window.
.../cebollita/apps/os/
.
bootloader.s
is the boot loader.
When the machine (or simulator) is powered on, the hardware loads the
first block (512 bytes) of the disk into memory at location 0. The
bootloader is those first bytes on the disk. It (a) sits in a loop
moving itself to a higher memory address, and then (b)loads the remainder
of the OS from the disk into memory, starting at location 0
(which is why it has to move itself). The boot loader is not linked with
the OS -- it's a
stand-alone program that sets things up to the get the OS going.
The OS is in files trap-handler.s
and os.c
.
The former is a small set of routines that need to be written in
assembler. For example, there is a routine to save all the registers
on entry into the OS, and another one to restore them on return from
the OS.
os.c
is the bulk of the OS. Once it gets going, it's
entered at exceptionHandler()
. Entries into the OS are
not procedure calls -- the OS doesn't do a "return"
to go back to the application. Instead, the trap handler has saved
the application's registers on entry. dispatch()
restores them, and sets the PC to the EPC. Voila, you're back in the
application.
The OS has a $gp and $sp, just like anyone else. To preserve them across dispatches of applications, it saves them in memory locations 0 and 4. When control comes back to the OS, the trap handler retrieves them from there, and then the OS can run as normal C code again.
On boot, the OS initializes itself and then launches the first
program, a shell. This shell is very primitive. You type a disk
block number and it tries to launch a new process, assuming that the
block you gave was the first block of an executable stored on disk.
The standard built of the image
file loads
the disk with six applications: the shell, a helloworld application,
a quicksort application, a character IO test, a syscall test, and
a program that generates an addressing exception.
(Yes, you can launch a shell process from the shell.)
You can terminate a shell's execution by giving a
negative number for the disk block. If it is the original shell,
well, the OS crashes. That's intentional.
What you type into the shell is not echo'ed back to you. Ever. (The extra credit piece of the assignment "fixes" this.)