CS111 Scribe Notes 5/4/2006
Today’s lecture will cover:
Lock Contention
Deadlock
I/O Interactions
-Many threads waiting on a lock
-This is a software problem that should be avoided by better scheduling
-How to improve?
-Think about WeensyOS2:
for (i = 0; i < 320; i++) *cursorpos++ = *PRINTCHAR; //critical section
-One solution (Fine grained locking)
for (i = 0; i < 320; i++) {
lock(&m);
*cursorpos++ = *PRINTCHAR; //critical section
unlock(&m);
}
-Second solution (Coarse grained locking):
lock(&m);
for (i = 0; i < 320; i++) {
*cursorpos++ = *PRINTCHAR; //critical section
}
unlock(&m);
Realistic example where lock contention takes place: Creating a new process
Pseudocode to add processes to the run queue:
lock(&kernel_mutex); add process to run queue; unlock(&kernel_mutex);
where kernel_mutex is a single mutex used by the kernel for all processes
What is wrong with this solution?
Solution:
New pseudocode:
lock(&runq_mutex); add process to run queue; unlock(&runq_mutex);
What if there was still lock contention here?
Deadlock Example #1:
Process 1
lock(&runq_mutex) add p to runq <--inerrupt here unlock(&runq_mutex)
Process 2
lock(&runq_mutex) <--begin running here add p to runq unlock(&runq_mutex)
Circular wait between the tasks:
CPU is running Process 2, who wants runq_mutex. But T1 has the runq_mutex and is waiting for the CPU.
Solution #1:
Solution #2:
Deadlock Example #2:
Int v1, v2;
Mutex_t m1, m2;
F1() {
lock(&m1);
lock(&m2);
x = v1 + v2;
unlock(&m2);
unlock(&m1);
}
F2() {
lock(&m2);
lock(&m1);
x = v1 - v2;
unlock(&m1);
unlock(&m2);
}
Problem:
4 Deadlock conditions
We can prevent deadlock by eliminating just one of the 4 deadlock conditions.
One solution:
F2() {
lock(&m1);
lock(&m2);
x = v1 - v2;
unlock(&m2);
unlock(&m1);
}
Return to Deadlock Example #1
Solution 2: Fix the problem using ordering.
Deadlock Avoidance
Deadlock Avoidance Example #1:
This would result in a deadlock.
Solution: Keep system in safe state -Kernel allocates a kernel stack before it is needed
Problems with Solution: There is less memory for the system to use
Deadlock Detection and Recovery
I/O Interactions Example #1: We want to read 40B from disk given:
Disk latency: 50 microseconds
Cycle: 1 ns = 10E-9 s (1 Ghz processor)
Programmed IO instruction (PIO) = 1000 cycles = 1 ns
Clock interrupt frequency = 1 ms
Interrupt Overhead: 5 PIO = 5 microseconds
Solution #1 (busy waiting):
Actual instructions we might use:
outb(0x1F2, 1) outb(0x1F3, 0) outb(0x1F4, 0) outb(0x1F5, 0) outb(0x1F6, 0) // Construct 0 from 4 bytes of 0: 0x00000000 outb(0x1F7, 0x20) // Read Sector while((inb(0x1F7) & 0xC0) != 0x40) /* do nothing */
Overhead: how much time the CPU is busy for this disk request
Turnaround time: Time until 40B overhead)
Throughput: # requests per second (1/Overhead)
Solution #2:
Use Clock interrupts with device buffering instead of polling
Overhead: 46 microseconds
Turnaround Time: 546 microseconds (500 microseconds because on average we wait ½ of a clock interrupt)
Throughput: 1/46 microseconds = 21700 requests/s
Device controller has an internal request buffer
Solution #3:
Device interrupts
(picture here)
Overhead: 51 microseconds
Turnaround Time: 101 microseconds (51 microseconds overhead + 50 microseconds latency)
Throughput: 19,000 requests/s
Solution #4:
Direct Memory Access
Overhead: 11 microseconds
Turnaround Time: 61 microseconds (11 microseconds overhead + 50 microseconds latency)
Throughput 90,900 requests/second
Solution #5:
Direct Memory Access with Polling
Overhead: 1 microsecond
Turnaround Time: 51 microseconds
Throughput: 1,000,000 requests/second
Polling is a good thing!
.