Presentation is loading. Please wait.

Presentation is loading. Please wait.

Discussions on hw3 Objective

Similar presentations


Presentation on theme: "Discussions on hw3 Objective"— Presentation transcript:

1 Discussions on hw3 Objective
To implement multitasking of 3 Tiny-UNIX processes on the SAPC Each process tries to output data to the COM2 output port Process 1 Process 2 Process 3 Process 0

2 Details on the Processes
Processes 1, 2 and 3 user processes doing outputs using syscall write and then exit have output queues with length = 6 chars Process 0 kernel process turns on interrupt by setting IF =1. It then loops and calls the scheduler with IF=0 and then IF=1 when it enters critical regions has a non-preemptive scheduler that will allow one (or more) process(es) to be blocked while another runs the simple scheduler looks for user process (1,2, or 3) to run. If none, chooses 0

3 Zombie Process After a process (1, 2, or 3) exits, it is a "zombie" process. The reason is: All that is really left is its exit value, waiting to be picked up by its parent. But there is no parent process to pick up.

4 Program Operations 3 user programs each outputting >6 chars to TTY
one user process runs until it has filled the output buffer, then blocks(call scheduler and looks for another process to run) another process runs, also blocks because the output queue is full a third runs, blocks and no user processes are left to run 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 TX int. handler. Process 0 calls the scheduler and it finds a process to run the chosen user process runs and refills the output buffer, then blocks the other two user processes run in turn and fill the output buffer and block again process 0 runs again over and over until the output is done

5 lib calls to do write, exit
User Program Flow startup0.s: Same as $pclibsrc/startup0.s - init stack Start from Tutor startup1.c: init kernel Init memory Part of tunix.c: new module -call ioinit, set_trap_gate(0x80, &syscall): -init PEntry[] for Proc 0,1,2,3 -call schedule() to start Proc 0 Init kernel sched.c: new module -schedule() ; sleep();wakeup() -call _ustart1, _ustart2, or _ustart3 Scheduler Start-up crt01.s, crt02.s,crt03.s: -entry points:_ustart1, _ustart2, _ustart3 -call _main1, _main2, or _main3 -call _exit Start-up Start-up User program User program uprog1.c,uprog2,uprog3: Modify from testio.c: -entry points: main1, main2, main3 -call write User program ulib.s: syscall lib setups; -add _write, _exit lib calls to do write, exit

6 Kernel Program Flows sysentry.s: same as hw2
-push eax,ebx,ecx,edx on stack -call system call dispatcher: _syscallc -pop stack and iret Trap handler wrapper System call dispatcher Part of tunix.c: same as hw2 -entry point _syscallc -depend on syscall #, call the handler routine -need to modify sysexit to change state, and call schedule() System call trap handler routine tty.c: modify from hw2 -need to add debug_log -need to add wakeup at irqinthandc for tx interrupt -need to add sleep at ttywrite when queue is full

7 Process Entry Table #define N_SAVED_REGS 7 /* 7 non-scratch CPU registers, saved in this order: */ #define ESP1 0x /* process 1 stack starting address */ #define ESP2 0x /* process 2 stack starting address */ #define ESP3 0x2a0000 /* process 3 stack starting address */ #define N_SAVED_REGS 7 enum cpu_regs {SAVED_ESP, SAVED_EBX, SAVED_ESI, SAVED_EDI, SAVED_EBP, SAVED_EFLAGS, SAVED_PC}; /* for p_status ( RUN=1, BLOCKED=2, ZOMBIE=3) */ typedef enum proc_status {RUN = 1, BLOCKED, ZOMBIE} ProcStatus; /* for p_waitcode, what the process is waiting for */ typedef enum proc_wait {TTY0_OUTPUT = 1, TTY1_OUTPUT} WaitCode; /* Process Entry */ typedef struct { int p_savedregs[N_SAVED_REGS]; /* saved non-scratch registers */ ProcStatus p_status; /* RUN, BLOCKED, or ZOMBIE */ WaitCode p_waitcode; /* valid only if status==BLOCKED: TTY0_OUT, etc. */ int p_exitval; /* valid only if status==ZOMBIE */ } PEntry;

