This pull request contains the following changes for UML:

- A new timer mode, time travel, for testing with UML
 - Many bugixes/improvements for the serial line driver
 - Various bugfixes
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCAA0FiEEdgfidid8lnn52cLTZvlZhesYu8EFAl0reewWHHJpY2hhcmRA
 c2lnbWEtc3Rhci5hdAAKCRBm+VmF6xi7wd4QEAC2KZoi6omge+nD7+tglfAdbZ0E
 X2HE3clA2tE5KqbgasT1IGZeZ/JE5wzjYJ38U1qdHd9RPeerX/snib4vyru4FSMd
 a39wjNbaqP/csPMLBukYEGs7Y4sSl1KzkRUdS9XkskCymkduhYyNbCc2WPMvAwBG
 xw6ffQzY/+zvC0e974jygjKbIEpU+uQ9LzwLnCKM/qKih4owwSA6Rj3tZwBSSQdG
 0BKR3o2J06ZXBiJjW+5vyMRU7N5Id/t6hf9OBhLqRk1YbfbebjVRNR2ghLSNvCF+
 3arPlE4T9tsjuZY+CCZh2LrrG8gzTx1M8pVlSFdgtqKCCp7MO40Q9cIhjmMYevym
 Zct8iLUtSUuIHU4/q2k7LeSPOiF6eEjbuVj2aEFTc8LSg/zYG/lF7xXESPkm2pf+
 eYQN2f8ML9fL183nEVkRxXhZwqCKSS7ktcKO0bRj3UsbdiJxRVvfe1POTWsvvuVi
 uV5YHgFBAhqcVabM2F9dOwk/4JRnNqJTGAUAVOwiyvk64sXLp/44DM/GbHgPMkSH
 uVqt70Yzt07RZ/2xDODW51xFx3WgbvmsKB6zN4Y7CAuc0CXBOSc61xgNFVhQdTrP
 sfAph4yUGs9mMyhFrdTVZaleZXA3Eo3V5FRvrESNNj53UzD2dRFKO065t58YMFuS
 UTqTJA2AHsJQ9j2TRA==
 =xxGu
 -----END PGP SIGNATURE-----

Merge tag 'for-linus-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rw/uml

Pull UML updates from Richard Weinberger:

 - A new timer mode, time travel, for testing with UML

 - Many bugixes/improvements for the serial line driver

 - Various bugfixes

* tag 'for-linus-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rw/uml:
  um: fix build without CONFIG_UML_TIME_TRAVEL_SUPPORT
  um: Fix kcov crash during startup
  um: configs: Remove useless UEVENT_HELPER_PATH
  um: Support time travel mode
  um: Pass nsecs to os timer functions
  um: Remove drivers/ssl.h
  um: Don't garbage collect in deactivate_all_fds()
  um: Silence lockdep complaint about mmap_sem
  um: Remove locking in deactivate_all_fds()
  um: Timer code cleanup
  um: fix os_timer_one_shot()
  um: Fix IRQ controller regression on console read
This commit is contained in:
Linus Torvalds 2019-07-14 17:17:34 -07:00
commit f2772a0e48
15 changed files with 321 additions and 143 deletions

View File

@ -184,6 +184,18 @@ config SECCOMP
If unsure, say Y.
config UML_TIME_TRAVEL_SUPPORT
bool
prompt "Support time-travel mode (e.g. for test execution)"
help
Enable this option to support time travel inside the UML instance.
After enabling this option, two modes are accessible at runtime
(selected by the kernel command line), see the kernel's command-
line help for more details.
It is safe to say Y, but you probably don't need this.
endmenu
source "arch/um/drivers/Kconfig"

View File

@ -36,7 +36,6 @@ CONFIG_XTERM_CHAN=y
CONFIG_CON_CHAN="pts"
CONFIG_SSL_CHAN="pts"
CONFIG_UML_SOUND=m
CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_BLK_DEV_UBD=y

View File

@ -34,7 +34,6 @@ CONFIG_XTERM_CHAN=y
CONFIG_CON_CHAN="pts"
CONFIG_SSL_CHAN="pts"
CONFIG_UML_SOUND=m
CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_BLK_DEV_UBD=y

