This second of the Weensy OS problem sets introduces you to a slightly more advanced, yet still weensy, operating system. WeensyOS 1 introduced you to boot loading, different methods of hardware interaction, and the need for protection. WeensyOS 2 shows off process descriptors, scheduling, and synchronization, and has a true kernel!
||Source code for WeensyOS 2.0, the Scheduler OS|
You will electronically hand in code and a small writeup
containing answers to the numbered exercises.
The problem set code,
weensyos2.tar.gz, unpacks into a
weensyos2. (We explain how to unpack it
You'll modify the code in this directory, and add a text file with your
answers to the numbered exercises.
When you're done, run the command
This should create a file named
weensyos2-yourusername.tar.gz, which you will submit to
Answers to the numbered exercises should be in a file named
Text files are strongly preferred.
No Microsoft Word documents (or other binary format, except for PDF)
will be accepted!
For coding exercises, it's OK for
answers.txt to just refer to
your code (as long as you comment your code).
answers.pdf) in that
gmake tarballfrom the
weensyos2directory. This will create a file named
weensyos2-yourusername.tar.gzfile to CourseWeb.
First, you must set up your machine to compile and run WeensyOSes. We have set up the Linux Lab and SEASnet Solaris machines already, but you can also set up a home Linux machine. See the minilab tools page for instructions. You can use the setup that worked for you for WeensyOS 1, if you did WeensyOS 1.
Download and unpack the source for weensyos2.
% gtar xzf weensyos2.tar.gz % ls weensyos2 COPYRIGHT schedos-2.c schedos-trap.S GNUmakefile schedos-3.c schedos-x86.c bootstart.S schedos-4.c schedos.h conf schedos-app.h types.h elf.h schedos-boot.c x86.h mergedep.pl schedos-kern.c x86struct.h mkbootdisk.c schedos-kern.h x86sync.h schedos-1.c schedos-symbols.ld %
Change into the weensyos2 directory and run gmake run-schedos.
This will build and run the single operating system you'll use in
WeensyOS 2, the "scheduler OS" or SchedOS. As before, this will start up
Bochs, but not the emulated computer. To start the emulated computer, type
"c" at the
After a moment you should see a window like this:
The SchedOS consists of a kernel and four user processes, or
"applications". The processes are extremely simple: the
schedos-1 process prints 320 red "
schedos-2 process prints 320 green "
2"s, and so
forth. Each process yields control to the kernel after each character, so
that the kernel can choose another process to run. After printing all 320
characters, each process exits. The four processes coordinate their
printing with a shared variable,
cursorpos, located at memory
address 0x190000. The kernel initializes
cursorpos to point
at address 0xB8000, the start of CGA console memory. Processes write their
*cursorpos, and then increment
cursorpos to the next position.
Read and understand the SchedOS process code.
Specifically, read and understand
The SchedOS has a real kernel: a privileged piece of code that arbitrates between the machine's user processes. (The SchedOS kernel does not isolate processes, but that's OK for now.) Once we have a kernel, we need a way for applications to transfer control into kernel space. The way we do it on x86, and most modern architectures, is with a trap: a special instruction that makes the processor save its state and switch into kernel mode. When the user executes a trap instruction, the processor:
To sum up, when the user executes a trap instruction, the processor switches into kernel mode and starts running kernel code. This is called a kernel crossing.
Read and understand the comments in
schedos-app.h. Don't worry too much about the
asm statements; just understand them at a high level.
The kernel's job is very simple. At boot time, it initializes the hardware,
initializes a process descriptor for each process, and runs the first
process. At that point it loses control of the machine until a system call
or interrupt occurs. System calls and interrupts restart the kernel by
effectively calling the
trap function. Note that this simple
kernel has no persistent stack: every time a system call occurs, the
kernel stack starts over again from the very top, and any previous stack
information is thrown away. Thus, all persistent kernel data must be
stored in global variables.
schedos-kern.h. This refers to the
registers_tstructure defined in
schedos-kern.c, which initializes the kernel.
schedos-kern.c, which handles all interrupts and system call traps.
SchedOS supports two system calls,
sys_yield call yields control to
another process, and
sys_exit exits the current process,
marking it as nonrunnable. The kernel implementations of these system
trap()) both call the
function. This function is SchedOS's scheduler: it chooses a process from
the current set of runnable processes, then runs it. In the first part of
this problem set, you'll focus on this function, and SchedOS's scheduling
Read and understand the
Exercise 1. What scheduling algorithm does
schedule() currently implement? (What is
Exercise 2. Add code to
scheduling_algorithm 1 implements strict priority
scheduling, with the priority order
schedos-4. Thus, the first process,
will run until it exits; then
schedos-2 will run until it
exits; and so forth. You will also need to change
so that the
schedos processes actually exit
sys_exit(), instead of just yielding forever. Test your
Exercise 3. Compare
scheduling_algorithms 0 and 1 in terms of the average
turnaround time and average response time across all four jobs.
Assume that printing 1 character takes 1 millisecond.
Now complete at least one of Exercises 4A and 4B.
Exercise 4A. Change
1 so that the priority order is defined by a separate
p_priority field of the process descriptor, rather than
simply process ID. To be really cool, implement a system call that lets
processes set their own priority. Make sure you correctly handle the case
when more than one process has the same priority.
Exercise 4B. Add another scheduling algorithm,
scheduling_algorithm 2, that implements proportional-share
scheduling. In proportional-share scheduling, each process gets a share of
the CPU proportional to its priority. For example, say
schedos-1 has priority 1 and
priority 4. Under proportional-share scheduling,
will run 4 times as often as
schedos-1 (at least until it
exits); so we might expect to see output like
441444414444144...". (Note that this is different from the
priority order in Exercise 2.)
In this section of the problem set, you'll investigate synchronization issues. But synchronization isn't interesting without concurrency, and right now, our operating system is cooperatively multithreaded: each application decides when to give up control. We introduce concurrency by turning on clock interrupts and introducing preemptive multithreading. When a clock interrupt happens, the CPU will stop the currently-running process -- no matter where it is -- and transfer control to the kernel. This indicates that the current process's time quantum has expired, so the kernel will switch to another process. However, note that clock interrupts will never affect the kernel: this simple kernel runs with interrupts entirely disabled. Interrupts can only happen in user level processes.
scheduling_algorithm back to 0.
Then change the
interrupt_controller_init(0) call in
This turns on clock interrupts.
gmake run-schedos, you should
see a window like this:
Exercise 5. Explain what has happened. Why does this output look different from the output without clock interrupts? Be specific (talk about particular lines of process code).
But we're not done! Let's cause clock interrupts to happen a little bit more frequently.
HZ constant in
schedos-kern.h equals the number of times per second that the
clock interrupts the processor. It is set to 100 by default, meaning the
clock interrupts the processor once every 10 milliseconds. Set this
constant to 1000, so that the clock interrupts the processor every
gmake run-schedos, you should
see a window like this:
Note that the output has less than 320 * 4 characters! Clearly there is a race condition somewhere!
Exercise 6. Explain what has happened. Why does this output look different from the earlier two? Be specific (talk about particular lines of process code, and why the higher clock rate made a difference). Where is the race condition?
Exercise 7. Implement a synchronization mechanism that fixes this race condition.
There are lots of ways to implement the synchronization mechanism; here are a couple.
x86sync.hto build a lock data type, then use
However, you must not turn off clock interrupts. That would be cheating.
(Hint: You may need to use typecasts to get the
x86sync.h atomic operations to work. And note that
cursorpos points to a 16-bit integer, so the C statement
cursorpos++; actually increments the address stored in
cursorpos by 2 bytes, not one.)
This completes the problem set.