-
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.
...and embedded as we know it.

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.
The example code implements a Synchronisation Barrier - so-called Rendezvous. The Barrier is structured as a Monitor using a Condition Variable model. Note that a Condition Variable is not a primitive (there is no RK_COND_VAR). Alternatively a general (stateless) Sleep Queue is used along with a Mutex Semaphore. There are helpers functions to manipulate any combination of Sleep Queues and Mutexes atomically for signal, wait and broadcast operations - suitable for a Mesa monitor test-loop:
while(condition == false)
{
sleep on event(condition == true);
} Suppose three tasks are cooperating - each one executes part of a job. For every round before making the result available, no task can execute again before all others also are done. We make them 'meet' on a synchronisation point before starting the next round of work.
Knowing that we want to use the application logger facility:
3 solution tasks + 1 logger = 4 tasks.
Each solution task has a different priority (we will use 1, 2, 3). The logger task priority must be lower than any solution task; we set 4 as the lowest priority.
Therefore in kconfig.h we have:
/***[• USER-DEFINED TASKS (NUMBER) ********************************************/
/* !Account for the logger task if using it. */
#define RK_CONF_N_USRTASKS (4)
/***[• MINIMAL EFFECTIVE PRIORITY (HIGHEST PRIORITY NUMBER) ******************/
#define RK_CONF_MIN_PRIO (4)
/***[• SYSTEM CORE CLOCK *****************************************************/
/* If using CMSIS-Core HAL you can set this value to 0, so it will fallback */
/* to the HAL value set at SystemCoreClock. (Not valid for QEMU buildings). */
/* Note CMSIS-Core is not bundled in RK0. */
#define RK_CONF_SYSCORECLK (2000000UL)
/***[• KERNEL TICK ************************************************************/
/* This will set the tick as 1/RK_SYSTICK_DIV millisec */
/* 1000 -> 1 ms Tick, 500 -> 2 ms Tick, 100 -> 10ms Tick, and so forth */
#define RK_CONF_SYSTICK_DIV (100UL)We set the kernel to have a 10ms system tick, 4 user tasks and lowest priority of these tasks is 4.
The fourth user task is the LoggerTask. In app/inc/logger.h we allocate 128 words for its stack. Each Log message has a maximum length of 64 bytes, and there are 16 log buffers on a Memory Partition pool - every logPost(...) call takes a buffer, that is returned as soon as LoggerTask receives it from a queue, and prints the content on stderr. Now the buffer can be reused.
#define CONF_LOGGER 1 /* Turn logger on/off */
#if (RK_CONF_MESG_QUEUE == OFF)
#error "Need RK_CONF_MESG_QUEUE enabled for logger facility"
#endif
#endif
#if (CONF_LOGGER == 1)
#define LOGLEN 64 /* Max length of a single log message */
#define LOGPOOLSIZ 16 /* Number of log buffers */
#define LOG_STACKSIZE 128 /* Size of the stack */
Regarding kernel services, we need:
- Sleep Queues
- Mutexes
- Message Queues (used by the logger facility)
- (Memory Partitions for Dynamic allocation is always ON)
In kconfig.h:
/******************************************************************************/
/********* 3. INTER-TASK COMMUNICATION ****************************************/
/******************************************************************************/
#define RK_CONF_SLEEP_QUEUE (ON)
#define RK_CONF_SEMAPHORE (OFF)
#define RK_CONF_MUTEX (ON)
#define RK_CONF_MESG_QUEUE (ON)
#if (RK_CONF_MESG_QUEUE == ON)
#define RK_CONF_MESG_QUEUE_NOTIFY (OFF)
#define RK_CONF_PORTS (OFF)
#endif
#define RK_CONF_MRM (OFF)Depending on how you structure your application this can vary a little. Here, the main() function is already within the application.c file. If not and both compilation units need to be exposed to the same dependencies you might want to append them in application.h.
In application.c
#include <kapi.h> /* Kernel API */
/* Configure the application logger faciclity here */
#include <logger.h>
#include <bsp.h>
int main(void)
{
BSP_Init(); /* this might setup the lower layer, configure PLLs, etc. depends
on the platform. for QEMU it is not needed. */
/* < any other middleware initialisation might be placed here > */
kCoreInit(); /* Configure and initialise armv6/7M core interrupts needed.
This a RK0 function that works for its target plaftorms, with or without CMSIS-Core HAL */
kInit(); /* initialise internal kernel data structures and start the scheduler */
while(1)
{
kErrHandler(RK_FAULT_APP_CRASH);
}
return (0); /* keep it tight */
}
/* Declare objects needed for each task:
its Handle name, its entry function, stack buffer name and stack size */
#define STACKSIZE 256 /* 1024 Bytes for each stack */
RK_DECLARE_TASK(task1Handle, Task1, stack1, STACKSIZE)
RK_DECLARE_TASK(task2Handle, Task2, stack2, STACKSIZE)
RK_DECLARE_TASK(task3Handle, Task3, stack3, STACKSIZE)
/* Synchronisation Barrier Pattern code */
typedef struct
{
RK_MUTEX lock; /* this lock keeps a single active task in the monitor */
RK_SLEEP_QUEUE cond; /* queue tasks sleep for required==count */
UINT count; /* number of tasks in the barrier */
UINT required; /* number of required tasks */
UINT round; /* increased every time all tasks synch */
} Barrier_t;
VOID BarrierInit(Barrier_t *const barPtr, UINT requiredTasks)
{
kMutexInit(&barPtr->lock, RK_INHERIT);
kSleepQueueInit(&barPtr->cond);
barPtr->count = 0;
barPtr->round = 0;
barPtr->required = requiredTasks;
}
VOID BarrierWait(Barrier_t *const barPtr)
{
UINT myRound = 0;
kMutexLock(&barPtr->lock, RK_WAIT_FOREVER);
/* save round number */
myRound = barPtr->round;
/* increase count on this round */
barPtr->count++;
logPost("[BARRIER: %u/%u]: %s ENTERED ", barPtr->count, barPtr->required, RK_RUNNING_NAME);
if (barPtr->count == barPtr->required)
{
logPost("[BARRIER: %u/%u]: %s WAKING ALL TASKS", barPtr->count, barPtr->required, RK_RUNNING_NAME);
/* reset counter, inc round, broadcast to sleeping tasks */
barPtr->round++;
barPtr->count = 0;
kCondVarBroadcast(&barPtr->cond);
}
else
{
logPost("[BARRIER: %u/%u]: %s BLOCKED ", barPtr->count, barPtr->required, RK_RUNNING_NAME);
/* a proper wake signal might happen after inc round */
while ((UINT)(barPtr->round - myRound) == 0U)
{
/* helper: task sleeps and unlock the mutex (so another task can enter)
atomically */
kCondVarWait(&barPtr->cond, &barPtr->lock, RK_WAIT_FOREVER);
}
}
kMutexUnlock(&barPtr->lock);
}
/* declare barrier object */
Barrier_t syncBarrier;
#define REQUIRED_TASKS 3
#define LOG_PRIORITY 4 /* the lowest priority */
/* MANDATORY Function - initialise declared kernel objects. kCreateTasks will assemble a Task Control Block to each task
using the declared objects for each task and initialise it */
VOID kApplicationInit(VOID)
{
RK_ERR err = kCreateTask(&task1Handle, Task1, RK_NO_ARGS, "Task1", stack1, STACKSIZE, 1, RK_PREEMPT);
K_ASSERT(err==RK_ERR_SUCCESS);
err = kCreateTask(&task2Handle, Task2, RK_NO_ARGS, "Task2", stack2, STACKSIZE, 2, RK_PREEMPT);
K_ASSERT(err==RK_ERR_SUCCESS);
err = kCreateTask(&task3Handle, Task3, RK_NO_ARGS, "Task3", stack3, STACKSIZE, 3, RK_PREEMPT);
K_ASSERT(err==RK_ERR_SUCCESS);
BarrierInit(&syncBarrier, REQUIRED_TASKS); /* initialise barrier (sleep queues and mutexes) */
logInit(LOG_PRIORITY); /* initialise application logger */
}
/* Tasks definition */
VOID Task1(VOID* args)
{
RK_UNUSEARGS
while (1)
{
logPost("Task 1 dispatched. Working...");
kBusyDelay(100); /* simulate work */
BarrierWait(&syncBarrier);
logPost("Task 1 left the barrier!");
kSleep(1); /* suspend so other task can run */
}
}
VOID Task2(VOID* args)
{
RK_UNUSEARGS
while (1)
{
logPost("Task 2 dispatched. Working...");
kBusyDelay(200); /* simulate work */
BarrierWait(&syncBarrier);
logPost("Task 2 left the barrier!");
kSleep(1); /* suspend so other task can run */
}
}
VOID Task3(VOID* args)
{
RK_UNUSEARGS
while (1)
{
logPost("Task 3 dispatched. Working...");
kBusyDelay(300); /* simulate work */
BarrierWait(&syncBarrier);
logPost("Task 3 left the barrier!");
kSleep(1); /* suspend so other task can run */
}
}The result is as follows:
0 ms :: Task 1 dispatched. Working...
1000 ms :: [BARRIER: 1/3]: Task1 ENTERED
1000 ms :: [BARRIER: 1/3]: Task1 BLOCKED
1000 ms :: Task 2 dispatched. Working...
3000 ms :: [BARRIER: 2/3]: Task2 ENTERED
3000 ms :: [BARRIER: 2/3]: Task2 BLOCKED
3000 ms :: Task 3 dispatched. Working...
6000 ms :: [BARRIER: 3/3]: Task3 ENTERED
6000 ms :: [BARRIER: 3/3]: Task3 WAKING ALL TASKS
6000 ms :: Task 3 left the barrier!
6000 ms :: Task 1 left the barrier!
6000 ms :: Task 2 left the barrier!
6010 ms :: Task 1 dispatched. Working...
7010 ms :: [BARRIER: 1/3]: Task1 ENTERED
7010 ms :: [BARRIER: 1/3]: Task1 BLOCKED
7010 ms :: Task 2 dispatched. Working...
9010 ms :: [BARRIER: 2/3]: Task2 ENTERED
9010 ms :: [BARRIER: 2/3]: Task2 BLOCKED
9010 ms :: Task 3 dispatched. Working...
12010 ms :: [BARRIER: 3/3]: Task3 ENTERED
12010 ms :: [BARRIER: 3/3]: Task3 WAKING ALL TASKS RK0 architecture is logically modular and uses the Hansen's idea of Low-Level Scheduler + Executive. The the first is a software enxtension of the CPU and the second defines how calls to the kernel will translate into policies using mechanisms.
While not a Manifesto, RK0 endedup kind of pushing back this multi-programming wannabe trend on KiB RAM RTOSes. It is target applications are not a 96KiB RAM computer dangling online. Old-fashione, RK0 suits closed, application-, domain- and hardware-specific real-time work. The real real-time stuff – not the '''OpenVMS little brother'''.
Every single line written here was written by me. Who cares? A few. That said, RK0 of course has been inspired by other kernels and some routines are kind of staples. If you are blaffed by primitives in uppercase ('because in C they are macros') I can't help.
Copyright (C) 2025 Antonio Giacomelli | www.kernel0.org