Getting started with Revera

This walks you from nothing to a running kernel — first on your desktop (no hardware, no cross-toolchain), then under an emulator, then your own first app. It should take about ten minutes.

Revera is a small real-time kernel: your application is C that you compile and link with the kernel into one program. There is no installer and no daemon — you build an example (or your own project) for a board and run it.

1. What you need

To run on…

You need

your desktop (BOARD=host)

a C compiler — gcc or clang. Nothing else.

an emulator (QEMU boards)

QEMU + the target’s cross-compiler, e.g. qemu-system-riscv32 + riscv-none-elf-gcc, or qemu-system-aarch64 + aarch64-none-elf-gcc.

real hardware

the board’s cross-toolchain + its flash tool. See board-bringup.md.

Everything below is run from the repository root. To see every board you can target:

make -C examples/threads boards

2. Run your first kernel (on your desktop)

The threads example brings up the kernel and runs two threads. Run it natively:

make -C examples/threads BOARD=host run

You’ll see two lines repeating — one about every 100 ms, one about every 200 ms — each tagged with a distinct thread id and a timestamp that advances:

[tid=1 arg=100] iter=0 t=0 ms
[tid=2 arg=200] iter=0 t=0 ms
[tid=1 arg=100] iter=1 t=100 ms
...

Press Ctrl-C to stop. That’s the whole kernel — scheduler, threads, timekeeping, console — running on your machine.

3. Run it under an emulator

The same source runs on a bare-metal target under QEMU. On RISC-V:

make -C examples/threads BOARD=qemu_riscv32_virt run     # quit QEMU with Ctrl-A then X

or on 64-bit ARM (BOARD=qemu_arm64_virt). Nothing in main.c changed — only the board did. That is the point of the board contract: the kernel and your app are portable; the board fragment supplies the hardware.

4. Run the tests

The repo-root quality gate builds and runs the unit suites:

make test          # host unit-test suites (no hardware, no cross-toolchain)
make test-arch     # live-kernel context-switch + SMP under QEMU (needs cross-gcc + QEMU)

make test exits non-zero if any test fails, so it drops straight into CI.

5. Anatomy of the app

Open examples/threads/main.c — it’s commented as a guided tour. Every Revera program follows the same six steps:

#include "tk_kernel.h"
#include "tk_time.h"
#include "tk_board.h"
#include "tk_console.h"

/* Every thread needs its OWN stack. Size it for the work the thread does — the context-switch frame,
 * how deep it calls, and its locals. Cells are bytes (ADR 0001). */
static tk_stack_cell_t stack[1024];

static void* worker(void* arg)
{
    for (;;) {
        tk_print("hello from a thread\n");
        tk_sleep_ms(500);          /* blocks THIS thread and gives the CPU away — not a busy-wait */
    }
    return 0;
}

int main(void)
{
    tk_board_init();                       /* 1. clock + pins + UART — nothing works before this */
    tk_console_init(TK_UART_CH_DEBUG);     /* 2. point tk_print() at the debug UART              */
    tk_kernel_init();                      /* 3. initialise the kernel (before creating threads) */
    tk_time_init();                        /* 4. zero the millisecond clock                      */

    tk_thread_t* t = tk_thread_create(worker, NULL, stack, sizeof stack, TK_PRIO_MIN + 1u);
    if (t == NULL) { return -1; }          /* 5. create threads (always check the handle)        */

    tk_kernel_start();                     /* 6. hand control to the scheduler — does not return */
    return 0;
}

Two ideas worth internalising early:

  • A thread’s argument is not its identity. The arg you pass is your data; the thread’s id (tk_thread_id(tk_thread_self())) is assigned by the kernel. Two threads can share an argument and still have different ids.

  • Priority is a number, not a vague level. Higher = higher priority; equal priorities round-robin. Express priorities relative to TK_PRIO_MIN (one step above the idle floor). Don’t size a working thread at ARCH_MIN_STACK_SIZE — that’s only the bare context-switch floor, too small to call into.

6. Write your own app

A project is a directory with a main.c and a three-line Makefile:

mkdir -p examples/hello

examples/hello/Makefile:

PROJECT := hello
SRCS    := main.c
include ../../make/tk.mk

Put your code in examples/hello/main.c (start from the skeleton above), then:

make -C examples/hello BOARD=host run

To build a project that lives outside the tree, point TK_DIR at the kernel and include from there — see the header of make/tk.mk. make -C examples/hello print-config shows the resolved board, arch, and flags.

7. Tuning the build (profiles + knobs)

The kernel ships minimal-safe by default (no MPU guard, no profiling, no unprivileged threads). Turn features on with compile-time knobs, or pick a ready-made profile:

make -C examples/threads BOARD=host PROFILE=../../profiles/tk_profile_nano.h run

Profiles range from tk_profile_nano.h (smallest) to tk_profile_full.h. Every knob — its values, default, requirements, and effect — is documented in one place: kernel/tk_config.h. Ask for hardware a board or arch can’t provide and the build fails loud at compile time, never silently.

Where to go next