From 471dbed6557370adeb557cac0c11d495824c9214 Mon Sep 17 00:00:00 2001 From: visa Date: Sun, 13 Aug 2023 08:29:28 +0000 Subject: [PATCH] kevent: Add precision and abstimer flags for EVFILT_TIMER Add timer precision flags NOTE_SECONDS, NOTE_MSECONDS, NOTE_USECONDS and NOTE_NSECONDS for EVFILT_TIMER. Also, add an initial implementation of NOTE_ABSTIME timers. Similar kevent(2) flags exist on FreeBSD, NetBSD and XNU. Initial diff by and OK aisha@ OK mpi@ --- lib/libc/sys/kqueue.2 | 69 +++++++++-- regress/sys/kern/kqueue/kqueue-timer.c | 161 ++++++++++++++++++++++++- sys/kern/kern_event.c | 91 +++++++++++--- sys/sys/event.h | 9 +- usr.bin/kdump/mksubr | 25 +++- 5 files changed, 329 insertions(+), 26 deletions(-) diff --git a/lib/libc/sys/kqueue.2 b/lib/libc/sys/kqueue.2 index eb9b6e6526e..bb9a1eab9e5 100644 --- a/lib/libc/sys/kqueue.2 +++ b/lib/libc/sys/kqueue.2 @@ -1,4 +1,4 @@ -.\" $OpenBSD: kqueue.2,v 1.47 2022/10/22 06:27:46 jmc Exp $ +.\" $OpenBSD: kqueue.2,v 1.48 2023/08/13 08:29:28 visa Exp $ .\" .\" Copyright (c) 2000 Jonathan Lemon .\" All rights reserved. @@ -26,7 +26,7 @@ .\" .\" $FreeBSD: src/lib/libc/sys/kqueue.2,v 1.18 2001/02/14 08:48:35 guido Exp $ .\" -.Dd $Mdocdate: October 22 2022 $ +.Dd $Mdocdate: August 13 2023 $ .Dt KQUEUE 2 .Os .Sh NAME @@ -457,17 +457,71 @@ Establishes an arbitrary timer identified by .Fa ident . When adding a timer, .Fa data -specifies the timeout period in milliseconds. -The timer will be periodic unless +specifies the timeout period in units described below, or, if +.Dv NOTE_ABSTIME +is set in +.Va fflags , +specifies the absolute time at which the timer should fire. +The timer will repeat unless .Dv EV_ONESHOT -is specified. +is set in +.Va flags +or +.Dv NOTE_ABSTIME +is set in +.Va fflags . On return, .Fa data contains the number of times the timeout has expired since the last call to .Fn kevent . -This filter automatically sets the +This filter automatically sets .Dv EV_CLEAR -flag internally. +in +.Va flags +for periodic timers. +Timers created with +.Dv NOTE_ABSTIME +remain activated on the kqueue once the absolute time has passed unless +.Dv EV_CLEAR +or +.Dv EV_ONESHOT +are also specified. +.Pp +The filter accepts the following flags in the +.Va fflags +argument: +.Bl -tag -width NOTE_MSECONDS +.It Dv NOTE_SECONDS +The timer value in +.Va data +is expressed in seconds. +.It Dv NOTE_MSECONDS +The timer value in +.Va data +is expressed in milliseconds. +.It Dv NOTE_USECONDS +The timer value in +.Va data +is expressed in microseconds. +.It Dv NOTE_NSECONDS +The timer value in +.Va data +is expressed in nanoseconds. +.It Dv NOTE_ABSTIME +The timer value is an absolute time with +.Dv CLOCK_REALTIME +as the reference clock. +.El +.Pp +Note that +.Dv NOTE_SECONDS , +.Dv NOTE_MSECONDS , +.Dv NOTE_USECONDS , +and +.Dv NOTE_NSECONDS +are mutually exclusive; behavior is undefined if more than one are specified. +If a timer value unit is not specified, the default is +.Dv NOTE_MSECONDS . .Pp If an existing timer is re-added, the existing timer and related pending events will be cancelled. @@ -558,6 +612,7 @@ No memory was available to register the event. The specified process to attach to does not exist. .El .Sh SEE ALSO +.Xr clock_gettime 2 , .Xr poll 2 , .Xr read 2 , .Xr select 2 , diff --git a/regress/sys/kern/kqueue/kqueue-timer.c b/regress/sys/kern/kqueue/kqueue-timer.c index 060e753ae2c..81497304160 100644 --- a/regress/sys/kern/kqueue/kqueue-timer.c +++ b/regress/sys/kern/kqueue/kqueue-timer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kqueue-timer.c,v 1.4 2021/06/12 13:30:14 visa Exp $ */ +/* $OpenBSD: kqueue-timer.c,v 1.5 2023/08/13 08:29:28 visa Exp $ */ /* * Copyright (c) 2015 Bret Stephen Lambert * @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -31,9 +32,13 @@ int do_timer(void) { - int kq, n; + static const int units[] = { + NOTE_SECONDS, NOTE_MSECONDS, NOTE_USECONDS, NOTE_NSECONDS + }; struct kevent ev; - struct timespec ts; + struct timespec ts, start, end, now; + int64_t usecs; + int i, kq, n; ASS((kq = kqueue()) >= 0, warn("kqueue")); @@ -68,6 +73,125 @@ do_timer(void) n = kevent(kq, NULL, 0, &ev, 1, &ts); ASSX(n == 1); + /* Test with different time units */ + + for (i = 0; i < sizeof(units) / sizeof(units[0]); i++) { + memset(&ev, 0, sizeof(ev)); + ev.filter = EVFILT_TIMER; + ev.flags = EV_ADD | EV_ENABLE; + ev.fflags = units[i]; + ev.data = 1; + + n = kevent(kq, &ev, 1, NULL, 0, NULL); + ASSX(n != -1); + + ts.tv_sec = 2; /* wait 2s for kqueue timeout */ + ts.tv_nsec = 0; + + n = kevent(kq, NULL, 0, &ev, 1, &ts); + ASSX(n == 1); + + /* Delete timer to clear EV_CLEAR */ + + memset(&ev, 0, sizeof(ev)); + ev.filter = EVFILT_TIMER; + ev.flags = EV_DELETE; + + n = kevent(kq, &ev, 1, NULL, 0, NULL); + ASSX(n != -1); + + /* Test with NOTE_ABSTIME, deadline in the future */ + + clock_gettime(CLOCK_MONOTONIC, &start); + + clock_gettime(CLOCK_REALTIME, &now); + memset(&ev, 0, sizeof(ev)); + ev.filter = EVFILT_TIMER; + ev.flags = EV_ADD | EV_ENABLE; + ev.fflags = NOTE_ABSTIME | units[i]; + + switch (units[i]) { + case NOTE_SECONDS: + ev.data = now.tv_sec + 1; + break; + case NOTE_MSECONDS: + ev.data = now.tv_sec * 1000 + now.tv_nsec / 1000000 + + 100; + break; + case NOTE_USECONDS: + ev.data = now.tv_sec * 1000000 + now.tv_nsec / 1000 + + 100 * 1000; + break; + case NOTE_NSECONDS: + ev.data = now.tv_sec * 1000000000 + now.tv_nsec + + 100 * 1000000; + break; + } + + n = kevent(kq, &ev, 1, NULL, 0, NULL); + ASSX(n != -1); + + ts.tv_sec = 2; /* wait 2s for kqueue timeout */ + ts.tv_nsec = 0; + + n = kevent(kq, NULL, 0, &ev, 1, &ts); + ASSX(n == 1); + + clock_gettime(CLOCK_MONOTONIC, &end); + timespecsub(&end, &start, &ts); + usecs = ts.tv_sec * 1000000 + ts.tv_nsec / 1000; + ASSX(usecs > 0); + ASSX(usecs < 1500000); /* allow wide margin */ + + /* Test with NOTE_ABSTIME, deadline in the past. */ + + clock_gettime(CLOCK_MONOTONIC, &start); + + memset(&ev, 0, sizeof(ev)); + ev.filter = EVFILT_TIMER; + ev.flags = EV_ADD | EV_ENABLE; + ev.fflags = NOTE_ABSTIME | units[i]; + + clock_gettime(CLOCK_REALTIME, &now); + switch (units[i]) { + case NOTE_SECONDS: + ev.data = now.tv_sec - 1; + break; + case NOTE_MSECONDS: + ev.data = now.tv_sec * 1000 + now.tv_nsec / 1000000 + - 100; + break; + case NOTE_USECONDS: + ev.data = now.tv_sec * 1000000 + now.tv_nsec / 1000 + - 100 * 1000; + break; + case NOTE_NSECONDS: + ev.data = now.tv_sec * 1000000000 + now.tv_nsec + - 100 * 1000000; + break; + } + + n = kevent(kq, &ev, 1, NULL, 0, NULL); + ASSX(n != -1); + + n = kevent(kq, NULL, 0, &ev, 1, &ts); + ASSX(n == 1); + + clock_gettime(CLOCK_MONOTONIC, &end); + timespecsub(&end, &start, &ts); + usecs = ts.tv_sec * 1000000 + ts.tv_nsec / 1000; + ASSX(usecs > 0); + ASSX(usecs < 100000); /* allow wide margin */ + + /* Test that the event remains active */ + + ts.tv_sec = 2; /* wait 2s for kqueue timeout */ + ts.tv_nsec = 0; + + n = kevent(kq, NULL, 0, &ev, 1, &ts); + ASSX(n == 1); + } + return (0); } @@ -96,6 +220,37 @@ do_invalid_timer(void) (long long)invalid_ts[i].tv_sec, invalid_ts[i].tv_nsec)); } + /* Test invalid fflags */ + + memset(&ev, 0, sizeof(ev)); + ev.filter = EVFILT_TIMER; + ev.flags = EV_ADD | EV_ENABLE; + ev.fflags = ~NOTE_SECONDS; + ev.data = 1; + + n = kevent(kq, &ev, 1, NULL, 0, NULL); + ASSX(n == -1 && errno == EINVAL); + + memset(&ev, 0, sizeof(ev)); + ev.filter = EVFILT_TIMER; + ev.flags = EV_ADD | EV_ENABLE; + ev.fflags = NOTE_MSECONDS; + ev.data = 500; + + n = kevent(kq, &ev, 1, NULL, 0, NULL); + ASSX(n == 0); + + /* Modify the existing timer */ + + memset(&ev, 0, sizeof(ev)); + ev.filter = EVFILT_TIMER; + ev.flags = EV_ADD | EV_ENABLE; + ev.fflags = ~NOTE_SECONDS; + ev.data = 1; + + n = kevent(kq, &ev, 1, NULL, 0, NULL); + ASSX(n == -1 && errno == EINVAL); + return (0); } diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c index fabc238ce3e..a2933832b2c 100644 --- a/sys/kern/kern_event.c +++ b/sys/kern/kern_event.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kern_event.c,v 1.196 2023/04/11 00:45:09 jsg Exp $ */ +/* $OpenBSD: kern_event.c,v 1.197 2023/08/13 08:29:28 visa Exp $ */ /*- * Copyright (c) 1999,2000,2001 Jonathan Lemon @@ -449,17 +449,61 @@ filt_proc(struct knote *kn, long hint) return (kn->kn_fflags != 0); } +#define NOTE_TIMER_UNITMASK \ + (NOTE_SECONDS|NOTE_MSECONDS|NOTE_USECONDS|NOTE_NSECONDS) + +static int +filt_timervalidate(int sfflags, int64_t sdata, struct timespec *ts) +{ + if (sfflags & ~(NOTE_TIMER_UNITMASK | NOTE_ABSTIME)) + return (EINVAL); + + switch (sfflags & NOTE_TIMER_UNITMASK) { + case NOTE_SECONDS: + ts->tv_sec = sdata; + ts->tv_nsec = 0; + break; + case NOTE_MSECONDS: + ts->tv_sec = sdata / 1000; + ts->tv_nsec = (sdata % 1000) * 1000000; + break; + case NOTE_USECONDS: + ts->tv_sec = sdata / 1000000; + ts->tv_nsec = (sdata % 1000000) * 1000; + break; + case NOTE_NSECONDS: + ts->tv_sec = sdata / 1000000000; + ts->tv_nsec = sdata % 1000000000; + break; + default: + return (EINVAL); + } + + return (0); +} + static void -filt_timer_timeout_add(struct knote *kn) +filt_timeradd(struct knote *kn, struct timespec *ts) { - struct timeval tv; + struct timespec expiry, now; struct timeout *to = kn->kn_hook; int tticks; - tv.tv_sec = kn->kn_sdata / 1000; - tv.tv_usec = (kn->kn_sdata % 1000) * 1000; - tticks = tvtohz(&tv); - /* Remove extra tick from tvtohz() if timeout has fired before. */ + if (kn->kn_sfflags & NOTE_ABSTIME) { + nanotime(&now); + if (timespeccmp(ts, &now, >)) { + timespecsub(ts, &now, &expiry); + /* XXX timeout_abs_ts with CLOCK_REALTIME */ + timeout_add(to, tstohz(&expiry)); + } else { + /* Expire immediately. */ + filt_timerexpire(kn); + } + return; + } + + tticks = tstohz(ts); + /* Remove extra tick from tstohz() if timeout has fired before. */ if (timeout_triggered(to)) tticks--; timeout_add(to, (tticks > 0) ? tticks : 1); @@ -468,6 +512,7 @@ filt_timer_timeout_add(struct knote *kn) void filt_timerexpire(void *knx) { + struct timespec ts; struct knote *kn = knx; struct kqueue *kq = kn->kn_kq; @@ -476,28 +521,37 @@ filt_timerexpire(void *knx) knote_activate(kn); mtx_leave(&kq->kq_lock); - if ((kn->kn_flags & EV_ONESHOT) == 0) - filt_timer_timeout_add(kn); + if ((kn->kn_flags & EV_ONESHOT) == 0 && + (kn->kn_sfflags & NOTE_ABSTIME) == 0) { + (void)filt_timervalidate(kn->kn_sfflags, kn->kn_sdata, &ts); + filt_timeradd(kn, &ts); + } } - /* - * data contains amount of time to sleep, in milliseconds + * data contains amount of time to sleep */ int filt_timerattach(struct knote *kn) { + struct timespec ts; struct timeout *to; + int error; + + error = filt_timervalidate(kn->kn_sfflags, kn->kn_sdata, &ts); + if (error != 0) + return (error); if (kq_ntimeouts > kq_timeoutmax) return (ENOMEM); kq_ntimeouts++; - kn->kn_flags |= EV_CLEAR; /* automatically set */ + if ((kn->kn_sfflags & NOTE_ABSTIME) == 0) + kn->kn_flags |= EV_CLEAR; /* automatically set */ to = malloc(sizeof(*to), M_KEVENT, M_WAITOK); timeout_set(to, filt_timerexpire, kn); kn->kn_hook = to; - filt_timer_timeout_add(kn); + filt_timeradd(kn, &ts); return (0); } @@ -516,8 +570,17 @@ filt_timerdetach(struct knote *kn) int filt_timermodify(struct kevent *kev, struct knote *kn) { + struct timespec ts; struct kqueue *kq = kn->kn_kq; struct timeout *to = kn->kn_hook; + int error; + + error = filt_timervalidate(kev->fflags, kev->data, &ts); + if (error != 0) { + kev->flags |= EV_ERROR; + kev->data = error; + return (0); + } /* Reset the timer. Any pending events are discarded. */ @@ -533,7 +596,7 @@ filt_timermodify(struct kevent *kev, struct knote *kn) knote_assign(kev, kn); /* Reinit timeout to invoke tick adjustment again. */ timeout_set(to, filt_timerexpire, kn); - filt_timer_timeout_add(kn); + filt_timeradd(kn, &ts); return (0); } diff --git a/sys/sys/event.h b/sys/sys/event.h index 05139cfe751..cc5a69e6a8b 100644 --- a/sys/sys/event.h +++ b/sys/sys/event.h @@ -1,4 +1,4 @@ -/* $OpenBSD: event.h,v 1.69 2023/02/10 14:34:17 visa Exp $ */ +/* $OpenBSD: event.h,v 1.70 2023/08/13 08:29:28 visa Exp $ */ /*- * Copyright (c) 1999,2000,2001 Jonathan Lemon @@ -122,6 +122,13 @@ struct kevent { /* data/hint flags for EVFILT_DEVICE, shared with userspace */ #define NOTE_CHANGE 0x00000001 /* device change event */ +/* additional flags for EVFILT_TIMER */ +#define NOTE_MSECONDS 0x00000000 /* data is milliseconds */ +#define NOTE_SECONDS 0x00000001 /* data is seconds */ +#define NOTE_USECONDS 0x00000002 /* data is microseconds */ +#define NOTE_NSECONDS 0x00000003 /* data is nanoseconds */ +#define NOTE_ABSTIME 0x00000010 /* timeout is absolute */ + /* * This is currently visible to userland to work around broken * programs which pull in or . diff --git a/usr.bin/kdump/mksubr b/usr.bin/kdump/mksubr index 6dd94c00a45..2762a0d97a2 100644 --- a/usr.bin/kdump/mksubr +++ b/usr.bin/kdump/mksubr @@ -1,5 +1,5 @@ #!/bin/sh -# $OpenBSD: mksubr,v 1.39 2022/12/19 22:55:12 guenther Exp $ +# $OpenBSD: mksubr,v 1.40 2023/08/13 08:29:28 visa Exp $ # # Copyright (c) 2006 David Kirchner # @@ -560,6 +560,29 @@ _EOF_ printf "\t\tif_print_or(fflags, %s, or);\n", $i }' cat <<_EOF_ break; + case EVFILT_TIMER: +#define NOTE_TIMER_UNITMASK \ + (NOTE_SECONDS|NOTE_MSECONDS|NOTE_USECONDS|NOTE_NSECONDS) + switch (fflags & NOTE_TIMER_UNITMASK) { + case NOTE_SECONDS: + printf("NOTE_SECONDS"); + break; + case NOTE_MSECONDS: + printf("NOTE_MSECONDS"); + break; + case NOTE_USECONDS: + printf("NOTE_USECONDS"); + break; + case NOTE_NSECONDS: + printf("NOTE_NSECONDS"); + break; + default: + printf("invalid"); + break; + } + or = 1; + if_print_or(fflags, NOTE_ABSTIME, or); + break; } printf(">"); } -- 2.20.1