diff options
author | rsc <rsc> | 2008-09-03 04:50:04 +0000 |
---|---|---|
committer | rsc <rsc> | 2008-09-03 04:50:04 +0000 |
commit | f53494c28e362fb7752bbc83417b9ba47cff0bf5 (patch) | |
tree | 7a7474710c9553b0188796ba24ae3af992320153 /web/l-coordination.html | |
parent | ee3f75f229742a376bedafe34d0ba04995a942be (diff) | |
download | xv6-labs-f53494c28e362fb7752bbc83417b9ba47cff0bf5.tar.gz xv6-labs-f53494c28e362fb7752bbc83417b9ba47cff0bf5.tar.bz2 xv6-labs-f53494c28e362fb7752bbc83417b9ba47cff0bf5.zip |
DO NOT MAIL: xv6 web pages
Diffstat (limited to 'web/l-coordination.html')
-rw-r--r-- | web/l-coordination.html | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/web/l-coordination.html b/web/l-coordination.html new file mode 100644 index 0000000..b2f9f0d --- /dev/null +++ b/web/l-coordination.html @@ -0,0 +1,354 @@ +<title>L9</title> +<html> +<head> +</head> +<body> + +<h1>Coordination and more processes</h1> + +<p>Required reading: remainder of proc.c, sys_exec, sys_sbrk, + sys_wait, sys_exit, and sys_kill. + +<h2>Overview</h2> + +<p>Big picture: more programs than processors. How to share the + limited number of processors among the programs? Last lecture + covered basic mechanism: threads and the distinction between process + and thread. Today expand: how to coordinate the interactions + between threads explicitly, and some operations on processes. + +<p>Sequence coordination. This is a diferrent type of coordination + than mutual-exclusion coordination (which has its goal to make + atomic actions so that threads don't interfere). The goal of + sequence coordination is for threads to coordinate the sequences in + which they run. + +<p>For example, a thread may want to wait until another thread + terminates. One way to do so is to have the thread run periodically, + let it check if the other thread terminated, and if not give up the + processor again. This is wasteful, especially if there are many + threads. + +<p>With primitives for sequence coordination one can do better. The + thread could tell the thread manager that it is waiting for an event + (e.g., another thread terminating). When the other thread + terminates, it explicitly wakes up the waiting thread. This is more + work for the programmer, but more efficient. + +<p>Sequence coordination often interacts with mutual-exclusion + coordination, as we will see below. + +<p>The operating system literature has a rich set of primivites for + sequence coordination. We study a very simple version of condition + variables in xv6: sleep and wakeup, with a single lock. + +<h2>xv6 code examples</h2> + +<h3>Sleep and wakeup - usage</h3> + +Let's consider implementing a producer/consumer queue +(like a pipe) that can be used to hold a single non-null char pointer: + +<pre> +struct pcq { + void *ptr; +}; + +void* +pcqread(struct pcq *q) +{ + void *p; + + while((p = q->ptr) == 0) + ; + q->ptr = 0; + return p; +} + +void +pcqwrite(struct pcq *q, void *p) +{ + while(q->ptr != 0) + ; + q->ptr = p; +} +</pre> + +<p>Easy and correct, at least assuming there is at most one +reader and at most one writer at a time. + +<p>Unfortunately, the while loops are inefficient. +Instead of polling, it would be great if there were +primitives saying ``wait for some event to happen'' +and ``this event happened''. +That's what sleep and wakeup do. + +<p>Second try: + +<pre> +void* +pcqread(struct pcq *q) +{ + void *p; + + if(q->ptr == 0) + sleep(q); + p = q->ptr; + q->ptr = 0; + wakeup(q); /* wake pcqwrite */ + return p; +} + +void +pcqwrite(struct pcq *q, void *p) +{ + if(q->ptr != 0) + sleep(q); + q->ptr = p; + wakeup(q); /* wake pcqread */ + return p; +} +</pre> + +That's better, but there is still a problem. +What if the wakeup happens between the check in the if +and the call to sleep? + +<p>Add locks: + +<pre> +struct pcq { + void *ptr; + struct spinlock lock; +}; + +void* +pcqread(struct pcq *q) +{ + void *p; + + acquire(&q->lock); + if(q->ptr == 0) + sleep(q, &q->lock); + p = q->ptr; + q->ptr = 0; + wakeup(q); /* wake pcqwrite */ + release(&q->lock); + return p; +} + +void +pcqwrite(struct pcq *q, void *p) +{ + acquire(&q->lock); + if(q->ptr != 0) + sleep(q, &q->lock); + q->ptr = p; + wakeup(q); /* wake pcqread */ + release(&q->lock); + return p; +} +</pre> + +This is okay, and now safer for multiple readers and writers, +except that wakeup wakes up everyone who is asleep on chan, +not just one guy. +So some of the guys who wake up from sleep might not +be cleared to read or write from the queue. Have to go back to looping: + +<pre> +struct pcq { + void *ptr; + struct spinlock lock; +}; + +void* +pcqread(struct pcq *q) +{ + void *p; + + acquire(&q->lock); + while(q->ptr == 0) + sleep(q, &q->lock); + p = q->ptr; + q->ptr = 0; + wakeup(q); /* wake pcqwrite */ + release(&q->lock); + return p; +} + +void +pcqwrite(struct pcq *q, void *p) +{ + acquire(&q->lock); + while(q->ptr != 0) + sleep(q, &q->lock); + q->ptr = p; + wakeup(q); /* wake pcqread */ + release(&q->lock); + return p; +} +</pre> + +The difference between this an our original is that +the body of the while loop is a much more efficient way to pause. + +<p>Now we've figured out how to use it, but we +still need to figure out how to implement it. + +<h3>Sleep and wakeup - implementation</h3> +<p> +Simple implementation: + +<pre> +void +sleep(void *chan, struct spinlock *lk) +{ + struct proc *p = curproc[cpu()]; + + release(lk); + p->chan = chan; + p->state = SLEEPING; + sched(); +} + +void +wakeup(void *chan) +{ + for(each proc p) { + if(p->state == SLEEPING && p->chan == chan) + p->state = RUNNABLE; + } +} +</pre> + +<p>What's wrong? What if the wakeup runs right after +the release(lk) in sleep? +It still misses the sleep. + +<p>Move the lock down: +<pre> +void +sleep(void *chan, struct spinlock *lk) +{ + struct proc *p = curproc[cpu()]; + + p->chan = chan; + p->state = SLEEPING; + release(lk); + sched(); +} + +void +wakeup(void *chan) +{ + for(each proc p) { + if(p->state == SLEEPING && p->chan == chan) + p->state = RUNNABLE; + } +} +</pre> + +<p>This almost works. Recall from last lecture that we also need +to acquire the proc_table_lock before calling sched, to +protect p->jmpbuf. + +<pre> +void +sleep(void *chan, struct spinlock *lk) +{ + struct proc *p = curproc[cpu()]; + + p->chan = chan; + p->state = SLEEPING; + acquire(&proc_table_lock); + release(lk); + sched(); +} +</pre> + +<p>The problem is that now we're using lk to protect +access to the p->chan and p->state variables +but other routines besides sleep and wakeup +(in particular, proc_kill) will need to use them and won't +know which lock protects them. +So instead of protecting them with lk, let's use proc_table_lock: + +<pre> +void +sleep(void *chan, struct spinlock *lk) +{ + struct proc *p = curproc[cpu()]; + + acquire(&proc_table_lock); + release(lk); + p->chan = chan; + p->state = SLEEPING; + sched(); +} +void +wakeup(void *chan) +{ + acquire(&proc_table_lock); + for(each proc p) { + if(p->state == SLEEPING && p->chan == chan) + p->state = RUNNABLE; + } + release(&proc_table_lock); +} +</pre> + +<p>One could probably make things work with lk as above, +but the relationship between data and locks would be +more complicated with no real benefit. Xv6 takes the easy way out +and says that elements in the proc structure are always protected +by proc_table_lock. + +<h3>Use example: exit and wait</h3> + +<p>If proc_wait decides there are children to be waited for, +it calls sleep at line 2462. +When a process exits, we proc_exit scans the process table +to find the parent and wakes it at 2408. + +<p>Which lock protects sleep and wakeup from missing each other? +Proc_table_lock. Have to tweak sleep again to avoid double-acquire: + +<pre> +if(lk != &proc_table_lock) { + acquire(&proc_table_lock); + release(lk); +} +</pre> + +<h3>New feature: kill</h3> + +<p>Proc_kill marks a process as killed (line 2371). +When the process finally exits the kernel to user space, +or if a clock interrupt happens while it is in user space, +it will be destroyed (line 2886, 2890, 2912). + +<p>Why wait until the process ends up in user space? + +<p>What if the process is stuck in sleep? It might take a long +time to get back to user space. +Don't want to have to wait for it, so make sleep wake up early +(line 2373). + +<p>This means all callers of sleep should check +whether they have been killed, but none do. +Bug in xv6. + +<h3>System call handlers</h3> + +<p>Sheet 32 + +<p>Fork: discussed copyproc in earlier lectures. +Sys_fork (line 3218) just calls copyproc +and marks the new proc runnable. +Does fork create a new process or a new thread? +Is there any shared context? + +<p>Exec: we'll talk about exec later, when we talk about file systems. + +<p>Sbrk: Saw growproc earlier. Why setupsegs before returning? |