8 Process Entry Table(cont’d)
/* the process table with four entries loaded with dummy values */ PEntry proctab[] = { {{0,0,0,0,0,0,0}, RUN, TTY1_OUTPUT, 0}, }; proctab[3].p_savedregs[SAVED_ESP] = ESP3; proctab[3].p_savedregs[SAVED_PC] = (int) &ustart3; proctab[3].p_savedregs[SAVED_EFLAGS] = 0x1 << 9; proctab[3].p_savedregs[SAVED_EBP] = 0; proctab[3].p_status = RUN;

9 debug_log function sprintf(buf, “^%c”, ch); debug_log(buf); stores the report in memory starting at 0x and then outputs them If the program crashes, you can still use Tutor to see the log Tutor> md use debug_log(“~”) to mark each interrupt debug_log(“e”) for an echo debug_log(“s”) for shutdown Tx interrupts

10 debug_log function (cont’d)
For hw3, add debug_log() for process switches |(0-1) for switching from process 0 to process 1 |(1Z-2) for switching from process 1, now a zombie, to process 2 |(1b-2) for switching from process 1, now blocked, to process 2

11 Context Switching function: asmswtch.s
.globl _asmswtch # asmswtch--process switch # (but we don't need to save C scratch reg's, since they are assumed # to be smashed by calling this anyway) # Call from C: two arguments, pointers to old and new PEntry's: # asmswtch(oldpentryp, newpentryp) # # Stack when reach here: # %esp--> return pc # 4(%esp) oldpentryp # 8(%esp) newpentryp # # PEntry: saved esp # saved ebx # saved esi # saved edi # saved ebp # saved eflags # saved pc -- i.e., saved eip (where this was called from) newentryp oldpentryp return PC High address Low address %esp after Stack %esp before

12 asmswtch.s (cont’d) newpentryp oldpentryp return PC High address
Low address %esp Wait code Proc status Save pc Save eflags Save ebp Save edi Save esi Save ebx Save esp Stack Old Process Entry _asmswtch: movl 4(%esp),%eax movl %esp, (%eax) movl %ebx, 4(%eax) movl %esi, 8(%eax) movl %edi, 12(%eax) movl %ebp, 16(%eax) movl (%esp), %ecx movl %ecx, 24(%eax) pushfl #push old eflags on stack popl 20(%eax)# pop it and store in loc movl 8(%esp), %eax movl (%eax), %esp movl 4(%eax), %ebx movl 8(%eax), %esi movl 12(%eax), %edi movl 16(%eax), %ebp pushl 20(%eax) # new eflags--push stored eflags on stack popfl # restore eflags movl 24(%eax), %ecx movl %ecx,(%esp) # restore top of stack=saved pc ret # return on newly reinstated stack +24 +20 +16 +12 +8 +4 +0

13 sleep function: sleep()
Prototype void sleep(Waitcode event) Block the calling process by setting the process status = BLOCKED and waitcode = event Run the scheduler to start another process Disable interrupt at the beginning and restore interrupt at the end

14 wakeup function: wakeup()
Prototype: void wakeup(Waitcode event) Go through all user processes. If process status = BLOCKED and waitcode = event, then change process status = RUN Disable interrupt at the beginning and restore interrupt at the end

15 Suggested Steps 1. Make sure your hw2 solution is fully working, or use the provided solution. Note that the provided solution has a "debug_log" service that writes notes to memory, and prints out the log when the kernel shuts down. Add this logging functionality to your own solution if you're using it for hw3. Note that you can add your own entries in the log to figure out what's happening. 2. Write trivial user programs that just kprintf a message each, and the crt0's for them that call mainx, and then do an exit syscall. Write a fake scheduler that just loops through proctab until it finds a status = RUN process and then calls it at ustartx. If there aren't any RUN ones left, bring down the system. Inside sysexit, call the scheduler after marking the process ZOMBIE. This simple system will work because each process only needs to run once. They use the same stack area one after another. Make sure your makefile is right: see that it rebuilds the right things after each edit. (mainx and ustartx where x =1,2,3)

