Why ensuring mutual exclusion is important when accessing shared resources

A fundamental problem in multitasking is accessing shared resources. Text books often introduce this topic by considering the following problem. Imagine two tasks, both having access to a global variable. The job of one task, called an incrementer, is to increment the shared variable. The other task, called the decrementer, decrements the shared variable. The increment and decrement operations in each task are embedded within identical for loops. In this way, we arrange for the variable to be incremented and decremented the same number of times. The shared variable is reset to zero before the tasks are created and run. Once the tasks complete, one may expect the value of the shared variable to equal zero, as increment and decrement have been executed in equal measure by the two tasks. This recipe, named RTOS_Sem_c8v0, illustrates that, surprisingly, this is not the case.

How to do it…

  1. Create a new project and using the manager configure the RTE to provide support for the Graphic LCD.
  2. Add the following code to the project:
    #include "RTXSem.h"
    
    #define NCYCLES 500000        /* User Modified Value */
    int sharedVar;                   /* Shared Variable */
    
    /*--------------------------------------------------
     * Thread 1 'taskA': Increment Shared Variable
     *--------------------------------------------------*/
    void taskA (void const *argument) {
      uint32_t p;
      bool flag = true;
    
      for (;;) {
        if (flag==true) {
          /* Inccrement the Shared Variable */
          for (p=0; p<NCYCLES; p++)
            sharedVar++;
          /* set signal to taskC thread   */
          osSignalSet(tid_taskC, 0x0001);
          flag = false;
        }
      }
    }
    
    /*--------------------------------------------------
     * Thread 2 'taskB': Decrement Shared Variable
     *--------------------------------------------------*/
    void taskB (void const *argument) {
      uint32_t p;
      bool flag = true;
    
      for (;;) {
        if (flag==true) {
          /* Decrement the Shared Variable */
          for (p=0; p < NCYCLES; p++)
            sharedVar--;
          /* set signal to taskC thread */
          osSignalSet(tid_taskC, 0x0002);
          flag = false;
        }
      }
    }
    
    /*--------------------------------------------------
     * Thread 3 'taskC': Display Shared Variable
     *--------------------------------------------------*/
    void taskC (void const *argument) {
    
      for (;;) {
        /* wait for an event flag 0x0003 */
        osSignalWait(0x0003, osWaitForever);    
        GLCD_show_result(sharedVar);
        /* Kill Threads */
        osThreadTerminate (tid_taskA);
        osThreadTerminate (tid_taskB);
        osThreadTerminate (tid_taskC);
       }
    }
    
    /*--------------------------------------------------
     * Main: Initialize and start RTX Kernel
     *--------------------------------------------------*/
    int main (void) {
    
      HAL_Init ();   /* Init Hardware Abstraction Layer */
      SystemClock_Config ();           /* Config Clocks */        
    
      GLCD_setup();
    
      sharedVar = 0;
    
      tid_taskA = osThreadCreate(osThread(taskA), NULL);
      tid_taskB = osThreadCreate(osThread(taskB), NULL);
      tid_taskC = osThreadCreate(osThread(taskC), NULL);
      
      osDelay(osWaitForever);
      while(1);
    }
  3. Create file header file, RTXSem.h, and add the following code:
    #ifndef __RTX_SEM_H
    #define __RTX_SEM_H
    
    #include "stm32f4xx.h"     /* STM32F4xx Definitions */
    #include "RTXBlinkyUtils.h"
    #include "cmsis_os.h"
    
    /* Thread id of thread: task_a, b, c */
    osThreadId tid_taskA;                   
    osThreadId tid_taskB;                  
    osThreadId tid_taskC;
    
    /* Function Prototypes */
    void taskA (void const *argument);
    void taskB (void const *argument);
    void taskC (void const *argument);
    
    /* Thread Definitions */
    osThreadDef(taskA, osPriorityNormal, __FI, 0);
    osThreadDef(taskB, osPriorityNormal, __FI, 0);
    osThreadDef(taskC, osPriorityNormal, __FI, 0);
    
    #endif /* __RTX_SEM_H */
  4. Create the RTXBlinkyUtils.c file, enter the following code, and add it to the project:
    #include "RTXBlinkyUtils.h"
    
    void GLCD_setup(void) {
    
      GLCD_Initialize();              /* Initialise and */
      GLCD_SetBackgroundColor (GLCD_COLOR_WHITE);
      GLCD_ClearScreen ();            /* clear the GLCD */
      GLCD_SetBackgroundColor(GLCD_COLOR_BLUE);
      GLCD_SetForegroundColor(GLCD_COLOR_WHITE);
      GLCD_SetFont (&GLCD_Font_16x24);
      GLCD_DrawString(0, 0*24, " CORTEX-M4 COOKBOOK ");
      GLCD_DrawString(0, 1*24, "  PACKT Publishing  ");
    }
    
    void GLCD_show_result(int value) {
    
      char buffer[128];
      
      GLCD_SetBackgroundColor(GLCD_COLOR_WHITE);
      GLCD_SetForegroundColor(GLCD_COLOR_BLACK);  
      GLCD_DrawString (0, 3*24, "VAL =");
      sprintf (buffer, "%i   ", value);     /* make string */
      GLCD_DrawString (7*16, 3*24, buffer);   /* Display it */
    }
  5. Define the header file, RTXBlinkyUtils.h, and enter the following code:
    #ifndef __RTX_BLINKY_GLCD_UTILS_H
    #define __RTX_BLINKY_GLCD_UTILS_H
    
    #include "Board_GLCD.h"
    #include "GLCD_Config.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    
    #define __FI    1                       /* Font index */
    
    extern GLCD_FONT     GLCD_Font_16x24;
    
    /* Function Prototypes */
    void GLCD_setup(void);
    void GLCD_show_result(int );
    
    #endif /* __RTX_BLINKY_GLCD_UTILS_H */
  6. Build, download and run the program.

    Tip

    Note the value of the shared variable output to the GLCD (it should be 0). Try running the program a few times.

  7. Change the value of NCYCLES, as follows:
    #define NCYCLES 500000
  8. Build, download, and run the program. The value of the shared variable is output to the GLCD (it should be ≠ 0). Try running the program a few times.

