Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dev_tests/src/ratchet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fn ratchet_globals() -> Result<()> {
("litebox_platform_linux_userland/", 5),
("litebox_platform_lvbs/", 24),
("litebox_platform_multiplex/", 1),
("litebox_platform_windows_userland/", 7),
("litebox_platform_windows_userland/", 8),
("litebox_runner_linux_userland/", 1),
("litebox_runner_lvbs/", 4),
("litebox_runner_snp/", 1),
Expand Down
5 changes: 5 additions & 0 deletions litebox/src/event/wait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ impl<'a, Platform: RawSyncPrimitivesProvider + TimeProvider> WaitContext<'a, Pla
/// evaluating the wait and interrupt conditions so that wakeups are not
/// missed.
fn start_wait(&self) {
self.waker
.0
.platform
.on_interruptible_wait_start(self.waker);
self.waker
.0
.set_state(ThreadState::WAITING, Ordering::SeqCst);
Expand All @@ -384,6 +388,7 @@ impl<'a, Platform: RawSyncPrimitivesProvider + TimeProvider> WaitContext<'a, Pla
self.waker
.0
.set_state(ThreadState::RUNNING_IN_HOST, Ordering::Relaxed);
self.waker.0.platform.on_interruptible_wait_end();
}

/// Checks whether the wait should be interrupted. If not, then performs
Expand Down
47 changes: 47 additions & 0 deletions litebox/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,34 @@ pub trait ThreadProvider: RawPointerProvider {
/// [`EnterShim`]: crate::shim::EnterShim
/// [`EnterShim::interrupt`]: crate::shim::EnterShim::interrupt
fn interrupt_thread(&self, thread: &Self::ThreadHandle);

/// Runs `f` on the current thread after performing any platform-specific
/// thread registration needed for [`current_thread`](Self::current_thread)
/// and related functionality to work.
///
/// This is intended for test threads that do not go through the normal
/// [`spawn_thread`](Self::spawn_thread) / guest entry path. The platform
/// sets up thread state before calling `f` and tears it down afterward.
///
/// The default implementation simply calls `f()` with no additional setup.
/// Platforms that require explicit thread registration should override this.
#[cfg(debug_assertions)]
fn run_test_thread<R>(f: impl FnOnce() -> R) -> R {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we scope this function to tests only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it. Using cfg(test) does not work here because the tests reside in a different crate (litebox_shim_linux). Test functions cannot be used by a different crate. One possible solution is adding a new feature, but that adds complexity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we can use cfg(debug_assertions).

f()
}
}

/// Provider for consuming platform-originating signals.
///
/// Platforms can record signals (e.g., `SIGINT`) and the shim should call
/// [`SignalProvider::take_pending_signals`] to consume them.
pub trait SignalProvider {
/// Atomically take all pending asynchronous signals (e.g., SIGINT and SIGALRM)
/// for the current thread, passing each one to `f`.
///
/// Platforms that support asynchronous signals should override this method.
#[allow(unused_variables, reason = "no-op by default")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: prefer expect rather than allow

fn take_pending_signals(&self, f: impl FnMut(crate::shim::Signal)) {}
Comment on lines +115 to +125
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am quite surprised by this provider. There are some surprising inversions happening here, for example, it depends on crate::shim in its signature.

I don't think the Signal sits well in shim, since the shim module is meant for traits that the shim must implement but there isn't a trait being implemented there.

Also, "shim should call" implies that a shim that does not call stuff is incorrect. This sort of requirement is exactly what traits are for, and thus the way the design should be is to actually have the shim implement a trait that probably looks like the following:

pub trait ConsumeSignal {
  fn consume_signal(Signal);
}

Then the platform can always invoke this trait, and the shim can make a decision of it wants to buffer things or not. If you need/want the "all in a group" behavior, then you can make the trait (instead of the above):

pub trait ConsumeSignal {
  fn consume_signals(&[Signal]);
}

}

/// Punch through any functionality for a particular platform that is not explicitly part of the
Expand Down Expand Up @@ -220,6 +248,25 @@ where
/// A provider of raw mutexes
pub trait RawMutexProvider {
type RawMutex: RawMutex;

/// Called when a thread enters an interruptible wait.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Called by whom?

///
/// The passed `waker` should live at least until the thread leaves the interruptible
/// wait (i.e., [`on_interruptible_wait_end`](Self::on_interruptible_wait_end) is called).
Comment on lines +254 to +255
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a somewhat surprising requirement; we can simplify this by updating the signature in this to take a Waker rather than a &Waker, right?

/// The platform can use the `waker` to wake up the thread while it is in the interruptible wait.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"can use" -> "uses"?

///
/// This is a no-op by default.
#[allow(unused_variables)]
fn on_interruptible_wait_start(&self, waker: &crate::event::wait::Waker<Self>)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this function is somewhat weird. It is not actually beginning the wait, right? Maybe something like register_waker (and the other one should be unregister_waker)?

Also, what is the expected behavior if someone runs "start" multiple times in a row?

where
Self: crate::sync::RawSyncPrimitivesProvider + Sized,
{
}

/// Called when a thread leaves an interruptible wait.
///
/// This is a no-op by default.
fn on_interruptible_wait_end(&self) {}
Comment on lines +266 to +269
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can merge with the above one by taking in a Option<Waker> instead of a Waker; in that case, it is behaving more like update_interruptible_waker.

}

/// A raw mutex/lock API; expected to roughly match (or even be implemented using) a Linux futex.
Expand Down
105 changes: 105 additions & 0 deletions litebox/src/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,108 @@ impl Exception {
/// #PF
pub const PAGE_FAULT: Self = Self(14);
}

/// A signal number.
///
/// Signal numbers are 1-based and must be in the range 1–63.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Signal(u32);
Comment on lines +139 to +141
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: store as a u8?


impl Signal {
/// SIGINT (signal 2) — interrupt from keyboard (Ctrl+C).
pub const SIGINT: Self = Self(2);
/// SIGALRM (signal 14) — timer signal from `alarm`.
pub const SIGALRM: Self = Self(14);

/// Create a `Signal` from a raw signal number.
///
/// Returns `None` if `signum` is outside the valid range 1–63.
pub const fn from_raw(signum: u32) -> Option<Self> {
if signum >= 1 && signum <= 63 {
Some(Self(signum))
} else {
None
}
}

/// Returns the raw signal number.
pub const fn as_raw(self) -> u32 {
self.0
}
}

/// A set of [`Signal`]s, stored as a 64-bit bitmask.
///
/// Bit `(signum - 1)` is set when signal `signum` is present in the set.
/// Because signal numbers are 1-based and capped at 63, all 63 possible
/// signals fit in a single `u64`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SigSet(u64);
Comment on lines +166 to +172
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are explicitly defining the internal details of how things are stored, you can just expose the value inside it via pub struct SigSet(pub u64) to remove from_u64/as_u64. Alternatively, you should not be specifying how it is being internally stored in the docs.


impl SigSet {
/// An empty signal set.
pub const fn empty() -> Self {
Self(0)
}

/// Returns `true` if the set contains no signals.
pub const fn is_empty(&self) -> bool {
self.0 == 0
}

/// Adds `signal` to the set.
pub const fn add(&mut self, signal: Signal) {
self.0 |= 1 << (signal.0 - 1);
}

/// Returns a new set that is `self` with `signal` added.
#[must_use]
pub const fn with(self, signal: Signal) -> Self {
Self(self.0 | (1 << (signal.0 - 1)))
}

/// Removes `signal` from the set.
pub const fn remove(&mut self, signal: Signal) {
self.0 &= !(1 << (signal.0 - 1));
}

/// Returns `true` if the set contains `signal`.
pub const fn contains(&self, signal: Signal) -> bool {
(self.0 & (1 << (signal.0 - 1))) != 0
}

/// Removes and returns the lowest-numbered signal in the set, or `None`
/// if empty.
pub fn pop_lowest(&mut self) -> Option<Signal> {
if self.0 == 0 {
return None;
}
let bit = self.0.trailing_zeros();
self.0 &= !(1u64 << bit);
// bit is 0–62, so bit + 1 is 1–63 — always valid.
Some(Signal(bit + 1))
}

/// Creates a `SigSet` from a raw `u64` bitmask.
pub const fn from_u64(bits: u64) -> Self {
Self(bits)
}

/// Returns the underlying `u64` bitmask.
pub const fn as_u64(&self) -> u64 {
self.0
}
}

impl Iterator for SigSet {
type Item = Signal;

fn next(&mut self) -> Option<Signal> {
self.pop_lowest()
}

fn size_hint(&self) -> (usize, Option<usize>) {
let count = self.0.count_ones() as usize;
(count, Some(count))
}
}
11 changes: 11 additions & 0 deletions litebox_common_linux/src/signal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ impl TryFrom<i32> for Signal {
}
}
}
impl TryFrom<Signal> for litebox::shim::Signal {
type Error = Signal;

fn try_from(value: Signal) -> Result<Self, Self::Error> {
match value {
Signal::SIGINT => Ok(Self::SIGINT),
Signal::SIGALRM => Ok(Self::SIGALRM),
_ => Err(value),
Comment on lines +105 to +112
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite surprising, haven't you designed the litebox::shim::Signal literally to support the full set of Linux signals? You've even hard-coded the "up to 64" and so on, so I see no reason why this does not support SIGILL etc converted over.

I do think that the long-term move is for litebox::shim::Signal to not have this 64 restriction, but if you are assuming that right now, then wouldn't the correct move just be Signal::from_raw(...).ok_or(Err(...))?

}
}
}

