Support periodic kernel timers through shared event queue deadlines

This commit is contained in:
nmzik
2026-10-03 16:07:22 +04:00
parent a0858abe35
commit 92c22d60ef
4 changed files with 95 additions and 12 deletions
+26 -12
View File
@@ -138,7 +138,13 @@ int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) {
void KernelEqueuePrivate::TriggerExpiredTimers(uint64_t now_ns) {
for (auto& event: m_events) {
if (!event.triggered && event.deadline_ns != 0 && event.deadline_ns <= now_ns) {
if (event.deadline_ns != 0 && event.deadline_ns <= now_ns) {
if (event.event.filter == KERNEL_EVFILT_TIMER) {
const auto count = event.interval_ns == 0 ? !event.triggered :
1 + (now_ns - event.deadline_ns) / event.interval_ns;
event.event.data += static_cast<intptr_t>(count);
event.deadline_ns += count * event.interval_ns;
}
event.triggered = true;
}
}
@@ -209,7 +215,9 @@ int KernelEqueuePrivate::AddEvent(const KernelEqueueEvent& event) {
return e.event.ident == ident && e.event.filter == filter;
});
if (it != m_events.end()) {
TriggerExpiredTimers(MonotonicTimeNs());
it->deadline_ns = event.deadline_ns;
it->interval_ns = event.interval_ns;
it->event.udata = event.event.udata;
for (auto& pending: it->pending_events) {
pending.udata = event.event.udata;
@@ -511,6 +519,22 @@ int KYTY_SYSV_ABI KernelDeleteUserEvent(KernelEqueue eq, int id) {
return KernelDeleteEvent(eq, static_cast<uintptr_t>(id), KERNEL_EVFILT_USER);
}
static int AddTimerEvent(KernelEqueue eq, int id, uint64_t delay_ns, bool periodic, void* udata) {
const auto now_ns = MonotonicTimeNs();
KernelEqueueEvent event {};
event.deadline_ns = delay_ns <= UINT64_MAX - now_ns ? now_ns + delay_ns : UINT64_MAX;
event.interval_ns = periodic ? delay_ns : 0;
event.event.ident = static_cast<uintptr_t>(id);
event.event.filter = periodic ? KERNEL_EVFILT_TIMER : KERNEL_EVFILT_HRTIMER;
event.event.flags = EV_ADD | (periodic ? EV_CLEAR : EV_ONESHOT);
event.event.udata = udata;
return KernelAddEvent(eq, event);
}
int KYTY_SYSV_ABI KernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec, void* udata) {
return AddTimerEvent(eq, id, static_cast<uint64_t>(usec) * 1000, true, udata);
}
int KYTY_SYSV_ABI KernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts,
void* udata) {
if (ts == nullptr) {
@@ -524,17 +548,7 @@ int KYTY_SYSV_ABI KernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTim
const auto delay_ns =
static_cast<uint64_t>(ts->tv_sec) * 1000000000ull + static_cast<uint64_t>(ts->tv_nsec);
const auto now_ns = MonotonicTimeNs();
KernelEqueueEvent event {};
event.deadline_ns = delay_ns <= UINT64_MAX - now_ns ? now_ns + delay_ns : UINT64_MAX;
event.event.ident = static_cast<uintptr_t>(id);
event.event.filter = KERNEL_EVFILT_HRTIMER;
event.event.flags = EV_ADD | EV_ONESHOT;
event.event.fflags = 0;
event.event.data = 0;
event.event.udata = udata;
return KernelAddEvent(eq, event);
return AddTimerEvent(eq, id, delay_ns, false, udata);
}
int KYTY_SYSV_ABI KernelDeleteHRTimerEvent(KernelEqueue eq, int id) {
+3
View File
@@ -51,6 +51,7 @@ struct KernelFilter {
struct KernelEqueueEvent {
bool triggered = false;
uint64_t deadline_ns = 0;
uint64_t interval_ns = 0;
KernelEvent event;
KernelFilter filter;
std::deque<KernelEvent> pending_events;
@@ -72,6 +73,8 @@ int KYTY_SYSV_ABI KernelAddUserEventEdge(KernelEqueue eq, int id);
int KYTY_SYSV_ABI KernelTriggerUserEvent(KernelEqueue eq, int id, void* udata);
int KYTY_SYSV_ABI KernelTriggerUserEventForAll(int id, void* udata);
int KYTY_SYSV_ABI KernelDeleteUserEvent(KernelEqueue eq, int id);
int KYTY_SYSV_ABI KernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec,
void* udata);
int KYTY_SYSV_ABI KernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts,
void* udata);
int KYTY_SYSV_ABI KernelDeleteHRTimerEvent(KernelEqueue eq, int id);
+1
View File
@@ -3148,6 +3148,7 @@ LIB_DEFINE(InitLibKernel_1_Equeue) {
LIB_FUNC("WDszmSbWuDk", EventQueue::KernelAddUserEventEdge);
LIB_FUNC("F6e0kwo4cnk", EventQueue::KernelTriggerUserEvent);
LIB_FUNC("LJDwdSNTnDg", EventQueue::KernelDeleteUserEvent);
LIB_FUNC("57ZK+ODEXWY", EventQueue::KernelAddTimerEvent);
LIB_FUNC("R74tt43xP6k", EventQueue::KernelAddHRTimerEvent);
LIB_FUNC("J+LF6LwObXU", EventQueue::KernelDeleteHRTimerEvent);
LIB_FUNC("bBfz7kMF2Ho", EventQueue::KernelAddAmprEvent);
+65
View File
@@ -3,6 +3,7 @@
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <memory>
@@ -27,6 +28,69 @@ void CheckConcurrentResult(int result, const char* text) {
Check(result == OK || result == KERNEL_ERROR_EBADF || result == KERNEL_ERROR_ENOENT, text);
}
void TestPeriodicTimerEvents() {
using namespace std::chrono_literals;
using Libs::LibKernel::KERNEL_ERROR_ETIMEDOUT;
Check(EventQueue::KernelAddTimerEvent(EventQueue::KERNEL_EQUEUE_INVALID, 10, 1000, nullptr) ==
KERNEL_ERROR_EBADF,
"timer rejects invalid queue");
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
Check(EventQueue::KernelCreateEqueue(&queue, "periodic-timer") == OK, "create timer queue");
auto* user_data = reinterpret_cast<void*>(0x1234);
Check(EventQueue::KernelAddTimerEvent(queue, 10, 1000, user_data) == OK, "add periodic timer");
std::this_thread::sleep_for(5ms);
EventQueue::KernelEvent events[2] {};
int out = 0;
Libs::LibKernel::KernelUseconds timeout = 0;
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1,
"periodic occurrences are aggregated into one event");
Check(events[0].ident == 10 && events[0].filter == EventQueue::KERNEL_EVFILT_TIMER &&
events[0].data >= 5 && events[0].udata == user_data,
"timer reports identity, userdata and elapsed occurrence count");
timeout = 100000;
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1 &&
events[0].data >= 1,
"periodic timer remains registered after delivery");
std::this_thread::sleep_for(5ms);
user_data = reinterpret_cast<void*>(0x5678);
Check(EventQueue::KernelAddTimerEvent(queue, 10, 3000000000u, user_data) == OK,
"update periodic timer interval");
timeout = 0;
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1 &&
events[0].data >= 5 && events[0].udata == user_data,
"timer update preserves accumulated occurrences and updates userdata");
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == KERNEL_ERROR_ETIMEDOUT,
"timer update replaces the repeating interval");
Check(EventQueue::KernelDeleteEvent(queue, 10, EventQueue::KERNEL_EVFILT_TIMER) == OK,
"delete periodic timer");
Check(EventQueue::KernelAddTimerEvent(queue, 10, 0, user_data) == OK,
"add immediate periodic timer");
for (int i = 0; i < 2; ++i) {
Check(EventQueue::KernelAddTimerEvent(queue, 10, 0, user_data) == OK,
"update pending immediate timer");
}
for (int i = 0; i < 2; ++i) {
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1 &&
events[0].data == 1,
"zero interval remains immediately ready after delivery");
}
Check(EventQueue::KernelDeleteEvent(queue, 10, EventQueue::KERNEL_EVFILT_TIMER) == OK,
"delete immediate timer");
const Libs::LibKernel::KernelTimespec delay {0, 1000000};
Check(EventQueue::KernelAddHRTimerEvent(queue, 10, &delay, user_data) == OK,
"add high resolution timer");
timeout = 100000;
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK && out == 1 &&
events[0].filter == EventQueue::KERNEL_EVFILT_HRTIMER && events[0].udata == user_data,
"shared timer scheduling delivers high resolution event");
Check(EventQueue::KernelDeleteHRTimerEvent(queue, 10) == KERNEL_ERROR_ENOENT,
"high resolution timer is removed after one delivery");
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete timer queue");
}
void CountDeletedEvent(EventQueue::KernelEqueue, EventQueue::KernelEqueueEvent* event) {
auto* count = static_cast<std::atomic_uint32_t*>(event->filter.data);
count->fetch_add(1, std::memory_order_relaxed);
@@ -431,6 +495,7 @@ void TestConcurrentDelete() {
} // namespace
int main() {
TestPeriodicTimerEvents();
TestDuplicateAddPreservesEventState();
TestCallbackStateOutlivesPort();
TestCallbackOwnsPayload();