// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2013 Red Hat, Inc., Frederic Weisbecker * * Selftests for a few posix timers interface. * * Kernel loop code stolen from Steven Rostedt */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "../kselftest.h" #define DELAY 2 #define USECS_PER_SEC 1000000 #define NSECS_PER_SEC 1000000000 static void __fatal_error(const char *test, const char *name, const char *what) { char buf[64]; strerror_r(errno, buf, sizeof(buf)); if (name && strlen(name)) ksft_exit_fail_msg("%s %s %s %s\n", test, name, what, buf); else ksft_exit_fail_msg("%s %s %s\n", test, what, buf); } #define fatal_error(name, what) __fatal_error(__func__, name, what) static volatile int done; /* Busy loop in userspace to elapse ITIMER_VIRTUAL */ static void user_loop(void) { while (!done); } /* * Try to spend as much time as possible in kernelspace * to elapse ITIMER_PROF. */ static void kernel_loop(void) { void *addr = sbrk(0); int err = 0; while (!done && !err) { err = brk(addr + 4096); err |= brk(addr); } } /* * Sleep until ITIMER_REAL expiration. */ static void idle_loop(void) { pause(); } static void sig_handler(int nr) { done = 1; } /* * Check the expected timer expiration matches the GTOD elapsed delta since * we armed the timer. Keep a 0.5 sec error margin due to various jitter. */ static int check_diff(struct timeval start, struct timeval end) { long long diff; diff = end.tv_usec - start.tv_usec; diff += (end.tv_sec - start.tv_sec) * USECS_PER_SEC; if (llabs(diff - DELAY * USECS_PER_SEC) > USECS_PER_SEC / 2) { printf("Diff too high: %lld..", diff); return -1; } return 0; } static void check_itimer(int which, const char *name) { struct timeval start, end; struct itimerval val = { .it_value.tv_sec = DELAY, }; done = 0; if (which == ITIMER_VIRTUAL) signal(SIGVTALRM, sig_handler); else if (which == ITIMER_PROF) signal(SIGPROF, sig_handler); else if (which == ITIMER_REAL) signal(SIGALRM, sig_handler); if (gettimeofday(&start, NULL) < 0) fatal_error(name, "gettimeofday()"); if (setitimer(which, &val, NULL) < 0) fatal_error(name, "setitimer()"); if (which == ITIMER_VIRTUAL) user_loop(); else if (which == ITIMER_PROF) kernel_loop(); else if (which == ITIMER_REAL) idle_loop(); if (gettimeofday(&end, NULL) < 0) fatal_error(name, "gettimeofday()"); ksft_test_result(check_diff(start, end) == 0, "%s\n", name); } static void check_timer_create(int which, const char *name) { struct timeval start, end; struct itimerspec val = { .it_value.tv_sec = DELAY, }; timer_t id; done = 0; if (timer_create(which, NULL, &id) < 0) fatal_error(name, "timer_create()"); if (signal(SIGALRM, sig_handler) == SIG_ERR) fatal_error(name, "signal()"); if (gettimeofday(&start, NULL) < 0) fatal_error(name, "gettimeofday()"); if (timer_settime(id, 0, &val, NULL) < 0) fatal_error(name, "timer_settime()"); user_loop(); if (gettimeofday(&end, NULL) < 0) fatal_error(name, "gettimeofday()"); ksft_test_result(check_diff(start, end) == 0, "timer_create() per %s\n", name); } static pthread_t ctd_thread; static volatile int ctd_count, ctd_failed; static void ctd_sighandler(int sig) { if (pthread_self() != ctd_thread) ctd_failed = 1; ctd_count--; } static void *ctd_thread_func(void *arg) { struct itimerspec val = { .it_value.tv_sec = 0, .it_value.tv_nsec = 1000 * 1000, .it_interval.tv_sec = 0, .it_interval.tv_nsec = 1000 * 1000, }; timer_t id; /* 1/10 seconds to ensure the leader sleeps */ usleep(10000); ctd_count = 100; if (timer_create(CLOCK_PROCESS_CPUTIME_ID, NULL, &id)) fatal_error(NULL, "timer_create()"); if (timer_settime(id, 0, &val, NULL)) fatal_error(NULL, "timer_settime()"); while (ctd_count > 0 && !ctd_failed) ; if (timer_delete(id)) fatal_error(NULL, "timer_delete()"); return NULL; } /* * Test that only the running thread receives the timer signal. */ static void check_timer_distribution(void) { if (signal(SIGALRM, ctd_sighandler) == SIG_ERR) fatal_error(NULL, "signal()"); if (pthread_create(&ctd_thread, NULL, ctd_thread_func, NULL)) fatal_error(NULL, "pthread_create()"); if (pthread_join(ctd_thread, NULL)) fatal_error(NULL, "pthread_join()"); if (!ctd_failed) ksft_test_result_pass("check signal distribution\n"); else if (ksft_min_kernel_version(6, 3)) ksft_test_result_fail("check signal distribution\n"); else ksft_test_result_skip("check signal distribution (old kernel)\n"); } struct tmrsig { int signals; int overruns; }; static void siginfo_handler(int sig, siginfo_t *si, void *uc) { struct tmrsig *tsig = si ? si->si_ptr : NULL; if (tsig) { tsig->signals++; tsig->overruns += si->si_overrun; } } static void *ignore_thread(void *arg) { unsigned int *tid = arg; sigset_t set; sigemptyset(&set); sigaddset(&set, SIGUSR1); if (sigprocmask(SIG_BLOCK, &set, NULL)) fatal_error(NULL, "sigprocmask(SIG_BLOCK)"); *tid = gettid(); sleep(100); if (sigprocmask(SIG_UNBLOCK, &set, NULL)) fatal_error(NULL, "sigprocmask(SIG_UNBLOCK)"); return NULL; } static void check_sig_ign(int thread) { struct tmrsig tsig = { }; struct itimerspec its; unsigned int tid = 0; struct sigaction sa; struct sigevent sev; pthread_t pthread; timer_t timerid; sigset_t set; if (thread) { if (pthread_create(&pthread, NULL, ignore_thread, &tid)) fatal_error(NULL, "pthread_create()"); sleep(1); } sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = siginfo_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGUSR1, &sa, NULL)) fatal_error(NULL, "sigaction()"); /* Block the signal */ sigemptyset(&set); sigaddset(&set, SIGUSR1); if (sigprocmask(SIG_BLOCK, &set, NULL)) fatal_error(NULL, "sigprocmask(SIG_BLOCK)"); memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGUSR1; sev.sigev_value.sival_ptr = &tsig; if (thread) { sev.sigev_notify = SIGEV_THREAD_ID; sev._sigev_un._tid = tid; } if (timer_create(CLOCK_MONOTONIC, &sev, &timerid)) fatal_error(NULL, "timer_create()"); /* Start the timer to expire in 100ms and 100ms intervals */ its.it_value.tv_sec = 0; its.it_value.tv_nsec = 100000000; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 100000000; timer_settime(timerid, 0, &its, NULL); sleep(1); /* Set the signal to be ignored */ if (signal(SIGUSR1, SIG_IGN) == SIG_ERR) fatal_error(NULL, "signal(SIG_IGN)"); sleep(1); if (thread) { /* Stop the thread first. No signal should be delivered to it */ if (pthread_cancel(pthread)) fatal_error(NULL, "pthread_cancel()"); if (pthread_join(pthread, NULL)) fatal_error(NULL, "pthread_join()"); } /* Restore the handler */ if (sigaction(SIGUSR1, &sa, NULL)) fatal_error(NULL, "sigaction()"); sleep(1); /* Unblock it, which should deliver the signal in the !thread case*/ if (sigprocmask(SIG_UNBLOCK, &set, NULL)) fatal_error(NULL, "sigprocmask(SIG_UNBLOCK)"); if (timer_delete(timerid)) fatal_error(NULL, "timer_delete()"); if (!thread) { ksft_test_result(tsig.signals == 1 && tsig.overruns == 29, "check_sig_ign SIGEV_SIGNAL\n"); } else { ksft_test_result(tsig.signals == 0 && tsig.overruns == 0, "check_sig_ign SIGEV_THREAD_ID\n"); } } static void check_rearm(void) { struct tmrsig tsig = { }; struct itimerspec its; struct sigaction sa; struct sigevent sev; timer_t timerid; sigset_t set; sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = siginfo_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGUSR1, &sa, NULL)) fatal_error(NULL, "sigaction()"); /* Block the signal */ sigemptyset(&set); sigaddset(&set, SIGUSR1); if (sigprocmask(SIG_BLOCK, &set, NULL)) fatal_error(NULL, "sigprocmask(SIG_BLOCK)"); memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGUSR1; sev.sigev_value.sival_ptr = &tsig; if (timer_create(CLOCK_MONOTONIC, &sev, &timerid)) fatal_error(NULL, "timer_create()"); /* Start the timer to expire in 100ms and 100ms intervals */ its.it_value.tv_sec = 0; its.it_value.tv_nsec = 100000000; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 100000000; if (timer_settime(timerid, 0, &its, NULL)) fatal_error(NULL, "timer_settime()"); sleep(1); /* Reprogram the timer to single shot */ its.it_value.tv_sec = 10; its.it_value.tv_nsec = 0; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0; if (timer_settime(timerid, 0, &its, NULL)) fatal_error(NULL, "timer_settime()"); /* Unblock it, which should not deliver a signal */ if (sigprocmask(SIG_UNBLOCK, &set, NULL)) fatal_error(NULL, "sigprocmask(SIG_UNBLOCK)"); if (timer_delete(timerid)) fatal_error(NULL, "timer_delete()"); ksft_test_result(!tsig.signals, "check_rearm\n"); } static void check_delete(void) { struct tmrsig tsig = { }; struct itimerspec its; struct sigaction sa; struct sigevent sev; timer_t timerid; sigset_t set; sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = siginfo_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGUSR1, &sa, NULL)) fatal_error(NULL, "sigaction()"); /* Block the signal */ sigemptyset(&set); sigaddset(&set, SIGUSR1); if (sigprocmask(SIG_BLOCK, &set, NULL)) fatal_error(NULL, "sigprocmask(SIG_BLOCK)"); memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGUSR1; sev.sigev_value.sival_ptr = &tsig; if (timer_create(CLOCK_MONOTONIC, &sev, &timerid)) fatal_error(NULL, "timer_create()"); /* Start the timer to expire in 100ms and 100ms intervals */ its.it_value.tv_sec = 0; its.it_value.tv_nsec = 100000000; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 100000000; if (timer_settime(timerid, 0, &its, NULL)) fatal_error(NULL, "timer_settime()"); sleep(1); if (timer_delete(timerid)) fatal_error(NULL, "timer_delete()"); /* Unblock it, which should not deliver a signal */ if (sigprocmask(SIG_UNBLOCK, &set, NULL)) fatal_error(NULL, "sigprocmask(SIG_UNBLOCK)"); ksft_test_result(!tsig.signals, "check_delete\n"); } static inline int64_t calcdiff_ns(struct timespec t1, struct timespec t2) { int64_t diff; diff = NSECS_PER_SEC * (int64_t)((int) t1.tv_sec - (int) t2.tv_sec); diff += ((int) t1.tv_nsec - (int) t2.tv_nsec); return diff; } static void check_sigev_none(int which, const char *name) { struct timespec start, now; struct itimerspec its; struct sigevent sev; timer_t timerid; memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_NONE; if (timer_create(which, &sev, &timerid)) fatal_error(name, "timer_create()"); /* Start the timer to expire in 100ms and 100ms intervals */ its.it_value.tv_sec = 0; its.it_value.tv_nsec = 100000000; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 100000000; timer_settime(timerid, 0, &its, NULL); if (clock_gettime(which, &start)) fatal_error(name, "clock_gettime()"); do { if (clock_gettime(which, &now)) fatal_error(name, "clock_gettime()"); } while (calcdiff_ns(now, start) < NSECS_PER_SEC); if (timer_gettime(timerid, &its)) fatal_error(name, "timer_gettime()"); if (timer_delete(timerid)) fatal_error(name, "timer_delete()"); ksft_test_result(its.it_value.tv_sec || its.it_value.tv_nsec, "check_sigev_none %s\n", name); } static void check_gettime(int which, const char *name) { struct itimerspec its, prev; struct timespec start, now; struct sigevent sev; timer_t timerid; int wraps = 0; sigset_t set; /* Block the signal */ sigemptyset(&set); sigaddset(&set, SIGUSR1); if (sigprocmask(SIG_BLOCK, &set, NULL)) fatal_error(name, "sigprocmask(SIG_BLOCK)"); memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGUSR1; if (timer_create(which, &sev, &timerid)) fatal_error(name, "timer_create()"); /* Start the timer to expire in 100ms and 100ms intervals */ its.it_value.tv_sec = 0; its.it_value.tv_nsec = 100000000; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 100000000; if (timer_settime(timerid, 0, &its, NULL)) fatal_error(name, "timer_settime()"); if (timer_gettime(timerid, &prev)) fatal_error(name, "timer_gettime()"); if (clock_gettime(which, &start)) fatal_error(name, "clock_gettime()"); do { if (clock_gettime(which, &now)) fatal_error(name, "clock_gettime()"); if (timer_gettime(timerid, &its)) fatal_error(name, "timer_gettime()"); if (its.it_value.tv_nsec > prev.it_value.tv_nsec) wraps++; prev = its; } while (calcdiff_ns(now, start) < NSECS_PER_SEC); if (timer_delete(timerid)) fatal_error(name, "timer_delete()"); ksft_test_result(wraps > 1, "check_gettime %s\n", name); } static void check_overrun(int which, const char *name) { struct timespec start, now; struct tmrsig tsig = { }; struct itimerspec its; struct sigaction sa; struct sigevent sev; timer_t timerid; sigset_t set; sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = siginfo_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGUSR1, &sa, NULL)) fatal_error(name, "sigaction()"); /* Block the signal */ sigemptyset(&set); sigaddset(&set, SIGUSR1); if (sigprocmask(SIG_BLOCK, &set, NULL)) fatal_error(name, "sigprocmask(SIG_BLOCK)"); memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGUSR1; sev.sigev_value.sival_ptr = &tsig; if (timer_create(which, &sev, &timerid)) fatal_error(name, "timer_create()"); /* Start the timer to expire in 100ms and 100ms intervals */ its.it_value.tv_sec = 0; its.it_value.tv_nsec = 100000000; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 100000000; if (timer_settime(timerid, 0, &its, NULL)) fatal_error(name, "timer_settime()"); if (clock_gettime(which, &start)) fatal_error(name, "clock_gettime()"); do { if (clock_gettime(which, &now)) fatal_error(name, "clock_gettime()"); } while (calcdiff_ns(now, start) < NSECS_PER_SEC); /* Unblock it, which should deliver a signal */ if (sigprocmask(SIG_UNBLOCK, &set, NULL)) fatal_error(name, "sigprocmask(SIG_UNBLOCK)"); if (timer_delete(timerid)) fatal_error(name, "timer_delete()"); ksft_test_result(tsig.signals == 1 && tsig.overruns == 9, "check_overrun %s\n", name); } int main(int argc, char **argv) { ksft_print_header(); ksft_set_plan(18); ksft_print_msg("Testing posix timers. False negative may happen on CPU execution \n"); ksft_print_msg("based timers if other threads run on the CPU...\n"); check_itimer(ITIMER_VIRTUAL, "ITIMER_VIRTUAL"); check_itimer(ITIMER_PROF, "ITIMER_PROF"); check_itimer(ITIMER_REAL, "ITIMER_REAL"); check_timer_create(CLOCK_THREAD_CPUTIME_ID, "CLOCK_THREAD_CPUTIME_ID"); /* * It's unfortunately hard to reliably test a timer expiration * on parallel multithread cputime. We could arm it to expire * on DELAY * nr_threads, with nr_threads busy looping, then wait * the normal DELAY since the time is elapsing nr_threads faster. * But for that we need to ensure we have real physical free CPUs * to ensure true parallelism. So test only one thread until we * find a better solution. */ check_timer_create(CLOCK_PROCESS_CPUTIME_ID, "CLOCK_PROCESS_CPUTIME_ID"); check_timer_distribution(); check_sig_ign(0); check_sig_ign(1); check_rearm(); check_delete(); check_sigev_none(CLOCK_MONOTONIC, "CLOCK_MONOTONIC"); check_sigev_none(CLOCK_PROCESS_CPUTIME_ID, "CLOCK_PROCESS_CPUTIME_ID"); check_gettime(CLOCK_MONOTONIC, "CLOCK_MONOTONIC"); check_gettime(CLOCK_PROCESS_CPUTIME_ID, "CLOCK_PROCESS_CPUTIME_ID"); check_gettime(CLOCK_THREAD_CPUTIME_ID, "CLOCK_THREAD_CPUTIME_ID"); check_overrun(CLOCK_MONOTONIC, "CLOCK_MONOTONIC"); check_overrun(CLOCK_PROCESS_CPUTIME_ID, "CLOCK_PROCESS_CPUTIME_ID"); check_overrun(CLOCK_THREAD_CPUTIME_ID, "CLOCK_THREAD_CPUTIME_ID"); ksft_finished(); }