Cheatsheet
| Name | Purpose |
|---|---|
sstatus | S-mode status/control |
sie | |
sip | |
stvec | |
scause | |
stval | |
sepc | |
satp |
RISC-V has 32 general-purpose integer registers, x0 through x31, 32 floating-point registers, f0 through f31, and a Program Counter, which holds the address of the current instruction and determines where the processor fetches the next instruction.
Cheatsheet
| Register | ABI Name | Use |
|---|---|---|
x0 | zero | Always Zero |
x1 | ra | Return address |
x2 | sp | Stack pointer |
x3 | gp | Global pointer |
x4 | tp | Thread pointer |
x5 | t0 | Temporary / Alternate link register |
x6 - x7 | t1 - t2 | Temporary |
x8 | s0 or fp | Saved register / Frame pointer |
x9 | s1 | Saved register |
x10 - x11 | a0 - a1 | Function argument / Return value |
x12 - x17 | a2 - a7 | Function argument |
x18 - x27 | s2 - s11 | Saved register |
x28 - x31 | t3 - t6 | Temporary |
pc | (none) | Program counter |
f0 - f7 | ft0 - ft7 | Floating-point Temporary |
f8 - f9 | fs0 - fs1 | Floating-point Saved register |
f10 - f11 | fa0 - fa1 | Floating-point Function argument / Return value |
f12 - f17 | fa2 - fa7 | Floating-point Function argument |
f18 - f27 | fs2 - fs11 | Floating-point Saved register |
f28 - f31 | ft8 - ft11 | Floating-point Temporary |
Terminology
Caller-saved
Refers to registers whose values may be changed by a function call. If the caller needs their values after the call, the caller must save them before making the call.
Callee-saved
Refers to registers whose values must be preserved across a function call. The callee may change them during the function, but must save and restore their original values before returning.
Note: Restoring the original value is not enforced by hardware, but failing to do so is considered a bug and an ABI violation.
Zero
This register always carries the value 0. Writes to it are ignored, so its value can never be changed.
Think of it like /dev/zero: it always gives you a zero.
Return address
This register holds the address of the instruction that execution should return to after the called function finishes.
The ret instruction uses the address stored in ra to set pc, continuing execution from that address.
Stack pointer
This register points to the current top of the stack, the lowest-addressed part of the currently allocated stack.
See also: Stack.
Global pointer
This register points to a region in memory containing global/static data, allowing it to be accessed efficiently using offsets.
Thread pointer
This register points to a region in memory containing data specific to the currently running thread.
Temporary
This register is general-purpose and caller-saved. It is used for temporary values.
Its contents are not guaranteed to survive a function call.
Saved register
This register is similar to a temporary register, with the exception that its contents must be preserved across a function call.
Frame pointer
This register points to a fixed location in the current stack frame, providing a stable reference for accessing local variables and other stack data.
Function argument
This register is used to pass function arguments to a callee.
Return value
This register is used by a function to pass its return value back to the caller.
Program counter
This register holds the address of the current instruction and determines where the processor fetches the next instruction.
It cannot be accessed explicitly as a general-purpose register; instructions such as jal or jalr, and conditional branches modify it implicitly.
Terminology
Flint uses three conceptual execution domains:
Kernel space
The domain in which the Flint kernel executes.
Kernel space contains the small trusted core responsible for fundamental system mechanisms and resources.
System space
The domain in which Flint's operating-system services execute.
System-space processes are not part of the kernel. They are isolated processes that provide functionality such as filesystem services, device drivers, networking, and system supervision.
System-space processes receive the capabilities necessary to perform their responsibilities.
User space
The domain in which ordinary user applications execute.
User-space processes generally have fewer capabilities than system-space services and interact with the system through the interfaces provided by the kernel and system-space services.
Relationship
The three domains should be understood as:
Kernel space
│
│ provides mechanisms and resources
▼
System space
│
│ provides system services
▼
User space
│
│ runs applications
▼
User
"System space" is a Flint architectural term. It does not imply a separate CPU privilege level or hardware-enforced execution mode.
Core philosophy
Flint should be small and secure by design
The privileged kernel should contain the minimum functionality necessary to provide fundamental operating-system mechanisms. Higher-level functionality should be moved to userspace whenever practical.
Security and isolation should come from the architecture itself rather than relying primarily on conventions and the correctness of every individual component.
Core principles:
- Minimize privileged code.
- Prefer userspace over kernel space when practical.
- Isolate components from one another.
- Use explicit IPC for communication.
- Grant authority explicitly through capabilities.
- Keep security-critical mechanisms small and auditable.
- Do not introduce complexity without a clear benefit.
Goal: A small trusted computing base with strong isolation between components.
Architecture
Flint will use a microkernel architecture. The kernel will contain only core mechanisms that require privileged execution, while higher-level operating-system services will run in userspace and communicate with the kernel and each other through IPC.
The kernel will primarily provide mechanisms such as:
- Process and thread management
- Scheduling
- Memory and address-space management
- IPC
- Interrupt handling
- Timers
Higher-level services such as filesystems, device drivers, networking, and similar components should run in userspace where practical.
Principle: Keep the privileged kernel small; move policy and higher-level services into userspace.
Modularity
Flint's kernel will be composed of modular components with clearly defined interfaces.
Components such as the scheduler, memory manager, IPC subsystem, and interrupt handling should be independently structured rather than tightly coupled into a single implementation.
Modularity refers to separation of responsibilities and interfaces; it does not necessarily imply that components must be dynamically loadable at runtime.
Principle: Components should communicate through well-defined interfaces and minimize unnecessary dependencies on each other's implementation details.
Multitasking
Flint will use preemptive multitasking. The kernel's scheduler can interrupt a running task and switch execution to another task, preventing a task from monopolizing the CPU.
A timer interrupt will provide the mechanism for periodically preempting the currently running task.
Principle: No userspace task should be able to prevent other tasks from receiving CPU time simply by refusing to yield.
Symmetric multiprocessing
SMP will be omitted from early versions of Flint. The initial kernel will target a single CPU core/hart.
The architecture should avoid unnecessarily preventing future SMP support, but synchronization and other SMP-specific mechanisms will only be introduced when SMP becomes a requirement.
Principle: Keep initial implementation simple without deliberately making future SMP support impossible.
Memory allocation
Memory-management functionality should be kept in the kernel only when it requires privileged access or must enforce system-wide invariants.
Higher-level allocation mechanisms should be implemented in userspace where practical. For example, userspace should provide its own heap allocator rather than relying on a kernel-level malloc.
The kernel should provide the primitives required for userspace to obtain, map, and manage memory safely.
Device drivers
Device drivers should run in userspace whenever they can do so safely and reasonably.
Drivers may run in the kernel when they require privileged access, cannot be reasonably implemented in userspace, or when placing them in the kernel is necessary for correctness or practical performance.
Principle: Prefer userspace, but do not force functionality out of the kernel when doing so would be unreasonable.
Inter-process communication
Flint will use message-oriented IPC as the primary mechanism for communication between processes and userspace services.
Processes should not directly access each other's memory. Communication should instead occur through explicit messages mediated by the kernel.
This provides strong process isolation and makes communication between components explicit and controllable.
Shared memory may be introduced later for cases where message passing alone is impractical, such as transferring large amounts of data, but it should not be the default communication mechanism.
Principle: Prefer explicit, kernel-mediated communication over shared mutable state.
Security model
Flint will use a capability-based security model.
Processes will access resources through explicit capabilities granted by the kernel. Possession of a capability determines what a process is authorized to access or perform.
Processes should not receive unrestricted access to system resources by default.
Capabilities should be unforgeable and managed by the kernel, preventing userspace processes from gaining access to resources they have not been granted.
Principle: Authority should be explicit, minimal, and granted by the kernel.
System call interface
Flint will expose a small call interface focused on fundamental kernel mechanisms.
System calls should provide primitives such as process management, memory management, IPC, capability management, and synchronization rather than high-level operating-system functionality.
Higher-level functionality should be implemented by userspace services.
Principle: the kernel provides mechanisms; userspace provides policy.
Unsafe code
Flint should minimize the use of unsafe code. Unsafe code should be confined to the lowest-level portions of the system where interaction with hardware, architecture-specific mechanisms, or other inherently unsafe operation requires it.
Higher-level kernel code should remain safe Rust whenever practical.
Unsafe abstractions should expose safe interfaces to the rest of the kernel, with their safety invariants clearly documented.
Principle: Keep unsafe code small, isolated and auditable.
Fault isolation
Failures in userspace services should not inherently bring down the kernel or unrelated services.
Userspace services such as drivers, filesystems, and networking components should be designed to be independently restartable where practical.
The kernel should maintain enough isolation that a userspace service failure cannot directly corrupt the kernel or arbitrarily compromise unrelated processes.
Principle: A component failure should affect the smalles possible part of the system.
Userspace initialization
After kernel initialization, Flint will create an initial userspace process running /bin/uinit.
uinit will act as the initial userspace supervisor. It will initialize the userspace environment, start system services, establish their IPC relationships and capabilities, and manage their lifecycle.
The kernel will provide the mechanisms required to create and isolate the initial process, but userspace initialization policy will remain the responsibility of uinit.
Principle: Once the kernel has established the minimum environment required for userspace, initialization and service management should be delegated to userspace.
Flint's filesystem is a userspace subsystem.
The kernel does not fundamentally understand files, directories, paths, mounts, or filesystem semantics. Filesystem functionality is implemented by userspace services that use kernel-provided mechanisms and capabilities.
Philosophy
Userspace filesystem
The filesystem should run in userspace rather than being implemented as a kernel subsystem.
The kernel provides the resources and mechanisms required for filesystem services to operate, while userspace defines filesystem semantics.
Files are not universal resources
flint does not follow the Unix principle that everything is a file.
A file is a userspace abstraction. Not every resource needs to have a filesystem representation.
Resources such as devices, processes, memory, and kernel state can instead be accessed through capabilities and IPC.
A userspace service may choose to represent a resource as a file when doing so is useful.
Unix-inspired, not Unix-compatible
Flint's filesystem is inspired by the Unix filesystem model but is not required to preserve Unix or POSIX semantics.
Familiar concepts such as files, directoreis, paths and hierarchical storage may be retained where they provide a useful model.
Unnecessary historical complexity may be removed or simplified.
Principle: Borrow ideas, not obligations.
Filesystem hierarchy
Flint currently uses the following top-level hierarchy:
/
├── bin
├── boot
├── conf
├── data
├── home
├── lib
└── run
/
The root of the filesystem hierarchy.
/bin
Contains executable programs and binaries.
/conf
Contains system-wide configuration
This replaces the traditional Unix/Linux /etc.
/data
Contains general persistent data that is owned by the system or its userspace services and is not specific to an individual user.
This combined concepts traditionally separated between /usr, /var, and /srv.
/home
Contains user-specific data.
The root user's home directory is: /home/root
There is no separate /root directory.
/lib
Contains libraries and supporting runtime code.
Flint targets RV64, so a separate /lib64 hierarchy is unnecessary.
/run
Contains volatile runtime data
This is data that exists for currently running processes and does not need to persist as apart of the permanent filesystem state.
This includes both traditional runtime state and temporary data.
Examples include:
- Runtime sockets
- Locks
- Process state
- Temporary files
- Other process-owned transient data
There is no separate /tmp hierarchy.
System information
System and hardware information are not inherently represented as files.
the filesystem does not provide kernel information merely because Unix traditionally exposes it through /proc or /sys.
Instead, userspace services obtain authorized information through IPC and other kernel mechanisms.
A userspace service may subsequently choose to present that information through a filesystem interface.
Devices
Devices are resources rather than inherently being files.
Flint therefore has no kernel-provided /dev filesystem.
A userspace service may provide a filesystem representation of devices where useful, but the kernel does not need to understand device files or /dev.
Runtime and persistence
Flint distinguishes persistent data from volatile runtime data.
/run may be backed by volatile storage such as tmpfs, while /data is intended for persistent storage.
The physical storage mechanism is separate from the filesystem namespace. The distinction is semantic rather than a requirement about the underlying storage technology.
/data organization
/data uses a service-oriented layout.
Persistent data should generally be grouped according to the userspace service that owns and manages it rather than according to generic categories such as log, cache, or lib.
For example:
/data/
├── filesystem/
├── network/
├── init/
└── ...
A service may organize its own directory internally according to its needs.
This keeps ownership explicit and avoids imposing a universal data taxonomy on userspace services.
Principle: Persistent data belongs to the service that owns it.