Slides on threads borrowed by Chase Landon Cox. Thread states BlockedReady Running Thread is scheduled Thread is Pre-empted (or yields) Thread calls Lock.

Slides:



Advertisements
Similar presentations
Synchronization NOTE to instructors: it is helpful to walk through an example such as readers/writers locks for illustrating the use of condition variables.
Advertisements

1 Mutual Exclusion: Primitives and Implementation Considerations.
CS 162 Discussion Section Week 3. Who am I? Mosharaf Chowdhury Office 651 Soda 4-5PM.
CS444/CS544 Operating Systems Synchronization 2/21/2006 Prof. Searleman
CS444/CS544 Operating Systems Synchronization 2/16/2007 Prof. Searleman
Processes CSCI 444/544 Operating Systems Fall 2008.
Review: Process Management Objective: –Enable fair multi-user, multiprocess computing on limited physical resources –Security and efficiency Process: running.
Processes April 5, 2000 Instructor: Gary Kimura Slides courtesy of Hank Levy.
CPS110: Implementing threads/locks on a uni-processor Landon Cox.
1 Process Description and Control Chapter 3 = Why process? = What is a process? = How to represent processes? = How to control processes?
Threads CS 416: Operating Systems Design, Spring 2001 Department of Computer Science Rutgers University
Project Part I Background Tom Roeder CS sp.
U NIVERSITY OF M ASSACHUSETTS, A MHERST Department of Computer Science Emery Berger University of Massachusetts, Amherst Operating Systems CMPSCI 377 Lecture.
Computer Science 162 Discussion Section Week 3. Agenda Project 1 released! Locks, Semaphores, and condition variables Producer-consumer – Example (locks,
Threads and Critical Sections Vivek Pai / Kai Li Princeton University.
CPS110: Deadlock Landon Cox February 5, Concurrency so far  Mostly tried to constrain orderings  Locks, monitors, semaphores  It is possible.
Concurrency Recitation – 2/24 Nisarg Raval Slides by Prof. Landon Cox.
CS510 Concurrent Systems Introduction to Concurrency.
CS1550 Assignment 5 Multiprogramming Implementation notes Matt Craven.
Threads Many software packages are multi-threaded Web browser: one thread display images, another thread retrieves data from the network Word processor:
Implementing Processes and Process Management Brian Bershad.
Operating Systems ECE344 Ashvin Goel ECE University of Toronto Thread Scheduling.
© Janice Regan, CMPT 300, May CMPT 300 Introduction to Operating Systems Mutual Exclusion.
Operating Systems ECE344 Ashvin Goel ECE University of Toronto Mutual Exclusion.
COSC 3407: Operating Systems Lecture 7: Implementing Mutual Exclusion.
11/18/20151 Operating Systems Design (CS 423) Elsa L Gunter 2112 SC, UIUC Based on slides by Roy Campbell, Sam.
11/21/20151 Operating Systems Design (CS 423) Elsa L Gunter 2112 SC, UIUC Based on slides by Roy Campbell, Sam.
CPS110: Implementing threads Landon Cox. Recap and looking ahead Hardware OS Applications Where we’ve been Where we’re going.
Thread Implementations; MUTEX Reference on thread implementation –text: Tanenbaum ch. 2.2 Reference on mutual exclusion (MUTEX) –text: Tanenbaum ch
1 Computer Systems II Introduction to Processes. 2 First Two Major Computer System Evolution Steps Led to the idea of multiprogramming (multiple concurrent.
Introduction to Operating Systems and Concurrency.
Operating Systems CSE 411 CPU Management Sept Lecture 10 Instructor: Bhuvan Urgaonkar.
CS399 New Beginnings Jonathan Walpole. 2 Concurrent Programming & Synchronization Primitives.
CSCI-375 Operating Systems Lecture Note: Many slides and/or pictures in the following are adapted from: slides ©2005 Silberschatz, Galvin, and Gagne Some.
U NIVERSITY OF M ASSACHUSETTS A MHERST Department of Computer Science Computer Systems Principles Synchronization Emery Berger and Mark Corner University.
CPS110: Thread cooperation Landon Cox. Constraining concurrency  Synchronization  Controlling thread interleavings  Some events are independent  No.
Review: threads and processes Landon Cox January 20, 2016.
Implementing Lock. From the Previous Lecture  The “too much milk” example shows that writing concurrent programs directly with load and store instructions.
1 Critical Section Problem CIS 450 Winter 2003 Professor Jinhua Guo.
Concurrency: Locks and synchronization Slides by Prof. Cox.
Implementing Mutual Exclusion Andy Wang Operating Systems COP 4610 / CGS 5765.
CPS110: Implementing threads on a uni-processor Landon Cox January 29, 2008.
Scheduler activations Landon Cox March 23, What is a process? Informal A program in execution Running code + things it can read/write Process ≠
December 1, 2006©2006 Craig Zilles1 Threads & Atomic Operations in Hardware  Previously, we introduced multi-core parallelism & cache coherence —Today.
CSE 120 Principles of Operating
Multi-processor Scheduling
CS 6560: Operating Systems Design
CS703 – Advanced Operating Systems
CSCS 511 Operating Systems Ch3, 4 (Part B) Further Understanding Threads Dr. Frank Li Skipped sections 3.5 & 3.6 
Scheduler activations
Lecture Topics: 11/1 Processes Process Management
Review: threads and synchronization
CSCI 511 Operating Systems Ch3, 4 (Part B) Further Understanding Threads Dr. Frank Li Skipped sections 3.5 & 3.6 
Jonathan Walpole Computer Science Portland State University
Thread Implementation Issues
Lecture Topics: 11/1 General Operating System Concepts Processes
Processes Hank Levy 1.
Implementing Mutual Exclusion
Processes and Process Management
Slides by Prof. Landon Cox and Vamsi Thummala
Implementing Mutual Exclusion
CSE 451: Operating Systems Autumn 2003 Lecture 7 Synchronization
CSE 451: Operating Systems Autumn 2005 Lecture 7 Synchronization
CSE 451: Operating Systems Winter 2003 Lecture 7 Synchronization
CSE 153 Design of Operating Systems Winter 19
CS333 Intro to Operating Systems
Processes Hank Levy 1.
CS703 – Advanced Operating Systems
EECE.4810/EECE.5730 Operating Systems
CPS110: Thread cooperation
Presentation transcript:

Slides on threads borrowed by Chase Landon Cox

Thread states BlockedReady Running Thread is scheduled Thread is Pre-empted (or yields) Thread calls Lock or wait (or makes I/O request) Another thread calls unlock or signal (or I/O completes) ?

Project 1 (test cases) Remember to work on your thread library test suite too For each of your test cases No inputs (e.g., no input files, no command-line args) All output to stdout (i.e., use cout) Do not call start_preemption() Diff output of test case w/ your library vs. w/ thread.o Writing test cases Read through spec Write test cases to stress each required part E.g., lock() blocks if lock is already held Pre-emption is off, so use yield() to create the right interleavings Read through your code Write test cases to exercise all lines of code E.g. each clause of an if/else statement Micro-tests are better for debugging than macro-tests

Project 1 (garbage collection) Garbage collecting threads Do not want to run out of memory What needs to be (C++) “deleted”? Any state associated with the thread (e.g., stack, TCB) // simple network server while (1) { int s = socket.accept (); thread_create (handle_request, s); }

Project 1 (garbage collection) Two key questions: When can a stack be deleted and by whom? When can a stack be deleted? Only after the thread has finished its “work” Work = function passed to thread_create, thread_libinit Who definitely cannot delete a thread’s stack? The thread itself! Try deleting the stack you are running on … So which thread should delete the stack?

Project 1 (garbage collection) Hint: don’t use uc_link Only use set/swapcontext to jump to threads Anyone want to guess why? What can you say about state of interrupts? Interrupts are enabled inside “work” function After it exits, interrupts must be disabled Tricky to guarantee this using uc_link Can get an interrupt while switching to uc_link

Project 1 (garbage collection) What makes swapcontext simpler? uc_link loads threads outside of your lib Calls to swapcontext are explicit “Keep everything in front of you” Any other Project 1 questions?

Using interrupt disable-enable Disable-enable on a uni-processor Assume atomic (can use atomic load/store) How do threads get switched out (2 ways)? Internal events (yield, I/O request) External events (interrupts, e.g., timers) Easy to prevent internal events Use disable/enable to prevent external events

Disable interrupts, busy-waiting Lock implementation #1 lock () { disable interrupts while (value != FREE) { enable interrupts disable interrupts } value = BUSY enable interrupts } unlock () { disable interrupts value = FREE enable interrupts } Why is it ok for lock code to disable interrupts? It’s in the trusted kernel (we have to trust something).

Disable interrupts, busy-waiting Lock implementation #1 lock () { disable interrupts while (value != FREE) { enable interrupts disable interrupts } value = BUSY enable interrupts } unlock () { disable interrupts value = FREE enable interrupts } Do we need to disable interrupts in unlock? Only if “value = FREE” is multiple instructions (safer)

Disable interrupts, busy-waiting Lock implementation #1 lock () { disable interrupts while (value != FREE) { enable interrupts disable interrupts } value = BUSY enable interrupts } unlock () { disable interrupts value = FREE enable interrupts } Why enable-disable in lock loop body? Otherwise, no one else will run (including unlockers)

Using read-modify-write instructions Disabling interrupts Ok for uni-processor, breaks on multi-processor Why? Could use atomic load-store to make a lock Inefficient, lots of busy-waiting Hardware people to the rescue!

Using read-modify-write instructions Most modern processor architectures Provide an atomic read-modify-write instruction Atomically Read value from memory into register Write new value to memory Implementation details Lock memory location at the memory controller

Interrupt disable, no busy-waiting Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts }

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } Who gets the lock after someone calls unlock? This is called a “hand- off” lock.

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } Who might get the lock if it weren’t handed-off directly? (i.e., if value weren’t set BUSY in unlock) This is called a “hand- off” lock.

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } What kind of ordering of lock acquisition guarantees does the hand-off lock provide? This is called a “hand- off” lock.

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } What does this mean? Are we saving the PC? This is called a “hand- off” lock.

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { lockqueue.push(&current_thread->ucontext); swapcontext(&current_thread->ucontext, &new_thread->ucontext)); } enable interrupts } No, just adding a pointer to the TCB/context. This is called a “hand- off” lock. unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts }

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } Why a separate queue for the lock? This is called a “hand- off” lock.

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } Project 1 note: you must guarantee FIFO ordering of lock operations This is called a “hand- off” lock.

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { enable interrupts add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } When and how could this fail?

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock enable interrupts switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } When and how could this fail? 1 2 3

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } Putting lock thread on lock wait queue, switch must be atomic. Must call switch with interrupts off.

