Avoiding Priority Inversion

The following example demonstrates how mutex objects utilize priority inheritance to prevent priority inversion.

#include <stdio.h>
#include "OS_API.h"


HANDLE MutexHandle;
TIME StartupTime;
volatile int CurrentTask;


void Print(char *Str)
{
  BOOL PrevLockState;

  /* Disable interrupts */
  PrevLockState = arLock();

  /* Print specified string */
  printf("%6i msec: %s.\n", (int) (arGetTickCount() - StartupTime), Str);

  /* Restore interrupts */
  arRestore(PrevLockState);
}


void LongJob(TIME Time, int TaskID)
{
  TIME Start;

  /* Perform a job for specified amount of time */
  Start = arGetTickCount();
  while(arGetTickCount() < (Start + Time))
  {        
    BOOL PrevLockState;

    /* Disable interrupts */
    PrevLockState = arLock();

    /* Print ID of task currently executing a job */
    if(CurrentTask != TaskID)
    {
      switch(TaskID)
      {
        case 1: Print("Currently processing task is a Task1"); break;
        case 2: Print("Currently processing task is a Task2"); break;
        case 3: Print("Currently processing task is a Task3"); break;
      }

      CurrentTask = TaskID;
    }

    /* Restore interrupts */
    arRestore(PrevLockState);
  }
}


/* Example task with high priority */
ERROR Task1(PVOID Arg)
{
  /* Mark parameter as unused */
  AR_UNUSED_PARAM(Arg);

  /* Sleep for 2 seconds */
  Print("Task1 starts and sleeps for 2 seconds");
  osSleep(2000);

  /* Wait for the mutex */
  Print("Task1 is resumed after 2 seconds and begins waiting for the mutex");
  osWaitForObject(MutexHandle, AR_TIME_INFINITE);

  /* Execute job for 2 seconds */
  Print("Task1 acquires the mutex and executes a job for 2 seconds");
  LongJob(2000, 1);

  /* Release mutex */
  Print("Task1 finishes its job after 2 seconds and releases mutex");
  osReleaseMutex(MutexHandle);

  /* Exit task */
  Print("Task1 ends");
  return ERR_NO_ERROR;
}


/* Example task with medium priority */
ERROR Task2(PVOID Arg)
{
  /* Mark parameter as unused */
  AR_UNUSED_PARAM(Arg);

  /* Sleep for 1 second */
  Print("Task2 starts and sleeps for 1 second");
  osSleep(1000);

  /* Execute job for 10 seconds */
  Print("Task2 is resumed after 1 second and starts job for 4 seconds");
  LongJob(4000, 2);

  /* Exit task */
  Print("Task2 finishes its job after 4 seconds and ends");
  return ERR_NO_ERROR;
}


/* Example task with low priority */
ERROR Task3(PVOID Arg)
{
  /* Mark parameter as unused */
  AR_UNUSED_PARAM(Arg);

  /* Wait for the mutex */
  Print("Task3 starts and begins waiting for the mutex");
  osWaitForObject(MutexHandle, AR_TIME_INFINITE);

  /* Execute job for 5 seconds */
  Print("Task3 acquires the mutex and starts a job for 3 seconds");
  LongJob(3000, 3);

  /* Release mutex */
  Print("Task3 finishes its job after 3 seconds and releases the mutex");
  osReleaseMutex(MutexHandle);

  /* Exit task */
  Print("Task3 ends");
  return ERR_NO_ERROR;
}


int main(void)
{
  /* Initialization */
  arInit();
  stInit();
  osInit();

  /* Initialize global variables */
  CurrentTask = 0;
  StartupTime = arGetTickCount();

  /* Create objects */
  MutexHandle = osCreateMutex(NULL, FALSE);
  osCreateTask(Task1, NULL, 0, 1, FALSE);
  osCreateTask(Task2, NULL, 0, 2, FALSE);
  osCreateTask(Task3, NULL, 0, 3, FALSE);

  /* Start the operating system */
  osStart();

  /* Deinitialization */
  osDeinit();
  arDeinit();
  return 0;
}

The result on the console should look as follows:

     0 msec: Task1 starts and sleeps for 2 seconds.
     0 msec: Task2 starts and sleeps for 1 second.
     0 msec: Task3 starts and begins waiting for the mutex.
     0 msec: Task3 acquires the mutex and starts a job for 3 seconds.
     0 msec: Currently processing task is a Task3.
  1000 msec: Task2 is resumed after 1 second and starts job for 4 seconds.
  1000 msec: Currently processing task is a Task2.
  2000 msec: Task1 is resumed after 2 seconds and begins waiting for the mutex.
  2000 msec: Currently processing task is a Task3.
  3000 msec: Task3 finishes its job after 3 seconds and releases the mutex.
  3000 msec: Task1 acquires the mutex and executes a job for 2 seconds.
  3000 msec: Currently processing task is a Task1.
  5000 msec: Task1 finishes its job after 2 seconds and releases mutex.
  5000 msec: Task1 ends.
  5000 msec: Task2 finishes its job after 4 seconds and ends.
  5000 msec: Task3 ends.
SpaceShadow documentation