View File

@ -171,19 +171,55 @@ int enable_chan(struct line *line)
return err;
}
/* Items are added in IRQ context, when free_irq can't be called, and
* removed in process context, when it can.
* This handles interrupt sources which disappear, and which need to
* be permanently disabled. This is discovered in IRQ context, but
* the freeing of the IRQ must be done later.
*/
static DEFINE_SPINLOCK(irqs_to_free_lock);
static LIST_HEAD(irqs_to_free);
void free_irqs(void)
{
struct chan *chan;
LIST_HEAD(list);
struct list_head *ele;
unsigned long flags;
spin_lock_irqsave(&irqs_to_free_lock, flags);
list_splice_init(&irqs_to_free, &list);
spin_unlock_irqrestore(&irqs_to_free_lock, flags);
list_for_each(ele, &list) {
chan = list_entry(ele, struct chan, free_list);
if (chan->input && chan->enabled)
um_free_irq(chan->line->driver->read_irq, chan);
if (chan->output && chan->enabled)
um_free_irq(chan->line->driver->write_irq, chan);
chan->enabled = 0;
}
}
static void close_one_chan(struct chan *chan, int delay_free_irq)
{
unsigned long flags;
if (!chan->opened)
return;
/* we can safely call free now - it will be marked
* as free and freed once the IRQ stopped processing
*/
if (chan->input && chan->enabled)
um_free_irq(chan->line->driver->read_irq, chan);
if (chan->output && chan->enabled)
um_free_irq(chan->line->driver->write_irq, chan);
chan->enabled = 0;
if (delay_free_irq) {
spin_lock_irqsave(&irqs_to_free_lock, flags);
list_add(&chan->free_list, &irqs_to_free);
spin_unlock_irqrestore(&irqs_to_free_lock, flags);
} else {
if (chan->input && chan->enabled)
um_free_irq(chan->line->driver->read_irq, chan);
if (chan->output && chan->enabled)
um_free_irq(chan->line->driver->write_irq, chan);
chan->enabled = 0;
}
if (chan->ops->close != NULL)
(*chan->ops->close)(chan->fd, chan->data);

View File

@ -12,7 +12,6 @@
#include <linux/console.h>
#include <asm/termbits.h>
#include <asm/irq.h>
#include "ssl.h"
#include "chan.h"
#include <init.h>
#include <irq_user.h>

View File

@ -1,13 +0,0 @@
/*
* Copyright (C) 2000 Jeff Dike (jdike@karaya.com)
* Licensed under the GPL
*/
#ifndef __SSL_H__
#define __SSL_H__
extern int ssl_read(int fd, int line);
extern void ssl_receive_char(int line, char ch);
#endif

View File

@ -52,7 +52,7 @@ static inline void activate_mm(struct mm_struct *old, struct mm_struct *new)
* when the new ->mm is used for the first time.
*/
__switch_mm(&new->context.id);
down_write(&new->mmap_sem);
down_write_nested(&new->mmap_sem, 1);
uml_setup_stubs(new);
up_write(&new->mmap_sem);
}

View File