How is switch returned to? Think of switch as three phases 1.Save current location (SP, PC) 2.Point processor to another stack (SP’) 3.Jump to another instruction (PC’) Only way to get back to a switch Have another thread call switch

Lock implementation #3 lock () { disable interrupts if (value == FREE) { value = BUSY // lock acquire } else { add thread to queue of threads waiting for lock switch to next ready thread // don’t add to ready queue } enable interrupts } unlock () { disable interrupts value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } enable interrupts } What is lock() assuming about the state of interrupts after switch returns?

Interrupts and returning to switch Lock() can assume that switch Is always called with interrupts disabled On return from switch Previous thread must have disabled interrupts Next thread to run Becomes responsible for re-enabling interrupts Invariants: threads promise to Disable interrupts before switch is called Re-enable interrupts after returning from switch

Thread AThread B yield () { disable interrupts … switch (B->A) enable interrupts } // exit thread library lock () { disable interrupts … switch (A->B) back from switch (B->A) … enable interrupts } // exit yield unlock () // moves A to ready queue yield () { disable interrupts … switch (B->A) back from switch (A->B) … enable interrupts } // exit lock B holds lock

Test&set, minimal busy-waiting Lock implementation #4 lock () { while (test&set (guard)) {} // like interrupt disable if (value == FREE) { value = BUSY } else { put on queue of threads waiting for lock switch to another thread // don’t add to ready queue } guard = 0 // like interrupt enable } unlock () { while (test&set (guard)) {} // like interrupt disable value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } guard = 0 // like interrupt enable }

