280 lines
11 KiB
Rust
280 lines
11 KiB
Rust
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
//! The context that can be accessed from the current task, thread or process.
|
|
|
|
use core::cell::Ref;
|
|
|
|
use inherit_methods_macro::inherit_methods;
|
|
use ostd::{
|
|
mm::{Fallible, PodAtomic, VmIo, VmReader, VmWriter},
|
|
task::Task,
|
|
};
|
|
|
|
use crate::{
|
|
prelude::*,
|
|
process::{
|
|
Process,
|
|
posix_thread::{PosixThread, ThreadLocal},
|
|
},
|
|
thread::Thread,
|
|
vm::vmar::{VMAR_CAP_ADDR, VMAR_LOWEST_ADDR, Vmar},
|
|
};
|
|
|
|
/// The context that can be accessed from the current POSIX thread.
|
|
#[derive(Clone)]
|
|
pub struct Context<'a> {
|
|
pub process: Arc<Process>,
|
|
pub thread_local: &'a ThreadLocal,
|
|
pub posix_thread: &'a PosixThread,
|
|
pub thread: &'a Thread,
|
|
pub task: &'a Task,
|
|
}
|
|
|
|
impl Context<'_> {
|
|
/// Gets the userspace of the current task.
|
|
pub fn user_space(&self) -> CurrentUserSpace<'_> {
|
|
CurrentUserSpace(self.thread_local.vmar().borrow())
|
|
}
|
|
}
|
|
|
|
/// The user's memory space of the current task.
|
|
///
|
|
/// It provides methods to read from or write to the user space efficiently.
|
|
//
|
|
// FIXME: With `impl VmIo for &CurrentUserSpace<'_>`, the Rust compiler seems to think that
|
|
// `CurrentUserSpace` is a publicly exposed type, despite the fact that it is contained in a
|
|
// private module and is never actually exposed. Consequently, it incorrectly suppresses many dead
|
|
// code lints (for *lots of* types that are recursively reached via `CurrentUserSpace`'s APIs). As
|
|
// a workaround, we mark the type as `pub(crate)`. We can restore it to `pub` once the compiler bug
|
|
// is resolved.
|
|
pub(crate) struct CurrentUserSpace<'a>(Ref<'a, Option<Arc<Vmar>>>);
|
|
|
|
/// Gets the [`CurrentUserSpace`] from the current task.
|
|
///
|
|
/// This is slower than [`Context::user_space`]. Don't use this getter
|
|
/// If you get the access to the [`Context`].
|
|
#[macro_export]
|
|
macro_rules! current_userspace {
|
|
() => {
|
|
$crate::context::CurrentUserSpace::new(
|
|
$crate::process::posix_thread::AsThreadLocal::as_thread_local(
|
|
&ostd::task::Task::current().unwrap(),
|
|
)
|
|
.unwrap(),
|
|
)
|
|
};
|
|
}
|
|
|
|
impl<'a> CurrentUserSpace<'a> {
|
|
/// Creates a new `CurrentUserSpace` from the current task.
|
|
///
|
|
/// If you have access to a [`Context`], it is preferable to call [`Context::user_space`].
|
|
///
|
|
/// Otherwise, you can use the `current_userspace` macro
|
|
/// to obtain an instance of `CurrentUserSpace` if it will only be used once.
|
|
pub fn new(thread_local: &'a ThreadLocal) -> Self {
|
|
let vmar_ref = thread_local.vmar().borrow();
|
|
Self(vmar_ref)
|
|
}
|
|
|
|
/// Returns the `Vmar` of the current userspace.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This method will panic if the current process has cleared its `Vmar`.
|
|
pub fn vmar(&self) -> &Vmar {
|
|
self.0.as_ref().unwrap()
|
|
}
|
|
|
|
/// Returns whether the VMAR is shared with other processes or threads.
|
|
pub fn is_vmar_shared(&self) -> bool {
|
|
// If the VMAR is not shared, its reference count should be exactly 2:
|
|
// one reference is held by `ThreadLocal` and the other by `ProcessVm` in `Process`.
|
|
Arc::strong_count(self.0.as_ref().unwrap()) > 2
|
|
}
|
|
|
|
/// Creates a reader to read data from the user space of the current task.
|
|
///
|
|
/// Returns `Err` if `vaddr` and `len` do not represent a user space memory range.
|
|
pub fn reader(&self, vaddr: Vaddr, len: usize) -> Result<VmReader<'_, Fallible>> {
|
|
// Do NOT attempt to call `check_vaddr_lowerbound` here.
|
|
//
|
|
// Linux has a **delayed buffer validation** behavior:
|
|
// The Linux kernel assumes that a given user-space pointer is valid until it attempts to access it.
|
|
// For example, the following invocation of the `read` system call with a `NULL` pointer as the buffer
|
|
//
|
|
// ```c
|
|
// read(fd, NULL, 1);
|
|
// ```
|
|
//
|
|
// will return 0 (rather than an error) if the file referred to by `fd` has zero length.
|
|
//
|
|
// Asterinas's system call entry points follow a pattern of converting user-space pointers to
|
|
// a reader/writer first and using the reader/writer later.
|
|
// So adding any pointer check here would break Asterinas's delayed buffer validation behavior.
|
|
Ok(self.vmar().vm_space().reader(vaddr, len)?)
|
|
}
|
|
|
|
/// Creates a writer to write data into the user space of the current task.
|
|
///
|
|
/// Returns `Err` if `vaddr` and `len` do not represent a user space memory range.
|
|
pub fn writer(&self, vaddr: Vaddr, len: usize) -> Result<VmWriter<'_, Fallible>> {
|
|
// Do NOT attempt to call `check_vaddr_lowerbound` here.
|
|
// See the comments in the `reader` method.
|
|
Ok(self.vmar().vm_space().writer(vaddr, len)?)
|
|
}
|
|
|
|
/// Creates a reader/writer pair to read data from or write data into the user space
|
|
/// of the current task.
|
|
///
|
|
/// Returns `Err` if `vaddr` and `len` do not represent a user space memory range.
|
|
///
|
|
/// This method is semantically equivalent to calling [`Self::reader`] and [`Self::writer`]
|
|
/// separately, but it avoids double checking the validity of the memory region.
|
|
pub fn reader_writer(
|
|
&self,
|
|
vaddr: Vaddr,
|
|
len: usize,
|
|
) -> Result<(VmReader<'_, Fallible>, VmWriter<'_, Fallible>)> {
|
|
// Do NOT attempt to call `check_vaddr_lowerbound` here.
|
|
// See the comments in the `reader` method.
|
|
Ok(self.vmar().vm_space().reader_writer(vaddr, len)?)
|
|
}
|
|
|
|
/// Atomically loads a `PodAtomic` value with [`Ordering::Relaxed`] semantics.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This method will panic if `vaddr` is not aligned on an `align_of::<T>()`-byte boundary.
|
|
///
|
|
/// [`Ordering::Relaxed`]: core::sync::atomic::Ordering::Relaxed
|
|
pub fn atomic_load<T: PodAtomic>(&self, vaddr: Vaddr) -> Result<T> {
|
|
if size_of::<T>() > 0 {
|
|
check_vaddr_lowerbound(vaddr)?;
|
|
}
|
|
|
|
let user_reader = self.reader(vaddr, size_of::<T>())?;
|
|
Ok(user_reader.atomic_load()?)
|
|
}
|
|
|
|
/// Atomically updates a `PodAtomic` value with [`Ordering::Relaxed`] semantics.
|
|
///
|
|
/// This method internally fetches the old value via [`atomic_load`], applies `op` to compute a
|
|
/// new value, and updates the value via [`atomic_compare_exchange`]. If the value changes
|
|
/// concurrently, this method will retry so the operation may be performed multiple times.
|
|
///
|
|
/// If the update is completely successful, returns `Ok` with the old value (i.e., the value
|
|
/// _before_ applying `op`). Otherwise, it returns `Err`.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This method will panic if `vaddr` is not aligned on an `align_of::<T>()`-byte boundary.
|
|
///
|
|
/// [`Ordering::Relaxed`]: core::sync::atomic::Ordering::Relaxed
|
|
/// [`atomic_load`]: VmReader::atomic_load
|
|
/// [`atomic_compare_exchange`]: VmWriter::atomic_compare_exchange
|
|
pub fn atomic_fetch_update<T>(&self, vaddr: Vaddr, op: impl Fn(T) -> T) -> Result<T>
|
|
where
|
|
T: PodAtomic + Eq,
|
|
{
|
|
if size_of::<T>() > 0 {
|
|
check_vaddr_lowerbound(vaddr)?;
|
|
}
|
|
|
|
let (reader, writer) = self.reader_writer(vaddr, size_of::<T>())?;
|
|
|
|
let mut old_val = reader.atomic_load()?;
|
|
loop {
|
|
match writer.atomic_compare_exchange(&reader, old_val, op(old_val))? {
|
|
(_, true) => return Ok(old_val),
|
|
(cur_val, false) => old_val = cur_val,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Reads a C string from the user space of the current process.
|
|
///
|
|
/// The length of the string should not exceed `max_len`, including the final nul byte.
|
|
/// Otherwise, this method will fail with [`Errno::ENAMETOOLONG`].
|
|
///
|
|
/// This method is commonly used to read a file name or path. In that case, when the nul byte
|
|
/// cannot be found within `max_len` bytes, the correct error code is [`Errno::ENAMETOOLONG`].
|
|
/// However, in other cases, the caller may want to fix the error code manually.
|
|
pub fn read_cstring(&self, vaddr: Vaddr, max_len: usize) -> Result<CString> {
|
|
if max_len > 0 {
|
|
check_vaddr_lowerbound(vaddr)?;
|
|
}
|
|
|
|
// Adjust `max_len` to ensure `vaddr + max_len` does not exceed `VMAR_CAP_ADDR`.
|
|
// If `vaddr` is outside user address space, `userspace_max_len` will be set to zero and
|
|
// further call to `self.reader` will return `EFAULT`.
|
|
let userspace_max_len = VMAR_CAP_ADDR.saturating_sub(vaddr).min(max_len);
|
|
|
|
let mut user_reader = self.reader(vaddr, userspace_max_len)?;
|
|
user_reader.read_cstring_until_nul(userspace_max_len)?
|
|
.ok_or_else(|| if userspace_max_len == max_len {
|
|
// There may be more bytes in the userspace, but the length limit has been reached.
|
|
Error::with_message(
|
|
Errno::ENAMETOOLONG,
|
|
"the C string does not end before reaching the maximum length"
|
|
)
|
|
} else {
|
|
// There cannot be any bytes in the userspace, but the C string still does not end.
|
|
// This is the Linux behavior in its `do_strncpy_from_user` implementation.
|
|
Error::with_message(
|
|
Errno::EFAULT,
|
|
"the C string does not end before reaching the maximum userspace virtual address"
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
impl VmIo for CurrentUserSpace<'_> {
|
|
fn read(&self, offset: usize, writer: &mut VmWriter) -> ostd::Result<()> {
|
|
let copy_len = writer.avail();
|
|
|
|
if copy_len > 0 {
|
|
check_vaddr_lowerbound(offset)?;
|
|
}
|
|
|
|
let mut user_reader = self.vmar().vm_space().reader(offset, copy_len)?;
|
|
user_reader.read_fallible(writer).map_err(|err| err.0)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write(&self, offset: usize, reader: &mut VmReader) -> ostd::Result<()> {
|
|
let copy_len = reader.remain();
|
|
|
|
if copy_len > 0 {
|
|
check_vaddr_lowerbound(offset)?;
|
|
}
|
|
|
|
let mut user_writer = self.vmar().vm_space().writer(offset, copy_len)?;
|
|
user_writer.write_fallible(reader).map_err(|err| err.0)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[inherit_methods(from = "(**self)")]
|
|
impl VmIo for &CurrentUserSpace<'_> {
|
|
fn read(&self, offset: usize, writer: &mut VmWriter) -> ostd::Result<()>;
|
|
fn write(&self, offset: usize, reader: &mut VmReader) -> ostd::Result<()>;
|
|
}
|
|
|
|
/// Checks if the user space pointer is below the lowest userspace address.
|
|
///
|
|
/// If a pointer is below the lowest userspace address, it is likely to be a
|
|
/// NULL pointer. Reading from or writing to a NULL pointer should trigger a
|
|
/// segmentation fault.
|
|
///
|
|
/// If it is not checked here, a kernel page fault will happen and we would
|
|
/// deny the access in the page fault handler anyway. It may save a page fault
|
|
/// in some occasions. More importantly, double page faults may not be handled
|
|
/// quite well on some platforms.
|
|
fn check_vaddr_lowerbound(va: Vaddr) -> ostd::Result<()> {
|
|
if va < VMAR_LOWEST_ADDR {
|
|
return Err(ostd::Error::PageFault);
|
|
}
|
|
Ok(())
|
|
}
|