Exploring Edison - Almost Real Time Linux
Written by Harry Fairhead   
Tuesday, 20 October 2015
Article Index
Exploring Edison - Almost Real Time Linux
Scheduling Commands
Realtime FIFO Scheduling

 

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 Edison 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 two 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 Edison 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 using mraa: 

#include <stdio.h>
#include <stdlib.h>
#include "mraa.h"
#include <sched.h>
int main() {
 mraa_gpio_context pin = mraa_gpio_init(13);
 mraa_gpio_dir(pin, MRAA_GPIO_OUT);
 for (;;) {
  mraa_gpio_write(pin, 0);
  mraa_gpio_write(pin, 1);
 }
 return MRAA_SUCCESS;
}

As we have already discovered we can generate pulses at around 15 microseconds wide using this method.

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

Inspecting about one second's worth of readings with a logic analyser reveals that the pulse length can be as large as 100 microseconds:

 

Average 14.9
Max 106.9
Min 14.13

 

A frequency count of pulse sizes is also interesting:

micro seconds   no of pulses
0 0
10 0
20 65700
30 58
40 0
50 0
60 84
70 12
80 0
90 0
100 0
200 2
10000 0

 

You can see that there were nearly 100 pulses in the 60-70 microsecond range and just two in the 100-200 range. 

This might not seem too bad, but if the CPU is loaded just a little then things look much worse.

Add eight CPU-hogging processes and the results are:

Average  15.4863283326
Max  10090.0625
Min  14.1875

 

The frequency table is also interesting:

micro seconds no of pulses
0 0
10 0
20 65701
30 56
40 2
50 0
60 35
70 49
80 10
90 0
100 0
20000 4
30000 0
0

 

Yes, with these conditions there were four instances of greater than 10 millisecond pulses. This means that the program was suspended for 40 milliseconds in a one second sample. 

If you try the same thing using fast mraa memory mapping you will find that you get few interruptions on a lightly loaded system as long as you don't make a system call like usleep. However, if the loading is increased the thread is suspended for increasingly large amounts of time. 

<ASIN:B00ND1KH42@COM>

<ASIN:B00ND1KNXM>

<ASIN:B00ND1KH10>



Last Updated ( Tuesday, 14 March 2017 )