-
Notifications
You must be signed in to change notification settings - Fork 2
Home
The Docbook is the place to find what RK0 is on about.
The wiki provides some general information on its functionality (that is found on the Dockbook on details) and information on setting up QEMU and projects of RK0 on dev boards.
Important
RK0 is unapologetically real-time embedded.
It acknowledges (or has not forgotten) the processs/thread abstraction, and the software design approach inherited from general purpose systems has 'time' as an afterthought. Conservative policies make Response-Time-Analysis easier. A (comprehensive) set of modular services – some quite distinctive – are tailored for concrete real-time demands.
-
core/kconfig.hcontains defines to enable or disable features and other critical configurations. -
Task priorities range from 0 to 31. 0 is the highest.
-
It is essential that the number of tasks in your application matches the number defined in
RK_CONF_N_USRTASKS, and the lowest effective priority value (the highest number) is defined inRK_CONF_MIN_PRIO. -
Stacks:
-
Stack addresses are to be aligned to an 8-byte boundary. The stack size must be a multiple of 8. Failing to follow these rules will end up on a crash, sooner or later.
-
As a rule of thumb, the minimal stack size for a task should be 64 WORDs if little work is performed (and FPU is not enabled).
-
If FPU is enabled and the task uses float-point unit math, 96 WORDs is the theoretical minimum. You probably need more.
-
System Stack: normally initial size is defined on
linker.ld(for QEMU ARMv7M build is the symbolMin_Stack_Size(in BYTES)). In this case, you need to account for the depth ofmain(),kApplicationInit(), and all interrupt handlers. Assume interrupts will always add to the worst static depth, and make sure to account for nested interrupts.
-
- At any moment a single task is
RUNNING(occupying the CPU). If a task is notRUNNINGit is eitherREADYorSLEEPING. -
READYreads as 'waiting to be the highest priorityREADYto switch toRUNNING.' -
SLEEPINGreads as 'waiting for a condition to switchREADY'. This condition is generalised as an event. -
SLEEPINGis logically split into different pseudo-states (PENDING,BLOCKED,RECEIVING,SENDING, seekcommondefs.h). - A
RUNNINGtask does not belong to any queue- (some kernels implementRUNNINGqueues as logicalREADYqueues). - If the READY QUEUE is empty the IDLE TASK is the only task that can be
RUNNING. - There is no built-in time-slice: Tasks with equal priority follow a FIFO scheduling (such as POSIX
SCH_FIFO). - When a task is preempted by a higher priority task, it switches from
RUNNINGtoREADYand is placed back on the head position of its Ready Queue. This means it will resume as soon as it is the highest-priority ready task again. - On the contrary, if a task yields, it tells the scheduler it has completed its cycle. Then, it will be enqueued on the ready queue tail - the last queue position.
- When a task switches from
SLEEPINGtoREADYit is enqueued on the tail of its ready list. - So, tasks with the same priority cooperate by either yielding or blocking/waiting/sleeping.
- If a task never yields or sleeps, other tasks with the same or lower priority will starve.
- Finally, Tasks with the same priority are initially placed on the Ready Queue associated with that priority in the order they are created.
app\application.c
#include <board.h> /* platform specific */
#include <kapi.h> /* kernel api */
#include <application.h> /* application specific */
int main(void)
{
HAL_Init(); /* board init plls, io, etc. */
otherInits(); /* 3rd party init filesystems, comm stacks etc */
/* RK0 API */
kCoreInit(); /* init minimal core interrupts for the kernel */
kInit(); /* init scheduler */
while(1)
{
/* suggested */
kErrHandler(RK_FAULT_APP_CRASH);
}
}
/*** Declare kernel objects ***/
/* -- Tasks: */
/* this convenience macro */
RK_DECLARE_TASK(t1Handle, Task1, task1StackBuf, 128)
/* expands to:
VOID Task1(VOID *args);
RK_STACK task1StackBuf[128] K_ALIGN(8);
RK_TASK_HANDLE t1Handle;
/*
/* RK_ prefixed macros don’t require a trailing ‘;’ */
/* -- Other objects: e.g., mem allocator, mesg queues, semaphores, etc.,
and any backing storage they need
*/
RK_OBJ_TYPE objInstance;
myAppType_t typeMemPool[N_BLOCKS] K_ALIGN(4);
/* Mandatory function: initialise tasks and other objects */
VOID ApplicationInit(VOID)
{
/* initialise tasks and other kernel objects */
/* low-level scheduler will start when this function returns */
/* use assertions for init calls */
/* see kapi.h */
}
/* Define Tasks */
/* all task objects must be visible for the unit which
task functions are defined */
VOID Task1(VOID* args)
{
K_UNUSE(args);
while(1)
{
/* task will run until preempted by a higher priority task,
blocking or yielding
*/
}
}API Convention
- A kernel call starts with a lowercase
k. Typically it is followed by a kernel object identifier and an action.
/* pend on a semaphore; block indefinitely */
kSemaphorePend(&sema, RK_WAIT_FOREVER);
/* try to acquire a semaphore token; if unsuccessful return immediately */
kSemaphorePend(&sema, RK_NO_WAIT);
/* pend on a semaphore; return after 800 ticks if no token becomes available */
kSemaphorePend(&sema, 800); - When
kis followed by anaction, it operates on the caller task.
kSleepDelay(150); /* sleep-delay the caller task for 150 ticks */- Some calls can act either on the caller or on another task:
/* stores the signal flags of the task identified by task1Handle on ULONG queryVal */
ULONG queryVal;
kTaskFlagsQuery(task1Handle, &queryVal);
/* `NULL` is telling to query the caller's flags */
kTaskFlagsQuery(NULL, &queryVal);- With a few exceptions, kernel calls have a signed return value code (
RK_ERRtype). -
0(RK_ERR_SUCCESS) is a successful operation. - Any negative value indicates an illegal call (invalid parameters were passed, a blocking call within an ISR, etc.).
- A positive value indicates an unsuccessful operation, but it will not lead to a system failure (e.g., any unsuccessful try operation).
Q: Why there is no release yet?
A: A release is serious stuff. It will exist when:
- The system is characterised consistently.
- A seamless test harness for eventual contributors can be pushed;
- and a CI can be pushed.
Note
Expect v0.x.x for a long time, or maybe jump-in to collaborate.
Q: I heard not splitting user-space from kernel-space is bad, lame, last-week, toy-kernel, DOS-like. What you would say?*
A: First, to get some real-time literacy (Buttazzo, Bertolotti, Kopetz, etc.).
Second, not every chip RK0 supports has an MPU. Why? Because there is a demand for them. And I ensure they are not coffee-machines - they are controlling loops on plant floors.
Yes, Privilege levels are still allowed with no MPU. But not because it 'makes sense'. It is because tweaking an architectural feature that depends on the existence of an external co-processor is unfeasible.
An MPU can be extremely useful - it is a Memory Firewall. But it is not free: it has impact on determinism and jitter, coarse memory utilisation, not just on raw performance. Knowing when not to use it is also engineering.
Going further: Safety/security standards (IEC 61508, ISO 26262, DO-178, etc.) with regard to RK0:
-
As stated, RK0 links altogether into a single image**. It is application-specific, hardware-dependent.
-
A firmware update is updating the entire image. There is no 'hosted application'.
A system employing an RT-Executive like RK0: small codebase, static configuration bias, and strictly bounded behaviour, has gains for a certification process: these characteristics make analysis, testing, and traceability simpler. Even formal proofs would be made easier.
(* For the record, DOS was a decent OS for its purposes.)
(** Random funny: can't help remembering these were the exact words Prof Tanenbaum would use to point out why Linux as monolithic was crude.. :D).
Copyright (C) 2025 Antonio Giacomelli | www.kernel0.org