Lecture 8 - More on Synchronization
More on Assignment 2 (Review)
How do you load an save registers? Use swap_rfiles()
. It takes two FILE*
's for the structs of the registers and stacks and swaps their contents.
Think about it this way. lwp_yeild()
takes two parameters; the function call func_ptr*
and it's arguments void*
. The process between doing the context switch should be:
// <load regs, via swap_rfiles()
leave // move %rbp, %rsp; popq %rbp
ret // popq %rip
It is VERY IMPORTANT that swap_rfiles()
itself calls leave
and ret
so that it's adheres to the C standard:
swap_rfiles:
pushq %rbp // push base pointer
movq %rsp, %rbp // move stack pointer to base
From Last Time
Remember from Lecture 7 - Thread Schedulers, Race Conditions that software-based lock variables don't work. We discussed solutions that were software based. Let's look at some hardware versions.
Hardware Lock Variables
We'll create an atomic test and mutate. How do we do that? Have the CPEs do that (uhh wait that's me)! Assume that there's a test-and-set lock (TSL) that will read a memory location and set it to 1, such that this is not interruptable:
enter: tsl r1, lock # Continually sets our lock to 1 until it sets
cmp r1, #0
jne enter
ret
leave: mov lock, #0 # Writes 0 into lock and returns
ret
The advantage of this approach is that it works:
- It's fine grained, and can be tuned well.
The problem here is that the enter
is still doing a busy-waiting solution (it just keeps looping, rather than trying to maybe do something else). What we'd rather have is to have it sleep, or suspend its operation to another task, until it's ready.
Non-Busy Waiting Solutions
We're going to make 2 operations:
sleep()
: goes to sleepwakeup()
: poke a sleeping process, asking if it's ready.
This is all about the problems of the Producer-Consumer Problem. The idea is that there's a fixed buffer for there to be inputs and outputs:
Producers have to wait until the buffer is empty. The Consumers have to wait until there's something in the buffer.
Here's a potential solution:
#define N 100
int count = 0;
producer:
for(;;)
{
produce();
if(count == N)
sleep();
insert_item();
count++;
if(count == 1)
wakeup(consumer);
}
consumer:
while(true)
{
if(count == 0)
sleep();
remove_item();
count--;
if(count == N-1)
wakeup(producer);
consume();
}
These two will dance back and forth, doing a lot of producing, then a lot of consuming, ...
The advantage is that this is fine-grained, and is not busywaiting. But the problem is that it doesn't work!
The problem is that if the producer gets interrupted before going to sleep()
, then the consumer will consumer will try to wakeup the producer, who isn't asleep.
We have the same problem where the if(count == ...) sleep()
is two steps that just cannot be brought together. Next time we'll talk about a solution to this problem.