Real-time programming RTEMS with C/POSIX

Frank Singhoff
Université of Brest, Lab-STICC UMR CNRS 6285








Exercise 1 : Cross-compiling

A documentation for those exercises is available here:
  1. 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).
  2. Directory RTEMS-EXAMPLES : a set of RTEMS C/Ada examples.
  3. Directory RTEMS-SOFTWARES : RTEMS software to be run elsewhere.


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 rtems.bash

In exercise 1, 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. Compile this first example of C program with the following command:
    make clean
    make

    assuming you are in the EXO1 directory.
  2. If everything is OK, you should find a file named o-optimize/sched.exe.
  3. Launch the command file o-optimize/sched.exe : what is the meaning of the displayed information?
  4. Can we directly launch the binary o-optimize/sched.exe from your Unix Shell?
  5. Now, we will use a 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>


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

    tsim>load o-optimize/sched.exe


  7. Then, finally, run the program with the run command :

    tsim> run
    resuming at 0x40000000
    Max priority for SCHED_FIFO = 254
    Min priority for SCHED_FIFO = 1
    ...
  8. 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 feasibility interval. 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.

    Furthermore, before compiling, do not forget to fill up the system.h configuration file to allow resource allocations. Each thread T1 and T2 must run the function periodic_task. The period is sent to each thread throught the thread argument. In this exercise, you have three issues to solve:
    1. Assign and set threads priorities according to Rate Monotonic.
    2. Ensure that thread T1 and T2 will start synchronously : 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). To be sure that the initialization thread is completed without being preempted, you must assign a higher priority level to the thread running POSIX_init.
    3. Implement periodic release times. To implement that threads are released periodically, we need to use the nanosleep and the clock_gettime functions. Arithmetic operations on timespec structs can be made with C functions of files ts.h and ts.c. clock_gettime functions will allow to measure when a thread is completing its activation while nanosleep can be used to blocked the thread until it reaches its next periodic release.





    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. 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 (respectively 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);
    }
    
    1. This program cannot be compiled: at each location where you can find the string 'XXX', there is a C code to add in order to fully implement the synchronization. Modify this program, compile and test it.

      Furthermore, before compiling, do not forget to fill up the system.h configuration file to allow resource allocations.
    2. Change the 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.




    Exercise 4 : a resource monitor based on the private semaphore design pattern



    A monitor is a software component which is responsible for the allocation and deallocation of resources by threads/processes/tasks. Basically, a monitor provides to the threads an API composed of, at least, two functions: one function to allocate the resources according to thread requests, and a second function to release the resources.

    To illustrate the use and the implementation of a monitor, we follow an example of a streaming system composed of a set of threads. This streaming system allows each thread to allocate buffers and disks for the streaming of a given movie. To get a high quality streaming (respectively low quality), a thread must request a higher (resp. lower) number of disks and buffers. In the sequel, we assume 3 threads: The overall streaming system has 3 buffers and 3 disks.


    Questions:
    1. Download and save this new file and put it in the EXO4 directory. This program is a first solution to this synchronization problem. In this solution, we use counting semaphores to manage allocation/deallocation of the disk and buffer resources. To avoid deadlock, each resource is allocated in the same order by any threads.
    2. Before compiling, fill up the system.h configuration file to allow resource allocations. Run, compile and study this solution. What can you say about scalability of such solution?
    3. We now explore a solution with the private semaphore design pattern. Update monitor.c by applying the private semaphore design pattern.




    Exercise 7 : active redundancy design pattern



    The objective of this exercise is to experiment an implementation of the active redundancy design pattern for the POSIX interface/RTEMS. Active redundancy is a process consisting of a caller which invokes several implementations of a function producing a result. As calling several implementations means producing several results, a voting step decides the actual value that has to be returned to the caller.

    The file redundancy.h provides an implementation of such service. Several redundant threads run several implementations of a function and apply a vote to decide the actual result. This file contains:


    Questions:
    1. Download, save this file in a directory called EXO7.
    2. Read and analyze init.c. When the program will be fully implemented, it should produce this result:
      
       TSIM3 LEON3 SPARC simulator, version 3.1.7 (evaluation version)
      
       Copyright (C) 2022, Cobham Gaisler - all rights reserved.
       This software may only be used with a valid license.
       For latest updates, go to https://www.gaisler.com/
       Comments or bug-reports to support@gaisler.com
      
       This TSIM evaluation version will expire 2023-03-08
      
      Number of CPUs: 2
      system frequency: 50.000 MHz
      icache: 1 * 4 KiB, 16 bytes/line (4 KiB total)
      dcache: 1 * 4 KiB, 16 bytes/line (4 KiB total)
      Allocated 8192 KiB SRAM memory, in 1 bank at 0x40000000
      Allocated 32 MiB SDRAM memory, in 1 bank at 0x60000000
      Allocated 8192 KiB ROM memory at 0x00000000
      section: .text, addr: 0x40000000, size: 113984 bytes
      section: .data, addr: 0x4001bd40, size: 3648 bytes
      section: .jcr, addr: 0x4001cb80, size: 4 bytes
      read 1216 symbols
      
      tsim> run
        Initializing and starting from 0x40000000
      Call sensor1
      Call sensor2
      Call sensor3
      my_result = 100 and diff = 1
      Call sensor1
      Call sensor2
      Call sensor3
      my_result = 100 and diff = 0
      
      
      Explain what the program should do.
    3. Implement the missing C functions of the redundancy.c file. according to the design pattern and test them with the provided init.c file.




    Exercise 8 : thread pool design pattern



    The objective of this exercise is to implement the design pattern thread pool. This design pattern allows the programmer to create an array of threads that run various requests. Usually, a thread pool initially contains a given number of thread ; such number may or may not change during the execution time of the application. In the sequel, we assume that the number of thread cannot be changed during execution time: it is set at the system initialization and cannot be changed later. This static thread pool is better suited for critical real-time systems.

    The file pool.h is a the interface of this service. It contains the following definitions:


    Question:
    1. Save this file in a specific folder.
    2. Read and analyze the file init.c.
    3. Implement pool.c to provide the services described above. Test your implementation with init.c. You may extend init.c if more tests have to be made.





    Exercice 5 : Implantation d'un moniteur pour un automate industriel



    Dans cet exercice, on souhaite implanter un moniteur permettant de gérer les ressources partagées d'un contrôleur d'automate industriel.

    Le contrôleur pilote 3 automates industriels. Le contrôleur est un programme RTEMS composé d'une tâche par automate industriel, soit 3 tâches. Chaque automate requiert deux types de ressources pour produire une pièce: des tapis roulants et une certaine puissance électrique: On suppose que le contrôleur dispose de 3 tapis roulants et d'une puissance électrique totale de 10000 Watt. On vous demande d'implanter le controleur afin de gérer l'allocation et la libération de ces ressources. On suppose que chaque tâche controlant un des automates est construit de la façon suivante:
    void* tache_automate_1(...
    {
       allocate(1, 2, 4000);
       printf("L'automate numéro 1 dispose des ressources nécessaires et travaille ...\n");
       release(1, 2, 4000);
    }
    
    Dans cet exemple, les 3 paramètres des méthodes allocate et release sont respectivement l'identifiant de la tâche, le nombre de tapis nécessaire et la puissance électrique nécessaire.

    On vous demande principalement d'implanter les méthodes allocate et release.


    Questions:
    1. Vous travaillez seul ou à deux. Tous les supports sont autorisés. La communication entre groupes est par contre interdite. Une fois le travail terminé, vous devez l'envoyer par email à singhoff@univ-brest.fr et attendre la réception de l'email avant de quitter la salle.
    2. Récupérer ce fichier et sauvegarder le dans le répertoire EXO5.
    3. Compléter ce programme (fichiers init.c et moniteur.c) afin d'implanter le contrôleur d'automate industriel décrit ci-dessus.




    Exercice 6 : Implantation d'un moniteur pour la gestion d'un rondpoint










    On considère un rond-point avec 3 voies entrantes E_1, E_2, E_3 et 3 voies sortantes S_1, S_2, S_3. Le rond-point comprend 3 tronçons d'échange (notés TE_1, TE_2 et TE_3) pour l'entrée et la sortie des voitures et 3 tronçons internes (TI_1, TI_2 et TI_3) par lesquels transitent les voitures à l'intérieur du carrefour. Au tronçon d'échange TE_i correspond l'entrée E_i et la sortie S_i. La figure ci-dessus résume la structure du rond-point.

    Une voiture circule dans le rond-point jusqu'à atteindre le tronçon d'échange correspondant à la sortie désirée. D'un tronçon interne, une voiture passe au tronçon d'échange suivant. D'un tronçon d'échange, une voiture peut soit sortir du rond-point, soit passer au tronçon interne suivant. Une voiture qui désire sortir par le tronçon d'échange par lequel elle est entrée doit faire un tour complet.

    Au plus maxe voitures peuvent se trouver dans un tronçon d'échange et au plus maxi dans un tronçon interne. La priorité d'accès à un tronçon est donnée aux voitures se trouvant sur le carrefour : une voiture qui désire entrer dans le rond-point par l'entrée E_i ne peut le faire que si le tronçon d'échange TE_i et le tronçon interne TI_i sont libres.

    On souhaite modéliser le fonctionnement d'un tel système par un programme constitué d'un ensemble de tâches RTEMS. L'accès au rondpoint est géré par un moniteur dont l'interface est donné dans le fichier moniteur.h. Le programme de simulation est constitué de plusieurs tâches dont le code est similaire à celui ci:
    /* Exemple d'un vehicule
    */
    void un_vehicule (int id) {
    	
    
            /* Rentrer sur le segment TE2 depuis le segment E2 */
            rentrer(id, 2);
    
            /* Avancer dans le rond point (de TE2 vers TI1)  */
            avancer(id);
    
            /* Avancer dans le rond point (de TI1 vers TE1)  */
            avancer(id);
    
            /* Avancer dans le rond point (de TE1 vers TI3)  */
            avancer(id);
    
            /* Sortir du rond point par le segment S3 à partir de TE3 */
            sortir(id, 3);
    
    }
    
    Travail à faire



    Exercice 9 : publisher-subscriber périodique



    Cet exercice est à rendre sur moodle. Vous pouvez travailler à deux ou seul. Si vous travaillez à deux, indiquer le nom des 2 étudiants dans vos fichiers. Tous les documents sont autorisés pour ce TP.

    L'objectif de cet exercice est d'implanter un modèle publisher-subscriber. Le modèle publisher/subscriber est un modèle de communication et de synchronisation permettant à différentes entités (threads, processus, etc) d'échanger des messages de façon asynchrone. Ce modèle est construit avec 3 types d'entités :

    Les principes fondamentaux de ce modèle de communication/synchronisation sont les suivants :

    Dans cet exercice, on se propose d'implanter ce modèle sur RTEMS avec une restitution périodique des données aux subscribers afin de rendre les communications utilisables pour les applications temps réels. L'implantation à réaliser fonctionne de la façon suivante : Travail à faire: