implement itimer syscalls

This commit is contained in:
Ashwin Naren
2026-03-18 16:18:16 -07:00
parent 6b97bab57f
commit 65d0581736
13 changed files with 327 additions and 14 deletions

View File

@@ -102,8 +102,8 @@
| 0x63 (99) | set_robust_list | (struct robust_list_head *head, size_t len) | __arm64_sys_set_robust_list | true |
| 0x64 (100) | get_robust_list | (int pid, struct robust_list_head **head_ptr, size_t *len_ptr) | __arm64_sys_get_robust_list | false |
| 0x65 (101) | nanosleep | (struct __kernel_timespec *rqtp, struct __kernel_timespec *rmtp) | __arm64_sys_nanosleep | true |
| 0x66 (102) | getitimer | (int which, struct __kernel_old_itimerval *value) | __arm64_sys_getitimer | false |
| 0x67 (103) | setitimer | (int which, struct __kernel_old_itimerval *value, struct __kernel_old_itimerval *ovalue) | __arm64_sys_setitimer | false |
| 0x66 (102) | getitimer | (int which, struct __kernel_old_itimerval *value) | __arm64_sys_getitimer | partially |
| 0x67 (103) | setitimer | (int which, struct __kernel_old_itimerval *value, struct __kernel_old_itimerval *ovalue) | __arm64_sys_setitimer | partially |
| 0x68 (104) | kexec_load | (unsigned long entry, unsigned long nr_segments, struct kexec_segment *segments, unsigned long flags) | __arm64_sys_kexec_load | false |
| 0x69 (105) | init_module | (void *umod, unsigned long len, const char *uargs) | __arm64_sys_init_module | false |
| 0x6a (106) | delete_module | (const char *name_user, unsigned int flags) | __arm64_sys_delete_module | false |

View File

