TranquilOS

Privilege layers. Capability authority. Isolated services.

EL3 boots the system, EL2 runs the hypervisor, EL1 the microkernel, and EL0 the system services and applications. Layers interact only through capabilities, IPC and shared memory.

Four runtime layers. Clear boundaries.

Boot moves upward through EL3 → EL2 → EL1 → EL0; runtime communication uses capabilities, IPC and SHM.

EL0 EL1 EL2 EL3 Hypervisor Type-1 · optional stage stage-2 VM/VCPU vGIC vTimer vPMU hypcall Microkernel capability object dispatch capability migrating IPC SMP scheduler futex IRQ · timer drivers Bootloader platform init · image loading · exception-level handoff platform image load handoff boot handoff handoff SystemDaemon privileged EL0 · 17 IPC methods procmgr — process / thread lifecycle memmgr — physical pages · SHM ipcmgr — endpoint namespace virtmgr — VM lifecycle direct capcalls, no libsyscall Framework Services independent EL0 · endpoint pools windowmgr fsmgr netmgr devmgr appmgr audiomgr fontmgr statemgr timemgr agentmgr mmimgr imemgr interfaces in .idl · wrappers/dispatch codegen Applications regular user programs systemui launcher settings files monitor memo music calendar clock whiteboard calculator nes ai about IPC IPC

Every kernel object is reached through a capability.

Each process stores accessible kernel objects in a CNode. A 64-bit cref combines a cnode ID and slot; the kernel checks object type and rights before dispatch.

VSpaceCNodeEndpoint SContextXContextTimer FutexIRQConsole
Syscall pathFlagPurpose
Capability callCAP_CALL_MASKRouted to capability objects (VSpace, CNode, Endpoint, Timer…)
Fast callFAST_CALL_MASKHigh-performance kernel-inline calls (currently a stub)
Compact syscallLinux-compatible table; unknown calls are forwarded to userspace libsyscall via upcall

Upcall is the reverse path: the kernel hands syscalls it cannot handle, page faults and similar events to a handler registered by the process — policy stays in userspace.

Cross-process calls that behave like functions.

Synchronous IPC migrates the caller's context into the callee's address space—no server-thread scheduling wait.

MIGRATING THREAD

Switch execution context, not scheduling state

Thanks to the scontext/xcontext split, the kernel constructs the target process's execute_context_s on IPC: arguments go into x0–x7 per the AAPCS, the entry address into pc, and a trampoline address into lr — when the handler returns, the trampoline issues a REPLY call that switches back to the caller. Scheduling metadata stays under the scheduler's control the whole time, so even heavy IPC never starves other threads.

ENDPOINT & POOL

Single-threaded endpoints and elastic pools

An endpoint is single-threaded — one handler thread per endpoint. An endpoint pool load-balances across pre-allocated endpoints for multi-threaded services (elastic IPC), while the client API stays identical in both modes. A kernel name service lives at cref 0x1: services register with sys_register_service(), clients look them up with sys_get_service().

01alloc_shm

The client allocates shared memory from SystemDaemon.

02map & copy

Map the SHM and write the request data.

03ipc call

Issue the IPC call, passing the SHM handle as an argument.

04get_shm

The service maps the same SHM region.

05process

Operate directly on the shared data — zero copy.

06reply & free

Reply; the client frees the SHM.

SHM is the only data channel between processes — TranquilOS has no message-copying IPC. All bulk data moves through shared memory; IPC argument slots carry handles and scalars only.

Scheduling and execution are two data structures.

The TCB is split into scheduling and execution entities. Each CPU schedules locally and touches register state only on real switches; CFS plugs in as a kernel module.

per-CPU runqueueload balancing preemptiveCFS module
schedule_context_s   // scheduling metadata
  · priority / timeslice
  · runqueue node / CPU affinity
  · driven by the scheduler and its policy

execute_context_s    // execution state
  · x0–x30 · sp · pc · pstate
  · built/restored only on real context switches
  · constructed independently on IPC migration

The kernel keeps only a boot allocator — memory policy lives in EL0.

The kernel keeps only a boot allocator. Memory policy lives in SystemDaemon across four components: procmgr, memmgr, ipcmgr and virtmgr.

ComponentResponsibility
procmgrSpawn ELF, process/thread teardown, PID lookup
memmgrBuddy physical pages, virtual address spaces, SHM
ipcmgrEndpoint/pool creation, name service, capability cloning
virtmgrVM creation and lifecycle

Boot: leveled initcalls, fixed image layout.

The linker places initialization functions into dedicated sections for levels LV0–LV7, and the kernel runs them in order; the bootloader decides whether to start the hypervisor first or enter the kernel directly, based on CurrentEL.

LVMacroPurpose
0early_device_initEarliest device init
1early_device_percpu_initPer-CPU early devices
2key_device_initKey devices (GIC, timer…)
3key_device_percpu_initPer-CPU key devices
4normal_device_initNormal devices
5normal_device_percpu_initPer-CPU normal devices
6module_initKernel modules (e.g. CFS)
7module_percpu_initPer-CPU module init
boot.img (64MB)
  offset 0      Bootloader    8MB
  offset 2048   DTB           8MB
  offset 4096   Hypervisor   16MB
  offset 8192   Kernel       16MB
  offset 12288  SystemDaemon  8MB
  offset 14336  Ramdisk cpio  8MB

system.img (128MB, ext2)
  bin/  framework services
  apps/ user applications
  etc/  fonts · icons · init config
01init

First userspace process. Finds the ramdisk via the device tree, starts devmgr and fsmgr, then runs /root/etc/init.rc.

02zygote

Reads init.json, loads service ELFs from ext2 into SHM and starts framework services via start_process_from_shm().

03appmgr

Manages the app registry from applist.json and launches app processes on demand.

One service skeleton. IDL-generated interfaces.

main.c registers, service.c dispatches, and client/ wraps IPC; generation also validates the ABI.

// service.c — the same dispatch pattern in every service
static handler_fn handlers[] = {
  [IPC_SERVICE_FUNCTION_CREATE] = handler_create,
  [IPC_SERVICE_FUNCTION_DESTROY] = handler_destroy,
  // ...
};

IPC_ENDPOINT void service_entry(
    uint64_t cref, uint64_t method,
    uint64_t a1, uint64_t a2, uint64_t a3) {
  if (method < HANDLERS_COUNT && handlers[method])
    OSIpcEndPointPoolReply(handlers[method](a1, a2, a3));
  else
    OSIpcEndPointPoolReply(0);
}
// statemgr_ipc.idl
service statemgr_ipc {
    id = 0x9;

    struct query  { version = 1; char key[64]; };
    struct result { version = 1; uint32_t found; };

    uint64_t get(in query *request,
                 out result *response) id = 0x1;
};

Struct pointers use server-owned fixed receive buffers. The first call establishes SHM authorization per caller, method and parameter slot; later calls reuse the connection-level CRef and mapped address. A per-method lock covers copying, IPC and copy-back. Transport structs must declare a version; the generator adds abi_size / abi_version fields validated by the server. Variable-length data passes an explicit SHM handle and length.