CS111 Scribe Notes

Lecture 10: 11/4/2013

Contributors: Tania De Pasquale, Stephen Hernandez, David Powell


Condition variables

What is a condition variable?

  1. A Boolean condition defining the variable (programmer's responsibility)
  2. A blocking mutex to protect the condition, the condition can only change when you own the mutex
  3. A condition variable of type condvar_t representing the condition

API:

wait(condvar_t *c, bmutex_t *b)
// Precondition: you have acquired b (already blocked)
// Action: releases b, then blocks until some other thread/process notifies the OS that the condition is true, then reacquires b and returns

(bmutex blocks the condition variable)

notify(condvar_t *c) 
// Notifies 1 other thread that the condition is true
// Broadcast/notifyAll notifies all threads currently blocking
// -- Advantages of broadcast: application specific priorities (let the application decide who to wake up)

Example:

struct pipe {
  bmutex_t b;
  condvar_t nonfull, nonempty;
  size_t r, w;
  char buf[N];
}

void writec(struct pipe *p, char c) {
  acquire(&p->b);	//spinlock until we get lock: if we can't get block until we can
  while (p->w - p->r == N)	// not if condition in case something changed while waiting (race condition)
    wait(&p->nonfull, &p->b);
  p->buf[p->w++ % N] = c;
  release(&p->b);
  notify(&p->nonempty);
}
* wait / notify don't promise anything, they wake the process up so the process can check if it should do anything

Sephamores

What is a sephamore?

Hardware Lock Elision (HLE)

Released in June 2013:

Example:

xacquire lock xchgl %eax, lockvar	//optimistic variant of xchgl (load without waiting)
    // Under the assumption that the lock will work
lock:
try:
xacquire lock xchgl %eax, lockvar
cmp $0, %eax
jnz try

unlock:
xrelease movl $0, lockvar
    // If we didn't get the lock, the operations vanish like an undo (expensive) and we try again

Deadlock

Sometimes deadlock can come about in unanticipated ways and is tricky.

say: want a fast cat
eg: sort foo | cat | tr ...

We would want to skip the buffer like this:

while(copy(0, 1, 40%)>0)
  continue;
// The copy syscall should speed it up right?
// This introduces deadlock, however
// Acquires 0 then 1, if someone else does copy(1, 0, ..) acquires 1 then 0 = DEADLOCK!

Inside cat:

ssize_t r;
while ((r = read(0, buf, sizeof(buf))) > 0)
  write(1, buf, sizeof(buf));
exit(0);
// OS -> buffer -> userspace

How to avoid deadlock:

  1. Don't lock [don't require exclusion]
  2. Don't allow >1 lock per process at a time
    // If a process is holding a lock, then don't allow it to try to acquire another
  3. Lock ordering - "try acquire"
    // Assume some lock ordering (in practice oftentimes the memory address of locks)
    // Acquire locks in ascending order; if any lock is busy, then give up all locks and start over
  4. Dynamic detection of cycles (used in lab)
    // If the new lock would create a cycle, reject it
    // Actually very common [used by linux]

File System Performance

Problem: storage hierarchy

ALU (a few nanoseconds) (1 cycle)
Registers (sim. to ALU)
L1 cache (2 cycles)
L2 cache (4-8 cycles)
DRAM (100 cycles)
Flash ssd
Hard disk
Mag tape library (5 mins latency)

* Ideally disk and registers would be the same
* Why are the secondary devices slow? (SSD and HD are very slow)

Disk: 7200 rpm = 120 Hz read-heads float over any track; if data is all in the same track, then don't need to move the read-head To grab a random sector off disk:
  1. Accelerate read heads in direction of desired track
  2. Halfway there, decelerate
  3. "listen" for the correct track and settle down
  4. Wait for data to come under the head
  5. Copy data out of the sector and into the disk controller cache
  6. Transfer data from cache into CPU or RAM

//These are typically estimates based on previous disk implementation
Typical 1TB drive: (Seagate Barracuda ES2)

SSD (Corsair Force GT):

* Flash wears out! You can only do so many writes.
* Issues: wear leveling, if there is a "hot block" wore out, use a different block in its place

for (;;) {
  char c;
  if (read (0, &c, 1) != 1)
    break;
  process(c);
}
// assuming worst possible implementation (6 HDD Steps above):
// Each read will take a long time
// avg 4.16 ms + 8.5 ms + [.0005/100MB/s xfer rate (too small to matter)]

Batching: take control of setup/wait and do once, then grab as much data as you can

Ex. Say 1024 read and not 1, assuming process time is insignificant & read is bottleneck, 1000x faster Prefetching: Dallying:

// can be a win to save the USB from wearing as quickly