From 95c3442d595efa0a84badc33bd00bd9e10bb2034 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Fri, 1 Feb 2019 03:50:06 +0100 Subject: [PATCH] Implement event loop timers. GObjects can now register a timer with the GEventLoop. This will eventually cause GTimerEvents to be dispatched to the GObject. This needed a few supporting changes in the kernel: - The PIT now ticks 1000 times/sec. - select() now supports an arbitrary timeout. - gettimeofday() now returns something in the tv_usec field. With these changes, the clock window in guitest2 finally ticks on its own. --- AK/HashMap.h | 5 +++ Kernel/Process.cpp | 10 ++++-- Kernel/Process.h | 2 ++ Kernel/Scheduler.cpp | 12 +++++++- Kernel/i8253.cpp | 10 ++++++ Kernel/i8253.h | 5 ++- LibGUI/GEvent.h | 7 ++++- LibGUI/GEventLoop.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++- LibGUI/GEventLoop.h | 21 +++++++++++++ LibGUI/GObject.cpp | 4 +++ 10 files changed, 140 insertions(+), 7 deletions(-) diff --git a/AK/HashMap.h b/AK/HashMap.h index 1630aaebbb..952777ab34 100644 --- a/AK/HashMap.h +++ b/AK/HashMap.h @@ -83,6 +83,11 @@ public: return find(key) != end(); } + void remove(IteratorType it) + { + m_table.remove(it); + } + private: HashTable m_table; }; diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index 6571d8c180..901c16f711 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -1442,7 +1442,7 @@ int Process::sys$gettimeofday(timeval* tv) InterruptDisabler disabler; auto now = RTC::now(); tv->tv_sec = now; - tv->tv_usec = 0; + tv->tv_usec = PIT::ticks_since_boot() % 1000; return 0; } @@ -1976,8 +1976,12 @@ int Process::sys$select(const Syscall::SC_select_params* params) // FIXME: Implement exceptfds support. ASSERT(!exceptfds); - // FIXME: Implement timeout support. - ASSERT(!timeout || (!timeout->tv_sec && !timeout->tv_usec)); + if (timeout) { + m_select_timeout = *timeout; + m_select_has_timeout = true; + } else { + m_select_has_timeout = false; + } if (nfds < 0) return -EINVAL; diff --git a/Kernel/Process.h b/Kernel/Process.h index f388054014..ce465f3182 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -331,6 +331,8 @@ private: int m_blocked_fd { -1 }; Vector m_select_read_fds; Vector m_select_write_fds; + timeval m_select_timeout; + bool m_select_has_timeout { false }; size_t m_max_open_file_descriptors { 16 }; SignalActionData m_signal_action_data[32]; dword m_pending_signals { 0 }; diff --git a/Kernel/Scheduler.cpp b/Kernel/Scheduler.cpp index 54c6efad6a..8cc88c2492 100644 --- a/Kernel/Scheduler.cpp +++ b/Kernel/Scheduler.cpp @@ -1,6 +1,8 @@ #include "Scheduler.h" #include "Process.h" #include "system.h" +#include "RTC.h" +#include "i8253.h" //#define LOG_EVERY_CONTEXT_SWITCH //#define SCHEDULER_DEBUG @@ -29,7 +31,7 @@ bool Scheduler::pick_next() } // Check and unblock processes whose wait conditions have been met. - Process::for_each([] (auto& process) { + Process::for_each([] (Process& process) { if (process.state() == Process::BlockedSleep) { if (process.wakeup_time() <= system.uptime) process.unblock(); @@ -71,6 +73,14 @@ bool Scheduler::pick_next() process.unblock(); return true; } + if (process.m_select_has_timeout) { + auto now_sec = RTC::now(); + auto now_usec = PIT::ticks_since_boot() % 1000; + if (now_sec > process.m_select_timeout.tv_sec || (now_sec == process.m_select_timeout.tv_sec && now_usec >= process.m_select_timeout.tv_usec)) { + process.unblock(); + return true; + } + } for (int fd : process.m_select_read_fds) { if (process.m_fds[fd].descriptor->can_read(process)) { process.unblock(); diff --git a/Kernel/i8253.cpp b/Kernel/i8253.cpp index bb6a38f6f6..53ed76f5d9 100644 --- a/Kernel/i8253.cpp +++ b/Kernel/i8253.cpp @@ -57,16 +57,26 @@ asm( #define BASE_FREQUENCY 1193182 +static dword s_ticks_since_boot; + void timer_interrupt_handler(RegisterDump& regs) { IRQHandlerScope scope(IRQ_TIMER); + ++s_ticks_since_boot; Scheduler::timer_tick(regs); } namespace PIT { +dword ticks_since_boot() +{ + return s_ticks_since_boot; +} + void initialize() { + s_ticks_since_boot = 0; + word timer_reload; IO::out8(PIT_CTL, TIMER0_SELECT | WRITE_WORD | MODE_SQUARE_WAVE); diff --git a/Kernel/i8253.h b/Kernel/i8253.h index 58c00af1e5..8f2878ca31 100644 --- a/Kernel/i8253.h +++ b/Kernel/i8253.h @@ -1,9 +1,12 @@ #pragma once -#define TICKS_PER_SECOND 600 +#include + +#define TICKS_PER_SECOND 1000 namespace PIT { void initialize(); +dword ticks_since_boot(); } diff --git a/LibGUI/GEvent.h b/LibGUI/GEvent.h index 44e7de4dec..6909ec493a 100644 --- a/LibGUI/GEvent.h +++ b/LibGUI/GEvent.h @@ -131,7 +131,12 @@ private: class GTimerEvent final : public GEvent { public: - GTimerEvent() : GEvent(GEvent::Timer) { } + explicit GTimerEvent(int timer_id) : GEvent(GEvent::Timer), m_timer_id(timer_id) { } ~GTimerEvent() { } + + int timer_id() const { return m_timer_id; } + +private: + int m_timer_id; }; diff --git a/LibGUI/GEventLoop.cpp b/LibGUI/GEventLoop.cpp index 40e64cd969..fa42115cf8 100644 --- a/LibGUI/GEventLoop.cpp +++ b/LibGUI/GEventLoop.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -137,11 +138,29 @@ void GEventLoop::wait_for_event() FD_ZERO(&rfds); FD_SET(m_event_fd, &rfds); struct timeval timeout = { 0, 0 }; - int rc = select(m_event_fd + 1, &rfds, nullptr, nullptr, m_queued_events.is_empty() ? nullptr : &timeout); + if (!m_timers.is_empty()) + get_next_timer_expiration(timeout); + int rc = select(m_event_fd + 1, &rfds, nullptr, nullptr, (m_queued_events.is_empty() && m_timers.is_empty()) ? nullptr : &timeout); if (rc < 0) { ASSERT_NOT_REACHED(); } + for (auto& it : m_timers) { + auto& timer = *it.value; + if (!timer.has_expired()) + continue; +#ifdef GEVENTLOOP_DEBUG + dbgprintf("GEventLoop: Timer %d has expired, sending GTimerEvent to %p\n", timer.timer_id, timer.owner); +#endif + post_event(timer.owner, make(timer.timer_id)); + if (timer.should_reload) { + timer.reload(); + } else { + // FIXME: Support removing expired timers that don't want to reload. + ASSERT_NOT_REACHED(); + } + } + if (!FD_ISSET(m_event_fd, &rfds)) return; @@ -180,3 +199,53 @@ void GEventLoop::wait_for_event() } } } + +bool GEventLoop::EventLoopTimer::has_expired() const +{ + timeval now; + gettimeofday(&now, nullptr); + return now.tv_sec > fire_time.tv_sec || (now.tv_sec == fire_time.tv_sec && now.tv_usec >= fire_time.tv_usec); +} + +void GEventLoop::EventLoopTimer::reload() +{ + gettimeofday(&fire_time, nullptr); + fire_time.tv_sec += interval / 1000; + fire_time.tv_usec += interval % 1000; +} + +void GEventLoop::get_next_timer_expiration(timeval& soonest) +{ + ASSERT(!m_timers.is_empty()); + bool has_checked_any = false; + for (auto& it : m_timers) { + auto& fire_time = it.value->fire_time; + if (!has_checked_any || fire_time.tv_sec < soonest.tv_sec || (fire_time.tv_sec == soonest.tv_sec && fire_time.tv_usec < soonest.tv_usec)) + soonest = fire_time; + has_checked_any = true; + } +} + +int GEventLoop::register_timer(GObject& object, int milliseconds, bool should_reload) +{ + ASSERT(milliseconds >= 0); + auto timer = make(); + timer->owner = &object; + timer->interval = milliseconds; + timer->reload(); + timer->should_reload = should_reload; + int timer_id = ++m_next_timer_id; // FIXME: This will eventually wrap around. + ASSERT(timer_id); // FIXME: Aforementioned wraparound. + timer->timer_id = timer_id; + m_timers.set(timer->timer_id, move(timer)); + return timer_id; +} + +bool GEventLoop::unregister_timer(int timer_id) +{ + auto it = m_timers.find(timer_id); + if (it == m_timers.end()) + return false; + m_timers.remove(it); + return true; +} diff --git a/LibGUI/GEventLoop.h b/LibGUI/GEventLoop.h index d040c36900..e4063bf64b 100644 --- a/LibGUI/GEventLoop.h +++ b/LibGUI/GEventLoop.h @@ -1,6 +1,7 @@ #pragma once #include "GEvent.h" +#include #include #include @@ -23,6 +24,9 @@ public: bool running() const { return m_running; } + int register_timer(GObject&, int milliseconds, bool should_reload); + bool unregister_timer(int timer_id); + private: void wait_for_event(); void handle_paint_event(const GUI_Event&, GWindow&); @@ -30,6 +34,8 @@ private: void handle_key_event(const GUI_Event&, GWindow&); void handle_window_activation_event(const GUI_Event&, GWindow&); + void get_next_timer_expiration(timeval&); + struct QueuedEvent { GObject* receiver { nullptr }; OwnPtr event; @@ -38,4 +44,19 @@ private: int m_event_fd { -1 }; bool m_running { false }; + + int m_next_timer_id { 1 }; + + struct EventLoopTimer { + int timer_id { 0 }; + int interval { 0 }; + timeval fire_time; + bool should_reload { false }; + GObject* owner { nullptr }; + + void reload(); + bool has_expired() const; + }; + + HashMap> m_timers; }; diff --git a/LibGUI/GObject.cpp b/LibGUI/GObject.cpp index ec03adb47e..be8627dc86 100644 --- a/LibGUI/GObject.cpp +++ b/LibGUI/GObject.cpp @@ -60,12 +60,16 @@ void GObject::start_timer(int ms) dbgprintf("GObject{%p} already has a timer!\n", this); ASSERT_NOT_REACHED(); } + + m_timer_id = GEventLoop::main().register_timer(*this, ms, true); } void GObject::stop_timer() { if (!m_timer_id) return; + bool success = GEventLoop::main().unregister_timer(m_timer_id); + ASSERT(success); m_timer_id = 0; }