Critical SectionsΒΆ

A critical section is any code segment that needs to complete as a whole unit. For example updating a series of values in a shared data structure. With shared data we would want to be sure all the data was in the correct state before it is used by another task.

Often the only way to ensure the code can complete before another task runs is to turn off interrupts while the code segment is running. And turn interrupts back on once it’s finished.

Since turning off interrupts affects the whole system we want to minimize the amount of time the interrupts are off. To do we should try to turn off interrupts only around those code segments that are actually changing the data. In a real-time system, it is often better to switch interrupts off and on multiple times in a function then to turn them off for the whole function.

To make it easy to turn interrupts off and on, OSs usually implement two critical sections functions. One called right before entering a critical section and the called when exiting. In uCOS these functions are called OS_ENTER_CRITICAL and OS_EXIT_CRITICAL respectively.

The enter critical section functions work by using the status register instructions, MSR and MRS, to disable and re-enable interrupts. Though if we think about it what we really want to do is disable the interrupts but then set them back to however they where before. This is important because interrupts might have already been off for some other reason and we don’t want to inadvertantly turn them on when they should actually remain off.

To solving this problem is simply saving the original state of the interrupts on critical section enter and then restoring that state on exit. That ensure not only are interrupts off in the critical section but then setting back to how they where before after the critical section.

To do that we need to store the value of the status register somewhere while we’re in the critical section. That location should be local to the function since critical sections could end up nested inside one another and we don’t want the saved value to get corrupted. Because of that it makes sense to store the value in a local variable declared at the top of the function. Local variable are stored on the stack and are unique to each function which is just what we want.

Our enter critical section function should return the current value of the status register then disable the interrupt. The exit critical section function should take the value we saved and use it to restore the status register. As in the following example:

void ExampleFunction(void)
{
    int status_register_save = 0;

    status_register_save = Disable_IRQ_Save_Status();  // Enter

    // Code we need to protect from interrupts.

    Restore_Status(status_register_save);      // Exit
}

To make the code more readable we can replace the save and restore function calls with macros called ENTER_CRITICAL and EXIT_CRITICAL. As in:

// Define the macros for entering and exiting a critical section
#define  ENTER_CRITICAL() { status_register_save = Disable_IRQ_Save_Status(); }
#define  EXIT_CRITICAL()  { Restore_Status(status_register_save); }

void ExampleFunction(void)
{
    int status_register_save = 0;

    ENTER_CRITICAL();
    // Code we need to protect from interrupts.
    EXIT_CRITICAL();
}

All that is left is to implement the save and restore functions. We will need to do this in assembly since we have to use the MSR and MRS instructions.

The function are simple. To disable the interrupts we first save the current value in R0 which will also be our return value. We then use R0 and or

Disable_IRQ_Save_Status:
        MRS     r0, cpsr
        ORR     r1, r0, #INTERRUPTS_DISABLE      /* Disable interrupts */
        MSR     cpsr_c, r1
        BX      LR                               /* Returns the original CPSR in R0 */

Restore_Status:
        MSR     CPSR_c, R0                       /* Retore original CPSR */
        BX      LR
// Define OS_CPU_SR to be the size of the CPSR register
typedef unsigned int   OS_CPU_SR;

// Define the macros for entering and exiting a critical section
#define  OS_ENTER_CRITICAL()  {cpu_sr = CPU_SR_Save();}
#define  OS_EXIT_CRITICAL()   {CPU_SR_Restore(cpu_sr);}
CPU_SR_Save:
        MRS     r0, cpsr
        ORR     r1, r0, #INTERRUPTS_DISABLE              /* Disable interrupts */
        MSR     cpsr_c, r1
        BX      LR                                       /* Returns the original CPSR in R0 */

CPU_SR_Restore:
        MSR     CPSR_c, R0                               /* Retore original CPSR */
        BX      LR
void ExampleFunction(void)
{
    OS_CPU_SR  cpu_sr = 0;

    OS_ENTER_CRITICAL();
    // This code is protected from interrupts
    // Update important data structures
    OS_EXIT_CRITICAL();
}

Previous topic

Tasks

Next topic

Delay Timer

This Page