The Pedigree Project 0.1
cpu-itimers.c
1#define _GNU_SOURCE
2#include <errno.h>
3#include <signal.h>
4#include <stdint.h>
5#include <unistd.h>
6
7#include "contract.h"
8#include <sys/resource.h>
9#include <sys/time.h>
10
11static volatile sig_atomic_t virtual_expiries, profile_expiries;
12static volatile uint64_t checksum = 0x12345678;
13
14static void expired(int signal) {
15 if (signal == SIGVTALRM)
16 ++virtual_expiries;
17 else if (signal == SIGPROF)
18 ++profile_expiries;
19}
20
21static int arm_cpu(int timer, int value_ms, int interval_ms) {
22 struct itimerval setting = {.it_value = {value_ms / 1000, (value_ms % 1000) * 1000},
23 .it_interval = {interval_ms / 1000, (interval_ms % 1000) * 1000}};
24 return setitimer(timer, &setting, NULL);
25}
26
27static int64_t timeval_us(struct timeval value) {
28 return (int64_t)value.tv_sec * 1000000 + value.tv_usec;
29}
30
31static void user_chunk(void) {
32 uint64_t value = checksum;
33 for (unsigned i = 0; i < 65536; ++i) {
34 value ^= value << 13;
35 value ^= value >> 7;
36 value ^= value << 17;
37 }
38 checksum = value;
39}
40
41static int burn_user(int milliseconds) {
42 struct rusage before, after;
43 if (getrusage(RUSAGE_SELF, &before))
44 return -1;
45 int64_t deadline = st_now(CLOCK_MONOTONIC) + 5000000000;
46 do {
47 user_chunk();
48 if (getrusage(RUSAGE_SELF, &after) || st_now(CLOCK_MONOTONIC) >= deadline)
49 return -1;
50 } while (timeval_us(after.ru_utime) - timeval_us(before.ru_utime) < milliseconds * 1000);
51 return 0;
52}
53
54static int await_expiries(volatile sig_atomic_t* count, sig_atomic_t target) {
55 int64_t deadline = st_now(CLOCK_MONOTONIC) + 5000000000;
56 while (*count < target) {
57 user_chunk();
58 if (st_now(CLOCK_MONOTONIC) >= deadline)
59 return -1;
60 }
61 return 0;
62}
63
64int signal_timer_test_cpu_itimers(void) {
65 int failed = 0;
66 struct sigaction action = {.sa_handler = expired};
67 struct itimerval observed;
68 struct rusage before, after;
69 sigemptyset(&action.sa_mask);
70 CHECK(sigaction(SIGVTALRM, &action, NULL) == 0);
71 CHECK(sigaction(SIGPROF, &action, NULL) == 0);
72 CHECK(arm_cpu(ITIMER_VIRTUAL, 0, 0) == 0 && arm_cpu(ITIMER_PROF, 0, 0) == 0);
73 CHECK(getrusage(RUSAGE_SELF, &before) == 0 && burn_user(40) == 0 &&
74 getrusage(RUSAGE_SELF, &after) == 0);
75 // Sampled accounting may miss the brief syscalls in this user-heavy workload.
76 CHECK(timeval_us(after.ru_utime) > timeval_us(before.ru_utime) &&
77 timeval_us(after.ru_stime) >= timeval_us(before.ru_stime) && !virtual_expiries &&
78 !profile_expiries);
79 puts("CPU-ITIMER-CONTRACT: PASS disarmed-rusage");
80
81 CHECK(arm_cpu(ITIMER_VIRTUAL, 20, 0) == 0 && arm_cpu(ITIMER_PROF, 50, 0) == 0);
82 CHECK(await_expiries(&virtual_expiries, 1) == 0 && await_expiries(&profile_expiries, 1) == 0);
83 CHECK(getitimer(ITIMER_VIRTUAL, &observed) == 0 && !timeval_us(observed.it_value));
84 CHECK(getitimer(ITIMER_PROF, &observed) == 0 && !timeval_us(observed.it_value));
85 CHECK(virtual_expiries == 1 && profile_expiries == 1);
86 puts("CPU-ITIMER-CONTRACT: PASS one-shot-signals");
87
88 CHECK(arm_cpu(ITIMER_VIRTUAL, 20, 20) == 0 && arm_cpu(ITIMER_PROF, 20, 20) == 0);
89 CHECK(await_expiries(&virtual_expiries, 3) == 0 && await_expiries(&profile_expiries, 3) == 0);
90 CHECK(arm_cpu(ITIMER_VIRTUAL, 0, 0) == 0);
91 sig_atomic_t virtual_before = virtual_expiries;
92 CHECK(await_expiries(&profile_expiries, profile_expiries + 2) == 0);
93 // Disarming does not withdraw a signal whose expiry was already delivered.
94 CHECK(virtual_expiries <= virtual_before + 1);
95 CHECK(getitimer(ITIMER_VIRTUAL, &observed) == 0 && !timeval_us(observed.it_value));
96 virtual_before = virtual_expiries;
97 CHECK(arm_cpu(ITIMER_VIRTUAL, 20, 0) == 0);
98 CHECK(await_expiries(&virtual_expiries, virtual_before + 1) == 0);
99 CHECK(await_expiries(&profile_expiries, profile_expiries + 2) == 0);
100 CHECK(getitimer(ITIMER_PROF, &observed) == 0 && timeval_us(observed.it_interval) == 20000 &&
101 timeval_us(observed.it_value) > 0);
102 CHECK(arm_cpu(ITIMER_VIRTUAL, 20, 20) == 0 && arm_cpu(ITIMER_PROF, 0, 0) == 0);
103 CHECK(await_expiries(&virtual_expiries, virtual_expiries + 2) == 0);
104 CHECK(getitimer(ITIMER_PROF, &observed) == 0 && !timeval_us(observed.it_value));
105 puts("CPU-ITIMER-CONTRACT: PASS independent-periodic-interest");
106
107 CHECK(arm_cpu(ITIMER_VIRTUAL, 0, 0) == 0 && burn_user(80) == 0);
108 virtual_before = virtual_expiries;
109 CHECK(arm_cpu(ITIMER_VIRTUAL, 50, 0) == 0);
110 CHECK(getitimer(ITIMER_VIRTUAL, &observed) == 0 && timeval_us(observed.it_value) >= 35000);
111 CHECK(burn_user(10) == 0 && getitimer(ITIMER_VIRTUAL, &observed) == 0 &&
112 timeval_us(observed.it_value) > 0);
113 CHECK(await_expiries(&virtual_expiries, virtual_before + 1) == 0);
114 CHECK(getitimer(ITIMER_VIRTUAL, &observed) == 0 && !timeval_us(observed.it_value));
115 printf("CPU-ITIMER-CONTRACT: PASS rearm-baseline virtual=%d profile=%d checksum=%llu\n",
116 (int)virtual_expiries, (int)profile_expiries, (unsigned long long)checksum);
117out:
118 arm_cpu(ITIMER_VIRTUAL, 0, 0);
119 arm_cpu(ITIMER_PROF, 0, 0);
120 return failed;
121}