building concurrency primitives
play

Building Concurrency Primitives CS 450 : Operating Systems Michael - PowerPoint PPT Presentation

Building Concurrency Primitives CS 450 : Operating Systems Michael Lee <lee@iit.edu> Previously 1. Decided concurrency was a useful (sometimes necessary) thing to have 2. Assumed the presence of concurrent programming


  1. Building Concurrency Primitives CS 450 : Operating Systems Michael Lee <lee@iit.edu>

  2. Previously … 1. Decided concurrency was a useful (sometimes necessary) thing to have 2. Assumed the presence of concurrent programming “primitives” (e.g., locks) 3. Showed how to use these primitives in concurrent programming scenarios

  3. … but how are these primitives actually constructed? - as usual: responsibility is shared between kernel and hardware

  4. Agenda - The mutex lock - xv6 concurrency mechanisms - code review: implementation & usage

  5. § The mutex lock

  6. Thread A Thread B a1 count = count + 1 b1 count = count + 1 count allocated acquire e s u T A T B

  7. basic requirement: prevent other threads from entering their critical section while one thread holds the lock i.e., execute critical section in mutex

  8. lock-polling — “spinlock”: struct spinlock { int locked; }; void acquire(struct spinlock *l) { while (1) { if (!l->locked) { l->locked = 1; break; } } } void release(struct spinlock *l) { l->locked = 0; }

  9. if (!l->locked) { // test l->locked = 1; // set break; } problem: thread can be preempted between test & set - again, must guarantee execution of test & set in mutex … (using a lock?!)

  10. recognize that preemption is caused by a hardware interrupt … … so, disable interrupts!

  11. recall: x86 interrupt flag (IF) in FLAGS register - cleared/set by cli / sti instructions - restored by iret instruction - note: above are all privileged operations — i.e., must be performed by kernel

  12. can try to avoid spinlocks altogether: begin_mutex(); user /* critical section */ end_mutex(); kernel asm ("cli"); asm ("sti");

  13. horrible idea! - user code cannot be preempted ; kernel effectively neutered - also, prohibits all concurrency (not just for related critical sections)

  14. ought only block interrupts in kernel space, and minimize blocked time frame void acquire(struct spinlock *l) { int done = 0; while (!done) { asm ("cli"); if (!l->locked) done = l->locked = 1; asm ("sti"); } } void release(struct spinlock *l) { l->locked = 0; }

  15. but! - preventing interrupts only helps to avoid concurrency 
 due to preemption - insufficient on a multiprocessor system! - where we have true parallelism - each processor has its own interrupts

  16. (fail) asm ("cli"); if (!l->locked) done = l->locked = 1; asm ("sti");

  17. instead of general mutex, recognize that all we need is to make test (read) & set (write) operations on lock atomic if (! l->locked ) done = l->locked = 1 ;

  18. enter: x86 atomic exchange instruction ( xchg ) - atomically swaps reg/mem content - guarantees no out-of-order execution # note: pseudo-assembly! loop: movl $1, %eax # set up "new" value in reg xchgl l->locked, %eax # swap values in reg & lock test %eax, %eax jne loop # spin if old value ≠ 0

  19. xv6: spinlock.c void acquire(struct spinlock *lk) { ... // keep looping until we atomically “swap” a 0 out of the lock while( xchg(&lk->locked, 1) != 0 ) ; } void release(struct spinlock *lk) { xchg(&lk->locked, 0) ; ... }

  20. xv6 uses spinlocks internally e.g., to protect proc array in scheduler: void scheduler(void) { ... acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->state != RUNNABLE) continue; proc = p; swtch(&cpu->scheduler, proc->context); } release(&ptable.lock); } maintains mutex across parallel execution of scheduler on separate CPUs

  21. in theory, scheduler execution may also be interrupted by the clock ... which causes the current thread to yield : void yield(void) { acquire(&ptable.lock); proc->state = RUNNABLE; sched(); release(&ptable.lock); }

  22. what could go wrong? void yield(void) { void scheduler(void) { acquire(&ptable.lock); acquire(&ptable.lock); proc->state = RUNNABLE; ... release(&ptable.lock); sched(); release(&ptable.lock); } }

  23. Locks are designed to enforce mutex between threads . If one thread tries to acquire a lock more than once, it will have to wait for itself to release the lock … … which it can’t/won’t. Deadlock!

  24. xv6’s (ultra-conservative) policy: - never hold a lock with interrupts enabled - corollary: can only enable interrupts when all locks have been released (may hold more than one at any time) - must be careful about re-enabling interrupts prematurely when releasing a lock

  25. // maintain a “stack” of cli/sti calls void pushcli(void) { int eflags; void acquire(struct spinlock *lk) { pushcli(); eflags = readeflags(); while(xchg(&lk->locked, 1) != 0) ; cli(); ... if(cpu->ncli++ == 0) } cpu->intena = eflags & FL_IF; } void release(struct spinlock *lk) { void popcli(void) { ... if(readeflags()&FL_IF) xchg(&lk->locked, 0); panic("popcli - interruptible"); popcli(); if(--cpu->ncli < 0) } panic("popcli"); if(cpu->ncli == 0 && cpu->intena) sti(); }

  26. spinlock usage: - when to lock? - how long to hold onto a lock?

  27. spinlocks are very inefficient ! - lock polling is indistinguishable from application logic — will burn through scheduler time quanta - not intended for long-term synchronization (e.g., “blocking”)

  28. a “blocked” thread shouldn’t consume CPU cycles until some condition(s) necessary for it to run are true e.g., data from I/O request is ready; child process ready for 
 reaping by parent (via wait )

  29. xv6 implements sleep and wakeup mechanism for blocking threads on semantic “channels” ( proc.c ) - distinct scheduler state ( SLEEPING ) prevents re-activation

  30. // Put calling process to sleep on chan // Wake up all processes sleeping on chan. void static void sleep(void *chan) wakeup1(void *chan) { { proc->chan = chan; struct proc *p; proc->state = SLEEPING; for(p=ptable.proc; p<&ptable.proc[NPROC]; p++) sched(); // context switch away from proc if(p->state == SLEEPING && p->chan == chan) proc->chan = 0; p->state = RUNNABLE; } } Q: What happens if sleep and wakeup are 
 called simultaneously? A: Race condition! Wakeup may be “lost”.

  31. void // Wake up all processes sleeping on chan. sleep(void *chan, struct spinlock *lk) void { wakeup(void *chan) // Acquire ptable.lock so we don’t miss { // and wakeups acquire(&ptable.lock); if(lk != &ptable.lock){ wakeup1(chan); acquire(&ptable.lock); release(&ptable.lock); release(lk); } } // Wake up all processes sleeping on chan. // Go to sleep. // The ptable lock must be held. proc->chan = chan; static void proc->state = SLEEPING; wakeup1(void *chan) sched(); // note: scheduler releases lock { struct proc *p; proc->chan = 0; for(p=ptable.proc; p<&ptable.proc[NPROC]; p++) // Reacquire original lock. if(p->state == SLEEPING && p->chan == chan) if(lk != &ptable.lock){ p->state = RUNNABLE; release(&ptable.lock); } acquire(lk); } }

  32. Sample usage: wait / exit

  33. // Wait for a child process to exit. // Exit the current process. int // An exited process remains a zombie until its wait(void) // parent calls wait() to find out it exited. { void struct proc *p; exit(void) int havekids, pid; { struct proc *p; acquire(&ptable.lock); acquire(&ptable.lock); for(;;){ for(p=ptable.proc; p<&ptable.proc[NPROC]; p++){ wakeup1(proc->parent); if(p->parent != proc) continue; // Pass orphaned children to init. if(p->state == ZOMBIE){ for(p=ptable.proc; p<&ptable.proc[NPROC]; p++){ pid = p->pid; if(p->parent == proc){ release(&ptable.lock); p->parent = initproc; return pid; if(p->state == ZOMBIE) } wakeup1(initproc); } } } sleep(proc, &ptable.lock); } proc->state = ZOMBIE; } sched(); panic("zombie exit"); }

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend