Architecture

Kernel components, memory model, boot sequence and the reference ports.

The kernel owns the boot process, the scheduler and the hardware abstraction layer. Application code runs as tasks.

Source layout

bedrock-core/
├── include/bedrock/   Public headers
├── kernel/            Scheduler, tasks, time, IPC, panic
├── arch/
│   ├── arm-cortex-m/  Cortex-M3 port
│   └── host-x86-64/   Linux process port for tests
├── boards/
│   └── qemu-cortex-m3/  Linker script and defconfig
├── lib/               Fixed-block pool allocator
├── examples/          Example application and test programs
├── docs/              Documentation, EN and RU
├── 3rd/tools/         chorus and kconfig-tools submodules
├── Kconfig
└── chorus.build

Scheduler

kernel/br_sched.c

  • One ready queue per priority level, CONFIG_NUM_PRIORITIES in total. Priority 0 is the highest.
  • The highest-priority ready task runs. Tasks of equal priority are kept in FIFO order and rotate when their time slice expires.
  • The slice is CONFIG_RR_TIME_SLICE_US. The port’s timer interrupt calls br_sched_tick() with the elapsed time. If the slice is used up and another task of the same priority is ready, the scheduler switches. Otherwise the slice is renewed.
  • br_sched_lock() and br_sched_unlock() nest. Rescheduling is skipped while the lock depth is above zero.
  • Before each switch the outgoing task’s stack canary is checked with br_hal_check_stack_overflow().

Tasks

kernel/br_task.c

  • TCBs are stored in a static array of CONFIG_MAX_TASKS entries.
  • br_task_create() takes the first slot in state BR_TASK_INACTIVE. br_task_delete() returns a slot to that state.
  • States: INACTIVE, READY, RUNNING, BLOCKED, SUSPENDED.
  • br_kernel_init() creates an idle task at priority CONFIG_NUM_PRIORITIES - 1 with a stack of CONFIG_DEFAULT_STACK_SIZE bytes.
  • The word at the lowest address of each task stack is set to 0xDEADBEEF and used as the canary.

Time

kernel/br_time.c

  • br_time_t is a 64-bit count of microseconds.
  • Sleeping and timed-out tasks are kept in one list sorted by wake time. Each TCB has a separate link (sleep_next) for this list, so a task can be in an IPC wait queue and the sleep list at once.
  • After every change to the list the kernel calls br_hal_timer_set_alarm() with the earliest wake time, or br_hal_timer_cancel_alarm() when the list is empty.
  • When the alarm fires, the port calls br_time_alarm_handler(). It moves every expired task to its ready queue with result BR_ERR_TIMEOUT, reprograms the alarm and reschedules.

IPC

kernel/br_ipc.c

Object Behaviour
Semaphore Counting, with a maximum. give wakes the first waiter instead of incrementing when one exists.
Mutex Owned lock. When a higher-priority task blocks on it, the owner is raised to that priority until unlock. Ownership passes directly to the first waiter.
Message queue Ring buffer of fixed-size messages in caller memory. Separate wait queues for senders and receivers.

Wait queues are ordered by priority. Tasks of equal priority are kept in arrival order.

All blocking calls take a timeout. 0 returns immediately. BR_TIME_INFINITE waits without a timeout. Any other value also inserts the task into the sleep list.

Memory

  • The kernel does not call malloc.
  • Task stacks, message queue buffers and IPC objects are allocated by the caller, statically or on a stack.
  • lib/br_pool.c provides a fixed-block allocator over a caller buffer. Pool descriptors come from a static array of BR_POOL_MAX_POOLS entries.

Boot sequence

  1. Reset_Handler copies .data from flash, zeroes .bss and calls main().
  2. main() calls br_kernel_init(), which marks all TCBs inactive, calls br_hal_board_init() and br_hal_timer_init(), initializes the scheduler and creates the idle task.
  3. The application creates its tasks.
  4. br_kernel_start() starts the highest-priority ready task and does not return.

Cortex-M3 port

arch/arm-cortex-m/, boards/qemu-cortex-m3/

Target: QEMU lm3s6965evb, 256 KB flash at 0x00000000, 64 KB SRAM at 0x20000000.

  • br_hal_stack_init() builds an exception frame: xPSR, PC, LR, R12, R3 to R0, with arg in R0, followed by zeroed R4 to R11. LR points to a handler that loops forever, so a task entry function that returns stops in that loop.
  • br_hal_context_switch() stores the two stack pointer addresses and pends PendSV. PendSV_Handler saves and restores R4 to R11 and swaps PSP.
  • br_hal_start_first_task() enters the first task through an svc 0 exception.
  • Interrupt control uses PRIMASK. br_hal_in_isr() reads the active vector from SCB->ICSR.
  • UART0 at 0x4000C000, polled, transmit only.
  • br_hal_panic() disables interrupts, prints the message, file and line to UART0, then halts in a wfi loop.

Timer

The time source is SysTick with a reload value of 0xFFFFFF, running from the core clock. The microsecond counter is advanced on every SysTick wrap.

br_hal_timer_set_alarm() only records the target time. The alarm and the round-robin tick are both checked in SysTick_Handler, which runs once per wrap. At the default 16 MHz clock that is about every 1.05 s. Sleep wake-ups and time-slice rotation on this port are therefore rounded up to the wrap period.

Host port

arch/host-x86-64/

Runs the kernel as a Linux process for testing.

  • Tasks are ucontext_t contexts switched with swapcontext.
  • Time is read from CLOCK_MONOTONIC.
  • SIGALRM from setitimer(ITIMER_REAL) acts as the timer interrupt.
  • Interrupt disable and restore block and unblock SIGALRM with sigprocmask.