RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / timer.rs
10031 bytes · raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
//! Timers that send signals (stage 17): alarm, setitimer/getitimer and
//! the POSIX timers (timer_create, timer_settime, ...).
//!
//! A timer is a deadline, maybe with an interval to repeat at, and what to
//! do when it's reached: send a signal (SIGALRM for alarm and setitimer;
//! any signal, maybe to one thread, for timer_create).
//!
//! All timers live in one table. One kernel thread, "timerd", sleeps until
//! the nearest deadline, sends the signals that are due, and goes back to
//! sleep. Why a thread and not the timer interrupt? Sending a signal takes
//! locks and may wake threads; doing that from normal thread context keeps
//! the interrupt handler tiny and follows the locking rules in thread.rs.
//! timerd is started the first time anyone arms a timer.
//!
//! Resolution: timerd sleeps in PIT ticks (~55 ms), so a timer fires up
//! to ~110 ms late, never early. A periodic timer whose signals come late
//! counts the periods it missed as "overruns" (timer_getoverrun), like
//! Linux does when a signal is still pending.
//!
//! Lifetime, as on Linux: fork doesn't copy timers; execve deletes the
//! POSIX timers but keeps the alarm / ITIMER_REAL; exit deletes them all.

use crate::errno::Errno;
use crate::process;
use crate::signal::{self, SigInfo};
use crate::thread::WaitQueue;
use crate::time;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicBool, Ordering};
use spin::Mutex;
use x86_64::instructions::interrupts::without_interrupts;

/// si_code for a POSIX timer's signal.
pub const SI_TIMER: i32 = -2;

// The clocks timer_create understands.
pub const CLOCK_REALTIME: u64 = 0;
pub const CLOCK_MONOTONIC: u64 = 1;
pub const CLOCK_BOOTTIME: u64 = 7;

// sigev_notify: what to do when it fires.
pub const SIGEV_SIGNAL: i32 = 0; // send sigev_signo to the process
pub const SIGEV_NONE: i32 = 1; // nothing (the program polls timer_gettime)
pub const SIGEV_THREAD_ID: i32 = 4; // send sigev_signo to thread sigev_tid

/// At most this many POSIX timers per process: EAGAIN after that.
const MAX_TIMERS: usize = 32;
/// The id we use for a process's ITIMER_REAL (POSIX ids are 0..MAX_TIMERS).
const ITIMER_ID: i32 = -1;

#[derive(Clone, Copy)]
struct Timer {
    pid: u64,
    /// timer_create's id, or ITIMER_ID.
    id: i32,
    clock: u64,
    /// Next expiry, in `time::now_us` microseconds (None = disarmed).
    next: Option<u64>,
    /// Repeat every this many microseconds (0 = once).
    interval: u64,
    signo: u32,
    notify: i32,
    /// SIGEV_THREAD_ID's thread.
    tid: u64,
    sigval: u64,
    /// Expirations missed at the last firing (timer_getoverrun).
    overrun: u64,
}

static TIMERS: Mutex<Vec<Timer>> = Mutex::new(Vec::new());
/// timerd sleeps here; poked when the table changes.
static CHANGED_QUEUE: WaitQueue = WaitQueue::new();
static CHANGED: AtomicBool = AtomicBool::new(false);
static STARTED: AtomicBool = AtomicBool::new(false);

fn with_table<R>(f: impl FnOnce(&mut Vec<Timer>) -> R) -> R {
    without_interrupts(|| f(&mut TIMERS.lock()))
}

/// Tell timerd to look again (starting it the first time).
fn poke() {
    if !STARTED.swap(true, Ordering::AcqRel) {
        crate::thread::spawn("timerd", timerd);
    }
    CHANGED.store(true, Ordering::Release);
    CHANGED_QUEUE.wake_all();
}

/// How far CLOCK_REALTIME is ahead of the monotonic clock (µs), to turn
/// absolute wall-clock deadlines into ours.
fn realtime_offset() -> u64 {
    time::realtime_us().saturating_sub(time::now_us())
}

/// The kernel thread that fires timers (see the top).
fn timerd() {
    loop {
        let now = time::now_us();
        let mut due = Vec::new();
        let next = with_table(|t| {
            for timer in t.iter_mut() {
                let Some(at) = timer.next else { continue };
                if at > now {
                    continue;
                }
                if timer.interval > 0 {
                    let missed = (now - at) / timer.interval;
                    timer.overrun = missed;
                    timer.next = Some(at + (missed + 1) * timer.interval);
                } else {
                    timer.overrun = 0;
                    timer.next = None;
                }
                due.push(*timer);
            }
            t.iter().filter_map(|t| t.next).min()
        });
        for timer in due {
            fire(&timer);
        }
        let changed = || CHANGED.swap(false, Ordering::AcqRel).then_some(());
        match next {
            Some(at) => {
                let deadline = time::deadline_after_us(at.saturating_sub(time::now_us()));
                let _ = CHANGED_QUEUE.wait_until_deadline(deadline, changed);
            }
            None => CHANGED_QUEUE.wait_until(changed),
        }
    }
}

