Cheatsheet

NamePurpose
sstatusS-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

RegisterABI NameUse
x0zeroAlways Zero
x1raReturn address
x2spStack pointer
x3gpGlobal pointer
x4tpThread pointer
x5t0Temporary / Alternate link register
x6 - x7t1 - t2Temporary
x8s0 or fpSaved register / Frame pointer
x9s1Saved register
x10 - x11a0 - a1Function argument / Return value
x12 - x17a2 - a7Function argument
x18 - x27s2 - s11Saved register
x28 - x31t3 - t6Temporary
pc(none)Program counter
f0 - f7ft0 - ft7Floating-point Temporary
f8 - f9fs0 - fs1Floating-point Saved register
f10 - f11fa0 - fa1Floating-point Function argument / Return value
f12 - f17fa2 - fa7Floating-point Function argument
f18 - f27fs2 - fs11Floating-point Saved register
f28 - f31ft8 - ft11Floating-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.