Section notes for CS162 Section #3, February 11, 2003 Hakim Weatherspoon ADMINISTRIVIA - See if office hours times work for people - Nachos download - CVS info - Project meetings: - Will be done in week after you turn in reports - All members of group must be at meeting - You are graded on the report and the meeting REVIEW from last week - Issues with threads and concurrency (things to think about) - How to pick next thread to run? (SCHEDULING.) - Want to maximize fairness and still have low response time - Much detail on this later in the course - How to let threads interact safely? (SYNCHRONIZATION.) - I.e. two threads each writing to the same shared variable - How to avoid having one thread overwrite the other's value (i.e. T1 does: local := shared; local := f(local); shared := local; T2 does: local := shared; local := g(local); shared := local; RACE CONDITION: T1 writes new value of 'shared' while T2 computing g, so T2 writes 'shared' based on "old" value.) - Various ways of solving this: Condition variables, mutexes: next few lectures - How to make threads efficient - eg. Web server: Convenient to code as one thread per client request - But, there is some overhead with threads? QUESTION: What overhead is there? - TCB, stack, machine registers, etc. - Time to contex switch - Time to warm up cache, TLB, etc. after switch SYNCHRONIZATION - Critical Section, a piece of code that only one thread can execute at once. Only one thread at a time will get into the section of code. - Synchronization Requirements - Mutual Exclusion, ensuring only one thread can execute in a critical section at a time - Progress, one thread must always be allowed to enter the critical section - Bounded waiting, a thread should not wait indefinitely to enter its critical section - Locks are used to implement mutual exclusion. All require some level of hardware support. - Atomic operations are a set of instructions which execute as one uninterruptible unit. TestAndSet and Swap are atomic operations supported by most hardwares. /* returns the current value of lock and sets lock to true */ TestAndSet(lock) TestAndSet = lock lock = true /* swaps two values Swap(local(i), lock) temp = lock; lock = local(i) local(i) = temp ------------------------------------------------------------------------ - Critical Section Problem using TestAndSet ------------------------------------------------------------------------ global var waiting: array[0...n-1] of boolean, init to false; lock: boolean, init to false; local var j: 0...n-1; key: boolean; waiting[i] = true; /* Thread i */ key = true; while waiting[i] and key do key = TestAndSet(lock); waiting[i] = false; CRITICAL SECTION... j = j + 1 mod n; while (j != i) and (not waiting[j]) do j = j + 1 mod n; if j = i then lock = false else waiting[j] = false; I will use a "coke closet" analogy to explain this code. We have a coke closet (critical section), a lock, and a queue of people waiting on the coke closet (key is just a local variable). Only one person can be in the coke closet at a time. Person i (thread i) am waiting for the coke closet as long as there is someone in the coke closet (lock == true) AND person i am waiting for the coke closet (waiting[i] == true). When someone leaves the coke closet they tell the next person on the queue (waiting[j] == false) OR IF NO ONE IS WAITING they unlock the dressing room (lock == false). ------------------------------------------------------------------------ - Locks, condition variables, semaphores, interrupts, atomic ops - Locks: implement mutual exclusion - Condition variables: make it possible to go to sleep inside a critical section by atomically releasing a lock at the same time the thread goes to sleep - Semaphores: a generalized lock, having a non-negative integer value (classical definition) - Interrupts: an external event which causes the dispatcher to take the CPU away - These things can be built out of each other - Use semaphores to implement condition variables - Use interrupts to implement condition variables (uniprocessor) - Use semaphores to implement a lock - Use interrupts to implement a lock (uniprocessor) - Use TestAndSet to implement a lock - Question when are spinlocks (busy waiting) use full? - When the time to hold a spinlock is much smaller than the time required for a context switch. CVS QUICK START - Quick CVS Primer - Basic model: Single REPOSITORY which stores the "master" version of the code, as well as all previous versions - Users do NOT edit anything in the repository - rather, they "check out" a copy of the files, and edit that copy - After making changes, you "commit" the changes back to the repository - Other users only see those changes if they do an "update" - Basic CVS usage - Repository will be created for each project group - stay tuned - Repository should be readable ONLY to members of your group - so we will assign UNIX groups to you 1) Set your CVSROOT environment variable to point to the repository: setenv CVSROOT If you want to check the files out from a remote machine (say, over ssh), then you would use the following instead: setenv CVSROOT cs162@torus:/home/ff/cs162/repository setenv CVS_RSH ssh 2) Check out a local copy of the files you want to edit. For example, to modify your project's Nachos code, you might do: cvs co nachos This will create a "nachos" directory with all of the Nachos code in it. 3) Edit your local copy of the files. 4) Commit the changes back to the repository: cvs commit This will start up an editor for you to enter a log entry describing your changes. PLEASE enter a descriptive entry of the changes you made -- this will make tracking changes a lot easier. Note that you cannot commit changes unless your local files are up to date: that is, that you have been editing the most recent copy. See next step... 5) For other users to see the changes, they need to do: cvs update -d which will update their local files to be in sync with the repository. If there are conflicts (that is, someone else edited the same file while you were in the process of editing it), you will be notified at this point and given a chance to fix the conflict. To add a file or directory to the repository, just do: cvs add filenames... After 'cvs add' you need to 'cvs commit' for the addition to take effect. Also: 'cvs import' to add a whole tree (e.g. the nachos code for the first time): cvs import To remove a file, first remove it from your local copy: rm filename Then remove it from the CVS repository: cvs remove filename cvs commit Other features: - Can check out copy of code as it looked at a given time cvs co -D "yesterday" nachos cvs co -D "02/03/03 8:00" nachos - Can tag a version of the file for later use: cvs tag working1 cvs co -r working1 nachos - Can look at changes: cvs log cvs diff -D "yesterday" cvs diff -r 1.3 foo.java IMPORTANT CAVEATS: Note that CVS does not allow you to rename files - you need to "remove" the file and then "add" the file under the new name. In general it's best not to rename files, since this loses all of the editing history for that file. Also note that CVS does not allow you to remove or rename directories once they have been added to the repository. THIS IS VERY IMPORTANT!! Do not add directories to the repository temporarily - you can never get rid of them. This is a real drawback to CVS, but it's something you just have to live with. NACHOS WALKTHROUGH - Homework #1 posted: Need to understand this to do project - Overall: For this project, really need to understand nachos.threads.KThread nachos.threads.ThreadedKernel nachos.machine.TCB (not the internals, just the interface) - Only modify nachos.threads package - nothing else You cannot use Java threads or 'synchronized' keyword - Main structure: nachos.machine.* -- the internals of the implementation Not really important to understand how this works, but need to know what the public methods are Machine.interrupt().disable() Disable interrupts, return flag of previous interrupt state Machine.interrupt().enable() Enable interrupts Machine.interrupt().restore() Restore to previously saved flag TCB.contextSwitch() Context switch to this TCB. Used internally by KThread, you don't need to call this yourself but important to see where it's used TCB.start() Used to bootstrap a new TCB - used internally by KThread nachos.threads.* -- what you will be modifying KThread(Runnable target) Create a new kernel thread and associate with it the code in 'target.run()'. KThread.setName(String name) Associate a new, can be retrieved with getName KThread.fork() Fork the given thread - that is, start it running KThread.yield() Cause the current thread to yield the CPU KThread.sleep() Cause the current thread to block - will be woken up later KThread.ready() Move this thread to the ready queue, i.e. wake it up Lock.acquire() Sleep until this lock can be acquired private KThread lockHolder; intStatus = Machine.interrupt().disable(); if (lockHolder == null) { waitQueue.acquire(KThread.currentThread()); lockHolder = KThread.currentThread(); } else { waitQueue.waitForAccess(KThread.currentThread()); KThread.sleep(); } Machine.interrupt.restore(intStatus); Lock.release() Release the lock (Ask class how this would be implemented) int status = Machine.interrupt().disable(); lockHolder = waitQueue.nextThread(); lockHolder.ready(); // Wake it up Machine.interrupt().restore(intStatus);