Impl SafePtr

This commit is contained in:
Yuke Peng 2023-05-30 04:09:25 -07:00 committed by Tate, Hongliang Tian
parent 0c9495b726
commit b2f2c55c9b
5 changed files with 470 additions and 2 deletions

79
Cargo.lock generated
View File

@ -163,12 +163,53 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "darling"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote",
"syn 1.0.109",
]
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "font8x8"
version = "0.2.7"
@ -207,6 +248,12 @@ dependencies = [
"ahash",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "1.9.2"
@ -217,6 +264,17 @@ dependencies = [
"hashbrown 0.12.3",
]
[[package]]
name = "inherit-methods-macro"
version = "0.1.0"
source = "git+https://github.com/occlum/occlum?branch=1.0.0-preview#927512bc20b1221060bcfc505e71adae5f42b1ff"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "int-to-c-enum"
version = "0.1.0"
@ -306,6 +364,7 @@ dependencies = [
"bitflags",
"buddy_system_allocator",
"cfg-if",
"inherit-methods-macro",
"intrusive-collections",
"lazy_static",
"limine",
@ -359,6 +418,16 @@ dependencies = [
"spin 0.9.4",
]
[[package]]
name = "jinux-rights"
version = "0.1.0"
dependencies = [
"bitflags",
"jinux-rights-proc",
"typeflags",
"typeflags-util",
]
[[package]]
name = "jinux-rights-proc"
version = "0.1.0"
@ -382,6 +451,7 @@ dependencies = [
"jinux-block",
"jinux-frame",
"jinux-input",
"jinux-rights",
"jinux-rights-proc",
"jinux-time",
"jinux-util",
@ -415,7 +485,10 @@ name = "jinux-util"
version = "0.1.0"
dependencies = [
"jinux-frame",
"jinux-rights",
"jinux-rights-proc",
"pod",
"typeflags-util",
]
[[package]]
@ -646,6 +719,12 @@ dependencies = [
"lock_api",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"

View File

@ -6,8 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
jinux-frame = {path = "../../../framework/jinux-frame"}
jinux-frame = { path = "../../../framework/jinux-frame" }
pod = { git = "https://github.com/jinzhao-dev/pod", rev = "7fa2ed2" }
typeflags-util = { path = "../typeflags-util" }
jinux-rights-proc = { path = "../jinux-rights-proc" }
jinux-rights = { path = "../jinux-rights" }
[features]

View File

@ -0,0 +1,13 @@
/// This trait is a _fallible_ version of `Clone`.
///
/// If any object of a type `T` is duplicable, then `T` should implement
/// `Clone`. However, if whether an object is duplicable must be determined
/// on a per-object basis at runtime, then `T` should implement `Dup` as
/// the `dup` method is allowed to return an error.
///
/// As a best practice, the `Clone` and `Dup` traits should be implemented
/// _exclusively_ to one another. In other words, a type should not implement
/// both traits.
pub trait Dup: Sized {
fn dup(&self) -> jinux_frame::Result<Self>;
}

View File

@ -4,6 +4,8 @@
extern crate alloc;
pub mod dup;
pub mod frame_ptr;
pub mod safe_ptr;
pub mod slot_vec;
pub mod union_read_ptr;

View File

@ -0,0 +1,372 @@
use core::marker::PhantomData;
use jinux_frame::vm::VmIo;
use jinux_frame::Result;
use jinux_rights::{Dup, Exec, Full, Read, Signal, TRightSet, TRights, Write};
use jinux_rights_proc::require;
use pod::Pod;
/// Safe pointers.
///
/// # Overview
///
/// Safe pointers allows using pointers to access memory without
/// unsafe code, which is a key enabler for writing device drivers in safe Rust.
///
/// To ensure its soundness, safe pointers (`SafePtr<T, M, _>`) have to be
/// more restricted than raw pointers (`*const T` or `*mut T`).
/// More specifically, there are three major restrictions.
///
/// 1. A safe pointer can only refer to a value of a POD type `T: Pod`,
/// while raw pointers can do to a value of any type `T`.
/// 2. A safe pointer can only refer to an address within a virtual memory object
/// of type `M: VmIo` (e.g., VMAR and VMO), while raw pointers can do to
/// an address within any virtual memory space.
/// 3. A safe pointer only allows one to copy values to/from the target address,
/// while a raw pointer allows one to borrow an immutable or mutable reference
/// to the target address.
///
/// The expressiveness of safe pointers, although being less than that of
/// raw pointers, is sufficient for our purpose of writing an OS kernel in safe
/// Rust.
///
/// In addition, safe pointers `SafePtr<T, M, R>` are associated with access
/// rights, which are encoded statically with type `R: TRights`.
///
/// # Examples
///
/// ## Constructing a safe pointer
///
/// An instance of `SafePtr` can be created with a VM object and an address
/// within the VM object.
///
/// ```
/// let u32_ptr: SafePtr<u32, Vec<u8>, _> = {
/// let vm_obj = VmoOptions::new(PAGE_SIZE).alloc().unwrap();
/// let addr = 16;
/// SafePtr::new(vm_obj, addr)
/// };
/// ```
///
/// The generic parameter `M` of `SafePtr<_, M, _>` must implement the `VmIo`
/// trait. The most important `VmIo` types are `Vmar`, `Vmo`, `Mmio`, and
/// `VmFrame`. The blanket implementations of `VmIo` also include pointer-like
/// types that refer to a `VmIo` type. Some examples are `&Vmo`, `Box<Vmar>`,
/// and `Arc<Mmio>`.
///
/// The safe pointer itself does not and cannot guarantee that its address is valid.
/// This is because different VM objects may interpret addresses differently
/// and each VM object can have different restrictions for valid addresses.
/// So the detection of invalid addresses is delayed to the time when the
/// pointers are actually read from or written to.
///
/// Initially, a newly-created safe pointer has all access rights.
///
/// ## Reading and writing a safe pointer
///
/// The value pointed to by a safe pointer can be read or written with the
/// `read` or `write` method. Both methods may return errors. The possible reasons
/// of error are determined by the underlying VM objects.
///
/// ```
/// u32_ptr.write(1234).unwrap();
/// assert!(u32_ptr.read().unwrap() == 1234);
/// ```
///
/// ## Manipulating a safe pointer
///
/// The address of a safe pointer can be obtained by the `addr` method.
/// The address can be updated by assigning a new value with the `set_addr` method
/// or updated incrementally through methods like `add`, `offset`, `byte_addr`,
/// `byte_offset`.
///
/// The VM object of a safe pointer can also be obtained or updated through the
/// `vm` and `set_vm` methods. A new safe pointer that is backed by the same
/// VM object of an existing safe pointer can be obtained through the `borrow_vm`
/// method.
///
/// As an example, the code below shows how the `add` and `borrow_vm` methods
/// can be used together to to iterate all values pointed to by an array pointer.
///
/// ```
/// fn collect_values<T>(array_ptr: &SafePtr<T, M, _>, array_len: usize) -> Vec<T> {
/// let mut curr_ptr: SafePtr<T, &M, _> = array_ptr.borrow_vm();
/// (0..array_len)
/// .iter()
/// .map(|_| {
/// let val = curr_ptr.read().unwrap();
/// curr_ptr.add(1);
/// val
/// })
/// .collect()
/// }
/// ```
///
/// The data type of a safe pointer can be converted with the `cast` method.
///
/// ```rust
/// let u8_ptr: SafePtr<u8, _, _> = u32_ptr.cast();
/// ```
///
/// ## Reading and writing the fields of a struct
///
/// Given a safe pointer that points to a struct (say, `Foo`), one can read
/// the value of its field as follows.
///
/// ```
/// pub struct Foo {
/// first: u64,
/// second: u32,
/// }
///
/// fn read_second_field<M: VmIo>(ptr: &SafePtr<Foo, M, _>) -> u32 {
/// let field_ptr = ptr
/// .borrow_vm()
/// .byte_add(offset_of!(Foo, second) as usize)
/// .cast::<u32>();
/// field_ptr.read().unwrap()
/// }
/// ```
///
/// But this coding pattern is too tedius for such a common task.
/// To make the life of users easier, we provide a convinient macro named
/// `field_ptr`, which can be used to obtain the safe pointer of a field from
/// that of its containing struct.
///
/// ```
/// fn read_second_field<M: VmIo>(ptr: &SafePtr<Foo, M, _>) -> u32 {
/// let field_ptr = field_ptr!(ptr, Foo, second);
/// field_ptr.read().unwrap()
/// }
/// ```
///
/// # Access rights
///
/// A safe pointer may have a combination of three access rights:
/// Read, Write, and Dup.
pub struct SafePtr<T, M, R = Full> {
addr: usize,
vm_obj: M,
rights: R,
phantom: PhantomData<T>,
}
impl<T: Pod, M: VmIo> SafePtr<T, M> {
/// Create a new instance.
///
/// # Access rights
///
/// The default access rights of a new instance are `Read`, `Write`, and
/// `Dup`.
pub fn new(vm_obj: M, addr: usize) -> Self {
Self {
vm_obj,
addr,
rights: TRightSet(<TRights![Dup, Read, Write, Exec, Signal]>::new()),
phantom: PhantomData,
}
}
}
impl<T: Pod, M: VmIo, R: TRights> SafePtr<T, M, R> {
// =============== Read and write methods ==============
/// Read the value from the pointer.
///
/// # Access rights
///
/// This method requires the Read right.
#[require(R > Read)]
pub fn read(&self) -> Result<T> {
self.vm_obj.read_val(self.addr)
}
/// Read a slice of values from the pointer.
///
/// # Access rights
///
/// This method requires the Read right.
#[require(R > Read)]
pub fn read_slice(&self, slice: &mut [T]) -> Result<()> {
self.vm_obj.read_slice(0, slice)
}
/// Overwrite the value at the pointer.
///
/// # Access rights
///
/// This method requires the Write right.
#[require(R > Write)]
pub fn write(&self, val: &T) -> Result<()> {
self.vm_obj.write_val(self.addr, val)
}
/// Overwrite a slice of values at the pointer.
///
/// # Access rights
///
/// This method requires the Write right.
#[require(R > Write)]
pub fn write_slice(&self, slice: &[T]) -> Result<()> {
self.vm_obj.write_slice(0, slice)
}
// =============== Address-related methods ==============
pub const fn addr(&self) -> usize {
self.addr
}
pub fn set_addr(&mut self, addr: usize) {
self.addr = addr;
}
pub const fn is_aligned(&self) -> bool {
self.addr % core::mem::align_of::<T>() == 0
}
/// Increase the address in units of bytes occupied by the generic T.
pub fn add(&mut self, count: usize) {
let offset = count * core::mem::size_of::<T>();
self.addr += offset;
}
/// Increase or decrease the address in units of bytes occupied by the generic T.
pub fn offset(&mut self, count: isize) {
let offset = count * core::mem::size_of::<T>() as isize;
if count >= 0 {
self.addr += offset as usize;
} else {
self.addr -= (-offset) as usize;
}
}
/// Increase the address in units of bytes.
pub fn byte_add(&mut self, bytes: usize) {
self.addr += bytes;
}
/// Increase or decrease the address in units of bytes.
pub fn byte_offset(&mut self, bytes: isize) {
if bytes >= 0 {
self.addr += bytes as usize;
} else {
self.addr -= (-bytes) as usize;
}
}
// =============== VM object-related methods ==============
pub const fn vm(&self) -> &M {
&self.vm_obj
}
pub fn set_vm(&mut self, vm_obj: M) {
self.vm_obj = vm_obj;
}
/// Construct a new SafePtr which will point to the same address
pub const fn borrow_vm(&self) -> SafePtr<T, &M, R> {
let SafePtr {
addr,
vm_obj,
rights,
..
} = self;
SafePtr {
addr: *addr,
vm_obj,
rights: *rights,
phantom: PhantomData,
}
}
// =============== Type conversion methods ==============
/// Cast the accessed structure into a new one, which is usually used when accessing a field in a structure.
pub fn cast<U: Pod>(self) -> SafePtr<U, M, R> {
let SafePtr {
addr,
vm_obj,
rights,
..
} = self;
SafePtr {
addr,
vm_obj,
rights,
phantom: PhantomData,
}
}
/// Construct a new SafePtr and restrict the rights of it.
///
/// # Access rights
///
/// This method requires the target rights to be a subset of the current rights.
#[require(R > R1)]
pub fn restrict<R1: TRights>(self) -> SafePtr<T, M, R1> {
let SafePtr { addr, vm_obj, .. } = self;
SafePtr {
addr,
vm_obj,
rights: R1::new(),
phantom: PhantomData,
}
}
}
#[require(R > Dup)]
impl<T, M: Clone, R: TRights> Clone for SafePtr<T, M, R> {
fn clone(&self) -> Self {
Self {
addr: self.addr,
vm_obj: self.vm_obj.clone(),
rights: self.rights,
phantom: PhantomData,
}
}
}
#[require(R > Dup)]
impl<T, M: crate::dup::Dup, R: TRights> crate::dup::Dup for SafePtr<T, M, R> {
fn dup(&self) -> Result<Self> {
let duplicated = Self {
addr: self.addr,
vm_obj: self.vm_obj.dup()?,
rights: self.rights,
phantom: PhantomData,
};
Ok(duplicated)
}
}
/// Create a safe pointer for the field of a struct.
#[macro_export]
macro_rules! field_ptr {
($ptr:expr, $type:ty, $($field:tt)+) => {{
use jinux_frame::offset_of;
use jinux_frame::vm::VmIo;
// import more...
#[inline]
fn new_field_ptr<T, M, R, U>(
container_ptr: &SafePtr<T, M, R>,
field_offset: *const U
) -> SafePtr<U, &M, R>
where
T: Pod,
M: VmIo,
R: TRights,
U: Pod,
{
container_ptr
.borrow_vm()
.byte_add(offset as usize)
.cast()
}
let ptr = $ptr;
let field_offset = offset_of!(ty, $($field)*);
new_field_ptr(ptr, field_offset)
}}
}