16 Suggested Steps(cont’d)
3. Now use the supplied asmswtch code to start each process. For each user process, you need to initialize the PEntry's saved-pc to _ustartx, and the saved-eflags to allow interrupts, and the saved-esp to the proper stack. Also the saved-ebx should be 0. In your new fake scheduler, loop through the proctab until you find a RUN user process (1, then 2, then 3) and then call asmswtch with old-process = proc 0, new-process = proc 1, to switch from proc 0 to proc 1, thus running uprog1. Later, when proc 1 exits, sysexit calls the scheduler again, which finds proc 2, and calls asmswtch with pointers for (proc1, proc2) to switch from proc 1 to proc 2, and that runs uprog2. When uprog2 exits, sysexit calls the scheduler... Eventually the scheduler calls asmswtch with (proc3, proc0) to switch back to proc 0 when no more RUN processes exist. Then you can shut down the system using process 0. (Of course you can do just part of this to start, then write a more finished product.) Make sure the user processes are each using their own stack. 4. Add a debug_log call to the scheduler to report on each process switch, for example, "|(2z-3)" for a switch from process 2, a zombie, to process 3.With just kprintf's in your user programs, your debug log should look like this now: |(0-1)|(1z-2)|(2z-3)|(3z-0)

17 Suggested Steps(cont’d)
5. Add writes of one or two chars each to the three user programs and get this working--this output all fits in the 6-char queue, so no blocking is needed. This should work even though you haven't yet edited tty.c. Make sure the kernel waits for the output to drain while it's shutting down. Also make sure the user processes are running with interrupts enabled and using their appointed stack. You can check IF with get_eflags(), for example. Since the three writes return immediately, all the output will be done in the final kernel wait-for-output, and the debug log will look something like this (for 2 output chars for each process): |(0-1)~|(1z-2)~|(2z-3)|(3z-0)~~~~~s (Possibly more ~s will come earlier.) 6. Write the real scheduler after the class on this subject. Get it working in this too-easy user program environment. Change the code in tty.c to block and unblock at the right moments. Change to a 7-char output from one user program as soon as you want to try out blocking. When that works, try two, then all three with over-6 chars. Then the provided uprog's.

18 More on hw3 Define kernel global variables using PEntry structs
proctab[], *curproc There are 4 entries in proctab[]: for processes 0,1,2,3 Processes 1,2,3 are user processes Process 0 derives from the startup and is always runnable

19 Major Kernel Functions
schedule() : look for a user process to run. If not, run process 0. Call asmswtch. Called from process 0: from startup, sleep and sysexit sleep(event): block the calling process by changing p_status to BLOCKED, p_waitcode to event and then calling schedule() called from ttywrite (to replace the busy wait) wakeup(event): loop through proctab, changing all processes blocked on this event to p_status= RUN. Called from tty output interrupt handler

20 Interrupts for Major Functions
These are all critical codes and should be run with interrupt off (IF=0) For wakeup(), it is only called from the interrupt handler with IF =0. Make sure the wakeup code does not set IF=1 wakeup() does not call schedule(). It just set p_status = RUN

21 Code Example on Critical Functions
int sched(void){ … saved_eflags = get_eflags(); cli; …. asmswtch(…); set_eflags(saved_eflags); } Save a copy of eflags register Restore the eflags register

22 Schedule() Logic to determine Decision code which process to run
Call asmswtch return Provide oldproc, curproc After return, it is running the new process

23 More Details on sleep()
Modify ttywrite in hw2 to include sleep(): if (enqueue(…) = = FULLQUE) sleep(OUTPUT); /* can we enqueue now? Not for sure ! */ Suppose there are 2 processes A, and B that have been sleeping. Then they are unblocked by the wakeup function. If A runs first and it fills up the output queue. B runs sometime later and finds the buffer full. It should go to sleep again. The above code does not work this way. The correct code should look like: while (enqueue(…) = = FULLQUE) sleep(OUTPUT);

24 More Details on mutex In hw2, we use disabling/enabling interrupt (cli/sti) to protect concurrent modifications to the queue data structures In hw3, we use disabling/enabling interrupts (cli/sti) in sleep(), wakeup(), schedule() Where should we place the cli/sti’s? Look at the following example: while (guarded_enqueue(…) = = FULLQUE) guarded_sleep(OUTPUT); where guarded_xxx is the xxx function surrounded by cli and sti Interrupt occurs here; runs dequeue which wakes up the task; Wakeup will be ignored because the task is going to sleep

25 Solution to Lost Wakeup Problem
Expand the cli/sti pair: cli(); while (enqueue(…) = = FULLQUE) sleep(OUTPUT); sti(); This works because sleep() calls schedule() which runs a new process with the new process’s EFLAGS Need to exercise care when we use the above solution. Look at the following example: cli(); x=1; /* x is a global variable */ while (enqueue(…) = = FULLQUE) sleep(OUTPUT); /* what is value of x here? still =1? Not necessary */ sti();


Download ppt "Discussions on hw3 Objective"

Similar presentations


Ads by Google