Article Index

 

Setting Scheduling Priority

This sounds like chaos but if you think about it for a moment and start simply you will see that it provides most of what you are looking for. You are in full control of the Raspberry Pi and so you can determine exactly how many non-zero priority threads there are. By default all of the standard threads are priority zero and scheduled by the normal scheduler.

Now consider what happens if you start a FIFO scheduled thread with priority 1.

It starts and is added to the end of the priority 1 queue. Of course it is the only priority 1 process and so it starts immediately on one of the cores available. If the process never makes a call that causes it to wait for I/O say or become blocked in some other way then it will execute without being interrupted by any other process. 

In principle this should ensure that your process never delivers anything but its fastest response time. 

This is almost but not quite true. 

There are more complex situations you can invent with threads at different priorities according to how important they are but this gets complicated very quickly.

A modification to the SCHED_FIFO scheduler is SCHED_RR - for Round Robin. In this case everything works as for SCHED_FIFO except that each running process is only allowed to run for a single time slice. When the time slice is up the thread at the head of  the priority queue is started and the current thread is added to the end of the queue. You can see that this allows each thread to run for around one time slice in turn - which is a round robin scheduler. 

In most cases for real time programming with the Raspberry Pi the SCHED_FIFO scheduler is what you need and in its simplest form.

The complete set of scheduling commands are: 

  • sched_setscheduler Set the scheduling policy and parameters of a specified thread
     
  • sched_getscheduler Return the scheduling policy of a specified thread
     
  • sched_setparam Set the scheduling parameters of a specified thread
     
  • sched_getparam  Fetch the scheduling parameters of a specified thread
     
  • sched_get_priority_max Return the maximum priority available in a specified scheduling policy
     
  • sched_get_priority_min Return the minimum priority available in a specified scheduling policy
     
  • sched_rr_get_interval Fetch the quantum used for threads that are scheduled under the "round-robin" scheduling policy
     
  • sched_yield Cause the caller to relinquish the CPU, so that some other thread be executed
     
  • sched_setaffinity  Set the CPU affinity of a specified thread
     
  • sched_getaffinity  Get the CPU affinity of a specified thread
     
  • sched_setattr Set the scheduling policy and parameters of a specified thread; this Linux-specific system call provides a superset of the functionality of sched_setscheduler and sched_setparam
     
  • sched_getattr Fetch the scheduling policy and parameters of a specified thread; this Linux-specific system call provides a superset of the functionality of sched_getscheduler and sched_getparam.

The scheduling types supported are:

SCHED_OTHER the standard round-robin time-sharing policy

SCHED_BATCH for "batch" style execution of processes

SCHED_IDLE for running very low priority background jobs

SCHED_FIFO a first-in, first-out policy

SCHED_RR a round-robin policy

where only the final two are real time schedulers. 

Also notice that all of the scheduling function return an error code which you should check to make sure thing have worked. For simplicity the examples that follow ignore this advice. 

How Bad Is The Problem?

The first question we need to answer is how bad the situation is without real time scheduling.

This is not an easy question to answer because it depends on so many factors. Take, for example, a very simple program which toggles a GPIO line as fast as it can:

#include <bcm2835.h>
#include <stdio.h>
#include <sched.h>

int main(int argc, char **argv) {
 if (!bcm2835_init())
   return 1;
 bcm2835_gpio_fsel(RPI_GPIO_P1_07 ,
 BCM2835_GPIO_FSEL_OUTP);
 while (1) {
  bcm2835_gpio_write(RPI_GPIO_P1_07 , HIGH);
  bcm2835_gpio_write(RPI_GPIO_P1_07 , LOW);
 }
 bcm2835_close();
 return 0;
}

As we have already discovered we can generate pulses less than 1 microseconds wide using this method.

The real question is how does the scheduler change this pulse length by interrupting your program?

The Pi Zero Single Core 1GHz

The Pi Zero has a single core ARM processor so it can only do one thing at a time and makes a perfect worst case example. 

If you run the program with a logic analyzer connected and with a freshly booted Pi Zero and no other tasks running then you will see the usual moderately rough pulse train with the regular 0.08 to 0.1 microsecond pulses with .5 microsecond pulses every 1.25ms. 

However if you set the logic analyzer to capture long pulses even if the Pi isn't loaded you will see, after a few seconds, pulses as long as 0.5ms. 

 

ana1

 

Even on a lightly loaded Pi Linux will interrupt the running task every few seconds. 

On a Pi Zero running even one demanding task the story is worse. 

ana2

 

In this case the task is a simple infinite loop that will use as much of the CPUs time as it is allowed to. The result is a regular 10ms pulse. This is due to the Linux scheduler dividing up the time so that both tasks get a reasonably equal share of the single CPU. If the Pi you are using has more than one core then this behavior will occur when the number of equal priority tasks equals the number of cores. 

Yes, with these conditions the program can produce 10ms pulses mixed in with the 0.1ms pulses.