libkernel: add documentation and cargo metadata

Fix up missing documentaiton and add metadata to the cargo definition.
This commit is contained in:
Matthew Leach
2026-04-09 20:11:01 +01:00
committed by Ashwin Naren
parent 3f87c1f3d9
commit 8ec17724d9
54 changed files with 637 additions and 86 deletions

View File

@@ -2,6 +2,12 @@
name = "libkernel"
version = "0.1.0"
edition = "2024"
description = "Architecture-independent kernel building blocks for operating systems"
license = "MIT"
repository = "https://github.com/hexagonal-sun/moss-kernel/"
readme = "README.md"
keywords = ["kernel", "os", "no-std", "operating-system", "embedded"]
categories = ["no-std", "os", "embedded"]
[features]
default = []
@@ -43,3 +49,6 @@ tokio = { version = "1.47.1", features = ["full"] }
[lints]
workspace = true
[package.metadata.docs.rs]
all-features = true

41
libkernel/README.md Normal file
View File

@@ -0,0 +1,41 @@
# libkernel
Architecture-independent kernel building blocks for operating systems.
`libkernel` provides the core abstractions that a kernel needs to manage memory,
processes, filesystems, and synchronisation, agnostic of the an underlying CPU
architecture. It is designed to run in a `no_std` environment and uses feature
gates to keep the dependency footprint minimal.
## Feature gates
| Feature | Enables | Implies |
|-----------|-------------------------------------------------------|------------------|
| `sync` | Synchronisation primitives (spinlock, mutex, rwlock…) | — |
| `alloc` | Memory allocators (buddy, slab) and collection types | `sync` |
| `paging` | Page tables, address-space management, PTE helpers | `alloc` |
| `proc` | Process identity types (UID/GID, capabilities) | — |
| `fs` | VFS traits, path manipulation, block I/O | `proc`, `sync` |
| `proc_vm` | Process virtual-memory management (mmap, brk, CoW) | `paging`, `fs` |
| `kbuf` | Async-aware circular kernel buffers | `sync` |
| `all` | Everything above | all of the above |
## Quick start
Add `libkernel` to your `Cargo.toml` with only the features you need:
```toml
[dependencies]
libkernel = { version = "0.1", features = ["sync", "proc"] }
```
## The `CpuOps` trait
Most synchronisation and memory primitives are generic over a
[`CpuOps`](https://docs.rs/libkernel/latest/libkernel/trait.CpuOps.html)
implementation. This trait abstracts the handful of arch-specific operations
(core ID, interrupt masking, halt) that the portable code depends on.
## License
Licensed under the MIT license. See [LICENSE](../LICENSE) for details.

View File

@@ -1,3 +1,5 @@
//! AArch64 memory management types and page table support.
pub mod pg_descriptors;
pub mod pg_tables;
pub mod pg_tear_down;

View File

@@ -1,3 +1,5 @@
//! AArch64 page table entry (PTE) descriptor types and traits.
use paste::paste;
use tock_registers::interfaces::{ReadWriteable, Readable};
use tock_registers::{register_bitfields, registers::InMemoryRegister};
@@ -61,9 +63,12 @@ impl TableAddr {
}
}
/// The memory type attribute applied to a page table mapping.
#[derive(Debug, Clone, Copy)]
pub enum MemoryType {
/// Device (non-cacheable, non-reorderable) memory.
Device,
/// Normal (cacheable) memory.
Normal,
}
@@ -163,6 +168,7 @@ macro_rules! define_descriptor {
))
}
/// Returns a new descriptor with the given permissions applied.
pub fn set_permissions(self, perms: PtePermissions) -> Self {
let reg = InMemoryRegister::new(self.0);
use [<$name Fields>]::BlockPageFields;
@@ -287,9 +293,13 @@ define_descriptor!(
},
);
/// The decoded state of an L3 page descriptor.
pub enum L3DescriptorState {
/// The entry is not present.
Invalid,
/// The entry has been swapped out but retains address information.
Swapped,
/// The entry is a valid page mapping.
Valid,
}

View File

