Please check back every now and then, as we may clarify this problem set description. Recent updates:
This second of the Weensy OS problem sets introduces you to another weensy operating system. WeensyOS 1 introduced you to threads, and particularly thread creation and thread switching. WeensyOS 2 shows off scheduling and synchronization!
weensyos2.tar.gz |
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
directory called weensyos2. (We explain how to unpack it
below.)
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 gmake tarball.
This should create a file named
weensyos2-yourusername.tar.gz, which you will submit to
CourseWeb.
Answers to the numbered exercises should be in a file named
answers.txt, answers.html, or
answers.pdf.
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).
To review:
weensyos2.tar.gz.weensyos2 directory.answers.txt file (or answers.html or answers.pdf) in that weensyos2 directory.gmake tarball from the weensyos2 directory. This will create a file named weensyos2-yourusername.tar.gz.weensyos2-yourusername.tar.gz file 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.
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 <bochs:1> prompt.
After a moment you should see a window like this:
The SchedOS consists of a kernel and four user processes. The processes
are extremely simple: the schedos-1 process prints 320 red
"1"s, the 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 characters into
*cursorpos, and then increment cursorpos to the
next position.
Read and understand the SchedOS process code.
Specifically, read and understand schedos-1.c.
Read and understand the comments in
schedos-app.h. The basic feeling should be familiar to you
from WeensyOS 1.
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.
process_t defined in
schedos-kern.h. This is a lot like the thread descriptor
structure from WeensyOS 1; we call these "processes" rather than "threads"
because they run different applications and are linked differently, but
really they're basically the same in this OS.schedos-kern.c.start function from schedos-kern.c, which
initializes the kernel.trap function from schedos-kern.c, which
handles all interrupts and system call traps.SchedOS supports two system calls, sys_yield and
sys_exit. The 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
calls (in trap()) both call the schedule
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
algorithms.
Read and understand the schedule function
from schedos-kern.c.
Exercise 1. What scheduling algorithm does
schedule() currently implement? (What is
scheduling_algorithm 0?)
Exercise 2. Add code to schedule()
so that scheduling_algorithm 1 implements strict priority
scheduling. Your implementation should give schedos-1 higher
priority than schedos-2, which has higher priority than
schedos-3, which has higher priority than
schedos-4. SchedOS will run schedos-1 until it
exits, then schedos-2 until it exits, and so forth. Thus,
process IDs correspond to priority levels (assuming that numeric priority
levels are defined as usual, where smaller priority levels indicate
higher priority). You will also need to change
schedos-1.c so that the schedos processes
actually exit via sys_exit(), instead of just yielding
forever. Test your code.
Exercise 3. Calculate the average turnaround
time and average response time across all four jobs for
scheduling_algorithms 0 and 1. Assume that printing 1
character takes 1 millisecond and that everything else, including a context
switch, is free.
Now complete at least one of Exercises 4A and 4B.
Exercise 4A. Change scheduling_algorithm
1 so that priority levels are defined by a separate
p_priority field of the process descriptor, rather than simply
process ID. Also implement a system call that lets processes set their own
priority level. Make sure you correctly handle the case when more than one
process has the same priority level.
Exercise 4B. Add another scheduling algorithm,
scheduling_algorithm 2, that implements proportional-share
scheduling. In proportional-share scheduling, each process is allocated an
amount of CPU time proportional to its share. For example, say
schedos-1 has share 1 and schedos-4 has share 4.
Under proportional-share scheduling, schedos-4 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 a form of priority scheduling, but the priority levels are
defined differently: larger shares indicate higher priority.)
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.
Change scheduling_algorithm back to 0.
Then change the interrupt_controller_init(0) call in
schedos-kern.c to interrupt_controller_init(1).
This turns on clock interrupts.
After running 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 schedos-1.c).
But we're not done! Let's cause clock interrupts to happen a little bit more frequently.
The 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
milliscond.
After running 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! (Your particular output may differ; if you
still see 320 * 4 characters, try raising HZ to 2000 or
3000.)
Exercise 6. Explain what has happened. Why does
this output look different from the earlier two? Be specific (talk about
particular lines of schedos-1.c, 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. Your code should always print out 320 * 4 characters with no spaces. (The ordering of characters may vary, however, due to clock interrupts.)
There are lots of ways to implement the synchronization mechanism; here are a couple.
x86sync.h directly.x86sync.h to build a lock
data type, then use lock_acquire and lock_release
operations.lock_acquire and
lock_release operations.However, you must not turn off clock interrupts. That would be cheating. Some hints:
x86sync.h atomic
operations to work.cursorpos points to a 16-bit integer, so the C
statement cursorpos++; actually increments the address stored
in cursorpos by 2 bytes, not one.obj/schedos-[1-4].sym files, which tells you
where each symbol is located. Note that cursorpos has the
same address in each process.This completes the problem set.