How it works…

There are three tasks. Tasks A and B are incrementer and decrementer tasks, task C outputs the value of the shared variable to the GLCD. Task C waits for signals from both tasks, A and B, before calling the GLCD_show_result( ) function. To achieve this, task A sets flag 0x0001 and task B sets flag 0x0002; task C is released on flag 0x0003 (that is, the logical AND of task A and B flags).

To explain how the value of the shared variable can be anything other than zero, once the program terminates, we must consider how low-level machine instructions implementing increment or decrement operations will be executed for every possible scheduling of taskA and taskB. The increment operation involves reading a value from memory, storing it in one of the processor registers, adding one to it, and storing the result back in memory. Decrement will work in a similar way.

Assuming that the task switch between A and B always occurs after the task has written the updated value of the shared variable to memory, then the program operates successfully. When NCYCLES = 10, this will probably be the case. However, if the task switch occurs at the point just before the shared variable is written, then one task will be working with an outdated copy of the shared variable. This problem manifests as the error we observed.

There's more…

CMSIS-RTOS provides a solution to the problem of providing safe access to a shared resource (in this case a shared variable) by implementing a primitive known as a Semaphore. In general, a number of tasks (say, p tasks) may share a resource (that is, the resource can support a maximum of p tasks). To ensure that no more than p tasks access the resource at any time, we provide a variable (initialized to p) that will decrement each time a resource needs to use it and is incremented when the resource finishes with it. Thus, processes can only access the resource when p>0.

The case when a shared resource can only support one task (that is, p=1) can be managed by a binary semaphore called a Mutual Exclusion (Mutex). Mutexs are often used to ensure that critical sections of code are thread-safe. A piece of code is thread safe if it only manipulates shared data structures in a manner that guarantees safe execution by multiple threads at the same time. To ensure that the read, modify, or write operation produced by the increment or decrement is thread safe, we enclose the increment/decrement statement in task A or B as follows:

osMutexWait(mut_sharedVar, osWaitForever);
sharedVar++;
osMutexRelease(mut_sharedVar);

The variable named mut_sharedVar holds the semaphore. However, before we can use the semaphore, we must declare, register, and initialize it. The following recipe illustrates how this is done for a mutex used to control access to the GLCD. The same code statements can be used here; simply replace the mut_GLCD variable with mut_sharedVar. Once we've protected our critical section in this way, the program will run correctly and always return a value of zero, no matter how many cycles we specify.

Although the previous program is thread safe, there is another potential problem. Data is transmitted to the GLCD by a serial bus that is managed by functions that are defined in the GLCD library. If a task using the GLCD is switched while it is mid-way through writing to the GLCD, then there is a chance that the GLCD serial bus will stall and we'll lose data. This will manifest as a corruption of the screen and there is a chance that we'll misdiagnose this as a hardware fault, when in fact it is due to software. Many students try to fix this problem by arranging for all GLCD write statements to be in one task. This doesn't work because the serial bus is stalled as soon as a context switch occurs irrespective of what goes on in the other tasks. The solution is to treat the GLCD as a shared resource and enclose every invocation of the library code with calls to osMutexWait( ) and osMutexRelease( ), even if they occur within the same thread. The following recipe illustrates this by emulating the RTOS_Blinky_c8v0 folder in the Multithreaded programs using event flags recipe that we considered earlier in this chapter, this time using the GLCD to simulate the LEDs. We'll call this: RTOS_Blinky_c8v2.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.198.81