Lecture 9: Blocking and wait queues

Blocking

Is this process blocked?

void process_main() {
    sys_sleep_for_100_seconds();
}

...

uintptr_t proc::syscall(regstate* regs) {
    case SYSCALL_SLEEP_FOR_100_SECONDS: {
        unsigned long endticks = ticks + 100 * HZ;
        while (long(endticks - ticks) > 0) {
            this->yield();
        }
        return 0;
     }

It depends on which thread you mean!

Blocking goals

System calls to illustrate blocking

Bad blocking

// helpers
void proc::block() {
    this->pstate_ = ps_blocked;
    this->yield();
}

void proc::unblock() {
    this->pstate_ = ps_runnable;
    cpus[runq_cpu_].enqueue(this);
}

...

std::atomic<uint64_t> stage;

    case SYSCALL_SET_STAGE: {
        stage = max(stage.load(), egs->reg_rdi);
        spinlock_guard guard(ptable_lock);
        for (int i = 1; i != NPROC; ++i) {
            if (ptable[i])
                ptable[i]->unblock();
        }
        return 0;
    }

    case SYSCALL_BLOCK_UNTIL:
        while (stage < regs->reg_rdi) {
            this->block();
        }
        return 0;

Key problem: Atomic blocking and unlocking

while (stage < regs->reg_rdi) {
    ********** THE CRITICAL MOMENT **********
    this->pstate_ = ps_blocked;
    this->yield();
}

Synchronization invariants for blocking

Check twice?

while (stage < regs->reg_rdi) {
    this->pstate_ = ps_blocked;
    std::atomic_thread_fence();
    if (stage >= regs->reg_rdi) {
        this->pstate_ = ps_runnable;
    }
    this->yield();
}

Idea 1: Block, then check

Avoid spurious wakeups

Idea 2: Associate each condition with a list of blocked tasks

Linked list of waiters

spinlock waiters_lock;
list<proc, blocking_links_> waiters;

    case SYSCALL_SET_STAGE: {
        stage = max(stage.load(), regs->reg_rdi);
        spinlock_guard guard(waiters_lock);
        while (auto p = waiters.pop_front()) {
            p->unblock();
        }
        return 0;
    }

    case SYSCALL_BLOCK_UNTIL:
        assert(!this->blocking_links_.is_linked());
        while (stage < regs->reg_rdi) {
            spinlock_guard guard(waiters_lock);
            waiters.push_back(this);
            this->pstate_ = ps_blocked;
            this->yield();
        }

Linked list of waiters, attempt 2

    case SYSCALL_BLOCK_UNTIL:
        assert(!this->blocking_links_.is_linked());
        while (stage < regs->reg_rdi) {
            spinlock_guard guard(waiters_lock);
            if (stage < regs->reg_rdi) {
                waiters.push_back(this);
                this->pstate_ = ps_blocked;
            }
            this->yield();
        }

Linked list of waiters, attempt 3

    case SYSCALL_BLOCK_UNTIL:
        assert(!this->blocking_links_.is_linked());
        while (stage < regs->reg_rdi) {
            spinlock_guard guard(waiters_lock);
            if (stage < regs->reg_rdi) {
                waiters.push_back(this);
                this->pstate_ = ps_blocked;
            }
            guard.unlock();
            this->yield();
        }

Waiting on multiple conditions

Wait queues

struct waiter, struct wait_queue

struct waiter {
    proc* p_;
    wait_queue* wq_;
    list_links links_;
};

struct wait_queue {
    list<waiter, &waiter::links_> q_;
    spinlock lock_;
};

Using wait_queue

wait_queue stage_waiters;

    case SYSCALL_SET_STAGE: {
        stage = max(stage.load(), regs->reg_rdi);
        stage_waiters.wake_all();
        return 0;
    }

    case SYSCALL_BLOCK_UNTIL: {
        waiter w;
        while (true) {
            w.prepare(stage_waiters);
                // expands to `stage_waiters.push_back(&w)`
                // + `this->pstate_ = ps_blocked`
            if (stage >= regs->reg_rdi) {
                break;
            }
            w.maybe_block();
                // expands to `this->yield()` + other stuff
        }
        w.clear();
            // expands to `stage_waiters.erase(&w)`
            // + `this->pstate_ = ps_runnable`
        return 0;
    }

block_until shorthand

    case SYSCALL_BLOCK_UNTIL: {
        waiter().block_until(stage_waiters, [] (&) {
            return stage >= regs->reg_rdi;
        });
        return 0;
    }
  1. Process 2 calls sys_block_until(1)
  2. Process 3 calls sys_block_until(1)
  3. Process 4 calls sys_block_until(1)
  4. Process 5 calls sys_set_stage(1)