@@ -1,7 +1,8 @@
use crate::{
arch::{Arch, ArchImpl},
clock::{
clock::syscalls::{
gettime::sys_clock_gettime,
itimer::{sys_getitimer, sys_setitimer},
settime::sys_clock_settime,
timeofday::{sys_gettimeofday, sys_settimeofday},
},
@@ -514,6 +515,16 @@ pub async fn handle_syscall(mut ctx: ProcessCtx) {
}
0x63 => sys_set_robust_list(&mut ctx, TUA::from_value(arg1 as _), arg2 as _).await,
0x65 => sys_nanosleep(TUA::from_value(arg1 as _), TUA::from_value(arg2 as _)).await,
0x66 => sys_getitimer(&ctx, arg1 as _, TUA::from_value(arg2 as _)).await,
0x67 => {
sys_setitimer(
&ctx,
arg1 as _,
TUA::from_value(arg2 as _),
TUA::from_value(arg3 as _),
)
.await
}
0x70 => sys_clock_settime(arg1 as _, TUA::from_value(arg2 as _)).await,
0x71 => sys_clock_gettime(&ctx, arg1 as _, TUA::from_value(arg2 as _)).await,
0x73 => {

View File

@@ -1,7 +1,5 @@
pub mod gettime;
pub mod realtime;
pub mod settime;
pub mod timeofday;
pub mod syscalls;
pub mod timespec;
pub enum ClockId {

View File

@@ -5,7 +5,7 @@ use libkernel::{
memory::address::TUA,
};
use super::{ClockId, realtime::date, timespec::TimeSpec};
use crate::clock::{ClockId, realtime::date, timespec::TimeSpec};
use crate::drivers::timer::{Instant, now};
use crate::sched::syscall_ctx::ProcessCtx;
use crate::{drivers::timer::uptime, memory::uaccess::copy_to_user};

View File

@@ -0,0 +1,177 @@
use crate::clock::timespec::TimeSpec;
use crate::drivers::timer::{Instant, now, uptime};
use crate::memory::uaccess::{UserCopyable, copy_from_user, copy_to_user};
use crate::process::thread_group::signal::SigId;
use crate::process::{ITimer, Task, Tid, find_task_by_tid};
use crate::sched::syscall_ctx::ProcessCtx;
use core::time::Duration;
use libkernel::memory::address::TUA;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ITimerType {
Real = 0,
Virtual = 1,
Prof = 2,
}
impl TryFrom<i32> for ITimerType {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Real),
1 => Ok(Self::Virtual),
2 => Ok(Self::Prof),
_ => Err(()),
}
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ITimerVal {
it_interval: TimeSpec,
it_value: TimeSpec,
}
impl ITimerVal {
fn is_disabled(&self) -> bool {
self.it_value.tv_sec == 0 && self.it_value.tv_nsec == 0
}
fn is_oneshot(&self) -> bool {
self.it_interval.tv_sec == 0 && self.it_interval.tv_nsec == 0
}
}
impl Default for ITimerVal {
fn default() -> Self {
Self {
it_interval: TimeSpec {
tv_sec: 0,
tv_nsec: 0,
},
it_value: TimeSpec {
tv_sec: 0,
tv_nsec: 0,
},
}
}
}
unsafe impl UserCopyable for ITimerVal {}
pub fn itimer_irq_handler(tid: Tid, ty: ITimerType) -> Option<Instant> {
let task = find_task_by_tid(tid)?;
match ty {
ITimerType::Real => {
task.process.deliver_signal(SigId::SIGALRM);
let mut timers = task.i_timers.lock_save_irq();
if let Some(ref mut timer) = timers.real
&& let Some(interval) = timer.interval
{
timer.next = now().unwrap() + interval;
Some(timer.next)
} else {
timers.real = None;
None
}
}
_ => unimplemented!(),
}
}
async fn getitimer(current_task: &Task, which: ITimerType) -> libkernel::error::Result<ITimerVal> {
let now = match which {
ITimerType::Real => uptime(),
_ => unimplemented!(),
};
Ok(current_task
.i_timers
.lock_save_irq()
.real
.map(|t| {
let remaining = Duration::from(t.next) - now;
let interval = t.interval.unwrap_or_default();
ITimerVal {
it_interval: TimeSpec {
tv_sec: interval.as_secs() as _,
tv_nsec: interval.subsec_nanos() as _,
},
it_value: TimeSpec {
tv_sec: remaining.as_secs() as _,
tv_nsec: remaining.subsec_nanos() as _,
},
}
})
.unwrap_or_default())
}
/// <https://man7.org/linux/man-pages/man2/getitimer.2.html>
pub async fn sys_getitimer(
ctx: &ProcessCtx,
which: i32,
curr_value: TUA<ITimerVal>,
) -> libkernel::error::Result<usize> {
let timer_type =
ITimerType::try_from(which).map_err(|_| libkernel::error::KernelError::InvalidValue)?;
let value = getitimer(ctx.shared(), timer_type).await?;
copy_to_user(curr_value, value).await?;
Ok(0)
}
/// <https://man7.org/linux/man-pages/man2/setitimer.2.html>
pub async fn sys_setitimer(
ctx: &ProcessCtx,
which: i32,
new_value: TUA<ITimerVal>,
old_value: TUA<ITimerVal>,
) -> libkernel::error::Result<usize> {
let timer_type =
ITimerType::try_from(which).map_err(|_| libkernel::error::KernelError::InvalidValue)?;
if !old_value.is_null() {
let old_timer = getitimer(ctx.shared(), timer_type).await?;
copy_to_user(old_value, old_timer).await?;
}
let new_timer = copy_from_user(new_value).await?;
match timer_type {
ITimerType::Real => {
let current_task = ctx.shared();
let mut timers = current_task.i_timers.lock_save_irq();
let interval = if new_timer.is_oneshot() {
None
} else {
Some(Duration::new(
new_timer.it_interval.tv_sec as _,
new_timer.it_interval.tv_nsec as _,
))
};
let next = if new_timer.is_disabled() {
None
} else {
Some(
now().unwrap()
+ Duration::new(
new_timer.it_value.tv_sec as _,
new_timer.it_value.tv_nsec as _,
),
)
};
if timers.real.is_some() {
crate::drivers::timer::SYS_TIMER
.get()
.unwrap()
.remove_scheduled_itimer(current_task.tid(), ITimerType::Real);
}
if let Some(next) = next {
timers.real = Some(ITimer { interval, next });
crate::drivers::timer::SYS_TIMER
.get()
.unwrap()
.schedule_itimer(current_task.tid(), ITimerType::Real, next);
}
}
_ => unimplemented!(),
}
Ok(0)
}

View File

@@ -0,0 +1,4 @@
pub mod gettime;
pub mod itimer;
pub mod settime;
pub mod timeofday;

View File

@@ -1,5 +1,5 @@
use super::timespec::TimeSpec;
use crate::clock::realtime::{date, set_date};
use crate::clock::timespec::TimeSpec;
use crate::memory::uaccess::{UserCopyable, copy_from_user, copy_to_user};
use core::time::Duration;
use libkernel::{error::Result, memory::address::TUA};

View File

@@ -1,6 +1,8 @@
use super::Driver;
use crate::clock::syscalls::itimer::ITimerType;
use crate::interrupts::{InterruptDescriptor, InterruptHandler};
use crate::per_cpu_private;
use crate::process::Tid;
use crate::sync::OnceLock;
use alloc::{collections::binary_heap::BinaryHeap, sync::Arc};
use core::{
@@ -74,8 +76,13 @@ enum WakeupKind {
/// This wake up is for the kernel's preemption mechanism.
Preempt,
ITimer(Tid, ITimerType),
}
unsafe impl Send for WakeupKind {}
unsafe impl Sync for WakeupKind {}
struct WakeupEvent {
when: Instant,
what: WakeupKind,
@@ -174,6 +181,9 @@ impl InterruptHandler for SysTimer {
// Do nothing, the IRQ return-to-userspace code will
// call schedule() for us.
}
WakeupKind::ITimer(tid, ty) => {
crate::clock::syscalls::itimer::itimer_irq_handler(tid, ty);
}
}
} else {
// The next event is in the future, so we're done.
@@ -231,6 +241,41 @@ impl SysTimer {
.await;
}
pub fn schedule_itimer(&self, tid: Tid, ty: ITimerType, when: Instant) {
let mut wakeup_q = WAKEUP_Q.borrow_mut();
wakeup_q.push(WakeupEvent {
when,
what: WakeupKind::ITimer(tid, ty),
});
// After pushing, we must update the hardware timer in case our
// new event is the earliest one.
if let Some(next_event) = wakeup_q.peek() {
self.driver.schedule_interrupt(Some(next_event.when));
}
}
pub fn remove_scheduled_itimer(&self, tid: Tid, ty: ITimerType) {
let mut wakeup_q = WAKEUP_Q.borrow_mut();
// Remove any matching itimer events from the queue.
wakeup_q.retain(|event| {
if let WakeupKind::ITimer(event_tid, event_ty) = event.what {
!(event_tid == tid && event_ty == ty)
} else {
true
}
});
// TODO: Handle cross-CPU cases where the itimer event might be on a different CPU's queue.
// After removing, we must update the hardware timer in case we removed
// the earliest event.
if let Some(next_event) = wakeup_q.peek() {
self.driver.schedule_interrupt(Some(next_event.when));
}
}
/// Schedule a preemption event for the current CPU.
pub fn schedule_preempt(&self, when: Instant) {
let mut wake_q = WAKEUP_Q.borrow_mut();
@@ -306,8 +351,12 @@ pub fn schedule_preempt(when: Instant) {
}
}
static SYS_TIMER: OnceLock<Arc<SysTimer>> = OnceLock::new();
pub static SYS_TIMER: OnceLock<Arc<SysTimer>> = OnceLock::new();
per_cpu_private! {
static WAKEUP_Q: BinaryHeap<WakeupEvent> = BinaryHeap::new;
}
per_cpu_private! {
static NEXT_WAKE_ID: u32 = u32::default;
}

View File

@@ -181,6 +181,7 @@ pub async fn sys_clone(
fd_table: files,
cwd,
root,
i_timers: SpinLock::new(*current_task.i_timers.lock_save_irq()),
creds: SpinLock::new(creds),
ptrace: SpinLock::new(ptrace),
sig_mask: new_sigmask,

View File

@@ -16,6 +16,7 @@ use alloc::{
sync::{Arc, Weak},
};
use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use core::time::Duration;
use creds::Credentials;
use fd_table::FileDescriptorTable;
use libkernel::memory::proc_vm::address_space::{UserAddressSpace, VirtualMemory};
@@ -162,6 +163,22 @@ impl Comm {
}
}
#[derive(Copy, Clone, Debug)]
pub struct ITimer {
/// If interval is `None`, this timer is a one-shot timer.
pub interval: Option<Duration>,
/// Instant (wrt the needed clock) at which this timer will next expire.
pub next: Instant,
}
#[derive(Copy, Clone, Default)]
pub struct ITimers {
pub real: Option<ITimer>,
// virtual is a reserved keyword
pub virtual_: Option<ITimer>,
pub prof: Option<ITimer>,
}
pub struct Task {
pub tid: Tid,
pub comm: Arc<SpinLock<Comm>>,
@@ -170,6 +187,7 @@ pub struct Task {
pub cwd: Arc<SpinLock<(Arc<dyn Inode>, PathBuf)>>,
pub root: Arc<SpinLock<(Arc<dyn Inode>, PathBuf)>>,
pub creds: SpinLock<Credentials>,
pub i_timers: SpinLock<ITimers>,
pub fd_table: Arc<SpinLock<FileDescriptorTable>>,
pub ptrace: SpinLock<PTrace>,
pub sig_mask: AtomicSigSet,

View File

@@ -1,5 +1,5 @@
use super::{
Comm, Task, Tid,
Comm, ITimers, Task, Tid,
creds::Credentials,
ctx::{Context, UserCtx},
fd_table::FileDescriptorTable,
@@ -72,6 +72,7 @@ impl OwnedTask {
creds: SpinLock::new(Credentials::new_root()),
vm: Arc::new(SpinLock::new(vm)),
fd_table: Arc::new(SpinLock::new(FileDescriptorTable::new())),
i_timers: SpinLock::new(ITimers::default()),
ptrace: SpinLock::new(PTrace::new()),
utime: AtomicUsize::new(0),
stime: AtomicUsize::new(0),
@@ -102,6 +103,7 @@ impl OwnedTask {
vm: Arc::new(SpinLock::new(
ProcessVM::empty().expect("Could not create init process's VM"),
)),
i_timers: SpinLock::new(ITimers::default()),
fd_table: Arc::new(SpinLock::new(FileDescriptorTable::new())),
ptrace: SpinLock::new(PTrace::new()),
last_account: AtomicUsize::new(0),

View File

@@ -196,6 +196,59 @@ fn test_mincore() {
register_test!(test_mincore);
fn test_itimer() {
use libc::{ITIMER_REAL, itimerval};
use std::mem::MaybeUninit;
unsafe {
// Set signal handler for SIGALRM to avoid process termination when the timer expires
// We'll flip a bit in a static variable when the signal handler is called, and check that bit at the end of the test to verify the timer actually expired.
static TIMER_EXPIRED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
extern "C" fn sigalrm_handler(_signum: libc::c_int) {
TIMER_EXPIRED.store(true, std::sync::atomic::Ordering::SeqCst);
}
let mut sa: libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = sigalrm_handler as *const () as usize;
sa.sa_flags = libc::SA_RESTART;
libc::sigemptyset(&mut sa.sa_mask);
if libc::sigaction(libc::SIGALRM, &sa, std::ptr::null_mut()) != 0 {
panic!("sigaction failed: {}", std::io::Error::last_os_error());
}
let mut old_timer = MaybeUninit::<itimerval>::uninit();
let new_timer = itimerval {
it_interval: libc::timeval {
tv_sec: 0,
tv_usec: 0,
},
it_value: libc::timeval {
tv_sec: 1,
tv_usec: 0,
},
};
let ret = libc::setitimer(
ITIMER_REAL,
&new_timer as *const itimerval,
old_timer.as_mut_ptr(),
);
if ret != 0 {
panic!("setitimer failed: {}", std::io::Error::last_os_error());
}
let old_timer = old_timer.assume_init();
assert_eq!(old_timer.it_value.tv_sec, 0);
assert_eq!(old_timer.it_value.tv_usec, 0);
// Wait for 2 seconds to ensure the timer has time to expire
std::thread::sleep(std::time::Duration::from_secs(2));
assert!(
TIMER_EXPIRED.load(std::sync::atomic::Ordering::SeqCst),
"Expected timer to have expired and signal handler to have been called"
);
}
}
register_test!(test_itimer);
fn run_test(test_fn: fn()) -> Result<(), i32> {
// Fork a new process to run the test
unsafe {
@@ -238,10 +291,10 @@ fn main() {
let start = std::time::Instant::now();
let mut failures = 0;
for test in inventory::iter::<Test> {
if let Some(filter) = filter {
if !test.test_text.contains(filter) {
continue;
}
if let Some(filter) = filter
&& !test.test_text.contains(filter)
{
continue;
}
print!("{} ...", test.test_text);
let _ = stdout().flush();