@@ -1,3 +1,5 @@
//! AArch64 page table structures, levels, and mapping logic.
use core::marker::PhantomData;
use super::{
@@ -17,7 +19,9 @@ use crate::{
},
};
/// Number of page table descriptors that fit in a single 4 KiB page.
pub const DESCRIPTORS_PER_PAGE: usize = PAGE_SIZE / core::mem::size_of::<u64>();
/// Bitmask used to extract the page table index from a shifted virtual address.
pub const LEVEL_MASK: usize = DESCRIPTORS_PER_PAGE - 1;
/// Trait representing a single level of the page table hierarchy.
@@ -47,8 +51,10 @@ pub trait PgTable: Clone + Copy {
/// The descriptor (page table entry) type for this level.
type Descriptor: PageTableEntry;
/// Constructs this table handle from a typed virtual pointer to its backing array.
fn from_ptr(ptr: TVA<PgTableArray<Self>>) -> Self;
/// Returns the raw mutable pointer to the underlying descriptor array.
fn to_raw_ptr(self) -> *mut u64;
/// Compute the index into this page table from a virtual address.
@@ -81,6 +87,7 @@ pub(super) trait TableMapperTable: PgTable<Descriptor: TableMapper> + Clone + Co
}
}
/// A page-aligned array of raw page table entries for a given table level.
#[derive(Clone)]
#[repr(C, align(4096))]
pub struct PgTableArray<K: PgTable> {
@@ -89,6 +96,7 @@ pub struct PgTableArray<K: PgTable> {
}
impl<K: PgTable> PgTableArray<K> {
/// Creates a zeroed page table array (all entries invalid).
pub const fn new() -> Self {
Self {
pages: [0; DESCRIPTORS_PER_PAGE],
@@ -104,8 +112,9 @@ impl<K: PgTable> Default for PgTableArray<K> {
}
macro_rules! impl_pgtable {
($table:ident, $shift:expr, $desc_type:ident) => {
($(#[$outer:meta])* $table:ident, $shift:expr, $desc_type:ident) => {
#[derive(Clone, Copy)]
$(#[$outer])*
pub struct $table {
base: *mut u64,
}
@@ -145,22 +154,26 @@ macro_rules! impl_pgtable {
};
}
impl_pgtable!(L0Table, 39, L0Descriptor);
impl_pgtable!(/// Level 0 page table (512 GiB per entry).
L0Table, 39, L0Descriptor);
impl TableMapperTable for L0Table {
type NextLevel = L1Table;
}
impl_pgtable!(L1Table, 30, L1Descriptor);
impl_pgtable!(/// Level 1 page table (1 GiB per entry).
L1Table, 30, L1Descriptor);
impl TableMapperTable for L1Table {
type NextLevel = L2Table;
}
impl_pgtable!(L2Table, 21, L2Descriptor);
impl_pgtable!(/// Level 2 page table (2 MiB per entry).
L2Table, 21, L2Descriptor);
impl TableMapperTable for L2Table {
type NextLevel = L3Table;
}
impl_pgtable!(L3Table, 12, L3Descriptor);
impl_pgtable!(/// Level 3 page table (4 KiB per entry).
L3Table, 12, L3Descriptor);
/// Trait for temporarily mapping and modifying a page table located at a
/// physical address.
@@ -459,6 +472,7 @@ where
}
#[cfg(test)]
#[allow(missing_docs)]
pub mod tests {
use super::*;
use crate::{

View File

@@ -1,3 +1,5 @@
//! Utilities for tearing down and freeing page table hierarchies.
use super::pg_descriptors::{PaMapper, TableMapper};
use super::pg_tables::L0Table;
use super::{

View File

@@ -1,3 +1,5 @@
//! Page table walking and per-entry modification.
use super::{
pg_descriptors::{L3Descriptor, PageTableEntry, TableMapper},
pg_tables::{L0Table, L3Table, PageTableMapper, PgTable, PgTableArray, TableMapperTable},
@@ -17,7 +19,9 @@ pub struct WalkContext<'a, PM>
where
PM: PageTableMapper + 'a,
{
/// The mapper used to temporarily access page tables by physical address.
pub mapper: &'a mut PM,
/// The TLB invalidator invoked after modifying page table entries.
pub invalidator: &'a dyn TLBInvalidator,
}

View File

@@ -1,5 +1,9 @@
//! TLB invalidation helpers.
/// Trait for invalidating TLB entries after page table modifications.
pub trait TLBInvalidator {}
/// A no-op TLB invalidator used when invalidation is unnecessary.
pub struct NullTlbInvalidator {}
impl TLBInvalidator for NullTlbInvalidator {}

View File

@@ -1 +1,3 @@
//! AArch64 (ARM64) specific implementations.
pub mod memory;

View File

@@ -1 +1,3 @@
//! Architecture-specific support code.
pub mod arm64;

View File

@@ -1,5 +1,10 @@
//! Device descriptor types.
/// A major/minor pair identifying a character or block device.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct CharDevDescriptor {
/// The major device number (identifies the driver).
pub major: u64,
/// The minor device number (identifies the device instance).
pub minor: u64,
}

View File

@@ -1,217 +1,295 @@
//! Unified kernel error types.
//!
//! This module defines the error hierarchy used throughout the kernel.
//! [`KernelError`] is the top-level enum and wraps domain-specific errors such
//! as [`MapError`], [`FsError`], [`IoError`], and others. A [`Result<T>`] type
//! alias is provided for convenience.
//!
//! The [`syscall_error`] submodule maps [`KernelError`] variants to their POSIX
//! `errno` equivalents for returning values to user space.
use core::convert::Infallible;
use thiserror::Error;
pub mod syscall_error;
/// Errors that can occur during device probing.
#[derive(Debug, Error, PartialEq, Eq, Clone)]
pub enum ProbeError {
/// No registers present in the Flattened Device Tree.
#[error("No registers present in FDT")]
NoReg,
/// No register bank size found in the Flattened Device Tree.
#[error("No register bank size in FDT")]
NoRegSize,
/// No interrupts declared in the Flattened Device Tree.
#[error("No interrupts in FDT")]
NoInterrupts,
/// No parent interrupt controller found in the Flattened Device Tree.
#[error("No parent interrupt controller in FDT")]
NoParentInterrupt,
/// The specified interrupt parent is not an interrupt controller.
#[error("The specified interrupt parent isn't an interrupt controller")]
NotInterruptController,
// Driver probing should be tried again after other probes have succeeded.
/// Driver probing should be tried again after other probes have succeeded.
#[error("Driver probing deferred for other dependencies")]
Deferred,
// Device inspected but not a match for this driver; skip silently.
/// Device inspected but not a match for this driver; skip silently.
#[error("Device not matched by driver")]
NoMatch,
}
/// Errors that can occur during page table mapping.
#[derive(Debug, Error, PartialEq, Eq, Clone)]
pub enum MapError {
/// Physical address is not page aligned.
#[error("Physical address not page aligned")]
PhysNotAligned,
#[error("Physical address not page aligned")]
/// Virtual address is not page aligned.
#[error("Virtual address not page aligned")]
VirtNotAligned,
/// Physical and virtual range sizes do not match.
#[error("Physical and virtual range sizes do not match")]
SizeMismatch,
/// Failed to walk to the next level page table.
#[error("Failed to walk to the next level page table")]
WalkFailed,
/// Invalid page table descriptor encountered.
#[error("Invalid page table descriptor encountered")]
InvalidDescriptor,
/// The region to be mapped is smaller than `PAGE_SIZE`.
#[error("The region to be mapped is smaller than PAGE_SIZE")]
TooSmall,
/// The virtual address range has already been mapped.
#[error("The VA range is has already been mapped")]
AlreadyMapped,
/// Page table does not contain an L3 mapping.
#[error("Page table does not contain an L3 mapping")]
NotL3Mapped,
}
/// Errors from block-level I/O operations.
#[derive(Error, Debug, PartialEq, Eq, Clone)]
pub enum IoError {
/// The requested I/O operation was out of bounds for the block device.
#[error("The requested I/O operation was out of bounds for the block device")]
OutOfBounds,
/// Corruption found in the filesystem metadata.
#[error("Corruption found in the filesystem metadata")]
MetadataCorruption,
}
/// Errors from filesystem operations.
#[derive(Error, Debug, PartialEq, Eq, Clone)]
pub enum FsError {
/// The path or file was not found.
#[error("The path or file was not found")]
NotFound,
/// The path component is not a directory.
#[error("The path component is not a directory.")]
NotADirectory,
/// The path component is a directory.
#[error("The path component is a directory.")]
IsADirectory,
/// The file or directory already exists.
#[error("The file or directory already exists.")]
AlreadyExists,
/// The directory is not empty.
#[error("The directory is not empty.")]
DirectoryNotEmpty,
/// Invalid input parameters.
#[error("Invalid input parameters.")]
InvalidInput,
/// The filesystem is corrupted or has an invalid format.
#[error("The filesystem is corrupted or has an invalid format.")]
InvalidFs,
/// Attempted to access data out of bounds.
#[error("Attempted to access data out of bounds.")]
OutOfBounds,
/// The operation is not permitted.
#[error("The operation is not permitted.")]
PermissionDenied,
/// Could not find the specified filesystem driver.
#[error("Could not find the specified FS driver")]
DriverNotFound,
/// Too many open files.
#[error("Too many open files")]
TooManyFiles,
/// The device could not be found.
#[error("The device could not be found")]
NoDevice,
/// Too many symbolic links encountered.
#[error("Too many symbolic links encountered")]
Loop,
/// Attempted to rename across devices.
#[error("Attempted to rename from cross device")]
CrossDevice,
}
/// Errors that occur when loading or parsing an executable.
#[derive(Error, Debug, PartialEq, Eq, Clone)]
pub enum ExecError {
/// Invalid ELF format.
#[error("Invalid ELF Format")]
InvalidElfFormat,
/// Invalid script format.
#[error("Invalid Script Format")]
InvalidScriptFormat,
/// Invalid program header format.
#[error("Invalid Program Header Format")]
InvalidPHdrFormat,
}
/// Top-level kernel error type wrapping all domain-specific errors.
#[derive(Error, Debug, PartialEq, Eq, Clone)]
pub enum KernelError {
/// Cannot allocate memory.
#[error("Cannot allocate memory")]
NoMemory,
/// Memory region not found.
#[error("Memory region not found")]
NoMemRegion,
/// Invalid value.
#[error("Invalid value")]
InvalidValue,
/// The current resource is already in use.
#[error("The current resource is already in use")]
InUse,
/// Page table mapping failed.
#[error("Page table mapping failed: {0}")]
MappingError(#[from] MapError),
/// Provided object is too large.
#[error("Provided object is too large")]
TooLarge,
/// Operation not supported.
#[error("Operation not supported")]
NotSupported,
/// Address family not supported.
#[error("Address family not supported")]
AddressFamilyNotSupported,
/// Device probe failed.
#[error("Device probe failed: {0}")]
Probe(#[from] ProbeError),
/// I/O operation failed.
#[error("I/O operation failed: {0}")]
Io(#[from] IoError),
/// Filesystem operation failed.
#[error("Filesystem operation failed: {0}")]
Fs(#[from] FsError),
/// Exec error during executable loading.
#[error("Exec error: {0}")]
Exec(#[from] ExecError),
/// Not a tty.
#[error("Not a tty")]
NotATty,
/// Fault error during syscall.
#[error("Fault error during syscall")]
Fault,
/// Not an open file descriptor.
#[error("Not an open file descriptor")]
BadFd,
/// Cannot seek on a pipe.
#[error("Cannot seek on a pipe")]
SeekPipe,
/// Broken pipe.
#[error("Broken pipe")]
BrokenPipe,
/// Operation not permitted.
#[error("Operation not permitted")]
NotPermitted,
/// Buffer is full.
#[error("Buffer is full")]
BufferFull,
/// Operation would block.
#[error("Operation would block")]
TryAgain,
/// No such process.
#[error("No such process")]
NoProcess,
/// No child process.
#[error("No child process")]
NoChildProcess,
/// Operation timed out.
#[error("Operation timed out")]
TimedOut,
/// Value out of range.
#[error("Value out of range")]
RangeError,
/// Operation not supported on transport endpoint.
#[error("Operation not supported on transport endpoint")]
OpNotSupported,
/// Interrupted system call.
#[error("Interrupted system call")]
Interrupted,
/// Name too long.
#[error("Name too long")]
NameTooLong,
/// Not a socket.
#[error("Not a socket")]
NotASocket,
/// Other error with a static description.
#[error("{0}")]
Other(&'static str),
}
/// Convenience alias for a [`core::result::Result`] with [`KernelError`].
pub type Result<T> = core::result::Result<T, KernelError>;
impl From<Infallible> for KernelError {

View File

@@ -1,3 +1,8 @@
//! Maps [`KernelError`] variants to POSIX `errno` values
//! for returning results to user-space system calls.
#![allow(missing_docs)]
use crate::error::FsError;
use super::KernelError;

View File

@@ -1,3 +1,5 @@
//! File attribute types (permissions, modes, and metadata).
use crate::{
error::{KernelError, Result},
proc::{
@@ -7,10 +9,10 @@ use crate::{
};
use super::{FileType, InodeId};
use bitflags::bitflags;
use core::time::Duration;
bitflags::bitflags! {
/// POSIX access-mode flags for permission checks (`R_OK`, `W_OK`, `X_OK`).
#[derive(Debug, Clone, Copy)]
pub struct AccessMode: i32 {
/// Execution is permitted
@@ -22,58 +24,70 @@ bitflags::bitflags! {
}
}
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct FilePermissions: u16 {
const S_IXOTH = 0x0001;
const S_IWOTH = 0x0002;
const S_IROTH = 0x0004;
mod _file_permissions {
#![allow(missing_docs)]
use bitflags::bitflags;
bitflags! {
/// POSIX file permission bits (owner/group/other read/write/execute and setuid/setgid/sticky).
#[derive(Clone, Copy, Debug)]
pub struct FilePermissions: u16 {
const S_IXOTH = 0x0001;
const S_IWOTH = 0x0002;
const S_IROTH = 0x0004;
const S_IXGRP = 0x0008;
const S_IWGRP = 0x0010;
const S_IRGRP = 0x0020;
const S_IXGRP = 0x0008;
const S_IWGRP = 0x0010;
const S_IRGRP = 0x0020;
const S_IXUSR = 0x0040;
const S_IWUSR = 0x0080;
const S_IRUSR = 0x0100;
const S_IXUSR = 0x0040;
const S_IWUSR = 0x0080;
const S_IRUSR = 0x0100;
const S_ISVTX = 0x0200;
const S_ISVTX = 0x0200;
const S_ISGID = 0x0400;
const S_ISUID = 0x0800;
const S_ISGID = 0x0400;
const S_ISUID = 0x0800;
}
}
}
pub use _file_permissions::FilePermissions;
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct FileMode: u16 {
const S_IXOTH = 0x0001;
const S_IWOTH = 0x0002;
const S_IROTH = 0x0004;
mod _file_mode {
#![allow(missing_docs)]
use bitflags::bitflags;
bitflags! {
/// Combined file type and permission bits, as returned by `stat`.
#[derive(Clone, Copy, Debug)]
pub struct FileMode: u16 {
const S_IXOTH = 0x0001;
const S_IWOTH = 0x0002;
const S_IROTH = 0x0004;
const S_IXGRP = 0x0008;
const S_IWGRP = 0x0010;
const S_IRGRP = 0x0020;
const S_IXGRP = 0x0008;
const S_IWGRP = 0x0010;
const S_IRGRP = 0x0020;
const S_IXUSR = 0x0040;
const S_IWUSR = 0x0080;
const S_IRUSR = 0x0100;
const S_IXUSR = 0x0040;
const S_IWUSR = 0x0080;
const S_IRUSR = 0x0100;
const S_ISVTX = 0x0200;
const S_ISVTX = 0x0200;
const S_ISGID = 0x0400;
const S_ISUID = 0x0800;
const S_ISGID = 0x0400;
const S_ISUID = 0x0800;
// Mutually-exclusive file types:
const S_IFIFO = 0x1000;
const S_IFCHR = 0x2000;
const S_IFDIR = 0x4000;
const S_IFBLK = 0x6000;
const S_IFREG = 0x8000;
const S_IFLNK = 0xA000;
const S_IFSOCK = 0xC000;
// Mutually-exclusive file types:
const S_IFIFO = 0x1000;
const S_IFCHR = 0x2000;
const S_IFDIR = 0x4000;
const S_IFBLK = 0x6000;
const S_IFREG = 0x8000;
const S_IFLNK = 0xA000;
const S_IFSOCK = 0xC000;
}
}
}
pub use _file_mode::FileMode;
impl From<FileMode> for FilePermissions {
fn from(mode: FileMode) -> Self {
@@ -82,6 +96,7 @@ impl From<FileMode> for FilePermissions {
}
impl FileMode {
/// Constructs a `FileMode` from a file type and permission bits.
pub fn new(file_type: FileType, permissions: FilePermissions) -> Self {
let mut mode = FileMode::from_bits_truncate(permissions.bits());
mode |= match file_type {
@@ -98,6 +113,7 @@ impl FileMode {
}
/// Represents file metadata, similar to `stat`.
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub struct FileAttr {
pub id: InodeId,
@@ -116,6 +132,7 @@ pub struct FileAttr {
}
impl FileAttr {
/// Returns the combined file type and permission bits as a `FileMode`.
pub fn mode(&self) -> FileMode {
FileMode::new(self.file_type, self.permissions)
}

View File

@@ -1,3 +1,5 @@
//! Block I/O buffer management.
use core::{mem, slice};
use crate::{error::Result, fs::BlockDevice, pod::Pod};

View File

@@ -1,3 +1,5 @@
//! Block device layer.
pub mod buffer;
#[cfg(feature = "paging")]
pub mod ramdisk;

View File

@@ -1,3 +1,5 @@
//! RAM-backed block device implementation.
use crate::{
KernAddressSpace,
error::{IoError, KernelError, Result},
@@ -13,6 +15,7 @@ use alloc::boxed::Box;
use async_trait::async_trait;
use core::ptr;
/// A block device backed by a region of RAM.
pub struct RamdiskBlkDev {
base: TVA<u8>,
num_blocks: u64,

View File

@@ -110,6 +110,7 @@ impl From<Metadata> for FileAttr {
}
}
/// Wraps an ext4 directory iterator to produce VFS [`Dirent`] entries.
pub struct ReadDirWrapper {
inner: AsyncSkip<ReadDir>,
fs_id: u64,
@@ -117,6 +118,7 @@ pub struct ReadDirWrapper {
}
impl ReadDirWrapper {
/// Creates a new `ReadDirWrapper` starting at the given offset.
pub fn new(inner: ReadDir, fs_id: u64, start_offset: u64) -> Self {
Self {
inner: inner.skip(start_offset as usize),
@@ -187,6 +189,7 @@ impl DerefMut for InodeInner {
}
}
/// An inode within an ext4 filesystem.
pub struct Ext4Inode<CPU: CpuOps> {
fs_ref: Weak<Ext4Filesystem<CPU>>,
id: NonZeroU32,

View File

@@ -1,3 +1,5 @@
//! FAT32 filesystem driver.
use crate::{
error::{FsError, Result},
fs::{FileType, Filesystem, Inode, InodeId, attr::FileAttr, blk::buffer::BlockBuffer},
@@ -23,6 +25,7 @@ mod fat;
mod file;
mod reader;
/// A logical sector number on a FAT32 volume.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Sector(u32);
@@ -43,23 +46,28 @@ impl Add<Sector> for Sector {
}
impl Sector {
/// Returns an iterator over sectors from `self` (inclusive) to `other` (exclusive).
pub fn sectors_until(self, other: Self) -> impl Iterator<Item = Self> {
(self.0..other.0).map(Sector)
}
}
/// A FAT32 cluster number.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Cluster(u32);
impl Cluster {
/// Returns the raw cluster number as a `usize`.
pub fn value(self) -> usize {
self.0 as _
}
/// Constructs a cluster number from its high and low 16-bit halves.
pub fn from_high_low(clust_high: u16, clust_low: u16) -> Cluster {
Cluster((clust_high as u32) << 16 | clust_low as u32)
}
/// Returns `true` if this is a valid data cluster (number >= 2).
pub fn is_valid(self) -> bool {
self.0 >= 2
}
@@ -71,6 +79,7 @@ impl Display for Cluster {
}
}
/// A mounted FAT32 filesystem instance.
pub struct Fat32Filesystem {
dev: BlockBuffer,
bpb: BiosParameterBlock,
@@ -80,6 +89,7 @@ pub struct Fat32Filesystem {
}
impl Fat32Filesystem {
/// Creates a new FAT32 filesystem from the given block device buffer.
pub async fn new(dev: BlockBuffer, id: u64) -> Result<Arc<Self>> {
let bpb = BiosParameterBlock::new(&dev).await?;
let fat = Fat::read_fat(&dev, &bpb, 0).await?;

View File

@@ -1,3 +1,5 @@
//! Concrete filesystem implementations.
pub mod ext4;
pub mod fat32;
#[cfg(feature = "alloc")]

View File

@@ -1,3 +1,5 @@
//! In-memory temporary filesystem (tmpfs).
use crate::{
CpuOps,
error::{FsError, KernelError, Result},
@@ -783,6 +785,7 @@ impl<C: CpuOps> TmpFsSymlinkInode<C> {
}
}
/// An in-memory temporary filesystem backed by page allocations.
pub struct TmpFs<C, G, T>
where
C: CpuOps,
@@ -802,6 +805,7 @@ where
G: PageAllocGetter<C>,
T: AddressTranslator<()>,
{
/// Creates a new tmpfs instance with the given filesystem ID.
pub fn new(fs_id: u64) -> Arc<Self> {
Arc::new_cyclic(|weak_fs| {
let root =
@@ -817,6 +821,7 @@ where
})
}
/// Allocates the next unique inode ID for this filesystem.
pub fn alloc_inode_id(&self) -> u64 {
self.next_inode_id.fetch_add(1, Ordering::Relaxed)
}

View File

@@ -32,28 +32,38 @@ use async_trait::async_trait;
use attr::{FileAttr, FilePermissions};
use core::time::Duration;
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OpenFlags: u32 {
const O_RDONLY = 0b000;
const O_WRONLY = 0b001;
const O_RDWR = 0b010;
const O_ACCMODE = 0b011;
const O_CREAT = 0o100;
const O_EXCL = 0o200;
const O_TRUNC = 0o1000;
const O_DIRECTORY = 0o200000;
const O_APPEND = 0o2000;
const O_NONBLOCK = 0o4000;
const O_CLOEXEC = 0o2000000;
mod _open_flags {
#![allow(missing_docs)]
bitflags::bitflags! {
/// Flags used when opening a file, corresponding to POSIX `O_*` constants.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OpenFlags: u32 {
const O_RDONLY = 0b000;
const O_WRONLY = 0b001;
const O_RDWR = 0b010;
const O_ACCMODE = 0b011;
const O_CREAT = 0o100;
const O_EXCL = 0o200;
const O_TRUNC = 0o1000;
const O_DIRECTORY = 0o200000;
const O_APPEND = 0o2000;
const O_NONBLOCK = 0o4000;
const O_CLOEXEC = 0o2000000;
}
}
}
pub use _open_flags::OpenFlags;
// Reserved psuedo filesystem instances created internally in the kernel.
// Reserved pseudo filesystem instances created internally in the kernel.
/// Filesystem instance ID for the device filesystem.
pub const DEVFS_ID: u64 = 1;
/// Filesystem instance ID for the proc filesystem.
pub const PROCFS_ID: u64 = 2;
/// Filesystem instance ID for the sys filesystem.
pub const SYSFS_ID: u64 = 3;
/// Filesystem instance ID for the cgroup filesystem.
pub const CGROUPFS_ID: u64 = 4;
/// Starting ID for user-mounted filesystem instances.
pub const FS_ID_START: u64 = 10;
/// Trait for a mounted filesystem instance. Its main role is to act as a
@@ -78,30 +88,34 @@ pub trait Filesystem: Send + Sync {
}
}
// A unique identifier for an inode across the entire VFS. A tuple of
// (filesystem_id, inode_number).
/// A unique identifier for an inode across the entire VFS, combining a filesystem ID and inode number.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct InodeId(u64, u64);
impl InodeId {
/// Creates an `InodeId` from a filesystem ID and an inode number.
pub fn from_fsid_and_inodeid(fs_id: u64, inode_id: u64) -> Self {
Self(fs_id, inode_id)
}
/// Returns a sentinel `InodeId` used as a placeholder.
pub fn dummy() -> Self {
Self(u64::MAX, u64::MAX)
}
/// Returns the filesystem ID component.
pub fn fs_id(self) -> u64 {
self.0
}
/// Returns the inode number component.
pub fn inode_id(self) -> u64 {
self.1
}
}
/// Standard POSIX file types.
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FileType {
File,
@@ -138,13 +152,18 @@ pub trait DirStream: Send + Sync {
/// Represents a single directory entry.
#[derive(Debug, Clone)]
pub struct Dirent {
/// The inode identifier of this entry.
pub id: InodeId,
/// The name of this directory entry.
pub name: String,
/// The type of file this entry represents.
pub file_type: FileType,
/// The byte offset of this entry within the directory.
pub offset: u64,
}
impl Dirent {
/// Creates a new directory entry.
pub fn new(name: String, id: InodeId, file_type: FileType, offset: u64) -> Self {
Self {
id,
@@ -158,8 +177,11 @@ impl Dirent {
/// Specifies how to seek within a file, mirroring `std::io::SeekFrom`.
#[derive(Debug, Copy, Clone)]
pub enum SeekFrom {
/// Seek from the beginning of the file.
Start(u64),
/// Seek from the end of the file.
End(i64),
/// Seek relative to the current position.
Current(i64),
}
@@ -329,6 +351,7 @@ pub trait Inode: Send + Sync + Any {
Ok(())
}
/// Return this inode as an `Any` object, suitable for downcasting.
fn as_any(&self) -> &dyn Any;
}
@@ -336,14 +359,19 @@ pub trait Inode: Send + Sync + Any {
/// for common inode operations.
#[async_trait]
pub trait SimpleFile {
/// Returns the inode ID of this file.
fn id(&self) -> InodeId;
/// Returns the file metadata.
async fn getattr(&self) -> Result<FileAttr>;
/// Reads the entire file contents into a byte vector.
async fn read(&self) -> Result<Vec<u8>>;
/// Reads the target of a symbolic link, if applicable.
async fn readlink(&self) -> Result<PathBuf> {
Err(KernelError::NotSupported)
}
}
#[allow(missing_docs)]
#[async_trait]
impl<T> Inode for T
where
@@ -385,12 +413,14 @@ where
}
}
/// A simple in-memory directory stream backed by a `Vec` of entries.
pub struct SimpleDirStream {
entries: Vec<Dirent>,
idx: usize,
}
impl SimpleDirStream {
/// Creates a new `SimpleDirStream` starting at the given offset.
pub fn new(entries: Vec<Dirent>, start_offset: u64) -> Self {
Self {
entries,

View File

@@ -1,10 +1,59 @@
//! # libkernel
//!
//! Architecture-independent kernel building blocks for operating systems.
//!
//!
//! `libkernel` provides the core abstractions that a kernel needs to manage
//! memory, processes, filesystems, and synchronisation — all without tying the
//! implementation to a particular CPU architecture. It is designed to run in a
//! `no_std` environment and relies on feature gates to keep the dependency
//! footprint minimal.
//!
//! ## Feature gates
//!
//! Most of the crate is hidden behind Cargo features so that consumers only pay
//! for the subsystems they need:
//!
//! | Feature | Enables | Implies |
//! |-----------|-------------------------------------------------------|------------------|
//! | `sync` | Synchronisation primitives (spinlock, mutex, rwlock…) | — |
//! | `alloc` | Memory allocators (buddy, slab) and collection types | `sync` |
//! | `paging` | Page tables, address-space management, PTE helpers | `alloc` |
//! | `proc` | Process identity types (UID/GID, capabilities) | — |
//! | `fs` | VFS traits, path manipulation, block I/O | `proc`, `sync` |
//! | `proc_vm` | Process virtual-memory management (mmap, brk, CoW) | `paging`, `fs` |
//! | `kbuf` | Async-aware circular kernel buffers | `sync` |
//! | `all` | Everything above | all of the above |
//!
//! ## The `CpuOps` trait
//!
//! Nearly every synchronisation and memory primitive in the crate is generic
//! over a [`CpuOps`] implementation. This trait abstracts the handful of
//! arch-specific operations (core ID, interrupt masking, halt) that the
//! arch-independent code depends on, making the library portable while still
//! fully testable on the host.
//!
//! ## Crate layout
//!
//! - [`error`] — Unified kernel error types and POSIX errno mapping.
//! - [`memory`] — Typed addresses, memory regions, page allocators, and
//! address-space management.
//! - [`sync`] — Async-aware spinlocks, mutexes, rwlocks, condvars, channels,
//! and per-CPU storage *(feature `sync`)*.
//! - [`fs`] — VFS traits (`Filesystem`, `Inode`, `BlockDevice`), path
//! manipulation, and filesystem driver scaffolding *(feature `fs`)*.
//! - [`proc`] — Process identity types and Linux-compatible capabilities
//! *(feature `proc`)*.
//! - [`arch`] — Architecture-specific support code *(feature `paging`)*.
#![cfg_attr(not(test), no_std)]
#![warn(missing_docs)]
#[cfg(feature = "paging")]
pub mod arch;
pub mod error;
#[cfg(feature = "fs")]
pub mod driver;
pub mod error;
#[cfg(feature = "fs")]
pub mod fs;
pub mod memory;
@@ -18,10 +67,32 @@ pub mod sync;
extern crate alloc;
#[cfg(feature = "paging")]
pub use memory::address_space::{
KernAddressSpace, PageInfo, UserAddressSpace, VirtualMemory,
};
pub use memory::address_space::{KernAddressSpace, PageInfo, UserAddressSpace, VirtualMemory};
/// Trait abstracting the small set of CPU operations that the
/// architecture-independent kernel code requires.
///
/// Every concrete kernel target must provide an implementation of this trait.
/// The synchronisation primitives in [`sync`] and the memory subsystem in
/// [`memory`] are generic over `CpuOps`, which keeps this crate portable while
/// allowing the real kernel — and unit tests — to supply their own
/// implementations.
///
/// # Example (test mock)
///
/// ```
/// use libkernel::CpuOps;
///
/// struct MockCpu;
///
/// impl CpuOps for MockCpu {
/// fn id() -> usize { 0 }
/// fn halt() -> ! { loop { core::hint::spin_loop() } }
/// fn disable_interrupts() -> usize { 0 }
/// fn restore_interrupt_state(_flags: usize) {}
/// fn enable_interrupts() {}
/// }
/// ```
pub trait CpuOps: 'static {
/// Returns the ID of the currently executing core.
fn id() -> usize;
@@ -41,6 +112,7 @@ pub trait CpuOps: 'static {
}
#[cfg(test)]
#[allow(missing_docs)]
pub mod test {
use core::hint::spin_loop;

View File

@@ -1,4 +1,4 @@
//! `address` module: Type-safe handling of virtual and physical addresses.
//! Type-safe handling of virtual and physical addresses.
//!
//! This module defines strongly-typed address representations for both physical
//! and virtual memory. It provides abstractions to ensure correct usage and
@@ -147,20 +147,24 @@ impl<K: MemKind, T> Address<K, T> {
self.inner & PAGE_MASK
}
/// Returns the null (zero) address.
pub const fn null() -> Self {
Self::from_value(0)
}
/// Returns a new address offset forward by `n` bytes.
#[must_use]
pub const fn add_bytes(self, n: usize) -> Self {
Self::from_value(self.value() + n)
}
/// Returns a new address offset backward by `n` bytes.
#[must_use]
pub fn sub_bytes(self, n: usize) -> Self {
Self::from_value(self.value() - n)
}
/// Returns `true` if this is the null (zero) address.
pub fn is_null(self) -> bool {
self.inner == 0
}
@@ -291,6 +295,7 @@ impl PA {
TPA::from_value(self.value())
}
/// Converts this physical address to a page frame number.
pub fn to_pfn(&self) -> PageFrame {
PageFrame::from_pfn(self.inner >> PAGE_SHIFT)
}
@@ -298,7 +303,9 @@ impl PA {
/// Trait for translating between physical and virtual addresses.
pub trait AddressTranslator<T>: 'static + Send + Sync {
/// Translates a virtual address to its corresponding physical address.
fn virt_to_phys(va: TVA<T>) -> TPA<T>;
/// Translates a physical address to its corresponding virtual address.
fn phys_to_virt(pa: TPA<T>) -> TVA<T>;
}

View File

@@ -1,3 +1,5 @@
//! Kernel and user address-space management.
use alloc::vec::Vec;
use crate::{
@@ -14,7 +16,9 @@ use crate::{
/// An architecture-independent representation of a page table entry (PTE).
pub struct PageInfo {
/// The page frame number identifying the physical page.
pub pfn: PageFrame,
/// The permission bits for this page table entry.
pub perms: PtePermissions,
}

View File

@@ -1,3 +1,5 @@
//! Memory allocators.
mod frame;
pub mod phys;
pub mod slab;

View File

@@ -1,3 +1,5 @@
//! Physical page-frame allocator (buddy allocator).
use crate::{
CpuOps,
error::{KernelError, Result},
@@ -155,22 +157,28 @@ impl FrameAllocatorInner {
}
}
/// Thread-safe wrapper around the buddy frame allocator.
pub struct FrameAllocator<CPU: CpuOps> {
pub(super) inner: SpinLockIrq<FrameAllocatorInner, CPU>,
}
/// An RAII guard for a contiguous allocation of physical page frames.
///
/// When dropped, the pages are automatically returned to the allocator.
pub struct PageAllocation<'a, CPU: CpuOps> {
region: PhysMemoryRegion,
inner: &'a SpinLockIrq<FrameAllocatorInner, CPU>,
}
impl<CPU: CpuOps> PageAllocation<'_, CPU> {
/// Consumes the allocation without freeing it, returning the underlying region.
pub fn leak(self) -> PhysMemoryRegion {
let region = self.region;
core::mem::forget(self);
region
}
/// Returns a reference to the physical memory region backing this allocation.
pub fn region(&self) -> &PhysMemoryRegion {
&self.region
}
@@ -434,11 +442,14 @@ impl<CPU: CpuOps> FrameAllocator<CPU> {
}
}
/// Provides access to the global page-frame allocator.
pub trait PageAllocGetter<C: CpuOps>: Send + Sync + 'static {
/// Returns a reference to the global [`FrameAllocator`].
fn global_page_alloc() -> &'static FrameAllocator<C>;
}
#[cfg(test)]
#[allow(missing_docs)]
pub mod tests {
use super::*;
use crate::{

View File

@@ -1,4 +1,4 @@
/// A slab allocator for Moss.
//! A slab memory allocator.
use super::{
SLAB_FRAME_ALLOC_ORDER, SLAB_MAX_OBJ_SHIFT, alloc_order,
slab::{Slab, SlabState},
@@ -36,18 +36,18 @@ const MAX_FREE_SLABS: usize = 32;
///
/// There is no 'full' list. Full slabs are unlinked from both the 'partial' and
/// 'free' lists. They are allowed to float "in the ether" (referenced only by
/// the global [FrameList]). When freeing an object from a 'full' slab, the
/// the global `FrameList`). When freeing an object from a 'full' slab, the
/// allocator detects the state transition and re-links the frame into the
/// 'partial'/'free' list.
///
/// # Safety and Ownership
///
/// The FA and the `SlabManager` share a list of frame metadata via [FrameList]. To
/// The FA and the `SlabManager` share a list of frame metadata via `FrameList`. To
/// share this list safely, we implement an implicit ownership model:
///
/// 1. When the FA allocates a frame, it initializes the metadata.
/// 2. We convert this frame into a slab allocation via
/// [PageAllocation::as_slab].
/// `PageAllocation::as_slab`.
/// 3. Once that function returns, this `SlabManager` is considered the
/// exclusive owner of that frame's metadata.
///
@@ -55,7 +55,7 @@ const MAX_FREE_SLABS: usize = 32;
/// the metadata of any frame in its possession, as the `SpinLock` protecting
/// this struct guarantees exclusive access to the specific size class that
/// "owns" the frame. Ownership is eventually returned to the FA via
/// [FrameAllocatorInner::free_slab].
/// `FrameAllocatorInner::free_slab`.
pub struct SlabManager<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> {
pub(super) free: LinkedList<FrameAdapter>,
pub(super) partial: LinkedList<FrameAdapter>,
@@ -253,6 +253,7 @@ impl<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> SlabManager
}
}
/// The top-level slab allocator, dispatching allocations to size-appropriate [`SlabManager`]s.
pub struct SlabAllocator<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> {
pub(super) managers:
[SpinLockIrq<SlabManager<CPU, A, T>, CPU>; SLAB_MAX_OBJ_SHIFT as usize + 1],
@@ -268,6 +269,7 @@ unsafe impl<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> Sync
}
impl<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> SlabAllocator<CPU, A, T> {
/// Creates a new slab allocator backed by the given frame list.
pub fn new(frame_list: FrameList) -> Self {
Self {
managers: core::array::from_fn(|n| {
@@ -276,6 +278,7 @@ impl<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> SlabAllocat
}
}
/// Returns a reference to the slab manager responsible for the given layout, if one exists.
pub fn allocator_for_layout(
&self,
layout: core::alloc::Layout,

View File

@@ -1,3 +1,5 @@
//! Per-object-size slab cache management.
use super::{
alloc_order,
allocator::{SlabAllocator, SlabManager},
@@ -20,6 +22,7 @@ const NUM_PTR_CACHES: usize = SLAB_MAX_OBJ_SHIFT as usize + 1;
// Ensure that our cache fits in a single page.
const _: () = assert!(core::mem::size_of::<SlabCache>() <= PAGE_SIZE);
/// A fixed-size cache of recently freed pointers for a single size class.
#[repr(C)]
pub struct PtrCache {
next_free: usize,
@@ -27,6 +30,7 @@ pub struct PtrCache {
}
impl PtrCache {
/// Creates an empty pointer cache.
pub fn new() -> Self {
Self {
next_free: 0,
@@ -34,10 +38,12 @@ impl PtrCache {
}
}
/// Returns `true` if no pointers are cached.
pub fn is_empty(&self) -> bool {
self.next_free == 0
}
/// Returns `true` if the cache has no remaining capacity.
pub fn is_full(&self) -> bool {
self.next_free == PTRS_PER_SZ_CLASS
}
@@ -66,6 +72,7 @@ impl PtrCache {
}
}
/// Returns a cached pointer, or `None` if the cache is empty.
pub fn alloc(&mut self) -> Option<*mut u8> {
if self.is_empty() {
return None;

View File

@@ -1,3 +1,5 @@
//! Kernel heap built on top of the slab allocator.
use super::{allocator::SlabAllocator, cache::SlabCache};
use crate::{
CpuOps,
@@ -11,15 +13,21 @@ use crate::{
};
use core::{alloc::GlobalAlloc, marker::PhantomData, ops::DerefMut};
/// Provides access to the global slab allocator instance.
pub trait SlabGetter<CPU: CpuOps, A: PageAllocGetter<CPU>, T: AddressTranslator<()>> {
/// Returns a reference to the global slab allocator.
fn global_slab_alloc() -> &'static SlabAllocator<CPU, A, T>;
}
/// Per-CPU storage backend for a [`SlabCache`] pointer.
pub trait SlabCacheStorage {
/// Stores the slab cache pointer (e.g. into per-CPU data).
fn store(ptr: *mut SlabCache);
/// Retrieves the slab cache for the current CPU.
fn get() -> impl DerefMut<Target = SlabCache>;
}
/// The kernel heap allocator backed by the slab allocator and frame allocator.
pub struct KHeap<CPU, S, PG, T, SG>
where
CPU: CpuOps,
@@ -56,6 +64,7 @@ where
T: AddressTranslator<()>,
SG: SlabGetter<CPU, PG, T>,
{
/// Creates a new kernel heap instance.
pub const fn new() -> Self {
Self {
phantom1: PhantomData,
@@ -74,6 +83,7 @@ where
pages_needed.next_power_of_two().ilog2() as usize
}
/// Initializes the per-CPU slab cache for the current CPU.
pub fn init_for_this_cpu() {
let page: ClaimedPage<CPU, PG, T> =
ClaimedPage::alloc_zeroed().expect("Cannot allocate heap page");

View File

@@ -1,3 +1,5 @@
//! Slab allocator for small, fixed-size kernel objects.
use crate::memory::PAGE_SIZE;
// Allocations of order 2 (4 pages) from the FA for slabs.

View File

@@ -1,5 +1,4 @@
//! `smalloc` module: A simple physical memory allocator for early boot and
//! kernel use.
//! A simple physical memory allocator for early boot and kernel use.
//!
//! This allocator manages a fixed number of memory and reservation regions and
//! supports basic allocation and freeing of physical memory blocks.
@@ -34,6 +33,7 @@ use core::{
ops::{Index, IndexMut},
};
/// A fixed-capacity list of non-overlapping physical memory regions.
#[derive(Clone)]
pub struct RegionList {
count: usize,
@@ -42,10 +42,12 @@ pub struct RegionList {
}
impl RegionList {
/// Returns `true` if the list contains no regions.
pub fn is_empty(&self) -> bool {
self.count == 0
}
/// Creates a new region list backed by a pre-allocated buffer at `region_ptr`.
pub const fn new(max: usize, region_ptr: *mut PhysMemoryRegion) -> Self {
Self {
count: 0,
@@ -68,6 +70,7 @@ impl RegionList {
self.count -= 1;
}
/// Inserts a region, merging with adjacent neighbours when possible.
pub fn insert_region(&mut self, mut new_region: PhysMemoryRegion) {
if self.count == self.max {
panic!("Cannot insert into full region list");
@@ -108,6 +111,7 @@ impl RegionList {
self[insert_idx] = new_region;
}
/// Returns an iterator over the regions in this list.
pub fn iter(&self) -> impl Iterator<Item = PhysMemoryRegion> {
(0..self.count).map(|x| self[x])
}
@@ -156,8 +160,11 @@ impl IndexMut<usize> for RegionList {
}
}
/// A simple physical memory allocator that tracks free and reserved regions.
pub struct Smalloc<T: AddressTranslator<()>> {
/// Available memory regions.
pub memory: RegionList,
/// Reserved memory regions (e.g. ACPI tables, device memory).
pub res: RegionList,
permit_region_realloc: bool,
_phantom: PhantomData<T>,
@@ -171,6 +178,7 @@ enum RegionListType {
unsafe impl<T: AddressTranslator<()>> Send for Smalloc<T> {}
impl<T: AddressTranslator<()>> Smalloc<T> {
/// Creates a new allocator with the given memory and reservation lists.
pub const fn new(memory: RegionList, reserved_list: RegionList) -> Self {
Self {
memory,
@@ -230,6 +238,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
None
}
/// Allocates a region of the given `size` and alignment. Returns the base physical address.
pub fn alloc(&mut self, size: usize, align: usize) -> Result<PA> {
if self.res.requires_reallocation() {
self.grow_region_list(RegionListType::Res)?;
@@ -245,6 +254,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
Ok(address)
}
/// Frees a previously allocated region at `addr` with the given `size`.
pub fn free(&mut self, addr: PA, size: usize) -> Result<()> {
let region_to_remove = PhysMemoryRegion::new(addr, size);
@@ -344,6 +354,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
}
}
/// Marks a physical region as reserved without allocating from free memory.
pub fn add_reservation(&mut self, region: PhysMemoryRegion) -> Result<()> {
if self.res.requires_reallocation() {
self.grow_region_list(RegionListType::Res)?;
@@ -354,6 +365,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
Ok(())
}
/// Returns the base address of the first memory region, if any.
pub fn base_ram_base_address(&self) -> Option<PA> {
if self.memory.is_empty() {
None
@@ -362,6 +374,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
}
}
/// Adds a new physical memory region to the allocator.
pub fn add_memory(&mut self, region: PhysMemoryRegion) -> Result<()> {
if self.memory.requires_reallocation() {
self.grow_region_list(RegionListType::Mem)?;
@@ -372,16 +385,19 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
Ok(())
}
/// Allocates a single page-aligned page and returns its frame number.
pub fn alloc_page(&mut self) -> Result<PageFrame> {
let pa = self.alloc(PAGE_SIZE, PAGE_SIZE)?;
Ok(PageFrame::from_pfn(pa.value() >> PAGE_SHIFT))
}
/// Returns an iterator over all memory regions known to the allocator.
pub fn iter_memory(&self) -> impl Iterator<Item = PhysMemoryRegion> {
self.memory.iter()
}
/// Returns a copy of the memory region list.
pub fn get_memory_list(&self) -> RegionList {
self.memory.clone()
}
@@ -399,6 +415,7 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
}
}
/// Iterator over free (unreserved) physical memory regions.
pub struct FreeRegionsIter<M, R>
where
M: Iterator<Item = PhysMemoryRegion>,

View File

@@ -1,3 +1,5 @@
//! RAII wrapper for a page frame claimed from the physical page allocator.
use super::{
PAGE_SIZE,
address::AddressTranslator,
@@ -55,6 +57,7 @@ impl<A: CpuOps, G: PageAllocGetter<A>, T: AddressTranslator<()>> ClaimedPage<A,
)
}
/// Returns the physical address of this claimed page.
#[inline(always)]
pub fn pa(&self) -> PA {
self.0.region().start_address()
@@ -98,6 +101,7 @@ impl<A: CpuOps, G: PageAllocGetter<A>, T: AddressTranslator<()>> ClaimedPage<A,
unsafe { slice::from_raw_parts_mut(self.as_ptr_mut(), PAGE_SIZE) }
}
/// Consumes the claimed page, returning the underlying page frame without freeing it.
pub fn leak(self) -> PageFrame {
self.0.leak().start_address().to_pfn()
}

View File

@@ -22,6 +22,7 @@ struct KBufInner<T, S: Storage<Item = T>> {
write_waiters: WakerSet,
}
/// A page-backed, async-aware circular kernel buffer.
pub struct KBufCore<T, S: Storage<Item = T>, C: CpuOps> {
inner: Arc<SpinLockIrq<KBufInner<T, S>, C>>,
}
@@ -35,6 +36,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> Clone for KBufCore<T, S, C> {
}
impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
/// Creates a new kernel buffer backed by the given storage.
pub fn new(storage: S) -> Self {
let rb = unsafe { SharedRb::from_raw_parts(storage, 0, 0) };
@@ -47,6 +49,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
}
}
/// Returns a future that resolves when data is available for reading.
pub fn read_ready(&self) -> impl Future<Output = ()> + use<T, S, C> {
let lock = self.inner.clone();
@@ -59,6 +62,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
)
}
/// Waits until at least one slot is available for writing.
pub async fn write_ready(&self) {
wait_until(
self.inner.clone(),
@@ -81,6 +85,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
}
}
/// Attempts to push `obj` into the buffer without waiting. Returns `Err(obj)` if full.
pub fn try_push(&self, obj: T) -> core::result::Result<(), T> {
let mut inner = self.inner.lock_save_irq();
@@ -93,6 +98,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
res
}
/// Pops a value from the buffer, waiting asynchronously if it is empty.
pub async fn pop(&self) -> T {
loop {
self.read_ready().await;
@@ -103,6 +109,7 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
}
}
/// Attempts to pop a value without blocking, returning `None` if the buffer is empty.
pub fn try_pop(&self) -> Option<T> {
let mut inner = self.inner.lock_save_irq();
@@ -115,12 +122,14 @@ impl<T, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
res
}
/// Returns the capacity of the underlying ring buffer.
pub fn capacity(&self) -> NonZeroUsize {
self.inner.lock_save_irq().buf.capacity()
}
}
impl<T: Copy, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
/// Asynchronously pops up to `buf.len()` items into `buf`, blocking until at least one is available.
pub async fn pop_slice(&self, buf: &mut [T]) -> usize {
wait_until(
self.inner.clone(),
@@ -141,6 +150,7 @@ impl<T: Copy, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
.await
}
/// Attempts to pop up to `buf.len()` items without blocking.
pub fn try_pop_slice(&self, buf: &mut [T]) -> usize {
let mut guard = self.inner.lock_save_irq();
let size = guard.buf.pop_slice(buf);
@@ -150,6 +160,7 @@ impl<T: Copy, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
size
}
/// Asynchronously pushes items from `buf`, blocking until space is available.
pub async fn push_slice(&self, buf: &[T]) -> usize {
wait_until(
self.inner.clone(),
@@ -176,6 +187,7 @@ impl<T: Copy, S: Storage<Item = T>, C: CpuOps> KBufCore<T, S, C> {
.await
}
/// Attempts to push items from `buf` without blocking.
pub fn try_push_slice(&self, buf: &[T]) -> usize {
let mut guard = self.inner.lock_save_irq();
let size = guard.buf.push_slice(buf);

View File

@@ -1,3 +1,13 @@
//! Memory management primitives.
//!
//! This module contains the core building blocks for kernel memory management:
//! typed addresses, memory regions, page frame tracking, page allocators,
//! address-space abstractions, and kernel buffers.
//!
//! The always-available submodules ([`address`], [`page`], [`region`]) require
//! no features. Higher-level subsystems are gated behind their respective
//! feature flags.
pub mod address;
#[cfg(feature = "paging")]
pub mod address_space;
@@ -16,6 +26,9 @@ pub mod pg_offset;
pub mod proc_vm;
pub mod region;
/// The system page size in bytes (4 KiB).
pub const PAGE_SIZE: usize = 4096;
/// The number of bits to shift to convert between byte offsets and page numbers.
pub const PAGE_SHIFT: usize = PAGE_SIZE.trailing_zeros() as usize;
/// Bitmask for extracting the within-page offset from an address.
pub const PAGE_MASK: usize = PAGE_SIZE - 1;

View File

@@ -1,11 +1,14 @@
use super::{
PAGE_SHIFT,
address::PA,
region::PhysMemoryRegion,
};
//! Page frame numbers.
//!
//! A [`PageFrame`] is a lightweight handle for a physical page, identified by
//! its page frame number (PFN).
use super::{PAGE_SHIFT, address::PA, region::PhysMemoryRegion};
use crate::memory::PAGE_SIZE;
use core::fmt::Display;
/// A page frame number (PFN) — an index into physical memory in units of
/// [`PAGE_SIZE`].
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct PageFrame {
n: usize,
@@ -18,18 +21,22 @@ impl Display for PageFrame {
}
impl PageFrame {
/// Creates a `PageFrame` from a raw page frame number.
pub fn from_pfn(n: usize) -> Self {
Self { n }
}
/// Returns the physical address of the start of this page frame.
pub fn pa(&self) -> PA {
PA::from_value(self.n << PAGE_SHIFT)
}
/// Returns this page frame as a single-page physical memory region.
pub fn as_phys_range(&self) -> PhysMemoryRegion {
PhysMemoryRegion::new(self.pa(), PAGE_SIZE)
}
/// Returns the raw page frame number.
pub fn value(&self) -> usize {
self.n
}
@@ -41,6 +48,7 @@ impl PageFrame {
}
}
/// Returns a new `PageFrame` offset by `n` pages.
#[must_use]
pub fn add_pages(self, n: usize) -> Self {
Self { n: self.n + n }

View File

@@ -1,3 +1,5 @@
//! Page-table entry permission flags.
use core::fmt;
#[cfg(feature = "proc_vm")]

View File

@@ -1,7 +1,10 @@
//! Page-offset arithmetic helpers.
use super::address::{AddressTranslator, TPA, TVA};
use crate::VirtualMemory;
use core::marker::PhantomData;
/// Translates between physical and virtual addresses using a fixed page-offset mapping.
pub struct PageOffsetTranslator<VM: VirtualMemory> {
_phantom: PhantomData<VM>,
}

View File

@@ -1,3 +1,5 @@
//! Memory map management for a process address space.
use super::vmarea::{VMAPermissions, VMArea, VMAreaKind};
use crate::{
UserAddressSpace,
@@ -17,11 +19,20 @@ pub struct MemoryMap<AS: UserAddressSpace> {
address_space: AS,
}
/// Specifies how the kernel should choose the virtual address for a mapping.
#[derive(Debug, PartialEq, Eq)]
pub enum AddressRequest {
/// Let the kernel pick any suitable address.
Any,
/// Prefer the given address but fall back to any free region.
Hint(VA),
Fixed { address: VA, permit_overlap: bool },
/// Map at exactly the given address.
Fixed {
/// The exact virtual address to map at.
address: VA,
/// If `true`, existing mappings in the range may be replaced.
permit_overlap: bool,
},
}
impl<AS: UserAddressSpace> MemoryMap<AS> {
@@ -168,6 +179,7 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
self.unmap_region(range.align_to_page_boundary(), None)
}
/// Changes the memory protection flags for a page-aligned region.
pub fn mprotect(
&mut self,
protect_region: VirtMemoryRegion,
@@ -498,18 +510,22 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
})
}
/// Returns a mutable reference to the underlying address space.
pub fn address_space_mut(&mut self) -> &mut AS {
&mut self.address_space
}
/// Returns the number of VMAs in this memory map.
pub fn vma_count(&self) -> usize {
self.vmas.len()
}
/// Returns an iterator over all VMAs in address order.
pub fn iter_vmas(&self) -> impl Iterator<Item = &VMArea> {
self.vmas.values()
}
}
#[cfg(test)]
#[allow(missing_docs)]
pub mod tests;

View File

@@ -15,6 +15,7 @@ pub mod vmarea;
const BRK_PERMISSIONS: VMAPermissions = VMAPermissions::rw();
/// The virtual memory state of a user-space process.
pub struct ProcessVM<AS: UserAddressSpace> {
mm: MemoryMap<AS>,
brk: VirtMemoryRegion,
@@ -49,6 +50,7 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
Ok(Self { mm, brk })
}
/// Constructs a `ProcessVM` from an existing memory map.
pub fn from_map(map: MemoryMap<AS>) -> Self {
// Last entry will be the VMA with the highest address.
let brk = map
@@ -67,6 +69,7 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
}
}
/// Creates an empty `ProcessVM` with no mappings and a zero-sized heap.
pub fn empty() -> Result<Self> {
Ok(Self {
mm: MemoryMap::new()?,
@@ -74,6 +77,7 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
})
}
/// Finds the VMA covering `addr` if the given access type is permitted.
pub fn find_vma_for_fault(&self, addr: VA, access_type: AccessKind) -> Option<&VMArea> {
let vma = self.mm.find_vma(addr)?;
@@ -84,18 +88,22 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
}
}
/// Returns a non-mutable reference to the underlying memory map.
pub fn mm(&self) -> &MemoryMap<AS> {
&self.mm
}
/// Returns a mutable reference to the underlying memory map.
pub fn mm_mut(&mut self) -> &mut MemoryMap<AS> {
&mut self.mm
}
/// Returns the current start address of the program break (heap).
pub fn start_brk(&self) -> VA {
self.brk.start_address()
}
/// Returns the current end address of the program break (heap).
pub fn current_brk(&self) -> VA {
self.brk.end_address()
}
@@ -165,6 +173,7 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
Ok(new_end_addr)
}
/// Clones this process VM, marking all writable pages as copy-on-write.
pub fn clone_as_cow(&mut self) -> Result<Self> {
Ok(Self {
mm: self.mm.clone_as_cow()?,

View File

@@ -26,12 +26,16 @@ use object::{
/// Describes the permissions assigned for this VMA.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VMAPermissions {
/// Whether reads are allowed.
pub read: bool,
/// Whether writes are allowed.
pub write: bool,
/// Whether execution is allowed.
pub execute: bool,
}
impl VMAPermissions {
/// Read-write permissions.
pub const fn rw() -> Self {
Self {
read: true,
@@ -40,6 +44,7 @@ impl VMAPermissions {
}
}
/// Read-execute permissions.
pub const fn rx() -> Self {
Self {
read: true,
@@ -48,6 +53,7 @@ impl VMAPermissions {
}
}
/// Read-only permissions.
pub const fn ro() -> Self {
Self {
read: true,
@@ -156,10 +162,12 @@ pub enum VMAreaKind {
}
impl VMAreaKind {
/// Creates a new anonymous VMA kind.
pub fn new_anon() -> Self {
Self::Anon
}
/// Creates a new file-backed VMA kind.
pub fn new_file(file: Arc<dyn Inode>, offset: u64, len: u64) -> Self {
Self::File(VMFileMapping { file, offset, len })
}
@@ -173,6 +181,7 @@ impl VMAreaKind {
/// managing a process's memory layout.
#[derive(Clone, PartialEq)]
pub struct VMArea {
/// The virtual address range of this VMA.
pub region: VirtMemoryRegion,
pub(super) name: String,
pub(super) kind: VMAreaKind,
@@ -195,6 +204,7 @@ impl VMArea {
}
}
/// Sets a human-readable name for this VMA (e.g. `[stack]`, `[heap]`).
pub fn set_name<S: AsRef<str>>(&mut self, s: S) {
self.name = s.as_ref().to_string();
}
@@ -266,9 +276,9 @@ impl VMArea {
///
/// # Returns
///
/// - [`AccessValidation::Valid`]: If the address and permissions are valid.
/// - [`AccessValidation::NotPresent`]: If the address is outside this VMA.
/// - [`AccessValidation::PermissionDenied`]: If the address is inside this
/// - [`FaultValidation::Valid`]: If the address and permissions are valid.
/// - [`FaultValidation::NotPresent`]: If the address is outside this VMA.
/// - [`FaultValidation::PermissionDenied`]: If the address is inside this
/// VMA but the access is not allowed. This allows the caller to immediately
/// identify a segmentation fault without checking other VMAs.
pub fn validate_fault(&self, addr: VA, kind: AccessKind) -> FaultValidation {
@@ -371,10 +381,12 @@ impl VMArea {
})
}
/// Returns the memory permissions for this VMA.
pub fn permissions(&self) -> VMAPermissions {
self.permissions
}
/// Returns `true` if the given virtual address falls within this VMA.
pub fn contains_address(&self, addr: VA) -> bool {
self.region.contains_address(addr)
}
@@ -464,6 +476,7 @@ impl VMArea {
self.region
}
/// Returns the file offset for a file-backed VMA, or `None` for anonymous mappings.
pub fn file_offset(&self) -> Option<u64> {
match self.kind {
VMAreaKind::File(ref vmfile_mapping) => Some(vmfile_mapping.offset()),
@@ -471,6 +484,7 @@ impl VMArea {
}
}
/// Returns the inode ID of the backing file, or `None` for anonymous mappings.
pub fn inode_id(&self) -> Option<InodeId> {
match self.kind {
VMAreaKind::File(ref vmfile_mapping) => Some(vmfile_mapping.file().id()),
@@ -478,12 +492,14 @@ impl VMArea {
}
}
/// Returns the human-readable name of this VMA.
pub fn name(&self) -> &str {
&self.name
}
}
#[cfg(test)]
#[allow(missing_docs)]
pub mod tests {
use crate::fs::InodeId;
use core::any::Any;

View File

@@ -1,4 +1,4 @@
//! `region` module: Contiguous memory regions.
//! Contiguous memory regions.
//!
//! This module defines `MemoryRegion<T>`, a generic abstraction for handling
//! ranges of memory in both physical and virtual address spaces.
@@ -382,6 +382,7 @@ impl PhysMemoryRegion {
VirtMemoryRegion::new(self.address.to_va::<T>(), self.size)
}
/// Returns an iterator over the page frame numbers in this region.
pub fn iter_pfns(self) -> impl Iterator<Item = PageFrame> {
let mut count = 0;
let pages_count = self.size >> PAGE_SHIFT;

View File

@@ -1,3 +1,9 @@
//! Plain Old Data trait and blanket implementations.
//!
//! Types that implement [`Pod`] can be safely created by copying their raw byte
//! representation. This is useful for reading on-disk structures from block
//! devices.
/// An unsafe trait indicating that a type is "Plain Old Data".
///
/// A type is `Pod` if it is a simple collection of bytes with no invalid bit

View File

@@ -1,6 +1,22 @@
//! Linux-compatible process capabilities.
//!
//! This module implements the five capability sets (`effective`, `permitted`,
//! `inheritable`, `ambient`, `bounding`) used by the kernel for
//! fine-grained privilege checks.
#![allow(missing_docs)]
use crate::error::{KernelError, Result};
// Linux-compatible capability flags.
//
// Each flag corresponds to a single Linux capability as defined in
// `include/uapi/linux/capability.h`.
bitflags::bitflags! {
/// Linux-compatible capability flags.
///
/// Each flag corresponds to a single Linux capability as defined in
/// `include/uapi/linux/capability.h`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CapabilitiesFlags: u64 {
const CAP_CHOWN = 1 << 0;
@@ -47,6 +63,9 @@ bitflags::bitflags! {
}
}
/// The five capability sets associated with a process.
///
/// See `capabilities(7)` for the full semantics of each set.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Capabilities {
effective: CapabilitiesFlags,
@@ -57,6 +76,7 @@ pub struct Capabilities {
}
impl Capabilities {
/// Creates a new `Capabilities` with the given sets.
pub fn new(
effective: CapabilitiesFlags,
permitted: CapabilitiesFlags,
@@ -73,6 +93,7 @@ impl Capabilities {
}
}
/// Creates a root (all-capable) set.
pub fn new_root() -> Self {
Self {
effective: CapabilitiesFlags::all(),
@@ -83,6 +104,7 @@ impl Capabilities {
}
}
/// Creates an empty (no capabilities) set.
pub fn new_empty() -> Self {
Self {
effective: CapabilitiesFlags::empty(),
@@ -129,30 +151,37 @@ impl Capabilities {
Ok(())
}
/// Returns the effective capability flags.
pub fn effective(&self) -> CapabilitiesFlags {
self.effective
}
/// Returns the permitted capability flags.
pub fn permitted(&self) -> CapabilitiesFlags {
self.permitted
}
/// Returns the inheritable capability flags.
pub fn inheritable(&self) -> CapabilitiesFlags {
self.inheritable
}
/// Returns the ambient capability flags.
pub fn ambient(&self) -> CapabilitiesFlags {
self.ambient
}
/// Returns a mutable reference to the ambient capability flags.
pub fn ambient_mut(&mut self) -> &mut CapabilitiesFlags {
&mut self.ambient
}
/// Returns the bounding capability flags.
pub fn bounding(&self) -> CapabilitiesFlags {
self.bounding
}
/// Returns a mutable reference to the bounding capability flags.
pub fn bounding_mut(&mut self) -> &mut CapabilitiesFlags {
&mut self.bounding
}

View File

@@ -1,16 +1,25 @@
//! User and group identity types.
//!
//! [`Uid`] and [`Gid`] are thin wrappers around `u32` that prevent accidental
//! mixing of user IDs and group IDs at the type level.
/// A user identity (UID).
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Uid(u32);
impl Uid {
/// Creates a new `Uid` with the given numeric value.
pub const fn new(id: u32) -> Self {
Self(id)
}
/// Returns `true` if this is the root (UID 0) user.
pub fn is_root(self) -> bool {
self.0 == 0
}
/// Returns the root UID (0).
pub fn new_root() -> Self {
Self(0)
}
@@ -29,15 +38,18 @@ impl From<Uid> for u32 {
}
}
/// A group identity (GID).
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Gid(u32);
impl Gid {
/// Creates a new `Gid` with the given numeric value.
pub const fn new(id: u32) -> Self {
Self(id)
}
/// Returns the root group GID (0).
pub fn new_root_group() -> Self {
Self(0)
}

View File

@@ -1,2 +1,8 @@
//! Process identity and capability types.
//!
//! This module provides the fundamental types for process credentials:
//! user/group identifiers ([`ids`]) and Linux-compatible capabilities
//! ([`caps`]).
pub mod caps;
pub mod ids;

View File

@@ -1,3 +1,5 @@
//! Async-aware condition variable.
use super::spinlock::SpinLockIrq;
use super::waker_set::WakerSet;
use crate::CpuOps;
@@ -5,8 +7,11 @@ use alloc::sync::Arc;
/// The type of wakeup that should occur after a state update.
pub enum WakeupType {
/// Do not wake any waiting task.
None,
/// Wake exactly one waiting task.
One,
/// Wake all waiting tasks.
All,
}

View File

@@ -1,3 +1,8 @@
//! Synchronisation primitives for `no_std` kernel environments.
//!
//! All primitives are generic over [`CpuOps`](crate::CpuOps) so they can
//! disable/restore interrupts on the local core.
pub mod condvar;
pub mod mpsc;
pub mod mutex;

View File

@@ -1,3 +1,5 @@
//! Async-aware mutual-exclusion lock.
use alloc::collections::VecDeque;
use core::cell::UnsafeCell;
use core::future::Future;

View File

@@ -1,3 +1,5 @@
//! A thread-safe cell that is initialized exactly once.
use core::fmt;
use crate::CpuOps;

View File

@@ -65,6 +65,7 @@ where
}
}
/// A mutable borrow of per-CPU data that restores interrupts on drop.
pub struct IrqSafeRefMut<'a, T, CPU: CpuOps> {
borrow: ManuallyDrop<RefMut<'a, T>>,
flags: usize,
@@ -198,6 +199,7 @@ impl<T: Send, CPU: CpuOps> PerCpu<RefCell<T>, CPU> {
}
}
/// Attempts to mutably borrow the per-CPU data, returning `None` if already borrowed.
#[track_caller]
pub fn try_borrow_mut(&self) -> Option<IrqGuard<RefMut<'_, T>, CPU>> {
let flags = CPU::disable_interrupts();
@@ -225,6 +227,7 @@ impl<T: Send, CPU: CpuOps> PerCpu<RefCell<T>, CPU> {
}
impl<T: Send + Sync, CPU: CpuOps> PerCpu<T, CPU> {
/// Returns a reference to the data for the given CPU.
pub fn get_by_cpu(&self, cpu_id: usize) -> &T {
unsafe { self.get_for_cpu(cpu_id) }
}

View File

@@ -1,3 +1,5 @@
//! Async-aware readerswriter lock.
use super::spinlock::SpinLockIrq;
use crate::CpuOps;
use crate::sync::mutex::Mutex;

View File

@@ -1,3 +1,5 @@
//! Low-level spin lock primitives.
use core::cell::UnsafeCell;
use core::hint::spin_loop;
use core::marker::PhantomData;

View File

@@ -1,3 +1,5 @@
//! Waker registration and notification set.
use alloc::collections::BTreeMap;
use alloc::sync::Arc;
use core::{
@@ -9,6 +11,7 @@ use crate::CpuOps;
use super::spinlock::SpinLockIrq;
/// A set of registered [`Waker`]s that can be selectively or collectively woken.
pub struct WakerSet<T = ()> {
waiters: BTreeMap<u64, (Waker, T)>,
next_id: u64,
@@ -21,6 +24,7 @@ impl Default for WakerSet {
}
impl<T> WakerSet<T> {
/// Creates a new, empty waker set.
pub fn new() -> Self {
Self {
waiters: BTreeMap::new(),
@@ -38,6 +42,7 @@ impl<T> WakerSet<T> {
id
}
/// Returns `true` if the given token is still registered in the set.
pub fn contains_token(&self, token: u64) -> bool {
self.waiters.contains_key(&token)
}
@@ -84,6 +89,7 @@ impl<T> WakerSet<T> {
}
}
/// Registers a waker together with associated data, returning its token.
pub fn register_with_data(&mut self, waker: &Waker, data: T) -> u64 {
let id = self.allocate_id();