CS444 Class 17

For next time, read Chap. 3 to pg. 194, esp. 189-194

 

HW3 Setup—does multitasking, so need process switch code, very tricky—

Job of asmswtch from process i to process j:

1.      Save CPU non-scratch registers in process entry proctab[i]  (here *oldpentryp)

2.      Load CPU non-scratch registers from process entry proctab[j] (here *newpentryp)

We’re looking at asmswtch.s in handout

 

Last time we discussed how the “ordinary” non-scratch registers like EBX were saved and restored, using EAX to hold the PEntry pointer.

It’s OK to destroy the old value in EAX because it is a C scratch register.

 

Registers EFLAGS, ESP and EIP (PC) are more than ordinary.

EFLAGS needs the special instruction, and holds IF.

 

The ESP points to the whole stack (with kernel stack built on top of user stack), so the swap of ESP represents a swap of program-execution state.

 

 saved PC” (i.e. EIP) is actually asmswtch’s return PC.  This trick allows us to start a new process with asmswtch as well as switch processes once we’re going.  We can put ustart1 in the “saved-PC” spot in a PEntry, and it will be pushed onto the stack in asmswtch and then used in ret, causing a jump to ustart1.  The saved EFLAGS at this point needs IF=1.

 

We see that asmswtch takes two arguments, pointers to the old and new PEntry’s.  A first try at using it could be asmswtch(&proctab[0],&proctab[1]), to switch from process 0 to process 1, once kernel execution has turned itself into process 0 and set up proctab[1].  But we need good stuff in proctab[1] to load into the CPU.

 

We can use asmswtch to start a user process as well as switch processes later.  Just set up the processes PEntry with:

saved” ESP = appropriate stack start (more on this soon)

saved” PC = code’s start address, for example ustart1

saved” EFLAGS = 0 except IF = 1, so user code runs with interrupts enabled.

saved” EBP = 0 for clean debugging backtraces

p_status = RUN.

 

and it will be started by an asmswtch call with second argument pointing to this PEntry.  The very first call will be from process 0, the kernel itself, to user process 1, like this, from C code:

 

asmswtch(p0,p1)

where  

     p0 = &proctab[0] = pointer to PEntry for process 0

       p1 = &proctab[1] = pointer to PEntry for process 1

This will switch from process 0 (the kernel initialization turned process) to process 1 (the first user process).  It copies the “old” CPU registers to the p_saved_regs array in proctab[0], and gets new values for them from proctab[1].p_saved_regs.

 

To get started, code it like this and see it actually work, and later morph it into better general purpose code.  Note that we don’t need to explicitly set up saved registers for process 0, because they will just be overwritten in this first call anyway.

 

Note that each process needs its own stack, so these are set up, for example, growing down from 0x3f0000, 0x3e0000, 0x3d0000, and the original one at 0x3ffff0.  For example:

 

proctab[1].saved_regs[SAVED_ESP] = 0x3f0000; 

 

But it’s better to name this constant with #define.

 

How the System runs with supplied uprog1, uprog2, uprog3

Uprog1: write(TTY1, “aaaaaaaaaa”, 10);…

Uprog2: write(TTY1,”bbbbbbbbbb”, 10);…

Uprog3: <delay loop>, write(TTY1, “ccccccccccc”, 10);…

 

 

Here is what we expect to happen with two user programs starting off doing more than 6 chars of output (the output buffer size) to the same TTY device. 

 

As delivered 1 runs, then 2 runs, then 3 runs

As fixed:

--One user process runs until it has filled the output buffer, then blocks (via call to scheduler, which looks for another process to run),

--another process runs, also blocks, because it wants to output to the same device, and the output queue for it is full

--a third runs, goes into CPU-bound loop

-- output drains during loop (no preemption here), as soon as space in output queue, 1 and 2 unblocked

-- output finished, transmitter shuts down,

--loop finishes, enqueues 6 c’s, blocks, process switch to 1, blocks, process switch to 2, blocks

--so process 0 is chosen to run.  As process 0 runs, output drains at interrupt level, and the user processes are unblocked by a call to the scheduler from the transmitter interrupt handler.  Process 0 calls the scheduler and the scheduler finds a process to run

--the chosen user process, process 3 runs and refills the output buffer (1 char, a ‘c’), then blocks.

--the other two user processes run in turn and find the full output buffer, and block again.

--process 0 runs again…

…over and over... until the output is done.

 

hw3’s scheduler  

Scheduler setup, kernel global variables:

proctab[], curproc: global, used by tunix.c and sched.c

Recall our discussion of PEntry structs, one for each process in the proctab array.  There are 4 spots in the array, one for process 0 and 3 for the user processes 1, 2, and 3.

process 0 derives from the startup execution, i.e., the kernel initialization in tunix.c morphs itself into a process by filling in proctab[0] and curproc.  It is always runnable, so the scheduler can always find a process to run.

Scheduler API (not encapsulated: uses global proctab[], as do other functions in the kernel)

sched.c will contain the scheduler code—here are its major functions.

Sleep and wakeup are the traditional names for the functions: we’re adding t for tiny-UNIX, and to allow “sleep” to name a new system call.

Where are these called from?

tsleepcalled from ttywrite (to replace the busy wait)

twakeupcalled from the tty output interrupt handler, when a new spot in the output queue becomes available.

schedule—called from process 0 code, to get started, and as an “idle loop” to try to hand off the CPU to a user process.  Also from sysexit.

These are all critical code, so should run with interrupts off.  If you call wakeup only from int handlers, of course it will get IF=0 automatically, since we never turn ints back on in int handlers.  You could put checks in to make sure IF=0 ((get_eflags()>>9)&1 gives IF)

Note that wakeup is a “broadcast” action, so multiple processes may wake up for just one char-spot in the tty output queue, for example.  Also note that wakeup doesn’t call schedule, so it doesn’t actually get a process running, it just marks it runnable.  Sometime later, another process calls sleep, and that calls schedule, and chooses this one to run.  Or process 0 calls schedule, and that does the trick.