/// Send a due timer's signal.
fn fire(timer: &Timer) {
    if timer.notify == SIGEV_NONE {
        return;
    }
    let Some(p) = process::get(timer.pid) else { return };
    let info = if timer.id == ITIMER_ID {
        SigInfo::from_pid(timer.signo, 0) // SI_KERNEL, like Linux's itimers
    } else {
        let mut info = SigInfo::new(timer.signo, SI_TIMER);
        info.fields[0] = timer.id as u32 as u64 | (timer.overrun.min(i32::MAX as u64)) << 32; // si_tid, si_overrun
        info.fields[1] = timer.sigval; // si_value
        info
    };
    if timer.notify == SIGEV_THREAD_ID {
        if let Some(t) = p.tasks().into_iter().find(|t| t.tid() == timer.tid) {
            signal::send_thread(&t, timer.signo, info);
        }
        return;
    }
    signal::send(&p, timer.signo, info);
}

/// (value, interval) of a timer in microseconds: time left, and the period.
fn read(timer: &Timer, now: u64) -> (u64, u64) {
    (timer.next.map_or(0, |at| at.saturating_sub(now).max(1)), timer.interval)
}

// --- setitimer / getitimer / alarm ---------------------------------------

/// getitimer(ITIMER_REAL): (time left, interval), in microseconds.
pub fn get_itimer(pid: u64) -> (u64, u64) {
    let now = time::now_us();
    with_table(|t| t.iter().find(|x| x.pid == pid && x.id == ITIMER_ID).map_or((0, 0), |x| read(x, now)))
}

/// setitimer(ITIMER_REAL, value, interval) (µs; value 0 disarms). Returns
/// the old (value, interval).
pub fn set_itimer(pid: u64, value: u64, interval: u64) -> (u64, u64) {
    let now = time::now_us();
    let old = with_table(|t| {
        let old = t.iter().position(|x| x.pid == pid && x.id == ITIMER_ID).map(|i| t.remove(i));
        if value > 0 {
            t.push(Timer {
                pid,
                id: ITIMER_ID,
                clock: CLOCK_MONOTONIC,
                next: Some(now + value),
                interval,
                signo: signal::SIGALRM,
                notify: SIGEV_SIGNAL,
                tid: 0,
                sigval: 0,
                overrun: 0,
            });
        }
        old.map_or((0, 0), |x| read(&x, now))
    });
    if value > 0 {
        poke();
    }
    old
}

// --- POSIX timers --------------------------------------------------------

/// What timer_create's `struct sigevent` asked for.
pub struct SigEvent {
    pub notify: i32,
    pub signo: u32,
    pub tid: u64,
    /// None: no sigevent was given, so the value is the timer's id.
    pub sigval: Option<u64>,
}

/// timer_create: a new, disarmed timer. Returns its id.
pub fn create(pid: u64, clock: u64, ev: SigEvent) -> Result<i32, Errno> {
    with_table(|t| {
        let ids: Vec<i32> = t.iter().filter(|x| x.pid == pid && x.id != ITIMER_ID).map(|x| x.id).collect();
        if ids.len() >= MAX_TIMERS {
            return Err(Errno::EAGAIN);
        }
        let id = (0..).find(|i| !ids.contains(i)).unwrap();
        // No sigevent: SIGALRM with the timer id as the value (Linux's default).
        let sigval = ev.sigval.unwrap_or(id as u64);
        t.push(Timer { pid, id, clock, next: None, interval: 0, signo: ev.signo, notify: ev.notify, tid: ev.tid, sigval, overrun: 0 });
        Ok(id)
    })
}

/// timer_settime: arm (value > 0) or disarm. `absolute`: value is a time on
/// the timer's clock, not a delay. Returns the old (value, interval).
pub fn settime(pid: u64, id: i32, absolute: bool, value: u64, interval: u64) -> Result<(u64, u64), Errno> {
    let now = time::now_us();
    let old = with_table(|t| {
        let timer = t.iter_mut().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).ok_or(Errno::EINVAL)?;
        let old = read(timer, now);
        timer.overrun = 0;
        timer.interval = interval;
        timer.next = match (value, absolute) {
            (0, _) => None,
            (v, false) => Some(now + v),
            (v, true) if timer.clock == CLOCK_REALTIME => Some(v.saturating_sub(realtime_offset()).max(1)),
            (v, true) => Some(v.max(1)), // MONOTONIC/BOOTTIME: our own clock
        };
        Ok(old)
    })?;
    if value > 0 {
        poke();
    }
    Ok(old)
}

/// timer_gettime: (time left, interval) in microseconds.
pub fn gettime(pid: u64, id: i32) -> Result<(u64, u64), Errno> {
    let now = time::now_us();
    with_table(|t| t.iter().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).map(|x| read(x, now)).ok_or(Errno::EINVAL))
}

/// timer_getoverrun: periods missed at the last expiry.
pub fn getoverrun(pid: u64, id: i32) -> Result<u64, Errno> {
    with_table(|t| t.iter().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).map(|x| x.overrun.min(i32::MAX as u64)).ok_or(Errno::EINVAL))
}

/// timer_delete.
pub fn delete(pid: u64, id: i32) -> Result<(), Errno> {
    with_table(|t| {
        let i = t.iter().position(|x| x.pid == pid && x.id == id && id != ITIMER_ID).ok_or(Errno::EINVAL)?;
        t.remove(i);
        Ok(())
    })
}

/// execve: POSIX timers go, the alarm stays.
pub fn exec(pid: u64) {
    with_table(|t| t.retain(|x| x.pid != pid || x.id == ITIMER_ID));
}

/// The process ended: all its timers go.
pub fn process_exited(pid: u64) {
    with_table(|t| t.retain(|x| x.pid != pid));
}

/// How many timers exist (tests: nothing left behind).
pub fn count() -> usize {
    with_table(|t| t.len())
}