Title: CPS110: Implementing threads/locks on a uni-processor
1CPS110 Implementing threads/locks on a
uni-processor
2Recall, thread interactions
- Threads can access shared data
- Use locks, monitors, and semaphores
- What weve done so far
- Threads also share hardware
- CPU (uni-processor)
- Memory
3Private vs global thread state
- What state is private to each thread?
- Code (like lines of a play)
- PC (where actor is in his/her script)
- Stack, SP (actors mindset)
- What state is shared?
- Global variables, heap
- (props on set)
4Thread control block (TCB)
- Running threads private data is on CPU
- Non-running threads private state is in TCB
- Thread control block (TCB)
- Container for non-running threads private data
- (PC, code, SP, stack, registers)
5Thread control block
Address Space
TCB1 PC SP registers
TCB2 PC SP registers
TCB3 PC SP registers
Ready queue
Code
Code
Code
Stack
Stack
Stack
CPU
Thread 1 running
PC SP registers
6Thread control block
Address Space
TCB2 PC SP registers
TCB3 PC SP registers
Ready queue
Code
Stack
Stack
Stack
CPU
Thread 1 running
PC SP registers
7Thread states
- Running
- Currently using the CPU
- Ready
- Ready to run, but waiting for the CPU
- Blocked
- Stuck in lock (), wait () or down ()
8Switching threads
- What needs to happen to switch threads?
- Thread returns control to OS
- For example, via the yield call
- OS chooses next thread to run
- OS saves state of current thread
- To its thread control block
- OS loads context of next thread
- From its thread control block
- Run the next thread
Project 1 swapcontext
91. Thread returns control to OS
- How does the thread system get control?
- Could wait for thread to give up control
- (internal events)
- Thread might block inside lock or wait
- Thread might call into kernel for service
- (system call)
- Thread might call yield
- Are internal events enough?
101. Thread returns control to OS
- External events
- (events not initiated by the thread)
- Hardware interrupts
- Transfer control directly to OS interrupt
handlers - From 104
- CPU checks for interrupts while executing
- Jumps to OS code with interrupt mask set
- Interrupts lead to pre-emption (a forced yield)
- Common interrupt timer interrupt
112. Choosing the next thread
- If no ready threads, just spin
- Modern CPUs execute a halt instruction
- Project 1 exit if no ready threads
- Loop switches to thread if one is ready
- Many ways to prioritize ready threads
- Will discuss a little later in the semester
123. Saving state of current thread
- What needs to be saved?
- Registers, PC, SP
- What makes this tricky?
- Need registers to save state
- But youre trying to save all the registers
- Saving the PC
134. OS loads the next thread
- Where is the threads state/context?
- Thread control block (in memory)
- How to load the registers?
- Use load instructions to grab from memory
- How to load the stack?
- Stack is already in memory, load SP
145. OS runs the next thread
- How to resume threads execution?
- Jump to the saved PC
- On whose stack are these steps running?
- or Who is running these steps?
- The thread that called yield
- (or was interrupted or called lock/wait)
- How does this thread run again?
- Some other thread must switch to it
15Example thread switching
Thread 1 print start thread 1 yield ()
print end thread 1 Thread 2 print start
thread 2 yield () print end thread 2 yield
() print start yield (thread d) switch to
next thread (swapcontext) print end yield
(thread d)
Thread 1 output Thread 2 output -------------
------------------------------- start thread
1 start yield (thread 1) start thread 2
start yield (thread 2) end yield (thread
1) end thread 1 end yield (thread 2)
end thread 2
Note this assumes no pre-emptions. Programmers
must assume pre-emptions, so this output is not
guaranteed.
16Creating a new thread
- Also called forking a thread
- Idea create initial state, put on ready queue
- Allocate, initialize a new TCB
- Allocate a new stack
- 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
- Add thread to ready queue
17Using interrupt disable-enable
- Disable-enable on a uni-processor
- Assume atomic (can use atomic load/store)
- How do threads get switched out?
- Internal events (yield, I/O request)
- External events (interrupts, e.g. timers)
- Easy to prevent internal events
- Use disable-enable to prevent external events
18Why use locks?
- If we have disable-enable, why do we need locks?
- Program could bracket critical sections with
disable-enable - Might not be able to give control back to thread
library - Cant have multiple locks (over-constrains
concurrency) - Project 1 only disable interrupts in thread
library
disable interrupts while (1)
19Lock implementation 1
- Disable interrupts, busy-waiting
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?
Its in the trusted kernel (we have to trust
something).
20Lock implementation 1
- Disable interrupts, busy-waiting
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)
21Lock implementation 1
- Disable interrupts, busy-waiting
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)
22Using 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!
23Lock implementation 2
- Testset, busy-waiting
- Initially, value 0
unlock () value 0
lock () while (testset(value) 1)
What happens if value 1?
What happens if value 0?
24Locks and busy-waiting
- All implementations have used busy-waiting
- Wastes CPU cycles
- To reduce busy-waiting, integrate
- Lock implementation
- Thread dispatcher data structures
25Lock implementation 3
- Interrupt disable, no busy-waiting
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 // dont 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
26Lock 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 // dont add
to ready queue enable interrupts
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
Who gets the lock after someone calls unlock?
27Lock 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 // dont add
to ready queue enable interrupts
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
Who might get the lock if it werent handed-off
directly? (e.g. if value werent set BUSY in
unlock)
28Lock 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 // dont add
to ready queue enable interrupts
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
What does this mean? Are we saving the PC?
29Lock 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 // dont add
to ready queue enable interrupts
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
Why a separate queue for the lock?
30Lock 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 // dont add
to ready queue enable interrupts
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
Project 1 note you must guarantee FIFO ordering
of lock acquisition.
31Lock 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 // dont 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?
32Lock 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 // dont 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?
33Lock 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 // dont 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.
34How is switch returned to?
- Review from last time
- Think of switch as three phases
- Save current location (SP, PC)
- Point processor to another stack (SP)
- Jump to another instruction (PC)
- Only way to get back to a switch
- Have another thread call switch
35Lock 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 // dont 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?
36Interrupts 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
37Thread A Thread B yield () disable
interrupts switch enable
interrupts // exit thread library
function ltuser codegt lock () disable
interrupts switch back from
switch enable interrupts //
exit yield ltuser codegt unlock () // moves
A to ready queue yield () disable
interrupts switch back from
switch enable interrupts // exit
lock ltuser codegt