/// The default disposition of a signal.
pub enum SignalDisposition {
Expand Down
42 changes: 13 additions & 29 deletions litebox_platform_linux_kernel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ use core::sync::atomic::AtomicU64;
use core::{arch::asm, sync::atomic::AtomicU32};

use litebox::mm::linux::PageRange;
use litebox::platform::RawPointerProvider;
use litebox::platform::page_mgmt::FixedAddressBehavior;
use litebox::platform::{
DebugLogProvider, IPInterfaceProvider, ImmediatelyWokenUp, PageManagementProvider, Provider,
Punchthrough, PunchthroughProvider, PunchthroughToken, RawMutexProvider, TimeProvider,
UnblockedOrTimedOut,
Punchthrough, PunchthroughProvider, PunchthroughToken, RawMutexProvider, SignalProvider,
TimeProvider, UnblockedOrTimedOut,
};
use litebox::platform::{RawMutex as _, RawPointerProvider};
use litebox_common_linux::PunchthroughSyscall;
use litebox_common_linux::errno::Errno;

Expand Down Expand Up @@ -79,6 +79,7 @@ impl<'a, Host: HostInterface> PunchthroughToken for LinuxPunchthroughToken<'a, H
}

impl<Host: HostInterface> Provider for LinuxKernel<Host> {}
impl<Host: HostInterface> SignalProvider for LinuxKernel<Host> {}