Lock implementation #4 lock () { while (test&set (guard)) {} // like interrupt disable if (value == FREE) { value = BUSY } else { put on queue of threads waiting for lock switch to another thread // don’t add to ready queue } guard = 0 // like interrupt enable } unlock () { while (test&set (guard)) {} // like interrupt disable value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } guard = 0 // like interrupt enable } Why is this better than only spinning with test&set? Only busy- wait while another thread is in lock or unlock Before, we busy-waited while lock was held

Lock implementation #4 lock () { while (test&set (guard)) {} // like interrupt disable if (value == FREE) { value = BUSY } else { put on queue of threads waiting for lock switch to another thread // don’t add to ready queue } guard = 0 // like interrupt enable } unlock () { while (test&set (guard)) {} // like interrupt disable value = FREE if anyone on queue of threads waiting for lock { take waiting thread off queue, put on ready queue value = BUSY } guard = 0 // like interrupt enable } What is the switch invariant? Threads promise to call switch with guard set to 1.

const1=1 const2=0 main Example stack tmp=1 RA=0x804838c A RA=0x B const=0 RA=0x C tmp=0 RA=0x A 0xfffffff 0x0 Memory void C () { A (0); } void B () { C (); } void A (int tmp){ if (tmp) B (); } int main () { A (1); return 0; } 0x x x x804838c Code Stack … SP

