Skip to content
Snippets Groups Projects
sched.cc 11.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • Avi Kivity's avatar
    Avi Kivity committed
    #include "sched.hh"
    #include <list>
    #include "mutex.hh"
    #include <mutex>
    #include "debug.hh"
    
    Avi Kivity's avatar
    Avi Kivity committed
    #include "drivers/clockevent.hh"
    
    #include "irqlock.hh"
    
    #include "drivers/clock.hh"
    
    #include "interrupt.hh"
    
    #include "smp.hh"
    
    Avi Kivity's avatar
    Avi Kivity committed
    
    namespace sched {
    
    
    std::vector<cpu*> cpus;
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    thread __thread * s_current;
    
    elf::tls_data tls;
    
    
    // currently the scheduler will poll right after an interrupt, so no
    // need to do anything.
    inter_processor_interrupt wakeup_ipi{[] {}};
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    #include "arch-switch.hh"
    
    namespace sched {
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void schedule_force();
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    const thread::attr idle_thread_attr{{}, true};
    
    cpu::cpu()
        : idle_thread([this] { idle(); }, idle_thread_attr)
    {
        idle_thread._cpu = this;
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void cpu::schedule(bool yield)
    
    Avi Kivity's avatar
    Avi Kivity committed
        // FIXME: drive by IPI
        handle_incoming_wakeups();
    
    Avi Kivity's avatar
    Avi Kivity committed
        thread* p = thread::current();
    
    Avi Kivity's avatar
    Avi Kivity committed
        if (p->_status.load() == thread::status::running && !yield) {
    
    Avi Kivity's avatar
    Avi Kivity committed
            return;
        }
    
        with_lock(irq_lock, [this] {
    
    Avi Kivity's avatar
    Avi Kivity committed
            assert(!runqueue.empty());
    
            auto n = &runqueue.front();
    
            runqueue.pop_front();
    
    Avi Kivity's avatar
    Avi Kivity committed
            assert(n->_status.load() == thread::status::queued);
    
            n->_status.store(thread::status::running);
            if (n != thread::current()) {
                n->switch_to();
            }
    
    Avi Kivity's avatar
    Avi Kivity committed
    void cpu::do_idle()
    
    {
        do {
            // spin for a bit before halting
            for (unsigned ctr = 0; ctr < 100; ++ctr) {
                // FIXME: can we pull threads from loaded cpus?
                handle_incoming_wakeups();
                if (!runqueue.empty()) {
                    return;
                }
            }
            std::unique_lock<irq_lock_type> guard(irq_lock);
            handle_incoming_wakeups();
            if (!runqueue.empty()) {
                return;
            }
            guard.release();
            arch::wait_for_interrupt();
            handle_incoming_wakeups(); // auto releases irq_lock
        } while (runqueue.empty());
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void cpu::idle()
    {
        while (true) {
            do_idle();
            // FIXME: we don't have an idle priority class yet. so
            // FIXME: yield when we're done and let the scheduler pick
            // FIXME: someone else
            thread::yield();
        }
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void cpu::handle_incoming_wakeups()
    {
    
        cpu_set queues_with_wakes{incoming_wakeups_mask.fetch_clear()};
        for (auto i : queues_with_wakes) {
    
    Avi Kivity's avatar
    Avi Kivity committed
            incoming_wakeup_queue q;
            incoming_wakeups[i].copy_and_clear(q);
            while (!q.empty()) {
    
                auto& t = q.front();
    
    Avi Kivity's avatar
    Avi Kivity committed
                q.pop_front_nonatomic();
    
    Avi Kivity's avatar
    Avi Kivity committed
                irq_save_lock_type irq_lock;
    
    Avi Kivity's avatar
    Avi Kivity committed
                with_lock(irq_lock, [&] {
    
                    t._status.store(thread::status::queued);
                    runqueue.push_back(t);
    
    Avi Kivity's avatar
    Avi Kivity committed
                    t.resume_timers();
                });
    
    void cpu::init_on_cpu()
    {
        arch.init_on_cpu();
    
        clock_event->setup_on_cpu();
    
    unsigned cpu::load()
    {
        return runqueue.size();
    }
    
    void cpu::load_balance()
    {
        timer tmr(*thread::current());
        while (true) {
            tmr.set(clock::get()->time() + 100000000);
            thread::wait_until([&] { return tmr.expired(); });
            if (runqueue.empty()) {
                continue;
            }
            auto min = *std::min_element(cpus.begin(), cpus.end(),
                    [](cpu* c1, cpu* c2) { return c1->load() < c2->load(); });
            if (min == this) {
                continue;
            }
            with_lock(irq_lock, [this, min] {
    
                auto i = std::find_if(runqueue.rbegin(), runqueue.rend(),
                        [](thread& t) { return !t._attr.pinned; });
                if (i == runqueue.rend()) {
    
                auto& mig = *i;
                runqueue.erase(std::prev(i.base()));  // i.base() returns off-by-one
    
    Avi Kivity's avatar
    Avi Kivity committed
                // we won't race with wake(), since we're not thread::waiting
                assert(mig._status.load() == thread::status::queued);
                mig._status.store(thread::status::waking);
    
    Avi Kivity's avatar
    Avi Kivity committed
                mig.suspend_timers();
    
                mig._cpu = min;
                min->incoming_wakeups[id].push_front(mig);
    
                min->incoming_wakeups_mask.set(id);
    
                // FIXME: avoid if the cpu is alive and if the priority does not
                // FIXME: warrant an interruption
                wakeup_ipi.send(min);
    
    Avi Kivity's avatar
    Avi Kivity committed
    void schedule(bool yield)
    {
        cpu::current()->schedule(yield);
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void thread::yield()
    {
    
    Avi Kivity's avatar
    Avi Kivity committed
        auto t = current();
    
        // FIXME: drive by IPI
        t->_cpu->handle_incoming_wakeups();
    
    Avi Kivity's avatar
    Avi Kivity committed
        // FIXME: what about other cpus?
        if (t->_cpu->runqueue.empty()) {
    
    Avi Kivity's avatar
    Avi Kivity committed
            return;
        }
    
        with_lock(irq_lock, [t] {
            t->_cpu->runqueue.push_back(*t);
        });
    
    Avi Kivity's avatar
    Avi Kivity committed
        assert(t->_status.load() == status::running);
        t->_status.store(status::queued);
    
    Avi Kivity's avatar
    Avi Kivity committed
        t->_cpu->schedule(true);
    
    thread::stack_info::stack_info()
    
        : begin(nullptr), size(0), deleter(nullptr)
    
    thread::stack_info::stack_info(void* _begin, size_t _size)
    
        : begin(_begin), size(_size), deleter(nullptr)
    
    {
        auto end = align_down(begin + size, 16);
        size = static_cast<char*>(end) - static_cast<char*>(begin);
    }
    
    
    mutex thread_list_mutex;
    typedef bi::list<thread,
                     bi::member_hook<thread,
                                     bi::list_member_hook<>,
                                     &thread::_thread_list_link>
                    > thread_list_type;
    thread_list_type thread_list;
    
    unsigned long thread::_s_idgen;
    
    thread::thread(std::function<void ()> func, attr attr, bool main)
    
    Avi Kivity's avatar
    Avi Kivity committed
        : _func(func)
    
        , _status(status::unstarted)
    
        , _attr(attr)
    
    Avi Kivity's avatar
    Avi Kivity committed
        , _timers_need_reload()
    
        , _joiner()
    
        with_lock(thread_list_mutex, [this] {
            thread_list.push_back(*this);
    
            _id = _s_idgen++;
    
        setup_tcb();
        init_stack();
    
    Avi Kivity's avatar
    Avi Kivity committed
        if (!main) {
    
            _cpu = current()->tcpu(); // inherit creator's cpu
    
    Avi Kivity's avatar
    Avi Kivity committed
        } else {
            _status.store(status::running);
    
    Avi Kivity's avatar
    Avi Kivity committed
        }
    }
    
    thread::~thread()
    {
    
        with_lock(thread_list_mutex, [this] {
            thread_list.erase(thread_list.iterator_to(*this));
        });
    
        if (_attr.stack.deleter) {
            _attr.stack.deleter(_attr.stack.begin);
        }
    
    void thread::start()
    {
        assert(_status == status::unstarted);
        _status.store(status::waiting);
        wake();
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void thread::prepare_wait()
    {
    
    Avi Kivity's avatar
    Avi Kivity committed
        assert(_status.load() == status::running);
        _status.store(status::waiting);
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    void thread::wake()
    {
    
    Avi Kivity's avatar
    Avi Kivity committed
        status old_status = status::waiting;
        if (!_status.compare_exchange_strong(old_status, status::waking)) {
    
        unsigned c = cpu::current()->id;
        _cpu->incoming_wakeups[c].push_front(*this);
        _cpu->incoming_wakeups_mask.set(c);
    
        // FIXME: avoid if the cpu is alive and if the priority does not
        // FIXME: warrant an interruption
        if (_cpu != current()->tcpu()) {
            wakeup_ipi.send(_cpu);
        }
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    void thread::main()
    {
        _func();
    }
    
    thread* thread::current()
    {
        return sched::s_current;
    }
    
    void thread::wait()
    {
    
    Avi Kivity's avatar
    Avi Kivity committed
        schedule(true);
    
    Avi Kivity's avatar
    Avi Kivity committed
    void thread::sleep_until(u64 abstime)
    {
        timer t(*current());
        t.set(abstime);
        wait_until([&] { return t.expired(); });
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void thread::stop_wait()
    {
    
    Avi Kivity's avatar
    Avi Kivity committed
        status old_status = status::waiting;
        if (_status.compare_exchange_strong(old_status, status::running)) {
            return;
    
    Avi Kivity's avatar
    Avi Kivity committed
        while (_status.load() == status::waking) {
            schedule(true);
        }
        assert(_status.load() == status::running);
    
    void thread::complete()
    {
    
    Avi Kivity's avatar
    Avi Kivity committed
        _status.store(status::terminated);
    
        if (_joiner) {
            _joiner->wake();
        }
        while (true) {
            schedule();
        }
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void thread::suspend_timers()
    {
        if (_timers_need_reload) {
            return;
        }
        _timers_need_reload = true;
    
    Avi Kivity's avatar
    Avi Kivity committed
        _cpu->timers.suspend(_active_timers);
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    void thread::resume_timers()
    {
        if (!_timers_need_reload) {
            return;
        }
        _timers_need_reload = false;
    
    Avi Kivity's avatar
    Avi Kivity committed
        _cpu->timers.resume(_active_timers);
    
    void thread::join()
    {
        _joiner = current();
    
    Avi Kivity's avatar
    Avi Kivity committed
        wait_until([this] { return _status.load() == status::terminated; });
    
    thread::stack_info thread::get_stack_info()
    {
    
        return _attr.stack;
    
    unsigned long thread::id()
    {
        return _id;
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    timer_list::callback_dispatch::callback_dispatch()
    
    Avi Kivity's avatar
    Avi Kivity committed
    {
        clock_event->set_callback(this);
    }
    
    void timer_list::fired()
    {
    
        auto now = clock::get()->time();
        auto i = _list.begin();
        while (i != _list.end() && i->_time < now) {
            auto j = i++;
    
            _list.erase(j);
        }
        if (!_list.empty()) {
            clock_event->set(_list.begin()->_time);
        }
    
    Avi Kivity's avatar
    Avi Kivity committed
    // call with irq disabled
    void timer_list::suspend(bi::list<timer>& timers)
    {
        for (auto& t : timers) {
            assert(!t._expired);
            _list.erase(_list.iterator_to(t));
        }
    }
    
    // call with irq disabled
    void timer_list::resume(bi::list<timer>& timers)
    {
        bool rearm = false;
        for (auto& t : timers) {
            assert(!t._expired);
            auto i = _list.insert(t).first;
            rearm |= i == _list.begin();
        }
        if (rearm) {
            clock_event->set(_list.begin()->_time);
        }
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void timer_list::callback_dispatch::fired()
    {
        cpu::current()->timers.fired();
    }
    
    timer_list::callback_dispatch timer_list::_dispatch;
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    timer::timer(thread& t)
        : _t(t)
        , _expired()
    {
    }
    
    timer::~timer()
    {
        cancel();
    }
    
    
    void timer::expire()
    {
        _expired = true;
        _t._active_timers.erase(_t._active_timers.iterator_to(*this));
        _t.wake();
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    void timer::set(u64 time)
    {
    
    Avi Kivity's avatar
    Avi Kivity committed
        _time = time;
    
        with_lock(irq_lock, [=] {
    
    Avi Kivity's avatar
    Avi Kivity committed
            auto& timers = _t._cpu->timers;
    
            timers._list.insert(*this);
    
            _t._active_timers.push_back(*this);
    
            if (timers._list.iterator_to(*this) == timers._list.begin()) {
                clock_event->set(time);
            }
        });
    
    Avi Kivity's avatar
    Avi Kivity committed
    };
    
    void timer::cancel()
    {
    
        with_lock(irq_lock, [=] {
    
            if (_expired) {
                return;
            }
    
            _t._active_timers.erase(_t._active_timers.iterator_to(*this));
    
    Avi Kivity's avatar
    Avi Kivity committed
            _t._cpu->timers._list.erase(_t._cpu->timers._list.iterator_to(*this));
    
            _expired = false;
        });
    
    Avi Kivity's avatar
    Avi Kivity committed
        // even if we remove the first timer, allow it to expire rather than
        // reprogramming the timer
    }
    
    bool timer::expired() const
    {
        return _expired;
    }
    
    bool operator<(const timer& t1, const timer& t2)
    {
        if (t1._time < t2._time) {
            return true;
        } else if (t1._time == t2._time) {
            return &t1 < &t2;
        } else {
            return false;
        }
    }
    
    
    Avi Kivity's avatar
    Avi Kivity committed
    class detached_thread::reaper {
    public:
        reaper();
        void reap();
        void add_zombie(detached_thread* z);
    private:
        mutex _mtx;
        std::list<detached_thread*> _zombies;
        thread _thread;
    };
    
    detached_thread::reaper::reaper()
        : _mtx{}, _zombies{}, _thread([=] { reap(); })
    {
    
        _thread.start();
    
    Avi Kivity's avatar
    Avi Kivity committed
    }
    
    void detached_thread::reaper::reap()
    {
        while (true) {
            with_lock(_mtx, [=] {
    
                wait_until(_mtx, [=] { return !_zombies.empty(); });
    
    Avi Kivity's avatar
    Avi Kivity committed
                while (!_zombies.empty()) {
                    auto z = _zombies.front();
                    _zombies.pop_front();
                    z->join();
    
    Avi Kivity's avatar
    Avi Kivity committed
                }
            });
        }
    }
    
    void detached_thread::reaper::add_zombie(detached_thread* z)
    {
        with_lock(_mtx, [=] {
            _zombies.push_back(z);
            _thread.wake();
        });
    }
    
    detached_thread::detached_thread(std::function<void ()> f)
        : thread([=] { f(); _s_reaper->add_zombie(this); })
    {
    }
    
    detached_thread::~detached_thread()
    {
    }
    
    detached_thread::reaper *detached_thread::_s_reaper;
    
    void init_detached_threads_reaper()
    {
        detached_thread::_s_reaper = new detached_thread::reaper;
    }
    
    
    void init(elf::tls_data tls_data, std::function<void ()> cont)
    
        tls = tls_data;
    
        smp_init();
    
        thread::attr attr;
        attr.stack = { new char[4096*10], 4096*10 };
        thread t{cont, attr, true};
    
        t._cpu = smp_initial_find_current_cpu();
    
        t.switch_to_first();