blob: 8e72eeb2e72cdc635ca504b30c0bef00f2d4a06c [file] [log] [blame] [view]
Design Considerations
=====================
This document describes several of the factors that constrain the design of
`libtock_core`.
## Size impact
Tock is designed to run on hardware with limited program storage and limited
RAM. On Google's H1 chip, the Tock kernel and apps are limited to a 256 KiB
flash region. The HiFive1 Rev B board has only 16 KiB of RAM.
Note that a Tock system may have multiple process binaries, each of which uses
`libtock_core`, so `libtock_core` must minimize its size impact to the extent
possible.
A process binary is generally stored in non-volatile memory and contains:
* Runtime headers (the `.crt0_header` section)
* Executable code (the `.text` section)
* Read-only data (the `.rodata` section)
* Non-zero-initialized read-write data (the `.data` section)
A process' memory section is in RAM and contains:
* Heap memory (if a dynamic memory allocator is present)
* Zero-initialized read-write data (the `.bss` section)
* Non-zero-initialized read-write data (the `.data` section)
* Stack memory
`libtock_core` developers should consider the space usage of their code, and
where the space usage is (non-volatile memory or RAM). Important note: the
`.data` section consumes space in both non-volatile memory and RAM!
Most apps will not use all functionality in `libtock_core`. The size impact of
using `libtock_core` in an app should be commensurate with the `libtock_core`
functionality that app uses. If done correctly, this will allow `libtock_core`'s
users to build Tock systems with multiple small, single-purpose apps without
excessive code duplication.
## Testability
Programmatic tests are important to verify that new functionality works
correctly and to keep existing functionality working through nontrivial
refactorings. Different types of tests offer different capabilities:
* **Host-based unit tests** run on a non-Tock host OS (such as Linux). They
can only be used with portable `libtock_core` code, as the code they test
must run on both Tock and the host OS. These tests must either test code
that does not directly use system calls, or must direct the system calls to
a fake Tock kernel. These tests can be fast (compile and run in seconds),
and can easily simulate a variety of failure modes (e.g. kernel errors) that
may be difficult to generate with a real Tock kernel.
* **Emulation tests** run on an emulated Tock system. Currently, emulation
tests run in QEMU on an emulated HiFive1 Rev B board. These test
`libtock_core`'s interaction with a real Tock kernel, albeit with limited
hardware access.
* **Hardware tests** run on physical hardware with a real Tock kernel. These
are end-to-end tests that test not only `libtock_core`'s functionality but
also the Tock kernel's interaction with the real hardware. However,
automating these tests is difficult, and currently we do not have a way to
run hardware tests in CI.
If `libtock_core` is functional on real hardware with a real kernel, it should
be able to support emulation tests and hardware tests. However, host-based unit
tests require more work. In its intended use case, `libtock_core` is not
supposed to be portable, supporting only the Tock kernel. Hoever, host-based
unit tests are very valuable, both for rapid development and for testing error
handling code. To support host-based tests, `libtock_core` must be built out of
portable pieces that can be unit tested, even though the library as a whole is
not portable.
Testing pieces of `libtock_core` against a fake kernel can be done using
dependency injection, but most dependency injection techniques have considerable
code size and RAM usage costs. `libtock_core` needs either a dependency
injection technique that has minimal size impact or an alternative mechanism for
testing `libtock_core`'s system call error handling logic.
## Support multiple asynchronous programming models.
The general Rust ecosystem has converged on futures as its building block for
interoperable asynchronous APIs, but futures have a [size
cost](https://github.com/tock/design-explorations/tree/master/size_comparison)
that makes them impractical for some use cases of `libtock_core`. Although
`libtock_core` is still a work in progress (see [issue
217](https://github.com/tock/libtock-rs/issues/217)), it will probably use an
asynchronous API design based on the [Asynchronous Components using Zero Sized
Type
Pointers](https://github.com/tock/design-explorations/tree/master/zst_pointer_async)
exploration.
In order to interoperate with other libraries as well as the `async`/`await`
functionality built in to Rust, we need `libtock_core` to interface well with
futures. In addition, there are several use cases of `libtock_core` that consist
primarily of synchronous code, so we need to be able to use `libtock_core`'s
APIs in a synchronous manner.
The `libtock-c` developers are already familiar with some of the complications
that come up when synchronous code is interfaced to asynchronous code. For
example, calling `printf` -- an extremely common debugging tool -- causes
callbacks to run in the background. This causes surprising and hard-to-debug
reentrancy issues.
## Dependency tree minimization
Some of users of `libtock_core` intend to use it in applications that will
undergo code audits for security certifications. To make this auditing
practical, it is important to allow users to use `libtock_core` without pulling
in a large dependency tree.
As a general rule, a `libtock_core` should avoid having required dependencies
unless those dependencies are minimal (that is, avoiding the dependency would
require reimplementing nearly all of it). Dependencies included in the Rust
toolchain (such as the `core` crate and its dependencies) are an exception, as
they are a part of the language itself.
## No hard `alloc` dependency
It should be possible to use `libtock_core` without bringing in the `alloc`. Of
course, it is fine for `libtock_core` to offer a memory allocator as an optional
feature.