Human Factors of XR: Using Human Factors to Design XR Systems
AOS Lab 4: If you liked it, then you should have put a “lock” on it
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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 );
}
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. 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. 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. 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
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. 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. 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. 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. 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. 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. 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. 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. 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. Interrupt handlers
• Locks are also used to synchronize access between interrupt
handlers and non-interrupt code
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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