@ -250,15 +250,13 @@ extern void os_warn(const char *fmt, ...)
/* time.c */
extern void os_idle_sleep(unsigned long long nsecs);
extern int os_timer_create(void* timer);
extern int os_timer_set_interval(void* timer, void* its);
extern int os_timer_one_shot(int ticks);
extern long long os_timer_disable(void);
extern long os_timer_remain(void* timer);
extern int os_timer_create(void);
extern int os_timer_set_interval(unsigned long long nsecs);
extern int os_timer_one_shot(unsigned long long nsecs);
extern void os_timer_disable(void);
extern void uml_idle_timer(void);
extern long long os_persistent_clock_emulation(void);
extern long long os_nsecs(void);
extern long long os_vnsecs(void);
/* skas/mem.c */
extern long run_syscall_stub(struct mm_id * mm_idp,

View File

@ -10,4 +10,52 @@
#define TIMER_MULTIPLIER 256
#define TIMER_MIN_DELTA 500
enum time_travel_mode {
TT_MODE_OFF,
TT_MODE_BASIC,
TT_MODE_INFCPU,
};
enum time_travel_timer_mode {
TT_TMR_DISABLED,
TT_TMR_ONESHOT,
TT_TMR_PERIODIC,
};
#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
extern enum time_travel_mode time_travel_mode;
extern unsigned long long time_travel_time;
extern enum time_travel_timer_mode time_travel_timer_mode;
extern unsigned long long time_travel_timer_expiry;
extern unsigned long long time_travel_timer_interval;
static inline void time_travel_set_time(unsigned long long ns)
{
time_travel_time = ns;
}
static inline void time_travel_set_timer(enum time_travel_timer_mode mode,
unsigned long long expiry)
{
time_travel_timer_mode = mode;
time_travel_timer_expiry = expiry;
}
#else
#define time_travel_mode TT_MODE_OFF
#define time_travel_time 0
#define time_travel_timer_expiry 0
#define time_travel_timer_interval 0
static inline void time_travel_set_time(unsigned long long ns)
{
}
static inline void time_travel_set_timer(enum time_travel_timer_mode mode,
unsigned long long expiry)
{
}
#define time_travel_timer_mode TT_TMR_DISABLED
#endif
#endif

View File

@ -21,6 +21,8 @@
#include <irq_user.h>
extern void free_irqs(void);
/* When epoll triggers we do not know why it did so
* we can also have different IRQs for read and write.
* This is why we keep a small irq_fd array for each fd -
@ -100,6 +102,8 @@ void sigio_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
}
}
}
free_irqs();
}
static int assign_epoll_events_to_irq(struct irq_entry *irq_entry)
@ -380,10 +384,8 @@ EXPORT_SYMBOL(deactivate_fd);
*/
int deactivate_all_fds(void)
{
unsigned long flags;
struct irq_entry *to_free;
spin_lock_irqsave(&irq_lock, flags);
/* Stop IO. The IRQ loop has no lock so this is our
* only way of making sure we are safe to dispose
* of all IRQ handlers
@ -399,8 +401,7 @@ int deactivate_all_fds(void)
);
to_free = to_free->next;
}
garbage_collect_irq_entries();
spin_unlock_irqrestore(&irq_lock, flags);
/* don't garbage collect - we can no longer call kfree() here */
os_close_epoll_fd();
return 0;
}

View File

@ -203,10 +203,50 @@ void initial_thread_cb(void (*proc)(void *), void *arg)
kmalloc_ok = save_kmalloc_ok;
}
static void time_travel_sleep(unsigned long long duration)
{
unsigned long long next = time_travel_time + duration;
if (time_travel_mode != TT_MODE_INFCPU)
os_timer_disable();
if (time_travel_timer_mode != TT_TMR_DISABLED ||
time_travel_timer_expiry < next) {
if (time_travel_timer_mode == TT_TMR_ONESHOT)
time_travel_set_timer(TT_TMR_DISABLED, 0);
/*
* time_travel_time will be adjusted in the timer
* IRQ handler so it works even when the signal
* comes from the OS timer
*/
deliver_alarm();
} else {
time_travel_set_time(next);
}
if (time_travel_mode != TT_MODE_INFCPU) {
if (time_travel_timer_mode == TT_TMR_PERIODIC)
os_timer_set_interval(time_travel_timer_interval);
else if (time_travel_timer_mode == TT_TMR_ONESHOT)
os_timer_one_shot(time_travel_timer_expiry - next);
}
}
static void um_idle_sleep(void)
{
unsigned long long duration = UM_NSEC_PER_SEC;
if (time_travel_mode != TT_MODE_OFF) {
time_travel_sleep(duration);
} else {
os_idle_sleep(duration);
}
}
void arch_cpu_idle(void)
{
cpu_tasks[current_thread_info()->cpu].pid = os_getpid();
os_idle_sleep(UM_NSEC_PER_SEC);
um_idle_sleep();
local_irq_enable();
}

