Real-time programming with RTEMS

Frank Singhoff
Université of Brest



I. Installing the compilers and simulators



The material you need to do this set of exercises are available here:
  1. Directory RTEMS-SOFTWARES : contains the required software to do the exercises.
  2. Directory RTEMS-DOCS : contains a set of pdf files that are part of the RTEMS standard documentation (may help for the signature of each function you will need).
  3. Directory RTEMS-EXAMPLES : a set of RTEMS C/Ada examples.




To install the software required for this laboratory:
  1. Open a shell. We assume that you are in your home directory, called in the sequel $MY_HOME_DIR.
  2. Extract the software with :

    tar xvfz rtems_USTH.tar.gz

  3. Thanks to the tar command, a rtems_USTH directory has been created.
  4. Edit the file $MY_HOME_DIR/rtems_USTH/rtems.bash and assign to the $INSTALL_DIR variable the full directory name where rtems_USTH.tar.gz has been extracted.
  5. Edit and upate in the same way the file $MY_HOME_DIR/rtems_USTH/adalinux.bash




II. Programming RTEMS with C/POSIX






Exercise 1 : Cross-compiling

In this exercise, we experiment the C cross-compiler for RTEMS. On Linux, we will generate programs that are able to run on SPARC/Leon processor architectures.
Download and save the following file and put it in a directory called EXO1.
  1. Prior to compile any RTEMS program, we must put the RTEMS C cross-compiler in your $PATH shell variable. To do so, launch the following command in your working shell window:
    source $MY_HOME_DIR/rtems_USTH/rtems.bash
  2. Now, you can compile this first example of C program with the following command:
    make clean
    make

    assuming you are in the EXO1 directory.
  3. If everything is OK, you should find a file named o-optimize/sched.exe.
  4. Launch the command file o-optimize/sched.exe : what is the meaning of the displayed information?
  5. Can we directly launch the binary o-optimize/sched.exe from your Unix Shell?
  6. Now, we will use the Leon processor simulator to run this program. This simulator is gracefully provided by the Gaisler Research company (See http://www.gaisler.com). To launch this simulator, call the following command from your Unix Shell:

    $tsim
    tsim>


  7. Now, load the binary into the memory of the simulator with the load command:

    tsim>load o-optimize/sched.exe


  8. Then, finally, run the program with the go command :

    tsim> go
    resuming at 0x40000000
    Max priority for SCHED_FIFO = 254
    Min priority for SCHED_FIFO = 1
    ...
  9. Answer to these questions:





Exercise 2 : Fixed priority/Rate Monotonic scheduling and periodic tasks



In this exercise, we experiment the fixed priority scheduling of RTEMS. Download and save the following file and put it in a directory called EXO2.


Question 1:





Question 2:


  • Now, we work with a new version of the init.c file to experiment a preemptive Rate Monotonic scheduling of periodic tasks. Download and save this new file in the EXO2 directory.
  • We assume a set of two periodic threads defined with the following parameters:

  • Draw by hand the scheduling sequence of this thread set on the hyper-period. We assume a preemptive Rate Monotonic scheduling. Do the thread meet their deadlines?
  • We now try to write a C program that is running this periodic thread set. For such a purpose, we must fill the init.c file in order to schedule this thread set according to Rate Monotonic. The sourcecode you have to fill is shown by several stars. Each thread T1 and T2 must run the function periodic_task. The period is sent to each thread with the thread argument. Décrire les deux étapes : gestion des priorités avec les requirements et mise en oeuvre des dates de réveil périodique

    To implement this program, we need to use the nanosleep and the clock_gettime functions to ensure the periodic releases of T1 and T2. Arithmetic operations on timespec structs can be made with C functions of files ts.h and ts.c.

    Finally, the two threads T1 and T2 must be activated for their first activation at the same time. The critical_instant variable stores the first release time for T1 and T2. To reach this critical instant, the initialization thread (thread running POSIX_init function) must not be preempted before its completion (when it reaches its pthread_exit() statement). When the initialization thread is completed, T1 and T2 must start their execution simultaneously. To be sure that the initialization thread is completed without being preempted, you must correctly assign priorities to the 3 threads.




    Exercise 3 : Synchronization : mutex vs counting semaphore




    Question 1:


    During this question, we experiment the synchronization tools provided by RTEMS. First, download and save the following file and put it in a directory called EXO3. This program contains two threads reading a text stored in a shared memory. The text is read character by character and is written to a second memory area.
    1. Compile and test the program. What can you see? Explain this behavior.
    2. Modify this program to correct this wrong behavior. For such a purpose, you can use the pthread_mutex_lock and pthread_mutex_unlock functions or the sem_wait and sem_post functions.



    Question 2:


    For this second question, download and save this new file and put it in the EXO3 directory.
    1. Compile and test this program. This program implements a producer/consumer synchronization. The buffer stores only one character. Producers write data in the buffer character by character. Consumers read data from the buffer character by character. The buffer is built with two counting semaphores called number_of_full_positions and number_of_empty_positions (respectivly initialized with the values 0 and 1). Those semaphores model/store the number of busy and empty positions into the buffer. These semaphores allow you to block producers when no empty locations exist in the buffer and respectively to block consumers when no full locations exist in the buffer.
      The algorithm of a producer is :
      while(1)
      {
         P(number_of_empty_positions);
         Produce and put a character into the buffer;
         V(number_of_full_positions);
      }
      


      The algorithm of a consumer is :
      while(1)
      {
         P(number_of_full_positions);
         Read and display a character from the buffer;
         V(number_of_empty_positions);
      }
      
    2. Change this program in order to define a buffer that is able to store upto 4 characters (e.g. a buffer composed of 4 positions). The buffer must be a FIFO buffer.
    3. Change your program in order to allow several producers and several consumers to handle your 4 positions buffer.


    III. Programming RTEMS with Ada

    In the previous exercises, we have seen that C programs for RTEMS were different of classical Unix C programs : C real-time programs are usually not fully portable from one real-time operating system to another. In this second part of the laboratory, we will write Ada programs. Ada provides a higher level of portability for real-time programs and also enforce a higher level of reliability, thanks to its safety mechanisms (e.g. strong type checking). To introduce these Ada features, we will work with two runtimes:

    III.1 Introduction to Ada : sequential programming




    For exercises 4 to 10, in order to compile and run Ada programs on Linux, in a shell, launch the following command:

    source adalinux.bash




    Exercise 4 : hello world

    Save the file hello.adb. Compile this program with the following command:

    gnatmake -g hello



    What are the different files generated by gnatmake ?


    Exercise 5 : input/output

    1. Download, compile and test the following file: readint.adb.
    2. Change readint.adb in order to read an integer from the keyboard and to print it on the screen.
    3. In this changed program, what does the program do when we give it a string instead of an integer?
    4. Write a package called my_io which contains the source code to print to the screen/read from keyboard float and integer data. Change the program of question 3 in order to use your package my_io.


    Exercise 6 : compute average of floats


    Write a program that:



    Exercise 7 : generic package

    Download and save these files: stacks.ads, stacks.adb, and the main procedure test_stack.adb. These files implement a polish calculator.

    1. Compile and test these programs.
    2. Add three procedures :
      1. The first one implements the divide operator.
      2. The second one allows to reverse the order of the data stored in the stack.
      3. The last one allows to display to the screen the data stored in the stack.





    III.2 Concurrency with Ada


    Exercise 8 : declaring a task

    Write an Ada program :

    1. Which defines a task type element.
    2. Which declares 10 tasks of type element.
    3. The body of the tasks type element must display a message to the screen every second.


    Exercise 9 : tasks with entries : implementing a mutex


    Write an Ada program composed of two anonymous tasks : task tick and task tock. Each task handles a common/global counter. The counter is a Integer variable declared in the main procedure (this variable is shared by all tasks). Each task loops on the following statements:
    1. Increment the counter.
    2. Display the value of the counter and displays either "tick" or "tock".
    3. Wait for one second before starting a new iteration.
    The main procedure contains only one statement : the initialization of the counter to zero.

    Questions:

    1. Compile and test the program.
    2. How many tasks your program contains?
    3. Can you say when the tasks start to run ?
    4. Can you say when the main procedure is terminated?
    5. This program raises a very classical concurrency problem. What is this problem?

      To give of solution to the problem of the question 5, we would like to change the program. We add a new task called mutex. This task has two entries allowing a rendez-vous with the two other tasks : these two entries, called P and V have no parameter. This task should allow to implement critical sections on shared resources.

    6. Propose a source code for this third task. Explain how it works.
    7. Update the previous program in order to ensure critical section of the shared counter.




    Exercise 10 : protected types: producer and consumer

    In this program, we implement a producer/consumer synchronization. These consumers and producers share integer data by a buffer, which is a simple integer table. This table can store 4 integers. The buffer is managed according to a FIFO policy : the first inserted/produced integer will also be the first to be removed/consumed.

    1. In a first step, write an Ada program composed of only one producer and one consumer. For such a purpose, you must use :
      • The Semaphores package (both files Semaphores.ads and Semaphores.adb). This protected type implements a counting semaphore in Ada. You have a program that shows how to use this package in order to build a critical section between several tasks.
      • In your program, you must define two counting semaphores called number_of_full_positions et number_of_empty_positions (respectively initialized with the values 0 and 4). Those semaphores model/store the number of busy and empty positions into the buffer. These semaphores allow you to block producers when no empty location exist in the buffer and respectively to block consumers when no full locations exist in the buffer.
      • The algorithm of a producer is :
        loop
           P(number_of_empty_positions);
           Produce and put the integer into the buffer;
           V(number_of_full_positions);
        end loop;
        
      • The algorithm of a consumer is :
        loop
           P(number_of_full_positions);
           Read and display an integer from the buffer;
           V(number_of_empty_positions);
        end loop;
        
    2. Change your program in order to allow several producers and several consumers to handle the buffer.
    3. From a practical point of view, Ada programmers do not use semaphores to implement a semaphore. They usually directly implement the synchronization protocol into a protected object. Write a protected object that implements a FIFO buffer of 4 integers that can be handled by several producers and consumers. This protected object must provide two subprograms:
      1. A first sub-program that allows a producer to store a new integer in the buffer.
      2. A second sub-program that allows a consumer to read/remove an integer from the buffer.
      3. Here is a possible protected object specification for this question:
           Buffer_Size : constant Integer :=4;
           type Buffer_Type is array (1..Buffer_Size) of Integer;
        
           protected type Synchronized_Buffer is
              entry Write(Val : in Integer);
              entry Read(Val : in out Integer);
           private
              Buffer_Data : Buffer_Type;
              Index_Cons : Integer :=1;
              Index_Prod : Integer :=1;
              Number_Element : Integer:=0;
           end Synchronized_Buffer;
        




    Exercise 14 : a pipe-line of a set of Ada tasks

    We would like to write an Ada program to convert base 10 integers to base 2 integers. To convert, we use the method which consists in dividing several times the base 10 integer by 2. To increase efficiency, we would like to implement this program with a pipe-line of Ada tasks. The task interface is the following:

      type Stage; 
      type Stage_Ptr is access Stage; 
    
      task type Stage is
          entry Next_Stage (
                A_Task : in     Stage_Ptr ); 
          entry Divide (
                Value  : in     Integer ); 
      end Stage;
    


    Each stage/task contains two entries:
    1. An entry called Next_Stage: this entry allows a stage n to store a pointer to the next stage n+1. This entry is only used at program initialization in order to build the pipe-line.
    2. An entry called Divide: this entry of a stage n is called by the stage n-1. This entry does the following statements :
      • It divides by 2 the received data and displays to the screen the remainder (0 or 1).
      • After division, if the result is greater than 0, the current stage must send the result to the next stage to continue the computation.
      • After division, if the result is greater than 0 and if the data has reached the last stage, the program must display an error message.
      • Finally, after division, if the result is equal to 0, it means that the computation is completed.


    Write the body of the Stage task and a main procedure to use it. The main procedure must :



    III.3 Real-time programming with Ada on RTEMS





    Exercise 11 : cross compiling of an Ada program


    In this exercise, we will compare the "Linux/Intel 386" Ada runtime and the "RTEMS/Leon" Ada runtime. Download and save the following file. Put it in a directory called EXO11.

    1. Compile the hello.adb program with the command gnatmake -g hello.adb.
    2. Launch the command file hello : what is the meaning of the displayed information?
    3. Launch hello and check that the program works well on Linux.


    4. Now, we will run the same program on a RTEMS and Leon/Sparc processor. For such a purpose, launch the source rtems.bash command, as we made it to compile our first C programs. Now you can compile C or Ada programs for RTEMS.
    5. Compile again the hello.adb program, but now with the following command:
      make clean
      make
      
    6. This command has built a new executable file called o-optimize/hello.exe.
    7. Launch the file o-optimize/hello.exe. What is the meaning of the displayed information?
    8. Can we directly launch the binary o-optimize/hello.exe from your Unix Shell?
    9. Then, run o-optimize/hello.exe on the tsim simulator as follow:
      $tsim
      tsim>
      tsim>load o-optimize/hello.exe
      tsim>go
      Hello world
      



    Exercise 12 : fixed priority scheduling with Ada


    Remind that to do this exercise, you must use the RTEMS cross-compiler for the Sparc/Leon processor (available from rtems.bash) and the tsim simulator.

    In this exercise, we will experiment real-time scheduling of an Ada program on a RTEMS target. First, download this file and save it in a directory called EXO12.

    1. Edit the gnat.adc file. What is the meaning of each pragma that is present in this file?
    2. Edit the scheduling.adb file to assign a 12 priority level to task Task_A, a 11 priority level to task Task_B and a 13 priority level to the main procedure.
    3. Compile scheduling.adb.
    4. Test your program with the simulator and see the generated scheduling.
    5. Change your program in order to assign an equal level of priority to all tasks. Compile and run this new program.
    6. Add the statement delay 1.0 in the loop of each task body. What does this statement do? Compile and run this last program.


    Exercise 13 : implementing periodic tasks scheduled according to Rate Monotonic

    Remind that to do this exercise, you must use the RTEMS cross-compiler for the Sparc/Leon processor (available from rtems.bash) and the tsim simulator

    First, download this file and save it in a directory called EXO13.

    1. We assume the following task set :
      • Task T1 : period=1 second, capacity=10 millisecond.
      • Task T2 : period=500 millisecond, capacity=10 millisecond.
      • Task T3 : period=250 millisecond, capacity=10 millisecond.
      • Deadlines are equal to periods.
    2. Assuming we use a preemptive Rate Monotonic scheduling, can you prove that this task set will meet its deadlines? Give the scheduling sequence of this tasks set (compute this scheduling sequence by hand).
    3. We now write an Ada program that implements the previous periodic task set. You have to fill the files periodic.adb and generic_periodic_task.adb for such a purpose. The parts of the program you have to fill are shown by several stars:
      1. The file periodic.adb is the main procedure and has to create T1, T2 and T3. periodic.adb must also ensure that a critical instant will occur (e.g. T1, T2 and T3 must start at the same time, after completion time of the main procedure). First_Release_Time stores the first release time for T1, T2 and T3. Subprograms Program_T1, Program_T2 and Program_T3 are the code each task has to run at each of its release time.
      2. The generic_periodic_task.adb generic unit implements a periodic task. This generic package has to be instantiated three times : for T1, T2 and T3. For each task, this generic unit is parametrized by its period, its priority, its first release time and also the subprogram the task has to run each time the task is released.
    4. Fill periodic.adb : you must instantiate the 3 tasks T1, T2 and T3.
    5. Fill generic_periodic_task.adb : you must provide a body for the periodic task. In this task body, you must add the statements that allow the task to run 5 iterations. For each iteration, the task must run its code, compute and wait for its next periodic release time.
    6. Compile and test your program with tsim.



    V. Project 2013/2014 : implementing multi-processor scheduling algorithms



    The goal of this project is to implement a software to simulate the scheduling of a set of periodic task on multiprocessors. We give you a first release of this simulator. This software is composed of several Ada packages available in this directory.

    To compile and run this software, you must use the linux compiler that is available with the following command:

    source adalinux.csh.



    This software must be run on linux.

    This software is composed of the following packages:

    The main procedure main is an example of use of the simulator: This main procedure defines 3 tasks that are scheduled by a preemptive Rate Monotonic scheduler on a uniprocessor system.

    Work to do for this project:

    For this project: Question 1 : test of the current simulator implementation

    1. We assume a set of three periodic tasks defined by the following parameters S1=S2=S3=0, P1=5, C1=1, P2=10, C2=3, P3=30 et C3=8. Draw the scheduling sequence according to a preemptive Rate Monotonic scheduling. Is this task set schedulable?
    2. Change main.adb to run the task set of the previous question, compile and test your program. Check that the scheduling you computed by hand is similar to the one computed by the simulator.
    3. In the main procedure main.adb and in the file scheduler.adb, can you find :
      • Where and how the tasks of the system are initialized.
      • Where and how the task periodic releases are implemented.
    Question 2 : implementing global preemptive Rate Monotonic for a two cores processor

    We now assume that the task set is scheduled on a processor with two cores. We want to apply a global preemptive scheduling. Update the set of packages to implement this new scheduling method into the simulateur. A global preemptive Rate Monotonic works like as follow (see the lecture on global multiprocessor real-time scheduling for further details):
    1. At the same time, the scheduler can run two tasks if two tasks are ready to run. One task is run on each core.
    2. At any time, the two tasks running on the cores are the ready tasks which have the smallest period.
    3. If the two cores are busy and if a task A is released which has a smallest period than the two tasks running on the cores, then, task A must preempt the task which has the larger period.
    4. A task can migrate from one core to another one. Two kinds of migrations exist : 1) when tasks can migrate at any time 2) when tasks can migrate only at the release time of the task.
    Implement the global preemptive Rate Monotonic scheduler with the two types of migration. Test your program with at least two task sets that you will describe in your report and say which migration policy is better for each task set example. You must send me your program with the task sets you used to test your program. Your solution must also be explained in your report.

    Question 3 : implementing global preemptive EDF for a two cores processor

    Global scheduling can be applied with any kind of scheduler. For example, We can adapt preemptive EDF as a global preemptive scheduler as follow (again, we assume here a two cores processor) :
    1. At the same time, the scheduler can run two tasks if two tasks are ready to run. One task is run on each core.
    2. At any time, the two tasks running on the cores are the ready tasks which have the smallest dynamic deadline.
    3. If the two cores are busy and if a task A is released which has a dynamic deadline smaller than the two tasks running on the cores, then, task A must preempt the task which has the largest dynamic deadline.
    4. A task can migrate from one core to another one. Two kinds of migrations exist : 1) when tasks can migrate at any time 2) when tasks can migrate only at the release time of the task.
    Implement the global preemptive EDF scheduler with one of the two types of migrations. Test your program with at least two task sets that you will describe in your report. You must send me your program with the task sets you used to test your program. Your solution must also be explained in your report.

    VI. Project 2012/2013 : implementing an aperiodic server



    The goal of this project is to implement a software to simulate the scheduling of a set of aperiodic task servers such as the one we have seen during the lecture : the polling server. We give you a first release of this simulator. This software is composed of several Ada packages available in this directory.

    To compile and run this software, you must use the linux compiler that is available with the following command:

    source adalinux.csh.



    This software must be run on linux.

    This software is composed of the following packages:

    The main procedure main is an example of use of the simulator: This main procedure defines 3 tasks that are scheduled by Rate Monotonic.

    Work to do for this project:

    For this project: Question 1 : test of the current simulator implementation

    1. We assume a set of three periodic tasks defined by the following parameters S1=S2=S3=0, P1=5, C1=1, P2=10, C2=3, P3=30 et C3=8. Draw the scheduling sequence according to a preemptive Rate Monotonic scheduling. Is this task set schedulable?
    2. Change main.adb to run the task set of the previous question, compile and test your program. Check that the scheduling you computed by hand is similar to the one computed by the simulator.
    3. In the main procedure main.adb and in the file scheduler.adb, can you find :
      • Where and how the tasks of the system are initialized.
      • Where and how the task periodic releases are implemented.
    Question 2 : implementing the polling server into the simulator

    Update the set of packages to implement the polling server to schedule aperiodic tasks with periodic tasks. Remind that the polling server works like this (see page 25 of the lecture on real-time scheduling):
    1. The server run aperiodic tasks arrived before the server activation.
    2. It does not use processor if no aperiodic task is present : the server capacity is lost if no aperiodic task is present.
    3. The server stops aperiodic execution when its capacity is exhausted.
    Test your program with at least two task sets that you will describe in your report. You must send me your program with the task sets you used to test your program. Your solution must also be explained in your report.

    Question 3 : implementing a new aperiodic task server

    We now request you to implement a new aperiodic server algorithm in the simulator. This aperiodic server is described in this article. You must read this article, understand this scheduler and implement it in the simulator. This article presents several schedulers : the polling server, the priority exchange server and the deferrable server. You can choose to implement either the priority exchange server or the deferrable server.

    For this last question, you must :

    VII. Project 2011/2012 : implementing a real time scheduling simulator



    The goal of this project is to implement a software to simulate the scheduling of a set of task with classical real-time schedulers such as EDF or Rate Monotonic. This software is composed of several Ada packages available in this directory.

    To compile and run this software, you must use the linux compiler that is available with the following command:

    source adalinux.csh.



    This software must be run on linux.

    This software is composed of the following packages:

    The main procedure example and the package my_subprograms is an example of use of the simulator:
    1. my_subprograms contains the code each task has to run for each release. For this example, the code only contains a display on the screen.
    2. The main procedure example defines 3 tasks that are scheduled by Rate Monotonic.


    Work to do for this project:

    For this project: Question 1 : test of the current simulator implementation

    1. We assume a set of three periodic tasks defined by the following parameters S1=S2=S3=0, P1=5, C1=1, P2=10, C2=3, P3=30 et C3=8. Draw the scheduling sequence according to a preemptive Rate Monotonic scheduling. Is this task set schedulable?
    2. Compile and run example.adb. Check that the scheduling you computed by hand is similar to the one computed by the simulator.
    3. In the main procedure example.adb, can you find :
      • Where and how the tasks of the system are initialized.
      • Where and how the task periodic releases are implemented.
    Question 2 : implementing EDF into the simulator

    Add a new procedure in the simulator (in the package user_level_schedulers) to compute an EDF scheduling. Your program must support the scheduling of both periodic and aperiodic tasks. Do not forget to provide task sets to show how works your solution. Test your program with several task set. You must send me your program with the task sets you used to test your program. Your solution must also be explained in your report.

    Question 3 : implementing a new scheduler into the simulator

    We now request you to implement a new algorithm in the simulator. The algorithm to implement is called MUF. This scheduler is described in this article. You must read this article, understand this scheduler and implement it in the scheduler.

    For this last question: