Table of Contents

OSA : Critical sections

Intro

Critical sections are used to provide a task with unshared access to a resource when this resource is used by other tasks. For example: a task uses EEPROM and enters a critical section to avoid EEPROM data modification by other tasks.

In fact, this is a rare situation for a cooperative RTOS because the programmer switches context manually, but it is still possible. So OSA has services to protect critical sections of the program.

To allow usage of critical sections you must define the OS_ENABLE_CRITICAL_SECTION constant in OSAcfg.h.

There are two services to protect program's critical sections:

When entering a critical section, all interrupts are disabled (their current value is stored in internal system flags). Only the task that called the service OS_EnterCriticalSection can get control independently of priority, and all other tasks will be blocked until OS_LeaveCriticalSection is called. When in the critical section it still possible to return to the scheduler.

To work with critical sections you need to know some rules written below.

GIEx saving

The service OS_EnterCriticalSection saves current GIEx values, and then clears them. Thus if you call this service twice in a row, the GIEx value saved by the first call will be overwritten with zeroes by the second call, since after the first call GIEx has been cleared. In this situation OS_LeaveCriticalSection will restore the zeroed GIEx saved by the second call.

Avoid calling service OS_EnterCriticalSection twice in a row.

Timers

You will be able to use task delays (OS_Delay) and waiting with timeout services only if OS_Timer is not called from an interrupt (since interrupts are disabled while in a critical section). There are two ways of solving this problem:

  1. Enable interrupts manually after entering the critical section (if the program architecture allows the use of interrupts while in the critical section)
  2. Duplicate the call to OS_Timer in the main loop (near OS_Sched). See example below:
void interrupt int_routine (void)
{
    OS_EnterInt();
    if (TMR2IF)       // This code will not work in critical section
    {
         TMR2IF = 0;
         OS_Timer();  // This service will be called only outside
                      // critical section
    }
    OS_LeaveInt();
}
 
void Task1 (void)
{
    for (;;) {
        . . .
        OS_EnterCriticalSection();
        OS_Delay(100);
        OS_LeaveCriticalSection();
        . . .
    }
}
 
void main (void)
{
    OS_Init();
    OS_Task_Create(0, Task1);
    for (;;)
    {
        OS_Sched();
        if (OS_IsInCriticalSection()) {
            if (TMR2IF) {
                TMR2IF = 0;
                OS_Timer();    // This service will be called within critical section
            }
        }
    }
}

Wait for events

All task except one (the one that called OS_EnterCriticalSection) are blocked even if their priority is higher and they are ready to run. Thus if a task enters a critical section and then waits for an event that occurs in another task, the system will loop. To avoid this you can first wait for the event and then enter the critical section.

void Task1 (void)
{
    for (;;) {
        . . .
        // Incorrect
        OS_EnterCriticalSection();
        OS_Bsem_Wait(BS_USART_FREE);
        . . .
        // Correct
        OS_Bsem_Wait(BS_USART_FREE);
        OS_EnterCriticalSection();
        . . .
    }
}

Services

Service Arguments Description Properties
bool
OS_IsInCriticalSection
Return true if any task is in critical section
OS_EnterCriticalSection Enter critical section Allowed only in task
OS_LeaveCriticalSection Leave critical section Allowed only in task