View File

@ -12,4 +12,6 @@ obj-y := clone.o mmu.o process.o syscall.o uaccess.o
CFLAGS_clone.o := $(CFLAGS_NO_HARDENING)
UNPROFILE_OBJS := clone.o
KCOV_INSTRUMENT := n
include arch/um/scripts/Makefile.rules

View File

@ -10,12 +10,23 @@
#include <sysdep/ptrace.h>
#include <sysdep/ptrace_user.h>
#include <sysdep/syscalls.h>
#include <shared/timer-internal.h>
void handle_syscall(struct uml_pt_regs *r)
{
struct pt_regs *regs = container_of(r, struct pt_regs, regs);
int syscall;
/*
* If we have infinite CPU resources, then make every syscall also a
* preemption point, since we don't have any other preemption in this
* case, and kernel threads would basically never run until userspace
* went to sleep, even if said userspace interacts with the kernel in
* various ways.
*/
if (time_travel_mode == TT_MODE_INFCPU)
schedule();
/* Initialize the syscall number and default return value. */
UPT_SYSCALL_NR(r) = PT_SYSCALL_NR(r->gp);
PT_REGS_SET_SYSCALL_RETURN(regs, -ENOSYS);

View File

@ -19,11 +19,29 @@
#include <kern_util.h>
#include <os.h>
#include <timer-internal.h>
#include <shared/init.h>
#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
enum time_travel_mode time_travel_mode;
unsigned long long time_travel_time;
enum time_travel_timer_mode time_travel_timer_mode;
unsigned long long time_travel_timer_expiry;
unsigned long long time_travel_timer_interval;
static bool time_travel_start_set;
static unsigned long long time_travel_start;
#else
#define time_travel_start_set 0
#define time_travel_start 0
#endif
void timer_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
{
unsigned long flags;
if (time_travel_mode != TT_MODE_OFF)
time_travel_set_time(time_travel_timer_expiry);
local_irq_save(flags);
do_IRQ(TIMER_IRQ, regs);
local_irq_restore(flags);
@ -31,26 +49,47 @@ void timer_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
static int itimer_shutdown(struct clock_event_device *evt)
{
os_timer_disable();
if (time_travel_mode != TT_MODE_OFF)
time_travel_set_timer(TT_TMR_DISABLED, 0);
if (time_travel_mode != TT_MODE_INFCPU)
os_timer_disable();
return 0;
}
static int itimer_set_periodic(struct clock_event_device *evt)
{
os_timer_set_interval(NULL, NULL);
unsigned long long interval = NSEC_PER_SEC / HZ;
if (time_travel_mode != TT_MODE_OFF)
time_travel_set_timer(TT_TMR_PERIODIC,
time_travel_time + interval);
if (time_travel_mode != TT_MODE_INFCPU)
os_timer_set_interval(interval);
return 0;
}
static int itimer_next_event(unsigned long delta,
struct clock_event_device *evt)
{
return os_timer_one_shot(delta);
delta += 1;
if (time_travel_mode != TT_MODE_OFF)
time_travel_set_timer(TT_TMR_ONESHOT,
time_travel_time + delta);
if (time_travel_mode != TT_MODE_INFCPU)
return os_timer_one_shot(delta);
return 0;
}
static int itimer_one_shot(struct clock_event_device *evt)
{
os_timer_one_shot(1);
return 0;
return itimer_next_event(0, evt);
}
static struct clock_event_device timer_clockevent = {
@ -87,6 +126,17 @@ static irqreturn_t um_timer(int irq, void *dev)
static u64 timer_read(struct clocksource *cs)
{
if (time_travel_mode != TT_MODE_OFF) {
/*
* We make reading the timer cost a bit so that we don't get
* stuck in loops that expect time to move more than the
* exact requested sleep amount, e.g. python's socket server,
* see https://bugs.python.org/issue37026.
*/
time_travel_set_time(time_travel_time + TIMER_MULTIPLIER);
return time_travel_time / TIMER_MULTIPLIER;
}
return os_nsecs() / TIMER_MULTIPLIER;
}
@ -107,7 +157,7 @@ static void __init um_timer_setup(void)
printk(KERN_ERR "register_timer : request_irq failed - "
"errno = %d\n", -err);
err = os_timer_create(NULL);
err = os_timer_create();
if (err != 0) {
printk(KERN_ERR "creation of timer failed - errno = %d\n", -err);
return;
@ -123,7 +173,12 @@ static void __init um_timer_setup(void)
void read_persistent_clock64(struct timespec64 *ts)
{
long long nsecs = os_persistent_clock_emulation();
long long nsecs;
if (time_travel_start_set)
nsecs = time_travel_start + time_travel_time;
else
nsecs = os_persistent_clock_emulation();
set_normalized_timespec64(ts, nsecs / NSEC_PER_SEC,
nsecs % NSEC_PER_SEC);
@ -134,3 +189,65 @@ void __init time_init(void)
timer_set_signal_handler();
late_time_init = um_timer_setup;
}
#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
unsigned long calibrate_delay_is_known(void)
{
if (time_travel_mode == TT_MODE_INFCPU)
return 1;
return 0;
}
int setup_time_travel(char *str)
{
if (strcmp(str, "=inf-cpu") == 0) {
time_travel_mode = TT_MODE_INFCPU;
timer_clockevent.name = "time-travel-timer-infcpu";
timer_clocksource.name = "time-travel-clock";
return 1;
}
if (!*str) {
time_travel_mode = TT_MODE_BASIC;
timer_clockevent.name = "time-travel-timer";
timer_clocksource.name = "time-travel-clock";
return 1;
}
return -EINVAL;
}
__setup("time-travel", setup_time_travel);
__uml_help(setup_time_travel,
"time-travel\n"
"This option just enables basic time travel mode, in which the clock/timers\n"
"inside the UML instance skip forward when there's nothing to do, rather than\n"
"waiting for real time to elapse. However, instance CPU speed is limited by\n"
"the real CPU speed, so e.g. a 10ms timer will always fire after ~10ms wall\n"
"clock (but quicker when there's nothing to do).\n"
"\n"
"time-travel=inf-cpu\n"
"This enables time travel mode with infinite processing power, in which there\n"
"are no wall clock timers, and any CPU processing happens - as seen from the\n"
"guest - instantly. This can be useful for accurate simulation regardless of\n"
"debug overhead, physical CPU speed, etc. but is somewhat dangerous as it can\n"
"easily lead to getting stuck (e.g. if anything in the system busy loops).\n");
int setup_time_travel_start(char *str)
{
int err;
err = kstrtoull(str, 0, &time_travel_start);
if (err)
return err;
time_travel_start_set = 1;
return 1;
}
__setup("time-travel-start", setup_time_travel_start);
__uml_help(setup_time_travel_start,
"time-travel-start=<seconds>\n"
"Configure the UML instance's wall clock to start at this value rather than\n"
"the host's wall clock at the time of UML boot.\n");
#endif

View File

@ -26,11 +26,11 @@ static inline long long timeval_to_ns(const struct timeval *tv)
static inline long long timespec_to_ns(const struct timespec *ts)
{
return ((long long) ts->tv_sec * UM_NSEC_PER_SEC) +
ts->tv_nsec;
return ((long long) ts->tv_sec * UM_NSEC_PER_SEC) + ts->tv_nsec;
}
long long os_persistent_clock_emulation (void) {
long long os_persistent_clock_emulation(void)
{
struct timespec realtime_tp;
clock_gettime(CLOCK_REALTIME, &realtime_tp);
@ -40,94 +40,41 @@ long long os_persistent_clock_emulation (void) {
/**
* os_timer_create() - create an new posix (interval) timer
*/
int os_timer_create(void* timer) {
int os_timer_create(void)
{
timer_t *t = &event_high_res_timer;
timer_t* t = timer;
if(t == NULL) {
t = &event_high_res_timer;
}
if (timer_create(
CLOCK_MONOTONIC,
NULL,
t) == -1) {
if (timer_create(CLOCK_MONOTONIC, NULL, t) == -1)
return -1;
}
return 0;
}
int os_timer_set_interval(void* timer, void* i)
{
struct itimerspec its;
unsigned long long nsec;
timer_t* t = timer;
struct itimerspec* its_in = i;
if(t == NULL) {
t = &event_high_res_timer;
}
nsec = UM_NSEC_PER_SEC / UM_HZ;
if(its_in != NULL) {
its.it_value.tv_sec = its_in->it_value.tv_sec;
its.it_value.tv_nsec = its_in->it_value.tv_nsec;
} else {
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = nsec;
}
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = nsec;
if(timer_settime(*t, 0, &its, NULL) == -1) {
return -errno;
}
return 0;
}
/**
* os_timer_remain() - returns the remaining nano seconds of the given interval
* timer
* Because this is the remaining time of an interval timer, which correspondends
* to HZ, this value can never be bigger than one second. Just
* the nanosecond part of the timer is returned.
* The returned time is relative to the start time of the interval timer.
* Return an negative value in an error case.
*/
long os_timer_remain(void* timer)
int os_timer_set_interval(unsigned long long nsecs)
{
struct itimerspec its;
timer_t* t = timer;
if(t == NULL) {
t = &event_high_res_timer;
}
its.it_value.tv_sec = nsecs / UM_NSEC_PER_SEC;
its.it_value.tv_nsec = nsecs % UM_NSEC_PER_SEC;
if(timer_gettime(t, &its) == -1) {
its.it_interval.tv_sec = nsecs / UM_NSEC_PER_SEC;
its.it_interval.tv_nsec = nsecs % UM_NSEC_PER_SEC;
if (timer_settime(event_high_res_timer, 0, &its, NULL) == -1)
return -errno;
}
return its.it_value.tv_nsec;
return 0;
}
int os_timer_one_shot(int ticks)
int os_timer_one_shot(unsigned long long nsecs)
{
struct itimerspec its;
unsigned long long nsec;
unsigned long sec;
struct itimerspec its = {
.it_value.tv_sec = nsecs / UM_NSEC_PER_SEC,
.it_value.tv_nsec = nsecs % UM_NSEC_PER_SEC,
nsec = (ticks + 1);
sec = nsec / UM_NSEC_PER_SEC;
nsec = nsec % UM_NSEC_PER_SEC;
its.it_value.tv_sec = nsec / UM_NSEC_PER_SEC;
its.it_value.tv_nsec = nsec;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 0; // we cheat here
.it_interval.tv_sec = 0,
.it_interval.tv_nsec = 0, // we cheat here
};
timer_settime(event_high_res_timer, 0, &its, NULL);
return 0;
@ -135,24 +82,13 @@ int os_timer_one_shot(int ticks)
/**
* os_timer_disable() - disable the posix (interval) timer
* Returns the remaining interval timer time in nanoseconds
*/
long long os_timer_disable(void)
void os_timer_disable(void)
{
struct itimerspec its;
memset(&its, 0, sizeof(struct itimerspec));
timer_settime(event_high_res_timer, 0, &its, &its);
return its.it_value.tv_sec * UM_NSEC_PER_SEC + its.it_value.tv_nsec;
}
long long os_vnsecs(void)
{
struct timespec ts;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&ts);
return timespec_to_ns(&ts);
timer_settime(event_high_res_timer, 0, &its, NULL);
}
long long os_nsecs(void)
@ -169,21 +105,14 @@ long long os_nsecs(void)
*/
void os_idle_sleep(unsigned long long nsecs)
{
struct timespec ts;
if (nsecs <= 0) {
return;
}
ts = ((struct timespec) {
.tv_sec = nsecs / UM_NSEC_PER_SEC,
.tv_nsec = nsecs % UM_NSEC_PER_SEC
});
struct timespec ts = {
.tv_sec = nsecs / UM_NSEC_PER_SEC,
.tv_nsec = nsecs % UM_NSEC_PER_SEC
};
/*
* Relay the signal if clock_nanosleep is interrupted.
*/
if (clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL)) {
if (clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL))
deliver_alarm();
}
}