// TODO: implement pointer validation to ensure the pointers are in user space.
type UserConstPtr<T> = litebox::platform::common_providers::userspace_pointers::UserConstPtr<
Expand Down Expand Up @@ -180,33 +181,16 @@ impl<Host: HostInterface> RawMutex<Host> {
val: u32,
timeout: Option<core::time::Duration>,
) -> Result<UnblockedOrTimedOut, ImmediatelyWokenUp> {
loop {
// No need to wait if the value already changed.
if self
.underlying_atomic()
.load(core::sync::atomic::Ordering::Relaxed)
!= val
{
return Err(ImmediatelyWokenUp);
match Host::block_or_maybe_timeout(&self.inner, val, timeout) {
Ok(()) | Err(Errno::EINTR) => Ok(UnblockedOrTimedOut::Unblocked),
Err(Errno::EAGAIN) => {
// If the futex value does not match val, then the call fails
// immediately with the error EAGAIN.
Err(ImmediatelyWokenUp)
}

let ret = Host::block_or_maybe_timeout(&self.inner, val, timeout);

match ret {
Ok(()) => {
return Ok(UnblockedOrTimedOut::Unblocked);
}
Err(Errno::EAGAIN | Errno::EINTR) => {
// If the futex value does not match val, then the call fails
// immediately with the error EAGAIN.
return Err(ImmediatelyWokenUp);
}
Err(Errno::ETIMEDOUT) => {
return Ok(UnblockedOrTimedOut::TimedOut);
}
Err(e) => {
todo!("Error: {:?}", e);
}
Err(Errno::ETIMEDOUT) => Ok(UnblockedOrTimedOut::TimedOut),
Err(e) => {
todo!("Error: {:?}", e);
}
}
}
Expand Down
Loading