5. Avoiding list race
Lock list_lock; // one per list
insert(int data){
List *l = new List; insert(int data){
l->data = data; List *l = new List;
l->data = data;
acquire(&list_lock);
l->next = list ; // A
list = l; // B l->next = list ; // A critical section, or
list = l; // B atomic section
} release(&list_lock);
}
● multiple processes adding to the disk queue
6. Race Condition
Time
A B
CPU1
l->next list
Memory
l->next list
CPU2
A B
7. Implementing lock and release
● use atomic instructions (e.g., xchg)
For example, the x86 <tt>xchg %eax, addr</tt> instruction
does the following:
freeze other CPUs' memory activity for address addr
temp := *addr
*addr := %eax
%eax = temp
un-freeze other memory activity
8. x86 asm to implement a spinlock
lock: ; The lock variable. 1 = locked, 0 = unlocked.
dd 0
spin_lock:
mov eax, 1
xchg eax, [lock] ; Atomically swap EAX register with lock variable.
test eax, eax
jnz spin_lock
ret
spin_unlock:
mov eax, 0 ; Set the EAX register to 0.
xchg eax, [lock] ; Atomically swap EAX register with lock variable.
ret ; The lock has been released.
9. Design Issues - locks and interrupts
● ide disk generates interrupt when disk
operation completes
● interrupt causes ideintr to run
● ideintr acquires lock?
deadlock?
give interrupt handler lock? (recursive
locks)
● xv6: critical sections have no interrupts
turned on
10. Design Issues - locks and modularity
move(l1, l2) { move(l1, l2) { move(l1, l2) {
acquire(l1.lock); acquire(l1.lock);
acquire(l2.lock); e = del(l1)
e = del(l1) release(l1.lock)
e = del(l1)
insert(l2, e) acquire(l2.lock);
insert(l2, e)
release(l1.lock) insert(l2, e)
release(l2.lock) release(l2.lock)
} }
}
original recursive Modified
● recursive locks are a bad idea
ideintr should certainly not use that instead of disabling interrupts!
12. Goals for solution
● Switching transparent to user threads
● User thread cannot hog a processor
13. Code - Concurrency
- plock held across switch; why?
yield: p is set runnable, p must complete
switch before another scheduler chooses p
- hard to reason about; coroutine style helps
- can two schedulers select the same runnable
process?
- why does scheduler release after loop, and
re-acquire it immediately? (run with interrupts!)
14. Code - Thread clean up
● let's look at kill: can we clean up killed
process? (no: it might be running,
holding locks etc.) before returning to user
space: process kills itself by calling exit
● let's look at exit; can thread delete its stack?
(no: it has to switch off it!)
● wait() does the cleanup
15. ● Locking
● Scheduling
● Coordination and processes
Required reading: remainder of proc.c, sys_wait, sys_exit, and sys_kill.
17. Big picture
Multiple threads executing in the kernel and
sharing memory, devices and various data
structures.
● Allow more than one process to be
executing to take advantage of multiple
CPUs
● Allow system calls that block waiting for I/O.
18. producer consumer queue
struct pcq {
Need lock for pcq->ptr if multiple
void *ptr;
processors
};
void*
pcqread(struct pcq *q){
while((p=q->ptr)==0);
q->ptr = 0;
return p;
}
Waste CPU Time. Instead of polling,
pcqwrite(struct pcq *q, void *p) Use event-based (sleep and wakeup)
{
while(q->ptr != 0);
q->ptr = p;
}