syscalls: sleep: make interruptable()

Allow the `nanosleep` family of functions to be interrupted. When an
interruption occures, calculation the remaining duration and write that
back to user-space.

Add a test to ensure proper functionality into usertest.
This commit is contained in:
Matthew Leach
2026-01-16 15:32:46 +00:00
parent 770f32c30c
commit 2655e8e78a
3 changed files with 103 additions and 14 deletions

View File

@@ -1,23 +1,35 @@
use libkernel::{error::Result, memory::address::TUA};
use super::thread_group::signal::{InterruptResult, Interruptable};
use crate::{
clock::timespec::TimeSpec,
drivers::timer::{now, sleep},
memory::uaccess::copy_to_user,
};
use core::time::Duration;
use libkernel::{
error::{KernelError, Result},
memory::address::TUA,
};
use crate::{clock::timespec::TimeSpec, drivers::timer::sleep};
pub async fn sys_nanosleep(rqtp: TUA<TimeSpec>, rmtp: TUA<TimeSpec>) -> Result<usize> {
let timespec: Duration = TimeSpec::copy_from_user(rqtp).await?.into();
let started_at = now().unwrap();
pub async fn sys_nanosleep(rqtp: TUA<TimeSpec>, _rmtp: TUA<TimeSpec>) -> Result<usize> {
let timespec = TimeSpec::copy_from_user(rqtp).await?;
sleep(timespec.into()).await;
Ok(0)
match sleep(timespec).interruptable().await {
InterruptResult::Interrupted => {
if !rmtp.is_null() {
let elapsed = now().unwrap() - started_at;
copy_to_user(rmtp, (timespec - elapsed).into()).await?;
}
Err(KernelError::Interrupted)
}
InterruptResult::Uninterrupted(()) => Ok(0),
}
}
pub async fn sys_clock_nanosleep(
_clock_id: i32,
rqtp: TUA<TimeSpec>,
_rmtp: TUA<TimeSpec>,
rmtp: TUA<TimeSpec>,
) -> Result<usize> {
let timespec = TimeSpec::copy_from_user(rqtp).await?;
sleep(timespec.into()).await;
Ok(0)
sys_nanosleep(rqtp, rmtp).await
}

View File

@@ -7,8 +7,10 @@ use std::{
};
use futex_bitset::test_futex_bitset;
use signals::test_interruptible_nanosleep;
mod futex_bitset;
mod signals;
fn test_sync() {
print!("Testing sync syscall ...");
@@ -820,6 +822,7 @@ fn main() {
run_test(test_rust_mutex);
run_test(test_parking_lot_mutex_timeout);
run_test(test_thread_with_name);
run_test(test_interruptible_nanosleep);
let end = std::time::Instant::now();
println!("All tests passed in {} ms", (end - start).as_millis());
}

74
usertest/src/signals.rs Normal file
View File

@@ -0,0 +1,74 @@
use std::{
ptr,
sync::atomic::{AtomicBool, Ordering},
};
static SIGNAL_CAUGHT: AtomicBool = AtomicBool::new(false);
extern "C" fn signal_handler(_: libc::c_int) {
SIGNAL_CAUGHT.store(true, Ordering::Relaxed);
}
fn register_handler(signum: libc::c_int, restart: bool) {
unsafe {
SIGNAL_CAUGHT.store(false, Ordering::Relaxed);
let mut action: libc::sigaction = std::mem::zeroed();
action.sa_sigaction = signal_handler as *const () as usize;
action.sa_flags = if restart { libc::SA_RESTART } else { 0 };
libc::sigemptyset(&mut action.sa_mask);
if libc::sigaction(signum, &action, std::ptr::null_mut()) != 0 {
panic!("Failed to register signal handler");
}
}
}
pub fn test_interruptible_nanosleep() {
print!("Testing interruptible nanosleep (EINTR) ...");
register_handler(libc::SIGALRM, false);
unsafe {
let pid = libc::getpid();
if libc::fork() == 0 {
// in child.
let req = libc::timespec {
tv_sec: 1,
tv_nsec: 0,
};
libc::nanosleep(&req, ptr::null_mut());
libc::kill(pid, libc::SIGALRM);
libc::exit(0);
};
// Sleep for 5 seconds (much longer than the alarm)
let req = libc::timespec {
tv_sec: 5,
tv_nsec: 0,
};
let mut rem = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let ret = libc::nanosleep(&req, &mut rem);
let err = std::io::Error::last_os_error();
// Nanosleep should return -1
assert_eq!(ret, -1);
// Errno should be EINTR
assert_eq!(err.raw_os_error(), Some(libc::EINTR));
// The signal handler should have run
assert!(SIGNAL_CAUGHT.load(Ordering::Relaxed));
// The remaining time should be updated (approx 4 seconds left)
assert!(rem.tv_sec >= 3 && rem.tv_sec <= 5);
}
println!(" OK");
}