wait(cond_t *cv, mutex_t *mutex)
signal(cond_t *cv)
broadcast(cond_t *cv)
mutex_t m = MUTEX_INITIALIZER; // mutex
cond_t c = COND_INITIALIZER; // child의 역할이 끝났는지에 대한 CV
void *child(void *arg) {
...
// do something...
...
thread_exit();
return NULL;
}
int main(int argc, char *argv[]) {
thread_t p;
thread_create(&p, NULL, child, NULL);
thread_join(); // child가 끝날때까지 대기하기
return 0;
}
void thread_exit() {
mutex_lock(&m); // CV에 대한 mutex를 acquire
cond_signal(&c); // parent에 child가 종료되었음을 알리세요
mutex_unlock(&m);
}
void thread_join() {
mutex_lock(&m); // CV에 대한 mutex를 acquire
cond_wait(&c, &m); // child가 끝나면 cv를 통해서 signal이 올 것... 그 전까지는 mutex를 풀고 대기상태로 진입(sleep)
mutex_unlock(&m);
}
signal()
은 history를 남기지 않는다!cond_signal()
에 도입하면 parent는 cond_wait()
에서 나올 방법이 없다.mutex_t m = MUTEX_INITIALIZER; // mutex
cond_t c = COND_INITIALIZER; // child의 역할이 끝났는지에 대한 CV
int done = 0;
void *child(void *arg) {
...
// do something...
...
thread_exit();
return NULL;
}
int main(int argc, char *argv[]) {
thread_t p;
thread_create(&p, NULL, child, NULL);
thread_join(); // child가 끝날때까지 대기하기
return 0;
}
void thread_exit() {
mutex_lock(&m); // CV에 대한 mutex를 acquire
done = 1; // 당연하지만 done도 CS다...
cond_signal(&c); // parent에 child가 종료되었음을 알리세요
mutex_unlock(&m);
}
void thread_join() {
mutex_lock(&m); // CV에 대한 mutex를 acquire
while(done == 0) // 완료가 되지 않았으면... wait로 넘어간다.
cond_wait(&c, &m); // CV는 mesa semantics! - 반드시 while로 double check해야함
mutex_unlock(&m);
}
mutex_t m;
cond_t notfull, notempty;
int in, out, count;
void produce(data) {
mutex_lock(&m); // mutex!
while (count == N) // while(mesa semantics)
cond_wait(¬_full, &m); // 꽉 차있으면 대기(mutex넘김)
buffer[in] = data;
in = (in+1) % N;
count++;
cond_signal(¬_empty); // empty를 깨운다.
mutex_unlock(&m);
}
void consume(data) {
mutex_lock(&m);
while (count == 0) // while(mesa semantics)
cond_wait(¬_empty, &m); // 비어있으면 대기
data = buffer[out];
out = (out+1) % N;
count--;
cond_signal(¬_full); // full을 깨운다.
mutex_unlock(&m);
}
mutex 내부에 있는 code들은 atomic하게 동작해야하는 code임을 명심하라
Semaphore랑 비교해보자.
Semaphore mutex = 1; // mutex for CS(access to buffer)
empty = N; // trigger producer(is buffer full?)
full = 0; // trigger consumer(is buffer empty?)
void produce(data)
{
wait(&empty); // check buffer empty
wait(&mutex); // enter to CS
buffer[in] = data;
in = (in+1) % N;
signal(&mutex);
signal(&full); // signal full(buffer is no more empty)
}
void consume(&data)
{
wait(&full); // wait until buffer is not empty
wait(&mutex); // enter to CS
*data = buffer[out];
out = (out+1) % N;
signal(&mutex);
signal(&empty); // signal empty(buffer is no more full)
}
일반적인 CV는 queue를 쓰기 때문에 FIFO로 동작한다...
e.g. memory allocation
mutex_t m;
cond_t c;
int bytesLeft = MAX_HEAP_SIZE;
void free(void *p, int size) {
mutex_lock(&m);
bytesLeft += size;
cond_broadcast(&c); // wake all allocate function waiter
mutex_unlock(&m);
}
void *allocate (int size) {
mutex_lock(&m);
while (bytesLeft < size) // size check!
cond_wait(&c, &m);
void *ptr = ...;
bytesLeft -= size;
mutex_unlock(&m);
return ptr;
}
typedef struct sema_t {
int v;
cond_t c;
mutex_t m;
} sema_t;
void sema_init(sema_t *s, int v) {
s->v = v;
cond_init(&s->c);
mutex_init(&s->m);
}
void sema_wait(sema_t *s) {
mutex_lock(&s->m);
while (s->v <= 0)
cond_wait(&s->c, &s->m);
s->v--;
mutex_unlock(&s->m);
}
void sema_signal(sema_t *s) {
mutex_lock(&s->m);
s->v++;
cond_signal(&s->c);
mutex_unlock(&s->m);
}
struct sleeplock {
uint locked; // locked or not
struct spinlock lk; // spinlock worked as mutex
char *name;
int pid; // process that currently get lock
};
void initsleeplock(struct sleeplock *lk, char *name) {
initlock(&lk->lk, “sleep lock”);
lk->name = name;
lk->locked = 0;
lk->pid = 0;
}
void acquiresleep(struct sleeplock *lk) {
acquire(&lk->lk); // enter CS zone
while (lk->locked)
sleep(lk, &lk->lk); // if locked that go to sleep
// get lock
lk->locked = 1;
lk->pid = myproc()->pid;
release(&lk->lk); // out to CS zone
}
void releasesleep(struct sleeplock *lk) {
acquire(&lk->lk); // enter CS zone
// release lock
lk->locked = 0;
lk->pid = 0;
wakeup(lk); // wake all proc that try to access lk
release(&lk->lk); // out to CS zone
}
void sleep(void *chan, struct spinlock *lk) {
struct proc *p = myproc(); // get current process
if (lk != &p->lock) { // if lock is not gee
acquire(&p->lock); // get process lock
// we have to modify process state
release(lk); // release mutex
}
p->chan = chan; // which lock block this process?
p->state = SLEEPING; // sleeping!!!
sched(); // yield to sched
p->chan = 0;
if (lk != &p->lock) {
release(&p->lock); //release process lock
acquire(lk); // reacquire spinlock
}
}
void wakeup(void *chan) {
struct proc *p;
for (p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if (p->state == SLEEPING && p->chan == chan) // change all proc state that locked by spinlock
p->state = RUNNABLE;
release(&p->lock);
}
}