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.
This commit is contained in:
parent
9153666e72
commit
95c3442d59
Notes:
sideshowbarker
2024-07-19 15:54:21 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/95c3442d595
10 changed files with 140 additions and 7 deletions
|
@ -83,6 +83,11 @@ public:
|
|||
return find(key) != end();
|
||||
}
|
||||
|
||||
void remove(IteratorType it)
|
||||
{
|
||||
m_table.remove(it);
|
||||
}
|
||||
|
||||
private:
|
||||
HashTable<Entry, EntryTraits> m_table;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -331,6 +331,8 @@ private:
|
|||
int m_blocked_fd { -1 };
|
||||
Vector<int> m_select_read_fds;
|
||||
Vector<int> 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 };
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#define TICKS_PER_SECOND 600
|
||||
#include <AK/Types.h>
|
||||
|
||||
#define TICKS_PER_SECOND 1000
|
||||
|
||||
namespace PIT {
|
||||
|
||||
void initialize();
|
||||
dword ticks_since_boot();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <LibC/stdio.h>
|
||||
#include <LibC/fcntl.h>
|
||||
#include <LibC/string.h>
|
||||
#include <LibC/time.h>
|
||||
#include <LibC/sys/select.h>
|
||||
#include <LibC/gui.h>
|
||||
|
||||
|
@ -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<GTimerEvent>(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<EventLoopTimer>();
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "GEvent.h"
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
|
@ -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<GEvent> 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<int, OwnPtr<EventLoopTimer>> m_timers;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue