Merge pull request 'Pool docs improvements' (#105) from pool-docs-improvements into main
All checks were successful
Rust/sat-rs/pipeline/head This commit looks good

Reviewed-on: #105
This commit is contained in:
Robin Müller 2024-02-12 11:30:48 +01:00
commit 712dc718f9
3 changed files with 49 additions and 18 deletions

View File

@ -5,7 +5,14 @@ High-level documentation of the [sat-rs project](https://absatsw.irs.uni-stuttga
## Building ## Building
If you have not done so, install `mdbook` using `cargo install mdbook --locked`. If you have not done so, install the pre-requisites first:
```sh
cargo install mdbook --locked
cargo install mdbook-linkcheck --locked
```
After that, you can build the book with:
```sh ```sh
mdbook build mdbook build

View File

@ -11,7 +11,8 @@ time where the OBSW might be running on Linux based systems with hundreds of MBs
A useful pattern used commonly in space systems is to limit heap allocations to program A useful pattern used commonly in space systems is to limit heap allocations to program
initialization time and avoid frequent run-time allocations. This prevents issues like initialization time and avoid frequent run-time allocations. This prevents issues like
running out of memory (something even Rust can not protect from) or heap fragmentation. running out of memory (something even Rust can not protect from) or heap fragmentation on systems
without a MMU.
# Using pre-allocated pool structures # Using pre-allocated pool structures
@ -24,6 +25,12 @@ For example, a very small telecommand (TC) pool might look like this:
![Example Pool](images/pools/static-pools.png) ![Example Pool](images/pools/static-pools.png)
The core of the pool abstractions is the
[PoolProvider trait](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/trait.PoolProvider.html).
This trait specifies the general API a pool structure should have without making assumption
of how the data is stored.
This trait is implemented by a static memory pool implementation.
The code to generate this static pool would look like this: The code to generate this static pool would look like this:
```rust ```rust
@ -39,21 +46,27 @@ let tc_pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![
It should be noted that the buckets only show the maximum size of data being stored inside them. It should be noted that the buckets only show the maximum size of data being stored inside them.
The store will keep a separate structure to track the actual size of the data being stored. The store will keep a separate structure to track the actual size of the data being stored.
<!-- TODO: Add explanation and references for used data-structures. Also explain core trait
to work with the pool. -->
A TC entry inside this pool has a store address which can then be sent around without having A TC entry inside this pool has a store address which can then be sent around without having
to dynamically allocate memory. The same principle can also be applied to the telemetry (TM) and to dynamically allocate memory. The same principle can also be applied to the telemetry (TM) and
inter-process communication (IPC) data. inter-process communication (IPC) data.
You can read
- [`StaticPoolConfig` API](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/struct.StaticPoolConfig.html)
- [`StaticMemoryPool` API](https://docs.rs/satrs-core/0.1.0-alpha.3/satrs_core/pool/struct.StaticMemoryPool.html)
for more details.
In the future, optimized pool structures which use standard containers or are
[`Sync`](https://doc.rust-lang.org/std/marker/trait.Sync.html) by default might be added as well.
# Using special crates to prevent smaller allocations # Using special crates to prevent smaller allocations
Another common way to use the heap on host systems is using containers like `String` and `Vec<u8>` Another common way to use the heap on host systems is using containers like `String` and `Vec<u8>`
to work with data where the size is not known beforehand. The most common solution for embedded to work with data where the size is not known beforehand. The most common solution for embedded
systems is to determine the maximum expected size and then use a pre-allocated `u8` buffer and a systems is to determine the maximum expected size and then use a pre-allocated `u8` buffer and a
size variable. Alternatively, you can use the following crates for more convenience or a smart size variable. Alternatively, you can use the following crates for more convenience or a smart
behaviour which at the very least reduce heap allocations: behaviour which at the very least reduces heap allocations:
1. [`smallvec`](https://docs.rs/smallvec/latest/smallvec/). 1. [`smallvec`](https://docs.rs/smallvec/latest/smallvec/).
2. [`arrayvec`](https://docs.rs/arrayvec/latest/arrayvec/index.html) which also contains an 2. [`arrayvec`](https://docs.rs/arrayvec/latest/arrayvec/index.html) which also contains an

View File

@ -203,8 +203,12 @@ impl Error for StoreError {
} }
} }
/// Generic trait for pool providers where the data can be modified and read in-place. This /// Generic trait for pool providers which provide memory pools for variable sized data.
/// generally means that a shared pool structure has to be wrapped inside a lock structure. ///
/// It specifies a basic API to [Self::add], [Self::modify], [Self::read] and [Self::delete] data
/// in the store at its core. The API was designed so internal optimizations can be performed
/// more easily and that is is also possible to make the pool structure [Sync] without the whole
/// pool structure being wrapped inside a lock.
pub trait PoolProvider { pub trait PoolProvider {
/// Add new data to the pool. The provider should attempt to reserve a memory block with the /// Add new data to the pool. The provider should attempt to reserve a memory block with the
/// appropriate size and then copy the given data to the block. Yields a [StoreAddr] which can /// appropriate size and then copy the given data to the block. Yields a [StoreAddr] which can
@ -251,6 +255,7 @@ pub trait PoolProvider {
} }
} }
/// Extension trait which adds guarded pool access classes.
pub trait PoolProviderWithGuards: PoolProvider { pub trait PoolProviderWithGuards: PoolProvider {
/// This function behaves like [PoolProvider::read], but consumes the provided address /// This function behaves like [PoolProvider::read], but consumes the provided address
/// and returns a RAII conformant guard object. /// and returns a RAII conformant guard object.
@ -280,7 +285,8 @@ pub struct PoolGuard<'a, MemProvider: PoolProvider + ?Sized> {
deletion_failed_error: Option<StoreError>, deletion_failed_error: Option<StoreError>,
} }
/// This helper object /// This helper object can be used to safely access pool data without worrying about memory
/// leaks.
impl<'a, MemProvider: PoolProvider> PoolGuard<'a, MemProvider> { impl<'a, MemProvider: PoolProvider> PoolGuard<'a, MemProvider> {
pub fn new(pool: &'a mut MemProvider, addr: StoreAddr) -> Self { pub fn new(pool: &'a mut MemProvider, addr: StoreAddr) -> Self {
Self { Self {
@ -390,16 +396,21 @@ mod alloc_mod {
/// Pool implementation providing sub-pools with fixed size memory blocks. /// Pool implementation providing sub-pools with fixed size memory blocks.
/// ///
/// This is a simple memory pool implementation which pre-allocates all sub-pools using a given pool /// This is a simple memory pool implementation which pre-allocates all subpools using a given
/// configuration. After the pre-allocation, no dynamic memory allocation will be performed /// pool configuration. After the pre-allocation, no dynamic memory allocation will be
/// during run-time. This makes the implementation suitable for real-time applications and /// performed during run-time. This makes the implementation suitable for real-time
/// embedded environments. The pool implementation will also track the size of the data stored /// applications and embedded environments.
/// inside it. ///
/// The subpool bucket sizes only denote the maximum possible data size being stored inside
/// them and the pool implementation will still track the size of the data stored inside it.
/// The implementation will generally determine the best fitting subpool for given data to
/// add. Currently, the pool does not support spilling to larger subpools if the closest
/// fitting subpool is full. This might be added in the future.
/// ///
/// Transactions with the [pool][StaticMemoryPool] are done using a generic /// Transactions with the [pool][StaticMemoryPool] are done using a generic
/// [address][StoreAddr] type. /// [address][StoreAddr] type. Adding any data to the pool will yield a store address.
/// Adding any data to the pool will yield a store address. Modification and read operations are /// Modification and read operations are done using a reference to a store address. Deletion
/// done using a reference to a store address. Deletion will consume the store address. /// will consume the store address.
pub struct StaticMemoryPool { pub struct StaticMemoryPool {
pool_cfg: StaticPoolConfig, pool_cfg: StaticPoolConfig,
pool: Vec<Vec<u8>>, pool: Vec<Vec<u8>>,