From 0eab1db3adfdb53025c737048df689215c9900ac Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 12 Oct 2026 12:25:44 +0100 Subject: [PATCH 1/4] Add POSIX timers for signal notification POSIX timer syscalls return ENOSYS. Add support for up to 64 realtime or monotonic timers with signal or silent notification. Use the existing signal queues for delivery and overrun counts. Fork does not inherit timers, and exec removes them. --- src/syscall/abi.h | 5 + src/syscall/dispatch.tbl | 5 + src/syscall/fd.c | 16 +-- src/syscall/linux-wire.h | 14 ++ src/syscall/signal.c | 288 ++++++++++++++++++++++++++++++++++++++- src/syscall/signal.h | 18 +++ src/syscall/syscall.c | 5 + src/syscall/time.c | 64 +++++++++ src/syscall/time.h | 10 ++ 9 files changed, 414 insertions(+), 11 deletions(-) diff --git a/src/syscall/abi.h b/src/syscall/abi.h index 369bfda52..45eb38964 100644 --- a/src/syscall/abi.h +++ b/src/syscall/abi.h @@ -97,6 +97,11 @@ #define SYS_getitimer 102 #define SYS_setitimer 103 #define SYS_nanosleep 101 +#define SYS_timer_create 107 +#define SYS_timer_gettime 108 +#define SYS_timer_getoverrun 109 +#define SYS_timer_settime 110 +#define SYS_timer_delete 111 #define SYS_clock_gettime 113 #define SYS_clock_getres 114 #define SYS_clock_nanosleep 115 diff --git a/src/syscall/dispatch.tbl b/src/syscall/dispatch.tbl index 1f47ff0bc..77220ab1c 100644 --- a/src/syscall/dispatch.tbl +++ b/src/syscall/dispatch.tbl @@ -124,6 +124,11 @@ SYS_rt_tgsigqueueinfo sc_rt_tgsigqueueinfo 1 SYS_nanosleep sc_nanosleep 1 SYS_getitimer sc_getitimer 1 SYS_setitimer sc_setitimer 1 +SYS_timer_create sc_timer_create 0 +SYS_timer_gettime sc_timer_gettime 0 +SYS_timer_getoverrun sc_timer_getoverrun 0 +SYS_timer_settime sc_timer_settime 1 +SYS_timer_delete sc_timer_delete 0 SYS_clock_gettime sc_clock_gettime 0 SYS_clock_getres sc_clock_getres 0 SYS_clock_nanosleep sc_clock_nanosleep 1 diff --git a/src/syscall/fd.c b/src/syscall/fd.c index b905da4d6..57685330e 100644 --- a/src/syscall/fd.c +++ b/src/syscall/fd.c @@ -82,19 +82,12 @@ static int sfd_alloc_slot(const void *state, size_t count, size_t stride) * timerfd return an 8-byte counter of expirations. */ -/* Linux itimerspec for timerfd_settime/gettime */ -typedef struct { - int64_t it_interval_sec, it_interval_nsec, it_value_sec, it_value_nsec; -} linux_itimerspec_t; - /* Per-timerfd state (stored alongside the FD_TIMERFD entry) */ #define TIMERFD_MAX 32 #define LINUX_TFD_NONBLOCK LINUX_O_NONBLOCK #define LINUX_TFD_CLOEXEC LINUX_O_CLOEXEC #define LINUX_TFD_TIMER_ABSTIME 1 #define LINUX_TFD_TIMER_CANCEL_ON_SET 2 -#define LINUX_CLOCK_REALTIME 0 -#define LINUX_CLOCK_MONOTONIC 1 /* Linux CLOCK_BOOTTIME counts time including suspend; macOS has no equivalent. * timerfd_settime treats non-REALTIME slots as MONOTONIC for ABSTIME @@ -1318,10 +1311,15 @@ int64_t signalfd_read(int guest_fd, memset(&info, 0, sizeof(info)); info.ssi_signo = (uint32_t) pending[i].signum; info.ssi_code = pending[i].si_code; - info.ssi_pid = (uint32_t) pending[i].si_pid; - info.ssi_uid = pending[i].si_uid; info.ssi_int = pending[i].si_int; info.ssi_ptr = pending[i].si_ptr; + if (info.ssi_code == LINUX_SI_TIMER) { + info.ssi_tid = (uint32_t) pending[i].si_pid; + info.ssi_overrun = pending[i].si_uid; + } else { + info.ssi_pid = (uint32_t) pending[i].si_pid; + info.ssi_uid = pending[i].si_uid; + } uint64_t off = i * sizeof(linux_signalfd_siginfo_t); if (guest_write_small(g, buf_gva + off, &info, sizeof(info)) < 0) { diff --git a/src/syscall/linux-wire.h b/src/syscall/linux-wire.h index 7c7e1410b..08f945b74 100644 --- a/src/syscall/linux-wire.h +++ b/src/syscall/linux-wire.h @@ -377,6 +377,20 @@ typedef struct { int64_t tv_sec, tv_nsec; } linux_timespec_t; +/* Linux clocks and POSIX timer flags. */ +#define LINUX_CLOCK_REALTIME 0 +#define LINUX_CLOCK_MONOTONIC 1 +#define LINUX_TIMER_ABSTIME 1 +#define LINUX_SIGEV_SIGNAL 0 +#define LINUX_SIGEV_NONE 1 + +/* Linux struct itimerspec, shared by POSIX timers and timerfd. */ +typedef struct { + int64_t it_interval_sec, it_interval_nsec, it_value_sec, it_value_nsec; +} linux_itimerspec_t; + +_Static_assert(sizeof(linux_itimerspec_t) == 32, "Linux itimerspec size"); + /* Linux struct timeval (aarch64). */ typedef struct { int64_t tv_sec, tv_usec; diff --git a/src/syscall/signal.c b/src/syscall/signal.c index ccf62ad32..f9ec3a20c 100644 --- a/src/syscall/signal.c +++ b/src/syscall/signal.c @@ -71,6 +71,7 @@ #include "syscall/fd.h" /* signalfd_notify */ #include "syscall/proc.h" /* proc_get_pid, proc_get_uid, SYSCALL_EXEC_HAPPENED */ #include "proved/sigframe.h" +#include "proved/timespec.h" #include "syscall/signal.h" #include "syscall/time.h" /* linux_timespec_valid, linux_timespec_to_ns_sat */ #include "syscall/wakeup-pipe.h" @@ -248,6 +249,52 @@ static guest_itimer_t guest_itimer; /* ITIMER_REAL -> SIGALRM */ static guest_itimer_t guest_itimer_virt; /* ITIMER_VIRTUAL -> SIGVTALRM */ static guest_itimer_t guest_itimer_prof; /* ITIMER_PROF -> SIGPROF */ +/* Bounded per-process storage; Linux does not inherit POSIX timers at fork. */ +#define POSIX_TIMER_MAX 64 +typedef struct { + int32_t id, guest_clockid; + int signum; + int overruns, last_overrun; + clockid_t host_clock; + uint64_t value; + int64_t expiry_ns, interval_ns; + bool used, signal_pending, signal_queued; +} guest_posix_timer_t; + +/* sig_lock protects the slots and IDs. Release stores to the armed hint pair + * with acquire loads in posix_timer_check and signal_attention_needed; slot + * access still requires sig_lock. + */ +static guest_posix_timer_t posix_timers[POSIX_TIMER_MAX]; +/* Keep IDs unique so a deleted timer's queued signal cannot name a new timer. + */ +static int posix_next_id; +static _Atomic bool posix_timers_armed; + +static guest_posix_timer_t *posix_timer_find_locked(int id) +{ + for (int i = 0; i < POSIX_TIMER_MAX; i++) { + if (posix_timers[i].used && posix_timers[i].id == id) + return &posix_timers[i]; + } + return NULL; +} + +static void posix_timer_dequeued_locked(signal_rt_info_t *info) +{ + if (info->si_code != LINUX_SI_TIMER) + return; + guest_posix_timer_t *timer = posix_timer_find_locked(info->si_pid); + if (!timer) + return; + /* Linux's timer ID and overrun share the siginfo pid/uid union slots. */ + info->si_uid = (uint32_t) timer->overruns; + timer->last_overrun = timer->overruns; + timer->overruns = 0; + timer->signal_pending = false; + timer->signal_queued = false; +} + /* Default disposition table. Index 0 unused (signals are 1-based). */ static const sig_disposition_t default_dispositions[LINUX_NSIG + 1] = { [0] = SIG_DISP_IGN, /* Invalid signal 0 */ @@ -373,8 +420,10 @@ static bool signal_rt_dequeue_locked(signal_pending_t *p, return false; } + signal_rt_info_t info = p->rt_info[idx][p->rt_head[idx]]; + posix_timer_dequeued_locked(&info); if (out) - *out = p->rt_info[idx][p->rt_head[idx]]; + *out = info; p->rt_head[idx] = (uint8_t) ((p->rt_head[idx] + 1) % RT_SIGQUEUE_MAX); p->rt_queue[idx]--; if (p->rt_queue[idx] == 0) { @@ -575,6 +624,8 @@ void signal_reset_for_exec(void) thread_entry_t *t = current_thread; pthread_mutex_lock(&sig_lock); + memset(posix_timers, 0, sizeof(posix_timers)); + atomic_store_explicit(&posix_timers_armed, false, memory_order_release); for (int i = 0; i < LINUX_NSIG; i++) { /* POSIX: handlers reset to SIG_DFL, except SIG_IGN stays SIG_IGN. * Pending signals and signal mask are preserved across exec. @@ -812,7 +863,8 @@ bool signal_attention_needed(void) */ if (atomic_load_explicit(&guest_itimer.active, memory_order_acquire) || atomic_load_explicit(&guest_itimer_virt.active, memory_order_acquire) || - atomic_load_explicit(&guest_itimer_prof.active, memory_order_acquire)) + atomic_load_explicit(&guest_itimer_prof.active, memory_order_acquire) || + atomic_load_explicit(&posix_timers_armed, memory_order_acquire)) return true; return false; } @@ -1153,6 +1205,7 @@ size_t signal_take_signalfd_exact(const signal_rt_info_t *expected, current.si_int != want->si_int || current.si_ptr != want->si_ptr) break; + posix_timer_dequeued_locked(¤t); sp->std_info_valid[signum - 1] = false; pending_clear(&sp->pending, bit); } @@ -1389,6 +1442,232 @@ void signal_get_itimer(struct timeval *value, struct timeval *interval) pthread_mutex_unlock(&sig_lock); } +static int64_t posix_timer_now(clockid_t clockid) +{ + struct timespec now; + clock_gettime(clockid, &now); + return timespec_to_ns_sat(now.tv_sec, now.tv_nsec); +} + +static void posix_timer_refresh_armed_locked(void) +{ + bool armed = false; + for (int i = 0; i < POSIX_TIMER_MAX; i++) { + const guest_posix_timer_t *timer = &posix_timers[i]; + armed |= timer->used && (timer->expiry_ns || (timer->signal_pending && + !timer->signal_queued)); + } + atomic_store_explicit(&posix_timers_armed, armed, memory_order_release); +} + +static void posix_timer_value_locked(const guest_posix_timer_t *timer, + linux_itimerspec_t *value) +{ + int64_t now = posix_timer_now(timer->host_clock); + int64_t remaining = timer->expiry_ns > now ? timer->expiry_ns - now : 0; + *value = (linux_itimerspec_t) { + .it_interval_sec = timer->interval_ns / NSEC_PER_SEC, + .it_interval_nsec = timer->interval_ns % NSEC_PER_SEC, + .it_value_sec = remaining / NSEC_PER_SEC, + .it_value_nsec = remaining % NSEC_PER_SEC, + }; +} + +/* Count expired periods, then disarm or advance past now. */ +static uint64_t posix_timer_advance_locked(guest_posix_timer_t *timer) +{ + if (!timer->used || !timer->expiry_ns) + return 0; + int64_t now = posix_timer_now(timer->host_clock); + if (now < timer->expiry_ns) + return 0; + if (!timer->interval_ns) { + timer->expiry_ns = 0; + return 1; + } + + int64_t elapsed = now - timer->expiry_ns; + uint64_t count = 1 + (uint64_t) (elapsed / timer->interval_ns); + int64_t step = timer->interval_ns - elapsed % timer->interval_ns; + if (__builtin_add_overflow(now, step, &timer->expiry_ns)) + timer->expiry_ns = INT64_MAX; + return count; +} + +/* signalfd peeks before consuming, so its queued copy needs current overruns. + */ +static void posix_timer_update_pending_locked(const guest_posix_timer_t *timer) +{ + signal_pending_t *shared = &sig_state.shared; + if (timer->signum < LINUX_SIGRTMIN) { + signal_rt_info_t *info = &shared->std_info[timer->signum - 1]; + if (info->si_code == LINUX_SI_TIMER && info->si_pid == timer->id) + info->si_uid = (uint32_t) timer->overruns; + return; + } + + int idx = timer->signum - LINUX_SIGRTMIN; + for (int n = 0; n < shared->rt_queue[idx]; n++) { + int slot = (shared->rt_head[idx] + n) % RT_SIGQUEUE_MAX; + signal_rt_info_t *info = &shared->rt_info[idx][slot]; + if (info->si_code == LINUX_SI_TIMER && info->si_pid == timer->id) + info->si_uid = (uint32_t) timer->overruns; + } +} + +static void posix_timer_check(void) +{ + if (!atomic_load_explicit(&posix_timers_armed, memory_order_acquire)) + return; + uint64_t notify = 0; + pthread_mutex_lock(&sig_lock); + for (int i = 0; i < POSIX_TIMER_MAX; i++) { + guest_posix_timer_t *timer = &posix_timers[i]; + uint64_t count = posix_timer_advance_locked(timer); + if (!timer->used || !timer->signum) + continue; + if (count) { + uint64_t missed = timer->signal_pending ? count : count - 1; + timer->overruns = missed > (uint64_t) (INT32_MAX - timer->overruns) + ? INT32_MAX + : timer->overruns + (int) missed; + timer->signal_pending = true; + } + if (timer->signal_queued) { + posix_timer_update_pending_locked(timer); + continue; + } + if (!timer->signal_pending) + continue; + /* Each timer retains its notification until the shared queue has room, + * including when another timer holds the same standard signal. + */ + if (timer->signum < LINUX_SIGRTMIN && + (pending_load(&sig_state.shared.pending) & sig_bit(timer->signum))) + continue; + if (timer->signum >= LINUX_SIGRTMIN && + sig_state.shared.rt_queue[timer->signum - LINUX_SIGRTMIN] >= + RT_SIGQUEUE_MAX) + continue; + signal_rt_info_t info = { + .signum = timer->signum, + .si_code = LINUX_SI_TIMER, + .si_pid = timer->id, + .si_uid = (uint32_t) timer->overruns, + .si_int = (int32_t) timer->value, + .si_ptr = timer->value, + }; + signal_enqueue_locked(&sig_state.shared, timer->signum, &info); + timer->signal_queued = true; + notify |= sig_bit(timer->signum); + } + posix_timer_refresh_armed_locked(); + refresh_pending_hint_locked(); + pthread_mutex_unlock(&sig_lock); + for (uint64_t pending = notify; pending; pending &= pending - 1) + signalfd_notify(bit_ctz64(pending) + 1); + if (notify) + attention_raise(); +} + +int signal_posix_timer_create(int clockid, + int notify, + int signum, + uint64_t value, + bool default_event) +{ + if (clockid != LINUX_CLOCK_REALTIME && clockid != LINUX_CLOCK_MONOTONIC) + return -LINUX_EINVAL; + if (notify != LINUX_SIGEV_SIGNAL && notify != LINUX_SIGEV_NONE) + return -LINUX_EINVAL; + if (notify == LINUX_SIGEV_SIGNAL && !RANGE_CHECK(signum, 1, LINUX_NSIG + 1)) + return -LINUX_EINVAL; + int ret = -LINUX_EAGAIN; + pthread_mutex_lock(&sig_lock); + for (int i = 0; i < POSIX_TIMER_MAX && posix_next_id < INT32_MAX; i++) { + if (posix_timers[i].used) + continue; + ret = posix_next_id++; + posix_timers[i] = (guest_posix_timer_t) { + .id = ret, + .guest_clockid = clockid, + .host_clock = CLOCK_MONOTONIC, + .signum = notify == LINUX_SIGEV_SIGNAL ? signum : 0, + .value = default_event ? (uint64_t) ret : value, + .used = true, + }; + break; + } + pthread_mutex_unlock(&sig_lock); + return ret; +} + +int signal_posix_timer_get(int id, linux_itimerspec_t *value) +{ + posix_timer_check(); + pthread_mutex_lock(&sig_lock); + guest_posix_timer_t *timer = posix_timer_find_locked(id); + if (timer) + posix_timer_value_locked(timer, value); + pthread_mutex_unlock(&sig_lock); + return timer ? 0 : -LINUX_EINVAL; +} + +int signal_posix_timer_set(int id, + int flags, + const linux_itimerspec_t *value, + linux_itimerspec_t *old) +{ + posix_timer_check(); + pthread_mutex_lock(&sig_lock); + guest_posix_timer_t *timer = posix_timer_find_locked(id); + if (!timer) { + pthread_mutex_unlock(&sig_lock); + return -LINUX_EINVAL; + } + posix_timer_value_locked(timer, old); + timer->last_overrun = 0; + bool absolute = (flags & LINUX_TIMER_ABSTIME) != 0; + /* Relative realtime timers must ignore wall-clock adjustments. */ + timer->host_clock = absolute && timer->guest_clockid == LINUX_CLOCK_REALTIME + ? CLOCK_REALTIME + : CLOCK_MONOTONIC; + int64_t value_ns = + timespec_to_ns_sat(value->it_value_sec, value->it_value_nsec); + timer->expiry_ns = value_ns; + if (value_ns && !absolute && + __builtin_add_overflow(posix_timer_now(timer->host_clock), value_ns, + &timer->expiry_ns)) + timer->expiry_ns = INT64_MAX; + timer->interval_ns = + timespec_to_ns_sat(value->it_interval_sec, value->it_interval_nsec); + posix_timer_refresh_armed_locked(); + pthread_mutex_unlock(&sig_lock); + if (value_ns) + attention_raise(); + return 0; +} + +int signal_posix_timer_delete(int id) +{ + pthread_mutex_lock(&sig_lock); + guest_posix_timer_t *timer = posix_timer_find_locked(id); + if (timer) + memset(timer, 0, sizeof(*timer)); + posix_timer_refresh_armed_locked(); + pthread_mutex_unlock(&sig_lock); + return timer ? 0 : -LINUX_EINVAL; +} + +int signal_posix_timer_overrun(int id) +{ + pthread_mutex_lock(&sig_lock); + guest_posix_timer_t *timer = posix_timer_find_locked(id); + int ret = timer ? timer->last_overrun : -LINUX_EINVAL; + pthread_mutex_unlock(&sig_lock); + return ret; +} + /* Check a single timer; if expired, re-arm or deactivate, return signal to * queue. Must be called with sig_lock held. * @@ -1427,6 +1706,7 @@ static bool check_one_timer(guest_itimer_t *timer, const struct timeval *now) */ static void signal_check_timers(bool cpu_timers) { + posix_timer_check(); if (!atomic_load_explicit(&guest_itimer.active, memory_order_acquire) && (!cpu_timers || (!atomic_load_explicit(&guest_itimer_virt.active, memory_order_acquire) && @@ -1742,6 +2022,7 @@ static int signal_claim_waking_locked(uint64_t blocked) bool signal_claim_interruption(void) { + posix_timer_check(); /* Same lock-free fast path as signal_pending(). */ uint64_t hint = atomic_load_explicit(&sig_pending_hint, memory_order_acquire); @@ -1924,6 +2205,7 @@ static int sigtimedwait_try_dequeue(uint64_t mask, signal_rt_info_t *info_out) signal_rt_dequeue_locked(src, signum, info_out); } else { *info_out = signal_standard_peek_locked(src, signum); + posix_timer_dequeued_locked(info_out); src->std_info_valid[signum - 1] = false; pending_clear(&src->pending, sig_bit(signum)); } @@ -1976,6 +2258,7 @@ int64_t signal_rt_sigtimedwait(guest_t *g, while (1) { signal_rt_info_t info; + posix_timer_check(); int signum = sigtimedwait_try_dequeue(mask, &info); if (signum > 0) { /* Populate guest siginfo_t if the caller wants it. */ @@ -2793,6 +3076,7 @@ static int signal_deliver_one(hv_vcpu_t vcpu, guest_t *g, int *exit_code) signal_rt_dequeue_locked(src, signum, &rt_info); } else { rt_info = signal_standard_peek_locked(src, signum); + posix_timer_dequeued_locked(&rt_info); src->std_info_valid[signum - 1] = false; pending_clear(&src->pending, sig_bit(signum)); } diff --git a/src/syscall/signal.h b/src/syscall/signal.h index 25083d0ba..58df5c6bc 100644 --- a/src/syscall/signal.h +++ b/src/syscall/signal.h @@ -22,6 +22,7 @@ #include #include #include "core/guest.h" +#include "syscall/linux-wire.h" #include "linux-limits.h" /* Linux signal numbers (1-based, matching kernel). */ @@ -117,6 +118,7 @@ typedef struct { /* si_code values */ #define LINUX_SI_USER 0 +#define LINUX_SI_TIMER (-2) /* si_code values for SIGTRAP */ #define LINUX_TRAP_BRKPT 1 /* Process breakpoint (BRK instruction) */ @@ -709,3 +711,19 @@ void signal_check_timer(void); * host call spends none. */ void signal_check_timer_real(void); + +/* Process-wide POSIX timers. Supports REALTIME/MONOTONIC clocks and + * SIGEV_SIGNAL/SIGEV_NONE notification. These helpers acquire sig_lock. + */ +int signal_posix_timer_create(int clockid, + int notify, + int signum, + uint64_t value, + bool default_event); +int signal_posix_timer_get(int id, linux_itimerspec_t *value); +int signal_posix_timer_set(int id, + int flags, + const linux_itimerspec_t *value, + linux_itimerspec_t *old); +int signal_posix_timer_delete(int id); +int signal_posix_timer_overrun(int id); diff --git a/src/syscall/syscall.c b/src/syscall/syscall.c index 572d23a94..f0c618f05 100644 --- a/src/syscall/syscall.c +++ b/src/syscall/syscall.c @@ -357,6 +357,11 @@ SC_FORWARD(sc_nanosleep, sys_nanosleep(g, x0, x1)) SC_FORWARD(sc_clock_nanosleep, sys_clock_nanosleep(g, (int) x0, (int) x1, x2, x3)) SC_FORWARD(sc_gettimeofday, sys_gettimeofday(g, x0, x1)) SC_FORWARD(sc_setitimer, sys_setitimer(g, (int) x0, x1, x2)) +SC_FORWARD(sc_timer_create, sys_timer_create(g, (int) x0, x1, x2)) +SC_FORWARD(sc_timer_gettime, sys_timer_gettime(g, (int) x0, x1)) +SC_FORWARD(sc_timer_getoverrun, signal_posix_timer_overrun((int) x0)) +SC_FORWARD(sc_timer_settime, sys_timer_settime(g, (int) x0, (int) x1, x2, x3)) +SC_FORWARD(sc_timer_delete, signal_posix_timer_delete((int) x0)) SC_FORWARD(sc_getitimer, sys_getitimer(g, (int) x0, x1)) SC_FORWARD(sc_times, sys_times(g, x0)) diff --git a/src/syscall/time.c b/src/syscall/time.c index ed41cf312..99e71d831 100644 --- a/src/syscall/time.c +++ b/src/syscall/time.c @@ -97,6 +97,70 @@ static struct timespec ns_to_host_timespec(int64_t ns) }; } +/* Linux aarch64 sigevent. Thread notification fields are unsupported. */ +typedef struct { + uint64_t value; + int32_t signum, notify; + int32_t reserved[12]; +} linux_sigevent_t; + +_Static_assert(sizeof(linux_sigevent_t) == 64, + "Linux sigevent must be 64 bytes"); + +int64_t sys_timer_create(guest_t *g, + int clockid, + uint64_t event_gva, + uint64_t id_gva) +{ + linux_sigevent_t event = { + .signum = LINUX_SIGALRM, + .notify = LINUX_SIGEV_SIGNAL, + }; + if (event_gva && guest_read(g, event_gva, &event, sizeof(event)) < 0) + return -LINUX_EFAULT; + int32_t id = signal_posix_timer_create(clockid, event.notify, event.signum, + event.value, event_gva == 0); + if (id < 0) + return id; + if (guest_write_small(g, id_gva, &id, sizeof(id)) < 0) { + signal_posix_timer_delete(id); + return -LINUX_EFAULT; + } + return 0; +} + +int64_t sys_timer_gettime(guest_t *g, int id, uint64_t value_gva) +{ + linux_itimerspec_t value; + int ret = signal_posix_timer_get(id, &value); + if (ret < 0) + return ret; + if (guest_write(g, value_gva, &value, sizeof(value)) < 0) + return -LINUX_EFAULT; + return 0; +} + +int64_t sys_timer_settime(guest_t *g, + int id, + int flags, + uint64_t value_gva, + uint64_t old_gva) +{ + linux_itimerspec_t value, old; + if (guest_read(g, value_gva, &value, sizeof(value)) < 0) + return -LINUX_EFAULT; + if ((flags & ~LINUX_TIMER_ABSTIME) || + !timespec_valid(value.it_value_sec, value.it_value_nsec) || + !timespec_valid(value.it_interval_sec, value.it_interval_nsec)) + return -LINUX_EINVAL; + int ret = signal_posix_timer_set(id, flags, &value, &old); + if (ret < 0) + return ret; + if (old_gva && guest_write(g, old_gva, &old, sizeof(old)) < 0) + return -LINUX_EFAULT; + return 0; +} + static int write_remaining_sleep(guest_t *g, uint64_t rem_gva, int64_t remaining_ns) diff --git a/src/syscall/time.h b/src/syscall/time.h index 293f973e1..fd36d215f 100644 --- a/src/syscall/time.h +++ b/src/syscall/time.h @@ -44,3 +44,13 @@ int64_t sys_setitimer(guest_t *g, uint64_t new_gva, uint64_t old_gva); int64_t sys_getitimer(guest_t *g, int which, uint64_t val_gva); +int64_t sys_timer_create(guest_t *g, + int clockid, + uint64_t event_gva, + uint64_t id_gva); +int64_t sys_timer_gettime(guest_t *g, int id, uint64_t value_gva); +int64_t sys_timer_settime(guest_t *g, + int id, + int flags, + uint64_t value_gva, + uint64_t old_gva); From dd47e156776a556c17629b9ef1e680d3e948ba52 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 12 Oct 2026 12:26:07 +0100 Subject: [PATCH 2/4] Return pidfds before starting clone children clone leaves the CLONE_PIDFD output untouched, and clone3 ignores pidfd allocation and copyout errors after starting the child. Create a close-on-exec pidfd before either child path can run. Cancel creation on failure and keep clone3's pidfd and parent-TID outputs separate. Thread pidfds remain unsupported. --- src/runtime/forkipc.c | 104 +++++++++++++++++++++++++++------------ src/runtime/forkipc.h | 1 + src/syscall/proc-pidfd.c | 1 + src/syscall/syscall.c | 2 +- tests/test-pidfd.c | 53 +++++++++++++++++++- 5 files changed, 126 insertions(+), 35 deletions(-) diff --git a/src/runtime/forkipc.c b/src/runtime/forkipc.c index c9eb8ec8c..3f0349c27 100644 --- a/src/runtime/forkipc.c +++ b/src/runtime/forkipc.c @@ -67,6 +67,7 @@ #include "syscall/linux-wire.h" #include "syscall/chown-overlay.h" +#include "syscall/fs.h" #include "syscall/internal.h" #include "syscall/mem.h" #include "syscall/net.h" /* absock namespace IPC state */ @@ -83,11 +84,13 @@ * sys_clone further down. */ #define LINUX_CLONE_VM 0x00000100 +#define LINUX_CLONE_PIDFD 0x00001000 #define LINUX_CLONE_VFORK 0x00004000 #define LINUX_CLONE_THREAD 0x00010000 #define LINUX_CLONE_SETTLS 0x00080000 #define LINUX_CLONE_PARENT_SETTID 0x00100000 #define LINUX_CLONE_CHILD_CLEARTID 0x00200000 +#define LINUX_CLONE_DETACHED 0x00400000 #define LINUX_CLONE_CHILD_SETTID 0x01000000 /* LINUX_SIGCHLD defined in syscall_signal.h (included above) */ @@ -1162,6 +1165,25 @@ static void *thread_create_and_run(void *arg) */ static void *vm_clone_thread_run(void *arg); +/* Publish the pidfd before the child can run. Copyout failure must cancel + * creation, as in Linux copy_process(). + */ +static int clone_create_pidfd(guest_t *g, + int64_t child_pid, + pid_t host_pid, + uint64_t pidfd_gva) +{ + int fd = pidfd_create(g, child_pid, host_pid); + if (fd < 0) + return fd; + int32_t value = fd; + if (guest_write_small(g, pidfd_gva, &value, sizeof(value)) < 0) { + sys_close(fd); + return -LINUX_EFAULT; + } + return fd; +} + static int64_t sys_clone_vm(hv_vcpu_t parent_vcpu, guest_t *g, uint64_t flags, @@ -1169,6 +1191,7 @@ static int64_t sys_clone_vm(hv_vcpu_t parent_vcpu, uint64_t stack_map_start, uint64_t stack_map_end, uint64_t ptid_gva, + uint64_t pidfd_gva, uint64_t tls, uint64_t ctid_gva, bool verbose) @@ -1233,6 +1256,17 @@ static int64_t sys_clone_vm(hv_vcpu_t parent_vcpu, return -LINUX_EFAULT; } + int child_pidfd = -1; + if (flags & LINUX_CLONE_PIDFD) { + child_pidfd = clone_create_pidfd(g, child_tid, -1, pidfd_gva); + if (child_pidfd < 0) { + clone_rollback_tid_flags(g, &tid_rollback); + free(tca); + thread_deactivate(t); + return child_pidfd; + } + } + /* Create the host pthread */ pthread_t host_thread; pthread_attr_t attr; @@ -1244,6 +1278,8 @@ static int64_t sys_clone_vm(hv_vcpu_t parent_vcpu, if (err != 0) { log_error("clone_vm: pthread_create failed: %s", strerror(err)); + if (child_pidfd >= 0) + sys_close(child_pidfd); free(tca); thread_deactivate(t); @@ -1516,6 +1552,7 @@ int64_t sys_clone(hv_vcpu_t vcpu, uint64_t stack_map_start, uint64_t stack_map_end, uint64_t ptid_gva, + uint64_t pidfd_gva, uint64_t tls, uint64_t ctid_gva, bool verbose) @@ -1528,6 +1565,16 @@ int64_t sys_clone(hv_vcpu_t vcpu, if ((flags & ~(uint64_t) 0xff) & LINUX_CLONE3_NS_FLAGS) return -LINUX_EINVAL; + bool want_pidfd = (flags & LINUX_CLONE_PIDFD) != 0; + /* clone shares the parent_tid and pidfd argument; clone3 keeps them + * separate. Linux rejects aliased outputs and CLONE_DETACHED with PIDFD. + * Thread pidfds are unsupported here. + */ + if (want_pidfd && + ((flags & (LINUX_CLONE_THREAD | LINUX_CLONE_DETACHED)) || + ((flags & LINUX_CLONE_PARENT_SETTID) && pidfd_gva == ptid_gva))) + return -LINUX_EINVAL; + /* CLONE_THREAD: create a new thread in the same VM (not a new process) */ if (flags & LINUX_CLONE_THREAD) { return sys_clone_thread(vcpu, g, flags, child_stack, stack_map_start, @@ -1558,7 +1605,8 @@ int64_t sys_clone(hv_vcpu_t vcpu, if ((flags & LINUX_CLONE_VM) && !(flags & LINUX_CLONE_THREAD) && !is_vfork) { return sys_clone_vm(vcpu, g, flags, child_stack, stack_map_start, - stack_map_end, ptid_gva, tls, ctid_gva, verbose); + stack_map_end, ptid_gva, pidfd_gva, tls, ctid_gva, + verbose); } log_debug("clone(flags=0x%llx, vfork=%d)", (unsigned long long) flags, @@ -1738,6 +1786,7 @@ int64_t sys_clone(hv_vcpu_t vcpu, int snapshot_shm_fd = -1; bool siblings_quiesced = false; int64_t fail_rc = -LINUX_ENOMEM; + int clone_pidfd = -1; /* Quiesce sibling vCPUs for snapshot consistency. In multithreaded guests, * sibling vCPUs may be actively mutating guest memory during the fork @@ -2003,6 +2052,21 @@ int64_t sys_clone(hv_vcpu_t vcpu, log_error("clone: failed to commit child bookkeeping"); goto fail_snapshot; } + /* Publish the parent's pidfd after the child fd snapshot, but before the + * child can run. A failed allocation or copyout cancels the held child. + */ + if (want_pidfd) { + clone_pidfd = + clone_create_pidfd(g, child_guest_pid, child_host_pid, pidfd_gva); + if (clone_pidfd < 0) { + fail_rc = clone_pidfd; + goto fail_snapshot; + } + } + if (flags & LINUX_CLONE_PARENT_SETTID) { + int32_t tid = (int32_t) child_guest_pid; + (void) guest_write_small(g, ptid_gva, &tid, sizeof(tid)); + } uint8_t admission_ready = 1; if (fork_ipc_write_all(ipc_sock, &admission_ready, sizeof(admission_ready)) < 0) { @@ -2075,6 +2139,8 @@ int64_t sys_clone(hv_vcpu_t vcpu, return child_guest_pid; fail_snapshot: + if (clone_pidfd >= 0) + sys_close(clone_pidfd); proc_cancel_child(child_guest_pid); free(regions_snapshot); if (snapshot_shm_fd >= 0) @@ -2134,7 +2200,6 @@ struct linux_clone_args { #define CLONE_ARGS_SIZE_VER0 64 /* v5.3: first 8 fields (flags..tls) */ /* Unsupported clone3-only flags: reject early rather than silently ignoring. */ -#define LINUX_CLONE_PIDFD 0x00001000 #define LINUX_CLONE_INTO_CGROUP 0x200000000ULL int64_t sys_clone3(hv_vcpu_t vcpu, @@ -2204,13 +2269,9 @@ int64_t sys_clone3(hv_vcpu_t vcpu, /* Merge exit_signal into flags for sys_clone compatibility. clone3 moved * exit_signal out of the flags field; sys_clone expects it in the low byte. - * Safe because validation confirmed ca.flags low byte is zero. Strip - * CLONE_PIDFD before passing to sys_clone (which does not understand it). - * Pidfd creation happens after the clone returns. + * Validation confirmed ca.flags low byte is zero. */ - bool want_pidfd = (ca.flags & LINUX_CLONE_PIDFD) != 0; - uint64_t flags = - (ca.flags & ~(uint64_t) LINUX_CLONE_PIDFD) | ca.exit_signal; + uint64_t flags = ca.flags | ca.exit_signal; /* Compute child stack pointer. clone: child_stack is the TOP of the stack * (SP value). clone3: stack is the BOTTOM, stack_size is the length. SP = @@ -2231,28 +2292,7 @@ int64_t sys_clone3(hv_vcpu_t vcpu, (unsigned long long) ca.stack, (unsigned long long) ca.stack_size, (unsigned long long) ca.tls, (unsigned long long) cl_args_size); - int64_t ret = sys_clone(vcpu, g, flags, child_stack, ca.stack, - ca.stack + ca.stack_size, ca.parent_tid, ca.tls, - ca.child_tid, verbose); - - /* If clone succeeded and CLONE_PIDFD was requested, create a pidfd for the - * child and write the guest FD number to ca.pidfd. - */ - if (ret > 0 && want_pidfd && ca.pidfd != 0) { - /* A CLONE_VM child has no host pid of its own, so it resolves to -1 and - * its pidfd carries no monitor. - */ - int pfd = pidfd_create(g, ret, proc_resolve_guest_pid(ret)); - if (pfd >= 0) { - int32_t pfd32 = (int32_t) pfd; - if (guest_write_small(g, ca.pidfd, &pfd32, sizeof(pfd32)) < 0) { - /* GVA invalid; close the newly created pidfd. */ - fd_entry_t snap; - if (fd_snapshot_and_close(pfd, &snap)) - fd_cleanup_entry(pfd, &snap); - } - } - } - - return ret; + return sys_clone(vcpu, g, flags, child_stack, ca.stack, + ca.stack + ca.stack_size, ca.parent_tid, ca.pidfd, ca.tls, + ca.child_tid, verbose); } diff --git a/src/runtime/forkipc.h b/src/runtime/forkipc.h index 36e392853..ca851dd4d 100644 --- a/src/runtime/forkipc.h +++ b/src/runtime/forkipc.h @@ -39,6 +39,7 @@ int64_t sys_clone(hv_vcpu_t vcpu, uint64_t stack_map_start, uint64_t stack_map_end, uint64_t ptid_gva, + uint64_t pidfd_gva, uint64_t tls, uint64_t ctid_gva, bool verbose); diff --git a/src/syscall/proc-pidfd.c b/src/syscall/proc-pidfd.c index ad24c8395..3ed65e0c0 100644 --- a/src/syscall/proc-pidfd.c +++ b/src/syscall/proc-pidfd.c @@ -165,6 +165,7 @@ int pidfd_create(guest_t *g, int64_t target_pid, pid_t host_pid) close(pfd[1]); return -LINUX_EMFILE; } + fd_publish_linux_flags(gfd, LINUX_O_CLOEXEC); pthread_mutex_lock(&pidfd_lock); pidfd_entry_t *entry = pidfd_find_free_entry(); diff --git a/src/syscall/syscall.c b/src/syscall/syscall.c index f0c618f05..0902245c0 100644 --- a/src/syscall/syscall.c +++ b/src/syscall/syscall.c @@ -2309,7 +2309,7 @@ static int64_t sc_clone(guest_t *g, (unsigned long long) x0, (unsigned long long) x1, (unsigned long long) x2, (unsigned long long) x3, (unsigned long long) x4); - return sys_clone(current_thread->vcpu, g, x0, x1, 0, 0, x2, x3, x4, + return sys_clone(current_thread->vcpu, g, x0, x1, 0, 0, x2, x2, x3, x4, verbose); } diff --git a/tests/test-pidfd.c b/tests/test-pidfd.c index 89d686f26..f6a5dba6b 100644 --- a/tests/test-pidfd.c +++ b/tests/test-pidfd.c @@ -29,6 +29,7 @@ #define CLONE_PIDFD 0x00001000 #define CLONE_VM 0x00000100 +#define CLONE_PARENT_SETTID 0x00100000 #define __NR_nanosleep 101 #define SIGCHLD 17 @@ -46,6 +47,26 @@ struct linux_pollfd { short events, revents; }; +static void test_clone_failure(uint64_t output, long error) +{ + struct clone_args ca = { + .flags = CLONE_PIDFD, + .pidfd = output, + .exit_signal = SIGCHLD, + }; + long child = raw_syscall2(__NR_clone3, (long) &ca, sizeof(ca)); + if (child == 0) { + raw_syscall1(__NR_exit, 42); + __builtin_unreachable(); + } + if (child > 0) + raw_syscall4(__NR_wait4, child, 0, 0, 0); + EXPECT_EQ(child, error, "wrong clone3 error"); + TEST("failed clone leaves no child"); + EXPECT_RAW_ERRNO(raw_syscall4(__NR_wait4, -1, 0, 1, 0), -ECHILD, + "failed clone left a child"); +} + int main(void) { printf("test-pidfd: pidfd subsystem\n"); @@ -95,7 +116,31 @@ int main(void) EXPECT_TRUE(r == -38 /* LINUX_ENOSYS */, "expected -ENOSYS"); } - /* clone3 with CLONE_PIDFD */ + TEST("clone3 rejects null pidfd output"); + test_clone_failure(0, -EFAULT); + TEST("clone3 rejects unmapped pidfd output"); + test_clone_failure(UINT64_MAX - 4095, -EFAULT); + + /* Fill elfuse's bounded pidfd table before clone creates its child. */ + { + long fds[32]; + int count = 0; + while (count < 32) { + long fd = raw_syscall2(__NR_pidfd_open, pid, 0); + if (fd < 0) + break; + fds[count++] = fd; + } + TEST("failed clones release pidfds"); + EXPECT_EQ(count, 32, "failed clone leaked a pidfd"); + int32_t output = -1; + TEST("clone3 cancels on pidfd allocation failure"); + test_clone_failure((uint64_t) &output, -EMFILE); + for (int i = 0; i < count; i++) + raw_syscall1(__NR_close, fds[i]); + } + + /* clone3 with separate CLONE_PIDFD and CLONE_PARENT_SETTID outputs. */ TEST("clone3 CLONE_PIDFD"); { /* Allocate stack for child */ @@ -107,9 +152,11 @@ int main(void) } int32_t child_pidfd = -1; + int32_t parent_tid = -1; struct clone_args ca = {0}; - ca.flags = CLONE_PIDFD; + ca.flags = CLONE_PIDFD | CLONE_PARENT_SETTID; ca.pidfd = (uint64_t) &child_pidfd; + ca.parent_tid = (uint64_t) &parent_tid; ca.exit_signal = SIGCHLD; ca.stack = (uint64_t) stack; ca.stack_size = 65536; @@ -126,6 +173,8 @@ int main(void) } EXPECT_TRUE(child_pidfd >= 0, "pidfd not written"); + TEST("clone3 writes a separate parent TID"); + EXPECT_EQ(parent_tid, child, "parent TID not written"); /* Poll the pidfd for child exit (with 1s timeout) */ TEST("poll pidfd for exit"); From e4dcb029e03e07ff5bb5a401629ae907c070a5cc Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 12 Oct 2026 17:19:01 +0100 Subject: [PATCH 3/4] Correct POSIX timer notification lifetime Queued timer signals can survive delete, rearm and exec, while fast periodic timers can starve a waiting notification. Discard stale entries without changing other pending signals, and give timers a turn on each signal queue. Clear the interval on disarm and advance silent timers on query without keeping the polling hint set. Accept BOOTTIME with monotonic time, reject signals outside 1..64 and return ENOSYS for SIGEV_THREAD_ID. --- src/syscall/linux-wire.h | 2 + src/syscall/signal.c | 187 ++++++++++++++------ src/syscall/signal.h | 5 +- tests/manifest.txt | 2 + tests/test-matrix.sh | 6 + tests/test-posix-timers.c | 356 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 506 insertions(+), 52 deletions(-) create mode 100644 tests/test-posix-timers.c diff --git a/src/syscall/linux-wire.h b/src/syscall/linux-wire.h index 08f945b74..a87d8d629 100644 --- a/src/syscall/linux-wire.h +++ b/src/syscall/linux-wire.h @@ -380,9 +380,11 @@ typedef struct { /* Linux clocks and POSIX timer flags. */ #define LINUX_CLOCK_REALTIME 0 #define LINUX_CLOCK_MONOTONIC 1 +#define LINUX_CLOCK_BOOTTIME 7 #define LINUX_TIMER_ABSTIME 1 #define LINUX_SIGEV_SIGNAL 0 #define LINUX_SIGEV_NONE 1 +#define LINUX_SIGEV_THREAD_ID 4 /* Linux struct itimerspec, shared by POSIX timers and timerfd. */ typedef struct { diff --git a/src/syscall/signal.c b/src/syscall/signal.c index f9ec3a20c..0e33112c3 100644 --- a/src/syscall/signal.c +++ b/src/syscall/signal.c @@ -269,6 +269,7 @@ static guest_posix_timer_t posix_timers[POSIX_TIMER_MAX]; /* Keep IDs unique so a deleted timer's queued signal cannot name a new timer. */ static int posix_next_id; +static int posix_timer_next_slot[LINUX_NSIG]; static _Atomic bool posix_timers_armed; static guest_posix_timer_t *posix_timer_find_locked(int id) @@ -476,6 +477,46 @@ static void shared_claim_release_locked(int signum) shared_claim_owner[signum - 1] = NULL; } +/* A negative ID removes all POSIX timer signals at exec. Keep other entries + * in order, including user signals with the same signal number. + */ +static void posix_timer_discard_pending_locked(signal_pending_t *p, int id) +{ + for (uint64_t bits = pending_load(&p->pending); bits; bits &= bits - 1) { + int signum = bit_ctz64(bits) + 1; + if (signum < LINUX_SIGRTMIN) { + int idx = signum - 1; + const signal_rt_info_t *info = &p->std_info[idx]; + if (!p->std_info_valid[idx] || info->si_code != LINUX_SI_TIMER || + (id >= 0 && info->si_pid != id)) + continue; + p->std_info_valid[idx] = false; + pending_clear(&p->pending, sig_bit(signum)); + } else { + int idx = signum - LINUX_SIGRTMIN; + int kept = 0; + for (int n = 0; n < p->rt_queue[idx]; n++) { + int slot = (p->rt_head[idx] + n) % RT_SIGQUEUE_MAX; + signal_rt_info_t info = p->rt_info[idx][slot]; + if (info.si_code == LINUX_SI_TIMER && + (id < 0 || info.si_pid == id)) + continue; + int dest = (p->rt_head[idx] + kept++) % RT_SIGQUEUE_MAX; + p->rt_info[idx][dest] = info; + } + if (kept == p->rt_queue[idx]) + continue; + p->rt_queue[idx] = kept; + if (!kept) { + p->rt_head[idx] = 0; + pending_clear(&p->pending, sig_bit(signum)); + } + } + if (p == &sig_state.shared) + shared_claim_release_locked(signum); + } +} + /* The shared set as the current thread may take it. */ static uint64_t shared_visible_locked(void) { @@ -624,11 +665,16 @@ void signal_reset_for_exec(void) thread_entry_t *t = current_thread; pthread_mutex_lock(&sig_lock); + posix_timer_discard_pending_locked(&sig_state.shared, -1); + if (t) + posix_timer_discard_pending_locked(&t->tpending, -1); + refresh_pending_hint_locked(); memset(posix_timers, 0, sizeof(posix_timers)); + memset(posix_timer_next_slot, 0, sizeof(posix_timer_next_slot)); atomic_store_explicit(&posix_timers_armed, false, memory_order_release); for (int i = 0; i < LINUX_NSIG; i++) { /* POSIX: handlers reset to SIG_DFL, except SIG_IGN stays SIG_IGN. - * Pending signals and signal mask are preserved across exec. + * Non-timer pending signals and the signal mask survive exec. */ if (sig_state.actions[i].sa_handler != LINUX_SIG_IGN) { sig_state.actions[i].sa_handler = LINUX_SIG_DFL; @@ -1454,25 +1500,13 @@ static void posix_timer_refresh_armed_locked(void) bool armed = false; for (int i = 0; i < POSIX_TIMER_MAX; i++) { const guest_posix_timer_t *timer = &posix_timers[i]; - armed |= timer->used && (timer->expiry_ns || (timer->signal_pending && - !timer->signal_queued)); + armed |= timer->used && timer->signum && + (timer->expiry_ns || + (timer->signal_pending && !timer->signal_queued)); } atomic_store_explicit(&posix_timers_armed, armed, memory_order_release); } -static void posix_timer_value_locked(const guest_posix_timer_t *timer, - linux_itimerspec_t *value) -{ - int64_t now = posix_timer_now(timer->host_clock); - int64_t remaining = timer->expiry_ns > now ? timer->expiry_ns - now : 0; - *value = (linux_itimerspec_t) { - .it_interval_sec = timer->interval_ns / NSEC_PER_SEC, - .it_interval_nsec = timer->interval_ns % NSEC_PER_SEC, - .it_value_sec = remaining / NSEC_PER_SEC, - .it_value_nsec = remaining % NSEC_PER_SEC, - }; -} - /* Count expired periods, then disarm or advance past now. */ static uint64_t posix_timer_advance_locked(guest_posix_timer_t *timer) { @@ -1494,6 +1528,22 @@ static uint64_t posix_timer_advance_locked(guest_posix_timer_t *timer) return count; } +static void posix_timer_value_locked(guest_posix_timer_t *timer, + linux_itimerspec_t *value) +{ + /* Silent timers have no delivery work, so advance them only on query. */ + if (!timer->signum) + posix_timer_advance_locked(timer); + int64_t now = posix_timer_now(timer->host_clock); + int64_t remaining = timer->expiry_ns > now ? timer->expiry_ns - now : 0; + *value = (linux_itimerspec_t) { + .it_interval_sec = timer->interval_ns / NSEC_PER_SEC, + .it_interval_nsec = timer->interval_ns % NSEC_PER_SEC, + .it_value_sec = remaining / NSEC_PER_SEC, + .it_value_nsec = remaining % NSEC_PER_SEC, + }; +} + /* signalfd peeks before consuming, so its queued copy needs current overruns. */ static void posix_timer_update_pending_locked(const guest_posix_timer_t *timer) @@ -1515,40 +1565,29 @@ static void posix_timer_update_pending_locked(const guest_posix_timer_t *timer) } } -static void posix_timer_check(void) +static bool posix_timer_enqueue_waiting_locked(int signum) { - if (!atomic_load_explicit(&posix_timers_armed, memory_order_acquire)) - return; - uint64_t notify = 0; - pthread_mutex_lock(&sig_lock); - for (int i = 0; i < POSIX_TIMER_MAX; i++) { + bool queued = false; + int start = posix_timer_next_slot[signum - 1]; + /* Resume after the last queued timer so fast periodic timers cannot + * keep taking queue space from another timer on the same signal. + */ + for (int offset = 0; offset < POSIX_TIMER_MAX; offset++) { + int i = (start + offset) % POSIX_TIMER_MAX; guest_posix_timer_t *timer = &posix_timers[i]; - uint64_t count = posix_timer_advance_locked(timer); - if (!timer->used || !timer->signum) - continue; - if (count) { - uint64_t missed = timer->signal_pending ? count : count - 1; - timer->overruns = missed > (uint64_t) (INT32_MAX - timer->overruns) - ? INT32_MAX - : timer->overruns + (int) missed; - timer->signal_pending = true; - } - if (timer->signal_queued) { - posix_timer_update_pending_locked(timer); - continue; - } - if (!timer->signal_pending) + if (!timer->used || timer->signum != signum || !timer->signal_pending || + timer->signal_queued) continue; /* Each timer retains its notification until the shared queue has room, * including when another timer holds the same standard signal. */ - if (timer->signum < LINUX_SIGRTMIN && - (pending_load(&sig_state.shared.pending) & sig_bit(timer->signum))) - continue; - if (timer->signum >= LINUX_SIGRTMIN && - sig_state.shared.rt_queue[timer->signum - LINUX_SIGRTMIN] >= + if (signum < LINUX_SIGRTMIN && + (pending_load(&sig_state.shared.pending) & sig_bit(signum))) + break; + if (signum >= LINUX_SIGRTMIN && + sig_state.shared.rt_queue[signum - LINUX_SIGRTMIN] >= RT_SIGQUEUE_MAX) - continue; + break; signal_rt_info_t info = { .signum = timer->signum, .si_code = LINUX_SI_TIMER, @@ -1559,7 +1598,39 @@ static void posix_timer_check(void) }; signal_enqueue_locked(&sig_state.shared, timer->signum, &info); timer->signal_queued = true; - notify |= sig_bit(timer->signum); + posix_timer_next_slot[signum - 1] = (i + 1) % POSIX_TIMER_MAX; + queued = true; + } + return queued; +} + +static void posix_timer_check(void) +{ + if (!atomic_load_explicit(&posix_timers_armed, memory_order_acquire)) + return; + uint64_t waiting = 0, notify = 0; + pthread_mutex_lock(&sig_lock); + for (int i = 0; i < POSIX_TIMER_MAX; i++) { + guest_posix_timer_t *timer = &posix_timers[i]; + if (!timer->used || !timer->signum) + continue; + uint64_t count = posix_timer_advance_locked(timer); + if (count) { + uint64_t missed = timer->signal_pending ? count : count - 1; + timer->overruns = missed > (uint64_t) (INT32_MAX - timer->overruns) + ? INT32_MAX + : timer->overruns + (int) missed; + timer->signal_pending = true; + } + if (timer->signal_queued) + posix_timer_update_pending_locked(timer); + else if (timer->signal_pending) + waiting |= sig_bit(timer->signum); + } + for (uint64_t pending = waiting; pending; pending &= pending - 1) { + int signum = bit_ctz64(pending) + 1; + if (posix_timer_enqueue_waiting_locked(signum)) + notify |= sig_bit(signum); } posix_timer_refresh_armed_locked(); refresh_pending_hint_locked(); @@ -1576,11 +1647,14 @@ int signal_posix_timer_create(int clockid, uint64_t value, bool default_event) { - if (clockid != LINUX_CLOCK_REALTIME && clockid != LINUX_CLOCK_MONOTONIC) + if (clockid != LINUX_CLOCK_REALTIME && clockid != LINUX_CLOCK_MONOTONIC && + clockid != LINUX_CLOCK_BOOTTIME) return -LINUX_EINVAL; + if (notify == LINUX_SIGEV_THREAD_ID) + return -LINUX_ENOSYS; if (notify != LINUX_SIGEV_SIGNAL && notify != LINUX_SIGEV_NONE) return -LINUX_EINVAL; - if (notify == LINUX_SIGEV_SIGNAL && !RANGE_CHECK(signum, 1, LINUX_NSIG + 1)) + if (notify == LINUX_SIGEV_SIGNAL && !RANGE_CHECK(signum, 1, LINUX_NSIG)) return -LINUX_EINVAL; int ret = -LINUX_EAGAIN; pthread_mutex_lock(&sig_lock); @@ -1626,6 +1700,13 @@ int signal_posix_timer_set(int id, return -LINUX_EINVAL; } posix_timer_value_locked(timer, old); + /* Queue removal and rearming share sig_lock, so an old notification + * cannot consume the new arm's overrun state. + */ + posix_timer_discard_pending_locked(&sig_state.shared, id); + timer->signal_pending = false; + timer->signal_queued = false; + timer->overruns = 0; timer->last_overrun = 0; bool absolute = (flags & LINUX_TIMER_ABSTIME) != 0; /* Relative realtime timers must ignore wall-clock adjustments. */ @@ -1639,11 +1720,14 @@ int signal_posix_timer_set(int id, __builtin_add_overflow(posix_timer_now(timer->host_clock), value_ns, &timer->expiry_ns)) timer->expiry_ns = INT64_MAX; - timer->interval_ns = - timespec_to_ns_sat(value->it_interval_sec, value->it_interval_nsec); + timer->interval_ns = value_ns ? timespec_to_ns_sat(value->it_interval_sec, + value->it_interval_nsec) + : 0; + bool notify = value_ns && timer->signum; posix_timer_refresh_armed_locked(); + refresh_pending_hint_locked(); pthread_mutex_unlock(&sig_lock); - if (value_ns) + if (notify) attention_raise(); return 0; } @@ -1652,9 +1736,12 @@ int signal_posix_timer_delete(int id) { pthread_mutex_lock(&sig_lock); guest_posix_timer_t *timer = posix_timer_find_locked(id); - if (timer) + if (timer) { + posix_timer_discard_pending_locked(&sig_state.shared, id); memset(timer, 0, sizeof(*timer)); + } posix_timer_refresh_armed_locked(); + refresh_pending_hint_locked(); pthread_mutex_unlock(&sig_lock); return timer ? 0 : -LINUX_EINVAL; } diff --git a/src/syscall/signal.h b/src/syscall/signal.h index 58df5c6bc..909b4de7e 100644 --- a/src/syscall/signal.h +++ b/src/syscall/signal.h @@ -712,8 +712,9 @@ void signal_check_timer(void); */ void signal_check_timer_real(void); -/* Process-wide POSIX timers. Supports REALTIME/MONOTONIC clocks and - * SIGEV_SIGNAL/SIGEV_NONE notification. These helpers acquire sig_lock. +/* Process-wide POSIX timers with REALTIME/MONOTONIC clocks and + * SIGEV_SIGNAL/SIGEV_NONE notification. BOOTTIME uses monotonic time, as in + * translate_clockid. These helpers acquire sig_lock. */ int signal_posix_timer_create(int clockid, int notify, diff --git a/tests/manifest.txt b/tests/manifest.txt index 5a55653df..26fa8405b 100644 --- a/tests/manifest.txt +++ b/tests/manifest.txt @@ -113,6 +113,8 @@ test-stress # diff=skip test-mprotect-mt # diff=skip [section] Signal + thread tests +test-posix-timers +test-posix-timers elfuse test-signal-thread test-signal-in-shim # diff=skip test-shim-sigreturn-x8 diff --git a/tests/test-matrix.sh b/tests/test-matrix.sh index 0aa6894f1..215a2be0b 100755 --- a/tests/test-matrix.sh +++ b/tests/test-matrix.sh @@ -723,6 +723,12 @@ run_unit_tests() test_rc "$runner" "test-pidfd" 0 "$bindir/test-pidfd" printf "\nSignal tests\n" + test_check "$runner" "test-posix-timers" "0 failed" "$bindir/test-posix-timers" + if [ "$runner" = "run_elfuse" ]; then + # Pins unsupported thread notification and current Linux timer cleanup. + test_check "$runner" "test-posix-timers-elfuse" "0 failed" \ + "$bindir/test-posix-timers" elfuse + fi test_check "$runner" "test-signal" "PASS|0 failed" "$bindir/test-signal" test_check "$runner" "test-signal-thread" "PASS|0 failed" "$bindir/test-signal-thread" test_check "$runner" "test-signal-in-shim" "0 failed" \ diff --git a/tests/test-posix-timers.c b/tests/test-posix-timers.c new file mode 100644 index 000000000..e49ac69c7 --- /dev/null +++ b/tests/test-posix-timers.c @@ -0,0 +1,356 @@ +/* + * Copyright 2026 elfuse contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test-harness.h" + +static int passes, fails; + +static void require(int ok, const char *message) +{ + if (!ok) { + perror(message); + exit(2); + } +} + +static sigset_t signal_set(int signum) +{ + sigset_t set; + sigemptyset(&set); + sigaddset(&set, signum); + return set; +} + +static timer_t create_timer(int signum, int payload) +{ + struct sigevent event = { + .sigev_notify = SIGEV_SIGNAL, + .sigev_signo = signum, + .sigev_value.sival_int = payload, + }; + timer_t timer; + require(timer_create(CLOCK_MONOTONIC, &event, &timer) == 0, "timer_create"); + return timer; +} + +static void arm_timer(timer_t timer, int periodic) +{ + struct itimerspec value = { + .it_value.tv_nsec = 10000000, + .it_interval.tv_nsec = periodic ? 100000000 : 0, + }; + require(timer_settime(timer, 0, &value, NULL) == 0, "timer_settime"); +} + +static void expire_timers(void) +{ + struct timespec delay = {.tv_nsec = 350000000}; + require(nanosleep(&delay, NULL) == 0, "nanosleep"); +} + +static int take_signal(int signum, siginfo_t *info) +{ + sigset_t set = signal_set(signum); + struct timespec timeout = {.tv_nsec = 100000000}; + return sigtimedwait(&set, info, &timeout); +} + +static void test_create(void) +{ + /* Use the kernel ABI: libc can reject a bad signal before timer_create. */ + int signals[] = {0, -1, 65, INT_MAX, 1, 64}; + for (unsigned i = 0; i < sizeof(signals) / sizeof(signals[0]); i++) { + struct sigevent event = { + .sigev_notify = SIGEV_SIGNAL, + .sigev_signo = signals[i], + }; + int id = -1; + long rc = syscall(SYS_timer_create, CLOCK_MONOTONIC, &event, &id); + int error = errno; + if (rc == 0) + syscall(SYS_timer_delete, id); + TEST("timer signal range is 1..64"); + EXPECT_TRUE(signals[i] > 0 && signals[i] <= 64 + ? rc == 0 + : rc == -1 && error == EINVAL, + "wrong timer_create result"); + } + + struct sigevent event = {.sigev_notify = SIGEV_NONE}; + timer_t timer; + TEST("CLOCK_BOOTTIME is accepted"); + int rc = timer_create(CLOCK_BOOTTIME, &event, &timer); + EXPECT_EQ(rc, 0, "CLOCK_BOOTTIME rejected"); + if (rc == 0) + timer_delete(timer); + + require(timer_create(CLOCK_MONOTONIC, &event, &timer) == 0, + "create silent timer"); + arm_timer(timer, 1); + expire_timers(); + struct itimerspec value; + TEST("silent periodic timer advances"); + EXPECT_TRUE(timer_gettime(timer, &value) == 0 && + value.it_value.tv_sec == 0 && value.it_value.tv_nsec > 0 && + value.it_value.tv_nsec <= 100000000, + "silent timer did not advance"); + struct itimerspec stop = {.it_interval.tv_sec = 1}; + require(timer_settime(timer, 0, &stop, &value) == 0, "disarm timer"); + TEST("settime returns the old interval"); + EXPECT_EQ(value.it_interval.tv_nsec, 100000000, "old interval lost"); + TEST("disarm clears value and interval"); + EXPECT_TRUE(timer_gettime(timer, &value) == 0 && + value.it_value.tv_sec == 0 && value.it_value.tv_nsec == 0 && + value.it_interval.tv_sec == 0 && + value.it_interval.tv_nsec == 0, + "disarmed timer retains an interval"); + timer_delete(timer); + + timer = create_timer(SIGUSR1, 71); + struct itimerspec absolute = {0}; + require(clock_gettime(CLOCK_MONOTONIC, &absolute.it_value) == 0, + "clock_gettime"); + absolute.it_value.tv_nsec += 10000000; + if (absolute.it_value.tv_nsec >= 1000000000) { + absolute.it_value.tv_sec++; + absolute.it_value.tv_nsec -= 1000000000; + } + require(timer_settime(timer, TIMER_ABSTIME, &absolute, NULL) == 0, + "arm absolute timer"); + expire_timers(); + siginfo_t info; + TEST("absolute timer carries its payload"); + EXPECT_TRUE(take_signal(SIGUSR1, &info) == SIGUSR1 && + info.si_code == SI_TIMER && info.si_value.sival_int == 71, + "absolute timer notification missing"); + TEST("one-shot overrun is zero"); + EXPECT_EQ(timer_getoverrun(timer), 0, "unexpected overrun"); + timer_delete(timer); +} + +static void test_notifications(int signum, int use_fd, int periodic) +{ + int count = signum == SIGUSR1 ? 2 : 33; + sigset_t set = signal_set(signum); + int fd = use_fd ? signalfd(-1, &set, SFD_NONBLOCK) : -1; + require(!use_fd || fd >= 0, "signalfd"); + timer_t timers[33]; + for (int i = 0; i < count; i++) { + timers[i] = create_timer(signum, i + 1); + arm_timer(timers[i], periodic); + } + expire_timers(); + uint64_t seen = 0; + int received = 0, metadata_ok = 1; + while (received < count) { + int payload, overrun; + if (use_fd) { + struct signalfd_siginfo info; + ssize_t n = read(fd, &info, sizeof(info)); + if (n == -1 && errno == EAGAIN) + break; + require(n == sizeof(info), "read signalfd"); + metadata_ok &= + info.ssi_signo == signum && info.ssi_code == SI_TIMER; + payload = info.ssi_int; + overrun = info.ssi_overrun; + } else { + siginfo_t info; + if (take_signal(signum, &info) != signum) + break; + metadata_ok &= info.si_code == SI_TIMER; + payload = info.si_value.sival_int; + overrun = info.si_overrun; + } + require(payload > 0 && payload <= count, "timer payload"); + uint64_t bit = UINT64_C(1) << (payload - 1); + metadata_ok &= !(seen & bit); + seen |= bit; + metadata_ok &= periodic ? overrun > 0 : overrun == 0; + metadata_ok &= timer_getoverrun(timers[payload - 1]) == overrun; + received++; + } + printf(" signal=%d signalfd=%d periodic=%d\n", signum, use_fd, periodic); + TEST("each timer delivers once"); + EXPECT_EQ(received, count, "timer notification lost"); + TEST("payloads and overruns agree"); + EXPECT_TRUE(metadata_ok, "wrong payload or overrun"); + for (int i = 0; i < count; i++) + timer_delete(timers[i]); + if (fd >= 0) + close(fd); +} + +static void test_fairness(int signum) +{ + int count = signum == SIGUSR1 ? 3 : 33; + sigset_t set = signal_set(signum); + int fd = signalfd(-1, &set, SFD_NONBLOCK); + require(fd >= 0, "signalfd"); + timer_t timers[33]; + for (int i = 0; i < count; i++) { + timers[i] = create_timer(signum, i + 1); + struct itimerspec value = { + .it_value.tv_nsec = 1000000, + .it_interval.tv_nsec = i + 1 < count ? 1 : 0, + }; + require(timer_settime(timers[i], 0, &value, NULL) == 0, + "timer_settime"); + } + struct timespec delay = {.tv_nsec = 10000000}; + require(nanosleep(&delay, NULL) == 0, "nanosleep"); + int seen = 0; + for (int i = 0; i < 2 * count && !seen; i++) { + struct signalfd_siginfo info; + ssize_t n = read(fd, &info, sizeof(info)); + if (n == -1 && errno == EAGAIN) + break; + require(n == sizeof(info), "read signalfd"); + seen = info.ssi_code == SI_TIMER && info.ssi_int == count; + } + printf(" signal=%d competing_timers=%d\n", signum, count - 1); + TEST("fast periodic timers do not starve a one-shot"); + EXPECT_TRUE(seen, "one-shot notification starved"); + for (int i = 0; i < count; i++) + timer_delete(timers[i]); + siginfo_t info; + while (take_signal(signum, &info) == signum) + ; + close(fd); +} + +static void test_cleanup(int signum, int rearm) +{ + timer_t timer = create_timer(signum, 99); + union sigval first = {.sival_int = 10}, last = {.sival_int = 20}; + siginfo_t info; + if (signum >= SIGRTMIN) { + /* Exercise cleanup with the retained entries across the ring's end. */ + for (int i = 0; i < 32; i++) + require(sigqueue(getpid(), signum, first) == 0, + "queue first signal"); + for (int i = 0; i < 31; i++) + require(take_signal(signum, &info) == signum, + "advance signal queue"); + } + arm_timer(timer, 1); + expire_timers(); + if (signum >= SIGRTMIN) + require(sigqueue(getpid(), signum, last) == 0, "queue last signal"); + if (rearm) { + struct itimerspec value = {.it_value.tv_sec = 60}; + require(timer_settime(timer, 0, &value, NULL) == 0, "rearm timer"); + } else { + require(timer_delete(timer) == 0, "delete timer"); + } + if (signum < SIGRTMIN) + require(sigqueue(getpid(), signum, last) == 0, "queue last signal"); + if (signum >= SIGRTMIN) { + TEST("cleanup preserves earlier signal"); + EXPECT_TRUE(take_signal(signum, &info) == signum && + info.si_code == SI_QUEUE && + info.si_value.sival_int == 10, + "earlier signal lost or reordered"); + } + TEST(rearm ? "rearm discards stale notification" + : "delete discards stale notification"); + EXPECT_TRUE(take_signal(signum, &info) == signum && + info.si_code == SI_QUEUE && info.si_value.sival_int == 20, + "stale timer signal or lost user signal"); + if (rearm) { + arm_timer(timer, 0); + expire_timers(); + TEST("new arm starts with zero overruns"); + EXPECT_TRUE(take_signal(signum, &info) == signum && + info.si_code == SI_TIMER && info.si_overrun == 0 && + timer_getoverrun(timer) == 0, + "old arm changed new arm accounting"); + timer_delete(timer); + } + while (take_signal(signum, &info) == signum) + ; +} + +static void test_exec(const char *path) +{ + pid_t child = fork(); + require(child >= 0, "fork"); + if (child == 0) { + timer_t standard = create_timer(SIGUSR1, 1); + timer_t realtime = create_timer(SIGRTMIN, 2); + arm_timer(standard, 0); + arm_timer(realtime, 0); + expire_timers(); + union sigval value = {.sival_int = 42}; + require(sigqueue(getpid(), SIGRTMIN, value) == 0, "queue user signal"); + execl(path, path, "exec-child", NULL); + _exit(2); + } + int status; + require(waitpid(child, &status, 0) == child, "waitpid"); + TEST("exec drops timers, keeps user signal"); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "exec retained a timer signal or lost the user signal"); +} + +int main(int argc, char **argv) +{ + setvbuf(stdout, NULL, _IONBF, 0); + sigset_t blocked = signal_set(SIGUSR1); + sigaddset(&blocked, SIGRTMIN); + require(sigprocmask(SIG_BLOCK, &blocked, NULL) == 0, "block signals"); + if (argc > 1 && strcmp(argv[1], "exec-child") == 0) { + siginfo_t info; + int ok = take_signal(SIGRTMIN, &info) == SIGRTMIN && + info.si_code == SI_QUEUE && info.si_value.sival_int == 42; + sigprocmask(SIG_UNBLOCK, &blocked, NULL); + return !ok; + } + if (argc > 1 && strcmp(argv[1], "elfuse") == 0) { + /* Older Linux kernels keep a queued signal after delete or rearm. */ + for (int rearm = 0; rearm < 2; rearm++) { + test_cleanup(SIGUSR1, rearm); + test_cleanup(SIGRTMIN, rearm); + } + struct sigevent event = { + .sigev_notify = SIGEV_THREAD_ID, + .sigev_signo = SIGUSR1, + }; +#ifdef __GLIBC__ + event._sigev_un._tid = getpid(); +#else + event.sigev_notify_thread_id = getpid(); +#endif + int id; + TEST("thread notification reports ENOSYS"); + EXPECT_ERRNO(syscall(SYS_timer_create, CLOCK_MONOTONIC, &event, &id), + ENOSYS, "unsupported notification misreported"); + } else { + test_create(); + for (int periodic = 0; periodic < 2; periodic++) { + for (int use_fd = 0; use_fd < 2; use_fd++) { + test_notifications(SIGUSR1, use_fd, periodic); + test_notifications(SIGRTMIN, use_fd, periodic); + } + } + test_fairness(SIGUSR1); + test_fairness(SIGRTMIN); + test_exec(argv[0]); + } + SUMMARY("test-posix-timers"); + return fails != 0; +} From 151685184a6496aa30aae52119a6f0c65efe23a9 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 12 Oct 2026 17:19:23 +0100 Subject: [PATCH 4/4] Make POSIX timer notification states explicit Two flags encode three valid notification states. Use one state field and name the polling hint for both armed and waiting timers. Keep the BOOTTIME constant in the shared Linux definitions. --- src/syscall/fd.c | 6 ---- src/syscall/signal.c | 70 ++++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/syscall/fd.c b/src/syscall/fd.c index 57685330e..b3267b74d 100644 --- a/src/syscall/fd.c +++ b/src/syscall/fd.c @@ -89,12 +89,6 @@ static int sfd_alloc_slot(const void *state, size_t count, size_t stride) #define LINUX_TFD_TIMER_ABSTIME 1 #define LINUX_TFD_TIMER_CANCEL_ON_SET 2 -/* Linux CLOCK_BOOTTIME counts time including suspend; macOS has no equivalent. - * timerfd_settime treats non-REALTIME slots as MONOTONIC for ABSTIME - * conversion, which matches translate_clockid() in time.c. - */ -#define LINUX_CLOCK_BOOTTIME 7 - static struct { int guest_fd; /* Guest fd (-1 if unused) */ int kq_fd; /* kqueue fd for this timer */ diff --git a/src/syscall/signal.c b/src/syscall/signal.c index 0e33112c3..ef65e636e 100644 --- a/src/syscall/signal.c +++ b/src/syscall/signal.c @@ -251,6 +251,12 @@ static guest_itimer_t guest_itimer_prof; /* ITIMER_PROF -> SIGPROF */ /* Bounded per-process storage; Linux does not inherit POSIX timers at fork. */ #define POSIX_TIMER_MAX 64 +typedef enum { + POSIX_TIMER_NONE, + POSIX_TIMER_WAITING, + POSIX_TIMER_QUEUED, +} posix_timer_notification_t; + typedef struct { int32_t id, guest_clockid; int signum; @@ -258,19 +264,20 @@ typedef struct { clockid_t host_clock; uint64_t value; int64_t expiry_ns, interval_ns; - bool used, signal_pending, signal_queued; + bool used; + posix_timer_notification_t notification; } guest_posix_timer_t; -/* sig_lock protects the slots and IDs. Release stores to the armed hint pair - * with acquire loads in posix_timer_check and signal_attention_needed; slot - * access still requires sig_lock. +/* sig_lock protects slots, IDs and queue positions. Release stores to the + * polling hint pair with acquire loads in posix_timer_check and + * signal_attention_needed; slot access still requires sig_lock. */ static guest_posix_timer_t posix_timers[POSIX_TIMER_MAX]; /* Keep IDs unique so a deleted timer's queued signal cannot name a new timer. */ static int posix_next_id; static int posix_timer_next_slot[LINUX_NSIG]; -static _Atomic bool posix_timers_armed; +static _Atomic bool posix_timers_need_poll; static guest_posix_timer_t *posix_timer_find_locked(int id) { @@ -292,8 +299,7 @@ static void posix_timer_dequeued_locked(signal_rt_info_t *info) info->si_uid = (uint32_t) timer->overruns; timer->last_overrun = timer->overruns; timer->overruns = 0; - timer->signal_pending = false; - timer->signal_queued = false; + timer->notification = POSIX_TIMER_NONE; } /* Default disposition table. Index 0 unused (signals are 1-based). */ @@ -671,7 +677,7 @@ void signal_reset_for_exec(void) refresh_pending_hint_locked(); memset(posix_timers, 0, sizeof(posix_timers)); memset(posix_timer_next_slot, 0, sizeof(posix_timer_next_slot)); - atomic_store_explicit(&posix_timers_armed, false, memory_order_release); + atomic_store_explicit(&posix_timers_need_poll, false, memory_order_release); for (int i = 0; i < LINUX_NSIG; i++) { /* POSIX: handlers reset to SIG_DFL, except SIG_IGN stays SIG_IGN. * Non-timer pending signals and the signal mask survive exec. @@ -903,14 +909,13 @@ bool signal_attention_needed(void) if (hint != 0 && thread_signal_deliverable(hint)) return true; - /* Active guest itimers: even if no signal is queued YET, the timer can fire - * at any moment, and signal_check_timer needs an HVC #5 epilogue to notice - * it. Keep attention raised while any timer is armed. + /* signal_check_timer needs an HVC #5 epilogue to see expired timers or + * retry POSIX notifications waiting for queue space. */ if (atomic_load_explicit(&guest_itimer.active, memory_order_acquire) || atomic_load_explicit(&guest_itimer_virt.active, memory_order_acquire) || atomic_load_explicit(&guest_itimer_prof.active, memory_order_acquire) || - atomic_load_explicit(&posix_timers_armed, memory_order_acquire)) + atomic_load_explicit(&posix_timers_need_poll, memory_order_acquire)) return true; return false; } @@ -1495,16 +1500,17 @@ static int64_t posix_timer_now(clockid_t clockid) return timespec_to_ns_sat(now.tv_sec, now.tv_nsec); } -static void posix_timer_refresh_armed_locked(void) +static void posix_timer_refresh_poll_hint_locked(void) { - bool armed = false; + bool needs_poll = false; for (int i = 0; i < POSIX_TIMER_MAX; i++) { const guest_posix_timer_t *timer = &posix_timers[i]; - armed |= timer->used && timer->signum && - (timer->expiry_ns || - (timer->signal_pending && !timer->signal_queued)); + needs_poll |= + timer->used && timer->signum && + (timer->expiry_ns || timer->notification == POSIX_TIMER_WAITING); } - atomic_store_explicit(&posix_timers_armed, armed, memory_order_release); + atomic_store_explicit(&posix_timers_need_poll, needs_poll, + memory_order_release); } /* Count expired periods, then disarm or advance past now. */ @@ -1575,8 +1581,8 @@ static bool posix_timer_enqueue_waiting_locked(int signum) for (int offset = 0; offset < POSIX_TIMER_MAX; offset++) { int i = (start + offset) % POSIX_TIMER_MAX; guest_posix_timer_t *timer = &posix_timers[i]; - if (!timer->used || timer->signum != signum || !timer->signal_pending || - timer->signal_queued) + if (!timer->used || timer->signum != signum || + timer->notification != POSIX_TIMER_WAITING) continue; /* Each timer retains its notification until the shared queue has room, * including when another timer holds the same standard signal. @@ -1597,7 +1603,7 @@ static bool posix_timer_enqueue_waiting_locked(int signum) .si_ptr = timer->value, }; signal_enqueue_locked(&sig_state.shared, timer->signum, &info); - timer->signal_queued = true; + timer->notification = POSIX_TIMER_QUEUED; posix_timer_next_slot[signum - 1] = (i + 1) % POSIX_TIMER_MAX; queued = true; } @@ -1606,7 +1612,7 @@ static bool posix_timer_enqueue_waiting_locked(int signum) static void posix_timer_check(void) { - if (!atomic_load_explicit(&posix_timers_armed, memory_order_acquire)) + if (!atomic_load_explicit(&posix_timers_need_poll, memory_order_acquire)) return; uint64_t waiting = 0, notify = 0; pthread_mutex_lock(&sig_lock); @@ -1616,15 +1622,18 @@ static void posix_timer_check(void) continue; uint64_t count = posix_timer_advance_locked(timer); if (count) { - uint64_t missed = timer->signal_pending ? count : count - 1; + uint64_t missed = count; + if (timer->notification == POSIX_TIMER_NONE) { + timer->notification = POSIX_TIMER_WAITING; + missed--; + } timer->overruns = missed > (uint64_t) (INT32_MAX - timer->overruns) ? INT32_MAX : timer->overruns + (int) missed; - timer->signal_pending = true; } - if (timer->signal_queued) + if (timer->notification == POSIX_TIMER_QUEUED) posix_timer_update_pending_locked(timer); - else if (timer->signal_pending) + else if (timer->notification == POSIX_TIMER_WAITING) waiting |= sig_bit(timer->signum); } for (uint64_t pending = waiting; pending; pending &= pending - 1) { @@ -1632,7 +1641,7 @@ static void posix_timer_check(void) if (posix_timer_enqueue_waiting_locked(signum)) notify |= sig_bit(signum); } - posix_timer_refresh_armed_locked(); + posix_timer_refresh_poll_hint_locked(); refresh_pending_hint_locked(); pthread_mutex_unlock(&sig_lock); for (uint64_t pending = notify; pending; pending &= pending - 1) @@ -1704,8 +1713,7 @@ int signal_posix_timer_set(int id, * cannot consume the new arm's overrun state. */ posix_timer_discard_pending_locked(&sig_state.shared, id); - timer->signal_pending = false; - timer->signal_queued = false; + timer->notification = POSIX_TIMER_NONE; timer->overruns = 0; timer->last_overrun = 0; bool absolute = (flags & LINUX_TIMER_ABSTIME) != 0; @@ -1724,7 +1732,7 @@ int signal_posix_timer_set(int id, value->it_interval_nsec) : 0; bool notify = value_ns && timer->signum; - posix_timer_refresh_armed_locked(); + posix_timer_refresh_poll_hint_locked(); refresh_pending_hint_locked(); pthread_mutex_unlock(&sig_lock); if (notify) @@ -1740,7 +1748,7 @@ int signal_posix_timer_delete(int id) posix_timer_discard_pending_locked(&sig_state.shared, id); memset(timer, 0, sizeof(*timer)); } - posix_timer_refresh_armed_locked(); + posix_timer_refresh_poll_hint_locked(); refresh_pending_hint_locked(); pthread_mutex_unlock(&sig_lock); return timer ? 0 : -LINUX_EINVAL;