440 likes | 528 Views
COS318 - Project #2. Non-Preemptive Scheduling Fall 2000. Overview. Implement non-preemptive OS with: User level processes and kernel level threads Circular non-preemptive scheduler System call mechanism yield exit Thread synchronization lock_acquire lock_release
E N D
COS318 - Project #2 Non-Preemptive Scheduling Fall 2000
Overview • Implement non-preemptive OS with: • User level processes and kernel level threads • Circular non-preemptive scheduler • System call mechanism • yield • exit • Thread synchronization • lock_acquire • lock_release • Context switch timing
Overview -Processes and Threads • Threads (trusted) • Linked with the kernel • Can share address space, variables, etc. • Access kernel services (yield, exit, lock_*) with direct calls to kernel functions • Use a single stack while running thread code and kernel code
Overview -Processes and Threads cont’d • Processes (untrusted) • Linked separately from kernel • Appear after kernel in image file • Cannot share address space, variables, etc. • Access kernel services (yield, exit) via a unified system call mechanism • Use a user stack while running process code and a kernel stack whilst running kernel level code
Overview -Process Control Block (PCB) • Each proc/thread has an associated PCB • PCB is struct which keeps track of essential proc/thread info • PCBs are initialized at start up time • Registers and flags are saved and restored from the PCB at context switch • PCBs of running proc/threads are assembled into linked circular list for scheduling purposes
Process Control Block -In Detail • pid • Unique proc/thread id (integer) • in_kernel • Flag indicating if thread or process • kernel_stack, user_stack • Initial address of stack(s) • start_address • Address to jump to when first executing
Process Control Block -In Detail cont’d • status • FIRST_TIME, READY, BLOCKED, EXITED • regs[] • Array for context storage i.e. registers (eax, esi, esp) and flags (eflags) • *next,*prev • Pointers for assembly of circular linked list • Running proc/threads are maintained in this list for scheduling purposes
Overview -Non-Preemptive Scheduler • Initial process or thread is dispatched at start time • Proc/threads voluntarily stop running by system calls yield(),exit() • Possibly due to blocking on lock_acquire() • System calls invoke a context save • Scheduler is invoked to select next available proc/thread from linked list • Dispatcher is called to instate context of next proc/thread
Overview -System Call Mechanism • Threads (linked with kernel) can call to system calls (yield(),exit(),lock_*()) directly • Processes use system call mechanism to access system services (yield(), exit()) • System call mechanism stores address of jump table in a known memory location • Jump table allow access to fixed set of kernel services
System Call Mechanism -In detail • ENTRY_POINT is memory location which holds address of jump table • #define ENTRY_POINT (void(**)(int))0xf00 • void (**entry_point)(int)=ENTRY_POINT • This location is loaded with address of jump table at startup time • *entry_point = kernel_entry • kernel_entry(int fn) saves context of process and calls service fn • fn is either SYSCALL_YIELD or _EXIT
System Call Mechanism -In detail cont’d • The following diagram shows the kernel_entry in the memory 0x1000 0xf00 kernel_entry()
Overview -Context switching • Multitasking operating system • Fool each proc/thread into believing it has sole possession of the CPU and resources • Each proc/thread runs within own context • Context describes system state • Register set, stack(s), address space • Note: All proc/threads run in same address space for now... • Context information is stored in PCB
Overview -Context switching cont’d • Contexts are initialized for each PCB during startup • Unique kernel and user stacks are assigned • Starting addresses are stored • Context is swapped in by dispatcher • First time - Set the stack pointer and jump to starting address • Subsequent times - Restore saved registers (including stack pointer) and return to saved program counter location (on stack)
Overview -Context switching cont’d • Context is saved upon interruption • For Threads - on yield call (maybe by block) • The entire register set including stack (esp) and system status word (eflags) are saved • Program counter (eip) is automatically stored on kernel stack when yield() is called • For Processes - on kernel_entry call • The entire register set including stack (esp) and system status word (eflags) are saved • Program counter (eip) is automatically stored on user stack when kernel_entry is called • Note: System service execution continues using the process’ kernel stack
Overview -Thread Synchronization • Only one thread can hold some lock, l, at any one time • Lock is acquired via lock_acquire (l) • If UNLOCKED set to LOCKED • If LOCKED thread is blocked (which places thread in a queue stored in the lock) • Lock is released via lock_release(l) • If queue is empty then set to UNLOCKED • If not empty then unblock (dequeue) a thread which now holds the lock
Implementation Details -General Considerations • BIOS loads the provided bootblock in real mode (as in previous project) • Bootblock switches the CPU in protected mode and launches kernel • Do not modify any segment registers • Access to entire first, flat 1MB of memory is available using the 32bit registers (e.g. eax, ebx, etc.) • For safety, keep all stacks, etc. within the first 640Kb of memory
Implementation Details -Code Files • You are responsible for: • kernel.h • You may desire to add additional function prototypes, data structures, etc. • kernel.c • _start() - Initial kernel startup code to init PCBs, store kernel entry address, launch first proc/thread • scheduler() - Selects next available proc/thread • dispatch() - Sets up context and runs proc/threads • kernel_entry() - Saves proc context and invokes appropriate system service
Implementation Details -Code Files cont’d • You are responsible for: • kernel.c cont’d • yield() - Saves context for threads, and invokes scheduler for proc/threads • exit() - Sets EXIT flag and invokes scheduler • block() - Sets BLOCKED flag and queues PCB • unblock() - Sets READY flag and dequeues PCB • Support for context switch timing • lock.c • lock_init(), lock_acquire(), lock_release() • Call to block(), unblock() are necessary
Implementation Details -Code Files cont’d • You are NOT responsible for: • common.h • Some general global definitions • syslib.h, syslib.c • Code to setup process system call mechanism • util.h, util.c • Useful utilities (no standard libraries are available) • th.h, th1.c, th2.c • Some threads that run in this project • process1.c, process2.c • Some processes that run in this project
Implementation Details -Startup - _start() • Store jump table location • Save kernel_entry to appropriate location • Initialize PCBs • Set unique pid • An integer value • Set in_kernel flag • YES for threads, NO for processes • Setup unique kernel stack pointers • For procs setup unique user stack pointers • See upcoming slide...
Implementation Details -Startup - _start() cont’d • Initialize PCBs cont’d • Store starting addresses • For threads use thread function name (e.g thread2) • For processes use hard coded def’s (e.g. PROC1) • Assemble circular linked list • Use *next, *prev pointers • Set status flag • Use FIRST_TIME so dispatcher knows to manually jump when running proc/thread for the first time
Implementation Details -Startup - _start() cont’d • Begin executing first proc/thread • Initialize *current_running • Points to PCB of currently running proc/thread • Initialize to first PCB in linked list • Invoke the dispatcher • This will cause current_running to have it’s context switched in and run
Implementation Details -Stacks • Where to put them in memory? • Below the upper 640 kB limit (0xa0000) • Suggestion: between 0x10000 and 0x20000 • See memory layout on the next slide • Size of each stack: • 4kB should be sufficient • Set them apart by 0x1000 with an offset of 0x0ffc e.g. • esp1 = 0x10000 + 0x1000 + 0x0ffc = 0x11ffc • esp2 = 0x10000 + 0x2000 + 0x0ffc = 0x12ffc
Implementation Details -Memory Layout BIOS 0x01000 Kernel and Processes 0x07C00 Bootblock 0x10000 Stacks 0xA0000 0xB8000 Video RAM
Implementation Details -Scheduling • The current set of running proc/threads are maintained in a circular linked list • The currently running proc/thread is pointed to by current_running • pcb.status • FIRST_TIME • If a proc/thread has not run yet this tells dispatch to manually run the process from its start_addr • READY • Indicates to the dispatcher to retrieve the saved context to invoke this proc/thread
Implementation Details -Scheduling cont’d • pcb.status cont’d • BLOCKED • Set and reset to READY by block() and unblock() • Indicates to scheduler to remove this PCB from the linked running list if encountered • EXITED • Set by the exit() system call • Indicates to scheduler to remove this PCB from the linked running list if encountered
Implementation Details -Scheduling - scheduler() • Invoked by yield(), exit() to select next available process • If current_running is BLOCKED or EXITED then remove from the linked list and set current_running to current_running->next() • If current_running is READY then set current running to current_running->next • We should never discover current_running = current_running->next. Why? • Invoke dispatcher to launch current_running.
Implementation Details -Scheduling - dispatcher() • Called by scheduler to execute current_running • Status is FIRST_TIME • Set status to READY • Put appropriate kernel or user stack pointer in esp • jmp to the starting address • Status is READY • Restore the eflags then register set • Do eflags first since it requires use of kernel stack • See notes on inline assembly in upcoming slides • Allow dispatch() to exit so that it will automatically return to the eip on the top of the context’s stack.
Implementation Details -System Call Mechanism • Processes access system calls yield() and exit() via kernel_entry() • Threads access system calls yield(), exit(), lock_*() via direct function call • A system call could lead to a context change, so context must be saved • Process context saved at start of kernel_entry() • Thread context saved at start of yield()
Implementation Details -System Call Mechanism cont’d • No need to save thread context in any other system call since exit() means we’ll never run that thread again, and lock_* only leads to a context change via an internal yield() call • You will probably want to define a different yield() for processes, say _yield(), which is invoked by kernel_entry(), since only threads save context in yield()
Implementation Details -System Calls - kernel_entry(int fn) • Address is stored at ENTRY_POINT • Invoked by system calls defined in syslib.c • fn can be YIELD or EXIT • Save process context in PCB and switch to kernel stack • Call to yield() or exit() appropriately • Note: C variable fn is not available because you must change stacks. See upcoming slides...
Implementation Details -Saving Context - kernel_entry() • Save the register set • EAX, EBX, ECD, EDX, ESI, EDI,EBP,ESP • Set esp to kernel stack • For security all system services run using a separate stack • Save the processor status word (eflags) • This requires use of a stack, so we must do this after changing to the kernel stack • Note that eip is automatically pushed on the user stack when kernel_entry called
Implementation Details -Saving Context - yield() • Save the register set • EAX, EBX, ECD, EDX, ESI, EDI,EBP,ESP • Save the processor status word (eflags) • This requires use of a stack, but we may use the current kernel stack • Note that eip is automatically pushed on the kernel stack when yield() called
Implementation Details -Saving Context - Inline Assembly • To access a C variable in inline assembly • asm volatile(“statements”,output_regs:input_regs:used_regs); • statements - assembler code - input, output registers are referred to by %0, %1, %2, etc. in order that they appear in following two fields • output_regs - “constraint”(lvalue), etc. • input_regs - “constraint”(expression), etc. • used_regs - clobber list - but you don’t need to worry about this • Examples (non-static variable foo) • asm volatile(“movl %%eax,%0”:”=q”(foo)); • =q instructs compiler to use some additional (unknown) register in this instruction to arrange that contents of eax end up in foo • The compiler might arrange that foo is kept in that register, or else generate a code to to get it into foo from the temporary register • asm volatile(“movl %0,%%esp”::”q”(foo)); • q instructs compiler to use some additional (unknown) register to ensure that contents of foo can be moved into esp
Implementation Details -Saving Context - Inline Assemblycont’d • Examples (static variable foos) • asm volatile(“movl %%eax,%0”:”=m”(foos)); • =m instructs the compiler move the contents of eax into to the memory address of foos without permitting the use of any temporary registers • Since foos is statically allocated, this addess is available at compile time so the memory address of foos is directly available • asm volatile(“movl %0,%%eax”::”m”(foos)); • m instructs the compiler move the contents of the memory address at which foos resides into to register eax without permitting the use of any temporary registers • Since foos is statically allocated, its address is available at compile time and can be used as a destination without appealing to any temporary registers
Implementation Details -Saving Context - Inline Assemblycont’d • Example (immediate values) • asm volatile(“addl $0,%%eax”::”n”( pcb[0].regs - pcb )); • n indicates that an immediate value (available at compile time) is the source for this instruction • Since the pcb array is statically allocated, the byte address offset of the regs[] array in the pcb struct is available as an immediate value • References • www.cs.princeton.edu/ courses/archive/fall99/cs318/Files/djgpp.html • www.uwsg.iu.edu/hypermail/linux/kernel/9804.2/0818.html • www.castle.net/~avly/djasm.html
Implementation Details -Saving Context - Assembly Hints • Saving/restoring eflags register • eflags register cannot generally be accessed via movl, etc. • Can push and pop eflags on and off of the stack with pushfl, popfl e.g.: • asm volatile(“pushl %0”::”q” (eflag_var));asm volatile(“popfl”); • asm volatile(“pushfl”);asm volatile(“popl %0”:”=q” (eflag_var)); • This uses the stack, so be certain that you use the kernel stack for this in kernel_entry()
Implementation Details -Saving Context - Assembly Hints cont’d • Saving/restoring register set • When saving and restoring the register set you must be certain not to inadvertently overwrite any of the registers. Do not use the “q”, “=q” constraints… • Try something like this: • asm volatile(“movl %0,%%eax”::”m”(curr_running));asm volatile(“addl %0,%%eax”,”n” (pcb[0].reg - pcb));asm volatile(“movl %ebx,4(%eax)”);asm volatile(“movl %ecx,8(%eax)”);etc… • You’ll find a statically allocated scratch variable might come in handy here...
Implementation Details -Saving Context - Assembly Hints cont’d • Issue in kernel_entry(int fn) • After saving the register set, we must change to kernel stack before saving the eflags • The fn argument is stored on the user stack and is not available to C after stack change • Any local function variables can become unavailable after the stack change • Safest bet is to manually load (esp+4) into a statically allocated variable before changing stacks, but after saving the registers
Implementation Details -System Services - yield(), exit() • yield() • Processes - Just invoke scheduler • Threads - Save context and invoke scheduler • exit() • Change status to EXITED • Invoke the scheduler
Implementation Details -System Services - block(), unblock() • These are generalized services invoked by our thread locking functions • block(struct pcb_t **q) • Change current_running status to BLOCKED • Add current_running to end of queue (*q) • Call to yield() • unblock(struct pcb_t **q) • Remove PCB at head of queue (*q) • Change PCB’s status to RUNNING • Insert PCB into running linked list
Implementation Details -Synchronization - lock_*() • These services invoke the lower level blocking functions to provide threads with synchronization • One one thread can possess a particular lock_t object at any one time • An attempt to lock an UNLOCKED lock_t sets the status to LOCKED • An attempt to lock a LOCKED lock_t results in the blocking of the requesting thread until it is released
Implementation Details -Synchronization - lock_*() • lock_init() • Initialize status to UNLOCKED • Initialize PCB queue to empty (NULL) • lock_acquire() • If UNLOCKED, change to LOCKED • If LOCKED block the requesting thread on the lock’s queue • lock_release() • If queue is empty, change to UNLOCKED • If queue not empty, unblock the head PCB
Implementation Details -Context Switch Timing • Use get_timer() to return the number of clock cycles since boot up time • Result is 64 bits (unsigned long long int) • Compute the average time for a change by measuring the time spent in the kernel • Be certain not to corrupt the registers, etc. by calling at wrong time… • Extra credit by tallying user and kernel time for each process (add fields to PCB)