Kernel

So, you need a kernel? In this tutorial you will learn how to create and execute processes in BeRTOS and what synchronization facilities you have in your toolbox

Processes

If you have programmed with threads in a desktop operating system, you will find some similarities with BeRTOS processes. Every process is defined by:

  • a function that is executed when the process is running;
  • some user data that the process can use to communicate with other processes;
  • a memory area used for the stack (remember that BeRTOS has a fully static memory model).

Unlike threads, however, processes have are independent from the parent process, they can have a priority (if enabled) and they can be monitored to detect stack overflows.

First example

Enough talking. In this section we are going to create three processes (one the main process plus two extra) that control some LEDs.

First, initialize the scheduler calling proc_init(), then create two processes using proc_new(). Remember that you need to provide some stack space to the process; usually CONFIG_KERN_MINSTACKSIZE should be enough, but read carefully the documentation of this variable if you're using a 16-bit architecture.

You can create the stack space for both processes with the following line:

cpu_stack_t stack1[CONFIG_KERN_MINSTACKSIZE / sizeof(cpu_stack_t)]
cpu_stack_t stack2[CONFIG_KERN_MINSTACKSIZE / sizeof(cpu_stack_t)]

CONFIG_KERN_MINSTACKSIZE is defined to be the memory needed for saving twice all the registers plus 32 variables. The type cpu_stack_t is used to indicate a location on the stack.

Then let's define the entry points for the two processes:

void proc1(void)
{
  int times = 0;
  bool light_on = false;
  while (true)
  {
    light_on = !light_on;
    // light on or off led1
    TURN_LED_ON(1, !light_on);

    ++times;
    timer_delay(20);
    if (times > 30)
      proc_exit();
  }
}

// proc2 is similar to proc1
void proc2(void)
{
  int times = 0;
  bool light_on = false;
  while (true)
  {
    light_on = !light_on;
    // light on or off led2
    TURN_LED_ON(2, !light_on);

    ++times;
    timer_delay(40);
    if (times > 30)
      proc_exit();
  }
}

As you can see, you can define variables inside the process and they will be automatically saved: that's why we defined a stack space a bit larger. Of course you may need more stack space in your application, so feel free to create a larger stack for your processes. You can also create a larger stack for one process only, if you need that.

The macro TURN_LED_ON(led, value) turns the led led on or off depending on value. This macro should be defined for example in hw/hw_leds.h, which contains all hardware specific macros and functions. See ProgramConfiguration for further explanations.

If you need to release the processor, for example in a tight busy loop, call cpu_relax(). In case you are wondering, timer_delay() does exactly the same, so you don't need to call cpu_relax() explicitly in this case.

Remeber: if the entry point of the process reaches the end of the function, the process will quit (just like any plain program). Thus, if you want to create a process that will run forever in your application, you must enclose the function with a neverending loop like while (true) { ... } above. You can still exit the process under certain conditions by using proc_exit().

Now in our main program we just need to create the two processes and set the priority for each of them.

void main(void)
{
  proc_init()
  // other initializations...
  // ...

  Process *p = proc_new(proc1, NULL, sizeof(stack1), stack1);
  proc_setPri(p, -5);

  p = proc_new(proc2, NULL, sizeof(stack2), stack2);
  proc_setPri(p, 10);

  // now wait for both processes to complete
  // note that "main" is itself a process
  ticks_t start = timer_clock();
  while (timer_clock() - start < ms_to_ticks(3000))
  {
    kprintf("main\n");
    timer_delay(500);
  }
}

We create two processes with proc_new(), which takes the entry point, some user data (NULL in this case) and the stack to operate on.

We also define a priority for each of the two processes. Priorities are signed integers, where positive numbers have a higher priority over negative numbers. You should set priorities for your processes in the range -10,+10 (inclusive) to avoid interfering with other system tasks.

We're done. If you run the example, you will note that the second led will light before the first at the start. You can also add some debug prints to see process scheduling.

Process monitor

This process monitors all the other process to check wheter a stack overflow has occurred; in this case you get a warning on the debug serial port.

The monitor is enabled with CONFIG_KERN_MONITOR flag in cfg/cfg_monitor.h, so you can enable it at debug time and remove it when releasing the software.

Synchronization example

You can synchronize processes using semaphores. In this example we will create one process that reads commands from the serial port and puts them in a queue and one process that reads such commands and lights the LEDs.

A common way to synchronize processes is to use semaphores. In our example you would lock the FIFO queue with the semaphore and then access it to read (or write) commands.

static Semaphore command_sem;

static void serialRead(void)
{
  while (1)
  {
    sem_obtain(&command_sem);
    // read data from serial line...
    size_t count = kfile_read(&ser_port.fd, buf, sizeof(buf));
    // ...put the data in the queue
    // ...
    sem_release(&command_sem);
    // let other processes run
    cpu_relax();
  }
}

The code for the second process (the "worker") has the same structure, but instead of reading from serial line, it will get some data from the queue, interpret it and it will finally light the LEDs.

I would like to stress that BeRTOS kernel is cooperative, so you must explicitly release the cpu to allow other processes to run. The function sem_obtain() does it for you, while sem_release() does not. You can also call sem_attempt() if you don't want to wait for a semaphore to get free. Have a look at semaphore API for further explanations.

Messages

TODO: use Msg facilities to communicate between the processes.

Have a look at the messages example.