1 / 44

COS318 - Project #2

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

larya
Download Presentation

COS318 - Project #2

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. COS318 - Project #2 Non-Preemptive Scheduling Fall 2000

  2. 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

  3. 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

  4. 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

  5. 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

  6. 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

  7. 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

  8. 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

  9. 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

  10. 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

  11. System Call Mechanism -In detail cont’d • The following diagram shows the kernel_entry in the memory 0x1000 0xf00 kernel_entry()

  12. 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

  13. 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)

  14. 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

  15. 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

  16. 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

  17. 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

  18. 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

  19. 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

  20. 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...

  21. 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

  22. 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

  23. 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

  24. Implementation Details -Memory Layout BIOS 0x01000 Kernel and Processes 0x07C00 Bootblock 0x10000 Stacks 0xA0000 0xB8000 Video RAM

  25. 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

  26. 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

  27. 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.

  28. 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.

  29. 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()

  30. 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()

  31. 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...

  32. 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

  33. 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

  34. 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

  35. 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

  36. 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

  37. 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()

  38. 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...

  39. 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

  40. 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

  41. 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

  42. 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

  43. 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

  44. 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)

More Related