Creating a new thread Also called “forking” a thread Idea: create initial state, put on ready queue 1. Allocate, initialize a new TCB 2. Allocate a new stack 3. Make it look like thread was going to call a function PC points to first instruction in function SP points to new stack Stack contains arguments passed to function Project 1: use makecontext 4. Add thread to ready queue

Example: thread-safe queue dequeue () { lock (qLock); element = NULL; // if queue non-empty if (head != NULL) { element = head; head = head->next; element->next = NULL; } unlock (qLock); return element; } enqueue () { lock (qLock) // ptr is private // head is shared new_element = new node(); if (head == NULL) { head = new_element; new_element->next=0; } else { node *ptr; // find queue tail for (ptr=head; ptr->next!=NULL; ptr=ptr->next){} ptr->next=new_element; } unlock(qLock); } What can go wrong? (1) 2 elements in queue, ptr->next is non-null, switch to other thread (2) previous head->next set to null (3) ptr->next now null, set ptr to null (4) dereference null pntr

Thread-safe queue Can enqueue unlock anywhere? No Must leave shared data In a consistent/sane state Data invariant “consistent/sane state” “always” true enqueue () { lock (qLock) // ptr is private // head is shared new_element = new node(); if (head == NULL) { head = new_element; } else { node *ptr; // find queue tail for (ptr=head; ptr->next!=NULL; ptr=ptr->next){} ptr->next=new_element; } unlock(qLock); // safe? new_element->next=0; } Another thread can call enqueue

How about this? I’m always holding a lock while accessing shared state. enqueue () { lock (qLock) // ptr is private // head is shared new_element = new node(); if (head == NULL) { head = new_element; } else { node *ptr; // find queue tail for (ptr=head; ptr->next!=NULL; ptr=ptr->next){} unlock(qLock); lock(qLock); ptr->next=new_element; } new_element->next=0; unlock(qLock); } ptr may not point to tail after lock/unlock. Lesson: Thinking about individual accesses is not enough Must reason about dependencies between accesses