proc: sleep interruption handling

This commit is contained in:
Mark Poliakov 2024-11-20 15:39:10 +02:00
parent efb4909fd5
commit 09a0b01855
14 changed files with 392 additions and 38 deletions

View File

@ -8,4 +8,4 @@ mod timer;
pub use executor::{run_to_completion, spawn, spawn_async_worker};
pub use task_queue::init_task_queue;
pub use timer::{maybe_timeout, sleep, tick, with_timeout, SleepFuture};
pub use timer::{maybe_timeout, sleep, sleep_until, tick, with_timeout, SleepFuture};

View File

@ -101,7 +101,7 @@ impl Future for SleepFuture {
}
}
/// Suspends the task until given duration passes
/// Suspends the task until given duration passes.
pub fn sleep(duration: Duration) -> SleepFuture {
let now = libk_device::monotonic_timestamp().unwrap();
let deadline = now + duration;
@ -109,6 +109,11 @@ pub fn sleep(duration: Duration) -> SleepFuture {
SleepFuture { deadline }
}
/// Suspends the task until the given deadline comes.
pub fn sleep_until(deadline: Duration) -> SleepFuture {
SleepFuture { deadline }
}
pub fn with_timeout<'a, T: 'a, F: Future<Output = T> + Send + 'a>(
fut: F,
timeout: Duration,

View File

@ -646,9 +646,9 @@ impl CurrentThread {
self.exit_process(ExitCode::BySignal(signal));
}
// TODO check if really in a syscall, lol
let syscall_return = -(u32::from(Error::Interrupted) as isize);
frame.set_return_value(syscall_return as u64);
// // TODO check if really in a syscall, lol
// let syscall_return = -(u32::from(Error::Interrupted) as isize);
// frame.set_return_value(syscall_return as u64);
// Setup signal frame
// FIXME use write_foreign_*** for this

View File

@ -1,4 +1,4 @@
use core::{num::NonZeroUsize, sync::atomic::AtomicU32, time::Duration};
use core::{mem::MaybeUninit, num::NonZeroUsize, sync::atomic::AtomicU32, time::Duration};
use abi::{
error::Error,
@ -18,6 +18,7 @@ use libk::{
},
vfs::IoContext,
};
use libk_device::monotonic_timestamp;
use libk_mm::{
process::VirtualRangeBacking,
table::{EntryLevelExt, MapAttributes},
@ -191,9 +192,23 @@ pub(crate) fn get_pid() -> ProcessId {
process.id
}
pub(crate) fn nanosleep(duration: &Duration) -> Result<(), Error> {
block! {
runtime::sleep(*duration).await
pub(crate) fn nanosleep(
duration: &Duration,
remaining: &mut MaybeUninit<Duration>,
) -> Result<(), Error> {
let duration = *duration;
let now = monotonic_timestamp().expect("Monotonic timestamp provider broke");
let deadline = now + duration;
let result = block! { runtime::sleep_until(deadline).await };
match result {
Ok(()) => Ok(()),
Err(Error::Interrupted) => {
let now = monotonic_timestamp().expect("Monotonic timestamp provider broke");
let rem = deadline - now;
remaining.write(rem);
Err(Error::Interrupted)
}
Err(_) => unreachable!(),
}
}

View File

@ -86,7 +86,7 @@ syscall wait_thread(tid: u32) -> Result<()>;
syscall get_thread_option(option: &mut ThreadOption) -> Result<()>;
syscall set_thread_option(option: &ThreadOption) -> Result<()>;
syscall nanosleep(dur: &Duration) -> Result<()>;
syscall nanosleep(duration: &Duration, remaining: &mut MaybeUninit<Duration>) -> Result<()>;
syscall exit_signal(frame: &SignalEntryData) -> !;
syscall set_signal_entry(ip: usize, sp: usize);

View File

@ -1,5 +1,12 @@
//! Yggdrasil OS application runtime
#![feature(rustc_private, decl_macro, f128, linkage, naked_functions)]
#![feature(
rustc_private,
decl_macro,
f128,
linkage,
naked_functions,
thread_local
)]
#![no_std]
#![deny(missing_docs)]
#![allow(nonstandard_style)]

View File

@ -1,10 +1,41 @@
//! Process management data types
use core::{mem::MaybeUninit, time::Duration};
use abi::error::Error;
pub use abi::process::{
auxv, AuxValue, AuxValueIter, ExecveOptions, ExitCode, MutexOperation, ProcessGroupId,
ProcessId, ProcessInfoElement, ProgramArgumentInner, Signal, SignalEntryData, SpawnFlags,
SpawnOption, SpawnOptions, StringArgIter, ThreadId, ThreadSpawnOptions,
};
use crate::sys;
pub mod signal;
pub mod thread;
pub mod thread_local;
/// Makes the current thread wait until *at least* the amount of time specified in `duration`
/// passes.
///
/// If the underlying sleep syscall is interrupted by a signal, the function will resume the sleep.
///
/// # Panics
///
/// Will panic if sleep returns any error besides [crate::Error::Interrupted].
pub fn uninterruptible_sleep(mut duration: Duration) {
let mut remaining = MaybeUninit::uninit();
loop {
match unsafe { sys::nanosleep(&duration, &mut remaining) } {
Ok(()) => return,
Err(Error::Interrupted) => {
let remaining = unsafe { remaining.assume_init() };
debug_assert!(remaining <= duration);
duration = remaining;
}
Err(err) => {
panic!("nanosleep syscall failed: {err:?}");
}
}
}
}

View File

@ -0,0 +1,242 @@
//! Runtime utilities for thread handling
use core::{
cell::UnsafeCell,
mem::MaybeUninit,
ptr,
sync::atomic::{AtomicU32, AtomicUsize, Ordering},
};
use abi::{
error::Error,
mem::{MappingFlags, MappingSource},
process::ThreadSpawnOptions,
};
use alloc::{boxed::Box, sync::Arc};
use super::signal;
/// Describes a runtime thread.
///
/// `R` generic parameter denotes the thread's return type.
pub struct Thread<R> {
id: AtomicU32,
// TODO mutex
result: UnsafeCell<MaybeUninit<R>>,
}
/// Describes a "handle" to some runtime thread. This is what gets returned to the caller
/// when a thread is spawned and can be used to, e.g., join the created thread later.
pub struct ThreadHandle<R> {
stack: Option<OwnedStack>,
thread: Arc<Thread<R>>,
}
struct OwnedStack {
base: usize,
size: usize,
}
/// Describes how a thread's signal stack should be set up.
pub enum ThreadSignalStack {
/// Thread signal stack will be allocated with the requested size.
Allocate(usize),
/// Do not set up a signal stack.
None,
}
/// Describes how a thread's main stack should be set up.
pub enum ThreadStack {
/// Thread stack will be allocated with the requested size.
Allocate(usize),
/// A raw value will be used for the stack pointer (unsafe).
Raw(usize),
}
/// Describes the thread's function that will get entered once the thread starts running.
///
/// `A` parameter denotes the argument type that will be passed to the thread.
pub enum ThreadFunction<A, R: Send> {
/// A boxed closure will be called.
Closure(Box<dyn FnOnce(A) -> R + 'static>),
/// A function pointer will be called.
Pointer(fn(A) -> R),
}
/// Describes how a thread should be constructed
pub struct ThreadCreateInfo<A, R: Send> {
/// See [ThreadSignalStack].
pub signal_stack: ThreadSignalStack,
/// See [ThreadStack].
pub stack: ThreadStack,
/// See [ThreadFunction].
pub entry: ThreadFunction<A, R>,
}
struct ThreadArgument<A, R: Send> {
entry: Option<(A, ThreadFunction<A, R>)>,
signal_stack: ThreadSignalStack,
thread_self: Arc<Thread<R>>,
set_thread_self: bool,
}
// TODO maybe put this under a `thread-self` feature and avoid a TLS allocation?
#[thread_local]
static mut SELF: usize = 0;
impl<R: Send> Thread<R> {
/// Creates a new thread with the requested options and passes it an argument.
pub fn spawn<A>(
info: ThreadCreateInfo<A, R>,
argument: A,
runtime: bool,
) -> Result<ThreadHandle<R>, Error> {
let (stack, sp) = match info.stack {
ThreadStack::Allocate(size) => {
let (stack, sp) = OwnedStack::allocate(size)?;
(Some(stack), sp)
}
ThreadStack::Raw(_) => todo!(),
};
let thread = Arc::new(Thread::<R> {
id: AtomicU32::new(0),
result: UnsafeCell::new(MaybeUninit::uninit()),
});
let thread_argument = Box::into_raw(Box::new(ThreadArgument {
entry: Some((argument, info.entry)),
thread_self: thread.clone(),
signal_stack: info.signal_stack,
set_thread_self: runtime,
}))
.addr();
let spawn_options = ThreadSpawnOptions {
entry: Self::thread_entry::<A>,
argument: thread_argument,
stack_top: sp,
};
let id = unsafe { crate::sys::spawn_thread(&spawn_options) }?;
thread.id.store(id, Ordering::Release);
Ok(ThreadHandle { stack, thread })
}
/// # Safety
///
/// This function has to be called with the same `Self` type as the thread it was created with.
pub unsafe fn current() -> Arc<Self> {
let raw: *const Self = ptr::with_exposed_provenance(SELF);
if raw.is_null() {
panic!("Thread::SELF == NULL, was spawn() called with runtime=false?");
}
// This will "move" the pointer, so an extra strong count increment is required.
Arc::increment_strong_count(raw);
Arc::from_raw(raw)
}
extern "C" fn thread_entry<A>(raw: usize) -> ! {
crate::debug_trace!("thread_entry {raw:#x}");
let raw: *mut ThreadArgument<A, R> = ptr::with_exposed_provenance_mut(raw);
// This scope will ensure all the stuff is dropped before thread exit is called.
{
let mut argument = unsafe { Box::from_raw(raw) };
// TODO there will be a better way to do this, I promise
while argument.thread_self.id.load(Ordering::Acquire) == 0 {
core::hint::spin_loop();
}
crate::debug_trace!(
"thread_entry id={}",
argument.thread_self.id.load(Ordering::Acquire)
);
// Setup SELF if needed
if argument.set_thread_self {
unsafe {
SELF = Arc::into_raw(argument.thread_self.clone()).addr();
debug_assert!(Arc::ptr_eq(&Thread::current(), &argument.thread_self));
}
crate::debug_trace!("thread_entry set SELF!!!");
}
// Setup signal stack if needed
let result = match argument.signal_stack {
ThreadSignalStack::None => Ok(()),
ThreadSignalStack::Allocate(size) => signal::setup_signal_stack(size),
};
if let Err(err) = result {
panic!("Failed to set up thread's signal stack: {err:?}");
}
// Call the inner function
let result = match argument.entry.take() {
Some((arg, ThreadFunction::Closure(f))) => f(arg),
Some((arg, ThreadFunction::Pointer(f))) => f(arg),
None => unreachable!(),
};
let result_ptr = argument.thread_self.result.get();
unsafe {
(*result_ptr).write(result);
}
}
crate::debug_trace!("thread_entry exit_thread()");
unsafe { crate::sys::exit_thread() };
}
}
impl<R: Send> ThreadHandle<R> {
/// Waits for the thread to finish and returns its result.
pub fn join(self) -> Result<R, Error> {
// TODO prevent threads from attempting to join themselves?
unsafe {
crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire))?;
Ok(self.into_result())
}
}
/// Waits for the thread to finish, ignoring interrupts.
///
/// # Panics
///
/// Will panic if the kernel returns any error besides [Error::Interrupted].
pub fn join_uninterruptible(self) -> R {
loop {
match unsafe { crate::sys::wait_thread(self.thread.id.load(Ordering::Acquire)) } {
Ok(result) => (),
Err(Error::Interrupted) => continue,
Err(error) => panic!("wait_thread syscall returned error: {error:?}"),
}
return unsafe { self.into_result() };
}
}
unsafe fn into_result(self) -> R {
(*self.thread.result.get()).assume_init_read()
}
}
impl OwnedStack {
fn allocate(mut size: usize) -> Result<(Self, usize), Error> {
if size < 0x1000 {
size = 0x1000;
}
size = (size + 0xFFF) & !0xFFF;
let base = unsafe {
crate::sys::map_memory(None, size, MappingFlags::WRITE, &MappingSource::Anonymous)
}?;
let top = base + size;
Ok((Self { base, size }, top))
}
}
impl Drop for OwnedStack {
fn drop(&mut self) {
unsafe { crate::sys::unmap_memory(self.base, self.size) }.ok();
}
}

30
test.c
View File

@ -1,3 +1,33 @@
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
static void silence_sigint(int signum) {}
int main(int argc, const char **argv) {
signal(silence_sigint, SIGINT);
struct timespec duration;
struct timespec remaining;
while (1) {
duration.tv_sec = 3;
duration.tv_nsec = 0;
int result = nanosleep(&duration, &remaining);
if (result != 0) {
assert(errno == EINTR);
printf(
"EINTR: remaining: sec=%lu, nsec=%u\n",
(unsigned long) remaining.tv_sec,
(unsigned int) remaining.tv_nsec
);
} else {
printf("NO EINTR\n");
}
}
return 0;
}

View File

@ -1,4 +1,4 @@
use core::ffi::{c_char, c_int, c_long, c_void};
use core::{ffi::{c_char, c_int, c_long, c_void}, time::Duration};
use super::sys_types::{suseconds_t, time_t};
@ -49,3 +49,21 @@ unsafe extern "C" fn setitimer(_timer: c_int, _new: *const itimerval, _old: *mut
unsafe extern "C" fn utimes(_path: *const c_char, _times: *const timeval) -> c_int {
todo!()
}
impl From<__ygg_timespec_t> for Duration {
fn from(value: __ygg_timespec_t) -> Self {
Self::new(
value.tv_sec.0.try_into().unwrap(),
value.tv_nsec.try_into().unwrap(),
)
}
}
impl From<Duration> for __ygg_timespec_t {
fn from(value: Duration) -> Self {
Self {
tv_sec: time_t(value.as_secs().try_into().unwrap()),
tv_nsec: value.subsec_nanos().try_into().unwrap()
}
}
}

View File

@ -1,7 +1,6 @@
use core::{
ffi::{c_char, c_int, c_long},
ptr::null_mut,
time::Duration,
};
use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike, Utc};
@ -116,15 +115,6 @@ impl<T: Datelike + Timelike> From<T> for tm {
}
}
impl From<__ygg_timespec_t> for Duration {
fn from(value: __ygg_timespec_t) -> Self {
Self::new(
value.tv_sec.0.try_into().unwrap(),
value.tv_nsec.try_into().unwrap(),
)
}
}
pub unsafe fn get_timezone() -> impl TimeZone {
// TODO get it from C
Utc

View File

@ -1,9 +1,9 @@
use core::{ffi::c_double, time::Duration};
use crate::{
error::CIntZeroResult,
headers::{sys_time::__ygg_timespec_t, sys_types::time_t},
process,
error::{self, CIntZeroResult, CResult},
headers::{errno, sys_time::__ygg_timespec_t, sys_types::time_t},
process, util::PointerExt,
};
#[no_mangle]
@ -11,16 +11,21 @@ unsafe extern "C" fn nanosleep(
rqtp: *const __ygg_timespec_t,
rmtp: *mut __ygg_timespec_t,
) -> CIntZeroResult {
let rqtp = rqtp.as_ref().unwrap();
if let Some(_rmtp) = rmtp.as_mut() {
todo!();
}
let rqtp = rqtp.ensure();
let amount = Duration::from(*rqtp);
process::sleep(amount).expect("TODO interruptible sleep in nanosleep()");
CIntZeroResult::SUCCESS
match process::sleep(amount) {
Ok(()) => {
CIntZeroResult::SUCCESS
}
Err(remaining) => {
if let Some(rmtp) = rmtp.as_mut() {
*rmtp = remaining.into();
}
error::errno = errno::EINTR;
CIntZeroResult::ERROR
}
}
}
#[no_mangle]

View File

@ -86,10 +86,12 @@ unsafe extern "C" fn pause() -> CIntZeroResult {
unsafe extern "C" fn setegid(gid: gid_t) -> c_int {
todo!()
}
#[no_mangle]
unsafe extern "C" fn seteuid(uid: uid_t) -> c_int {
todo!()
}
#[no_mangle]
unsafe extern "C" fn setgid(gid: gid_t) -> c_int {
todo!()
@ -98,26 +100,32 @@ unsafe extern "C" fn setgid(gid: gid_t) -> c_int {
unsafe extern "C" fn setpgid(pid: pid_t, pgid: pid_t) -> c_int {
todo!()
}
#[no_mangle]
unsafe extern "C" fn setpgrp() -> pid_t {
todo!()
}
#[no_mangle]
unsafe extern "C" fn setregid(rgid: gid_t, egid: gid_t) -> c_int {
todo!()
}
#[no_mangle]
unsafe extern "C" fn setreuid(ruid: uid_t, euid: uid_t) -> c_int {
todo!()
}
#[no_mangle]
unsafe extern "C" fn setsid() -> pid_t {
todo!()
}
#[no_mangle]
unsafe extern "C" fn setuid(uid: uid_t) -> c_int {
todo!()
}
#[no_mangle]
unsafe extern "C" fn sleep(seconds: c_uint) -> c_uint {
let duration = Duration::from_secs(seconds.try_into().unwrap());
@ -125,7 +133,7 @@ unsafe extern "C" fn sleep(seconds: c_uint) -> c_uint {
Ok(()) => 0,
Err(remaining) => {
error::errno = errno::EINTR;
remaining.as_secs() as _
remaining.as_secs() as c_uint + (remaining.subsec_nanos() != 0) as c_uint
}
}
}

View File

@ -1,6 +1,5 @@
use core::{
ffi::{c_char, c_int, CStr},
time::Duration,
ffi::{c_char, c_int, CStr}, mem::MaybeUninit, time::Duration
};
use yggdrasil_rt::{
@ -24,8 +23,12 @@ pub fn getpgrp() -> pid_t {
}
pub fn sleep(duration: Duration) -> Result<(), Duration> {
// TODO make nanosleep return duration
unsafe { syscall::nanosleep(&duration) }.map_err(|_| Duration::from_secs(1))
let mut remaining = MaybeUninit::uninit();
if unsafe { syscall::nanosleep(&duration, &mut remaining) }.is_ok() {
Ok(())
} else {
Err(unsafe { remaining.assume_init() })
}
}
pub fn abort() -> ! {