Lab 4: If you liked it, then you should have put a
“lock” on it
Advanced Operating Systems

Zubair Nabi
zubair.nabi@itu.ed...
Concurrency within the OS

1

Multiple CPUs share kernel data structures
• Can interfere with each other leading to incons...
Concurrency within the OS

1

Multiple CPUs share kernel data structures
• Can interfere with each other leading to incons...
Concurrency within the OS

1

Multiple CPUs share kernel data structures
• Can interfere with each other leading to incons...
Race conditions: Example

• Several processors share a single disk
• Disk driver has a single linked list idequeue of outs...
Race conditions: Example

• Several processors share a single disk
• Disk driver has a single linked list idequeue of outs...
Race conditions: Example

• Several processors share a single disk
• Disk driver has a single linked list idequeue of outs...
Race conditions (2): Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

struct list {
int data;
struct list ∗ next;
};
struct li...
Race conditions (3): Problem

• Possible race condition on line 13 and 14
• In case of two concurrent insertions, the first...
Race conditions (3): Problem

• Possible race condition on line 13 and 14
• In case of two concurrent insertions, the first...
Race conditions (4): Solution
1
2
3
4
5
6
7
8
9
10
11
12
13

struct list ∗ list = 0;
struct lock listlock ;
void insert (i...
Lock representation

1
2
3

struct spinlock {
uint locked ;
};
Acquire lock

1
2
3
4
5
6
7
8
9

void acquire ( struct spinlock ∗ lk)
{
for (;;) {
if(!lk −>locked ) {
lk −>locked = 1;
br...
Acquiring lock (2): Problem

• Possible race condition on line 4 and 5
• So the solution itself causes a race condition!
Acquiring lock (2): Problem

• Possible race condition on line 4 and 5
• So the solution itself causes a race condition!
Hardware support

1
2
3
4
5
6
7
8
9
10

static inline uint
xchg( volatile uint ∗ addr , uint newval )
{
uint result ;
asm ...
Atomic acquire lock

1
2
3
4
5
6
7
8
9
10

void acquire ( struct spinlock ∗ lk)
{
pushcli (); // disable interrupts .
if( ...
Atomic release lock

1
2
3
4
5
6
7
8
9

void release ( struct spinlock ∗ lk)
{
if(! holding (lk))
panic (" release ");
xch...
Recursive

• What happens if a callee tries to acquire a lock held by its caller?
• Solution: recursive locks
• But they d...
Recursive

• What happens if a callee tries to acquire a lock held by its caller?
• Solution: recursive locks
• But they d...
Recursive

• What happens if a callee tries to acquire a lock held by its caller?
• Solution: recursive locks
• But they d...
Recursive

• What happens if a callee tries to acquire a lock held by its caller?
• Solution: recursive locks
• But they d...
Lock usage example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

void iderw ( struct buf ∗ b)
{
struct buf ∗ ∗ pp;
acquire (& idelo...
When to use locks

• To use:
1
2

A variable can concurrently be written to by multiple CPUs
An invariant spans multiple d...
When to use locks

• To use:
1
2

A variable can concurrently be written to by multiple CPUs
An invariant spans multiple d...
When to use locks

• To use:
1
2

A variable can concurrently be written to by multiple CPUs
An invariant spans multiple d...
When to use locks

• To use:
1
2

A variable can concurrently be written to by multiple CPUs
An invariant spans multiple d...
When to use locks

• To use:
1
2

A variable can concurrently be written to by multiple CPUs
An invariant spans multiple d...
When to use locks

• To use:
1
2

A variable can concurrently be written to by multiple CPUs
An invariant spans multiple d...
When to use locks

• To use:
1
2

A variable can concurrently be written to by multiple CPUs
An invariant spans multiple d...
Lock ordering

• If code path x needs locks in the order A and B while y needs
them in order B and A, could there be a pro...
Lock ordering

• If code path x needs locks in the order A and B while y needs
them in order B and A, could there be a pro...
Interrupt handlers

• Locks are also used to synchronize access between interrupt
handlers and non-interrupt code
Interrupt handlers (2): Example

1
2
3
4
5
6
7
8
9

T_IRQ0 + IRQ_TIMER :
if(cpu −>id == 0){
acquire (& tickslock );
ticks ...
Interrupt handlers (3): Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

void sys_sleep (void ){
int n;
uint ticks0 ;
if( argi...
Interrupt handlers

• Interrupts lead to concurrency problems on uniprocessors too
• Disable interrupts before acquiring a...
Interrupt handlers

• Interrupts lead to concurrency problems on uniprocessors too
• Disable interrupts before acquiring a...
Interrupt handlers

• Interrupts lead to concurrency problems on uniprocessors too
• Disable interrupts before acquiring a...
Interrupt handlers

• Interrupts lead to concurrency problems on uniprocessors too
• Disable interrupts before acquiring a...
Today’s Task
• xv6 processes only have a single thread so processes do not
share any memory
• But it is definitely possible...
Today’s Task
• xv6 processes only have a single thread so processes do not
share any memory
• But it is definitely possible...
Today’s Task
• xv6 processes only have a single thread so processes do not
share any memory
• But it is definitely possible...
Today’s Task
• xv6 processes only have a single thread so processes do not
share any memory
• But it is definitely possible...
Today’s Task
• xv6 processes only have a single thread so processes do not
share any memory
• But it is definitely possible...
Today’s Task
• xv6 processes only have a single thread so processes do not
share any memory
• But it is definitely possible...
Today’s Task
• xv6 processes only have a single thread so processes do not
share any memory
• But it is definitely possible...
Today’s Task (2)

• Design a thread-safe library (in pseudo code) for xv6 that uses
these existing primitives, to implemen...
Reading

• Chapter 4 from “xv6: a simple, Unix-like teaching operating
system”
• Timothy L. Harris. 2001. A Pragmatic Impl...
Upcoming SlideShare
Loading in …5
×

AOS Lab 4: If you liked it, then you should have put a “lock” on it

404 views
319 views

Published on

Published in: Technology, Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
404
On SlideShare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
12
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

AOS Lab 4: If you liked it, then you should have put a “lock” on it

  1. 1. Lab 4: If you liked it, then you should have put a “lock” on it Advanced Operating Systems Zubair Nabi zubair.nabi@itu.edu.pk February 20, 2013
  2. 2. Concurrency within the OS 1 Multiple CPUs share kernel data structures • Can interfere with each other leading to inconsistencies 2 Even on uniprocessors, interrupt handlers can interfere with non-interrupt code 3 xv6 uses locks for both situations
  3. 3. Concurrency within the OS 1 Multiple CPUs share kernel data structures • Can interfere with each other leading to inconsistencies 2 Even on uniprocessors, interrupt handlers can interfere with non-interrupt code 3 xv6 uses locks for both situations
  4. 4. Concurrency within the OS 1 Multiple CPUs share kernel data structures • Can interfere with each other leading to inconsistencies 2 Even on uniprocessors, interrupt handlers can interfere with non-interrupt code 3 xv6 uses locks for both situations
  5. 5. Race conditions: Example • Several processors share a single disk • Disk driver has a single linked list idequeue of outstanding disk requests • Processors concurrently make a call to iderw which adds a request to the list
  6. 6. Race conditions: Example • Several processors share a single disk • Disk driver has a single linked list idequeue of outstanding disk requests • Processors concurrently make a call to iderw which adds a request to the list
  7. 7. Race conditions: Example • Several processors share a single disk • Disk driver has a single linked list idequeue of outstanding disk requests • Processors concurrently make a call to iderw which adds a request to the list
  8. 8. Race conditions (2): Example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct list { int data; struct list ∗ next; }; struct list ∗ list = 0; void insert (int data) { struct list ∗ l; l = malloc ( sizeof ∗ l); l −>data = data; l −>next = list; list = l; }
  9. 9. Race conditions (3): Problem • Possible race condition on line 13 and 14 • In case of two concurrent insertions, the first one will be lost
  10. 10. Race conditions (3): Problem • Possible race condition on line 13 and 14 • In case of two concurrent insertions, the first one will be lost
  11. 11. Race conditions (4): Solution 1 2 3 4 5 6 7 8 9 10 11 12 13 struct list ∗ list = 0; struct lock listlock ; void insert (int data) { struct list ∗ l; acquire (& listlock ); l = malloc ( sizeof ∗ l); l −>data = data; l −>next = list; list = l; release (& listlock ); }
  12. 12. Lock representation 1 2 3 struct spinlock { uint locked ; };
  13. 13. Acquire lock 1 2 3 4 5 6 7 8 9 void acquire ( struct spinlock ∗ lk) { for (;;) { if(!lk −>locked ) { lk −>locked = 1; break ; } } }
  14. 14. Acquiring lock (2): Problem • Possible race condition on line 4 and 5 • So the solution itself causes a race condition!
  15. 15. Acquiring lock (2): Problem • Possible race condition on line 4 and 5 • So the solution itself causes a race condition!
  16. 16. Hardware support 1 2 3 4 5 6 7 8 9 10 static inline uint xchg( volatile uint ∗ addr , uint newval ) { uint result ; asm volatile ("lock; xchgl %0, %1" : "+m" ( ∗ addr), "=a" ( result ) : "1" ( newval ) : "cc"); }
  17. 17. Atomic acquire lock 1 2 3 4 5 6 7 8 9 10 void acquire ( struct spinlock ∗ lk) { pushcli (); // disable interrupts . if( holding (lk)) panic (" acquire "); while (xchg (&lk −>locked , 1) != 0) ; }
  18. 18. Atomic release lock 1 2 3 4 5 6 7 8 9 void release ( struct spinlock ∗ lk) { if(! holding (lk)) panic (" release "); xchg (&lk −>locked , 0); popcli (); }
  19. 19. Recursive • What happens if a callee tries to acquire a lock held by its caller? • Solution: recursive locks • But they do not ensure mutual exclusion between the caller and the callee • Pass the buck to the programmer
  20. 20. Recursive • What happens if a callee tries to acquire a lock held by its caller? • Solution: recursive locks • But they do not ensure mutual exclusion between the caller and the callee • Pass the buck to the programmer
  21. 21. Recursive • What happens if a callee tries to acquire a lock held by its caller? • Solution: recursive locks • But they do not ensure mutual exclusion between the caller and the callee • Pass the buck to the programmer
  22. 22. Recursive • What happens if a callee tries to acquire a lock held by its caller? • Solution: recursive locks • But they do not ensure mutual exclusion between the caller and the callee • Pass the buck to the programmer
  23. 23. Lock usage example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void iderw ( struct buf ∗ b) { struct buf ∗ ∗ pp; acquire (& idelock ); b −>qnext = 0; for(pp =& idequeue ; ∗ pp; pp =&( ∗ pp)−>qnext) ; ∗ pp = b; // Wait for request to finish . while ((b −>flags & ( B_VALID | B_DIRTY )) != B_VALID ){ sleep (b, & idelock ); } release (& idelock ); }
  24. 24. When to use locks • To use: 1 2 A variable can concurrently be written to by multiple CPUs An invariant spans multiple data structures • Possible to protect the kernel through a “giant kernel lock” • Problem(s)? • Reminiscent of the GIL in Python • Possible to have fine-grained locks
  25. 25. When to use locks • To use: 1 2 A variable can concurrently be written to by multiple CPUs An invariant spans multiple data structures • Possible to protect the kernel through a “giant kernel lock” • Problem(s)? • Reminiscent of the GIL in Python • Possible to have fine-grained locks
  26. 26. When to use locks • To use: 1 2 A variable can concurrently be written to by multiple CPUs An invariant spans multiple data structures • Possible to protect the kernel through a “giant kernel lock” • Problem(s)? • Reminiscent of the GIL in Python • Possible to have fine-grained locks
  27. 27. When to use locks • To use: 1 2 A variable can concurrently be written to by multiple CPUs An invariant spans multiple data structures • Possible to protect the kernel through a “giant kernel lock” • Problem(s)? • Reminiscent of the GIL in Python • Possible to have fine-grained locks
  28. 28. When to use locks • To use: 1 2 A variable can concurrently be written to by multiple CPUs An invariant spans multiple data structures • Possible to protect the kernel through a “giant kernel lock” • Problem(s)? • Reminiscent of the GIL in Python • Possible to have fine-grained locks
  29. 29. When to use locks • To use: 1 2 A variable can concurrently be written to by multiple CPUs An invariant spans multiple data structures • Possible to protect the kernel through a “giant kernel lock” • Problem(s)? • Reminiscent of the GIL in Python • Possible to have fine-grained locks
  30. 30. When to use locks • To use: 1 2 A variable can concurrently be written to by multiple CPUs An invariant spans multiple data structures • Possible to protect the kernel through a “giant kernel lock” • Problem(s)? • Reminiscent of the GIL in Python • Possible to have fine-grained locks
  31. 31. Lock ordering • If code path x needs locks in the order A and B while y needs them in order B and A, could there be a problem? • Need to ensure that all code paths acquire locks in the same order
  32. 32. Lock ordering • If code path x needs locks in the order A and B while y needs them in order B and A, could there be a problem? • Need to ensure that all code paths acquire locks in the same order
  33. 33. Interrupt handlers • Locks are also used to synchronize access between interrupt handlers and non-interrupt code
  34. 34. Interrupt handlers (2): Example 1 2 3 4 5 6 7 8 9 T_IRQ0 + IRQ_TIMER : if(cpu −>id == 0){ acquire (& tickslock ); ticks ++; wakeup (& ticks ); release (& tickslock ); } lapiceoi (); break ;
  35. 35. Interrupt handlers (3): Example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void sys_sleep (void ){ int n; uint ticks0 ; if( argint (0, &n) < 0) return −1; acquire (& tickslock ); ticks0 = ticks; while ( ticks − ticks0 < n){ if(proc −>killed ){ release (& tickslock ); return −1; } sleep (& ticks , & tickslock ); } release (& tickslock );
  36. 36. Interrupt handlers • Interrupts lead to concurrency problems on uniprocessors too • Disable interrupts before acquiring a lock: pushcli() • Re-enable on release: popcli() • Avoids recursive locks
  37. 37. Interrupt handlers • Interrupts lead to concurrency problems on uniprocessors too • Disable interrupts before acquiring a lock: pushcli() • Re-enable on release: popcli() • Avoids recursive locks
  38. 38. Interrupt handlers • Interrupts lead to concurrency problems on uniprocessors too • Disable interrupts before acquiring a lock: pushcli() • Re-enable on release: popcli() • Avoids recursive locks
  39. 39. Interrupt handlers • Interrupts lead to concurrency problems on uniprocessors too • Disable interrupts before acquiring a lock: pushcli() • Re-enable on release: popcli() • Avoids recursive locks
  40. 40. Today’s Task • xv6 processes only have a single thread so processes do not share any memory • But it is definitely possible to add multithreading • Imagine that you have been provided a library with methods to create (xv6_thread_create()) and block (xv6_thread_join()) threads • xv6 already provides you with a spinlock with methods to acquire and release that lock. • In addition, it also has methods to: 1 Put a thread to sleep (sleep(void *chan, struct spinlock *lk)) on a variable (*chan) (it releases lk before 2 sleeping and then reacquires it after waking up) Wake up (wakeup(void *chan)) threads sleeping on a variable (*chan)
  41. 41. Today’s Task • xv6 processes only have a single thread so processes do not share any memory • But it is definitely possible to add multithreading • Imagine that you have been provided a library with methods to create (xv6_thread_create()) and block (xv6_thread_join()) threads • xv6 already provides you with a spinlock with methods to acquire and release that lock. • In addition, it also has methods to: 1 Put a thread to sleep (sleep(void *chan, struct spinlock *lk)) on a variable (*chan) (it releases lk before 2 sleeping and then reacquires it after waking up) Wake up (wakeup(void *chan)) threads sleeping on a variable (*chan)
  42. 42. Today’s Task • xv6 processes only have a single thread so processes do not share any memory • But it is definitely possible to add multithreading • Imagine that you have been provided a library with methods to create (xv6_thread_create()) and block (xv6_thread_join()) threads • xv6 already provides you with a spinlock with methods to acquire and release that lock. • In addition, it also has methods to: 1 Put a thread to sleep (sleep(void *chan, struct spinlock *lk)) on a variable (*chan) (it releases lk before 2 sleeping and then reacquires it after waking up) Wake up (wakeup(void *chan)) threads sleeping on a variable (*chan)
  43. 43. Today’s Task • xv6 processes only have a single thread so processes do not share any memory • But it is definitely possible to add multithreading • Imagine that you have been provided a library with methods to create (xv6_thread_create()) and block (xv6_thread_join()) threads • xv6 already provides you with a spinlock with methods to acquire and release that lock. • In addition, it also has methods to: 1 Put a thread to sleep (sleep(void *chan, struct spinlock *lk)) on a variable (*chan) (it releases lk before 2 sleeping and then reacquires it after waking up) Wake up (wakeup(void *chan)) threads sleeping on a variable (*chan)
  44. 44. Today’s Task • xv6 processes only have a single thread so processes do not share any memory • But it is definitely possible to add multithreading • Imagine that you have been provided a library with methods to create (xv6_thread_create()) and block (xv6_thread_join()) threads • xv6 already provides you with a spinlock with methods to acquire and release that lock. • In addition, it also has methods to: 1 Put a thread to sleep (sleep(void *chan, struct spinlock *lk)) on a variable (*chan) (it releases lk before 2 sleeping and then reacquires it after waking up) Wake up (wakeup(void *chan)) threads sleeping on a variable (*chan)
  45. 45. Today’s Task • xv6 processes only have a single thread so processes do not share any memory • But it is definitely possible to add multithreading • Imagine that you have been provided a library with methods to create (xv6_thread_create()) and block (xv6_thread_join()) threads • xv6 already provides you with a spinlock with methods to acquire and release that lock. • In addition, it also has methods to: 1 Put a thread to sleep (sleep(void *chan, struct spinlock *lk)) on a variable (*chan) (it releases lk before 2 sleeping and then reacquires it after waking up) Wake up (wakeup(void *chan)) threads sleeping on a variable (*chan)
  46. 46. Today’s Task • xv6 processes only have a single thread so processes do not share any memory • But it is definitely possible to add multithreading • Imagine that you have been provided a library with methods to create (xv6_thread_create()) and block (xv6_thread_join()) threads • xv6 already provides you with a spinlock with methods to acquire and release that lock. • In addition, it also has methods to: 1 Put a thread to sleep (sleep(void *chan, struct spinlock *lk)) on a variable (*chan) (it releases lk before 2 sleeping and then reacquires it after waking up) Wake up (wakeup(void *chan)) threads sleeping on a variable (*chan)
  47. 47. Today’s Task (2) • Design a thread-safe library (in pseudo code) for xv6 that uses these existing primitives, to implement semaphores (binary and counting) and conditional variables and their various methods • Also, add commenting to describe why your solution is thread-safe
  48. 48. Reading • Chapter 4 from “xv6: a simple, Unix-like teaching operating system” • Timothy L. Harris. 2001. A Pragmatic Implementation of Non-blocking Linked-Lists. In Proceedings of the 15th International Conference on Distributed Computing (DISC ’01), Jennifer L. Welch (Ed.). Springer-Verlag, London, UK, 300-314. Online: http: //www.timharris.co.uk/papers/2001-disc.pdf

×