<<< | >>>

ColdFire RTOS Implementation

Phase 3: Cooperative Multitasking

In cooperative multitasking it is the responsibility of the currently running task to give up the processor to allow other tasks to run. In FreeRTOS™ this is performed by calling taskYIELD(). When this function is called a context-switch is performed. This consists of three major steps:

  1. Saving the context of the currently running task,
  2. Selecting the next task to run, and
  3. Restoring the context of the next task.

The second step is performed by the FreeRTOS™ kernel whilst the third step was implemented in the previous phase. Therefore, this phase primarily consisted of writing the code to save the context of the currently running task. This is essentially the reverse process of restoring a task's context as can be seen in the code in Code Extract 1.

move.l %d0,%sp@-
move.w %sr,%d0
move.l %d0,%sp@-
move.l %d1,%sp@-
move.l %d2,%sp@-
move.l %d3,%sp@-
move.l %d4,%sp@-
move.l %d5,%sp@-
move.l %d6,%sp@-
move.l %d7,%sp@-
move.l %a0,%sp@-
move.l %a1,%sp@-
move.l %a2,%sp@-
move.l %a3,%sp@-
move.l %a4,%sp@-
move.l %a5,%sp@-
move.l %a6,%sp@-
move.l pxCurrentTCB,%a0
move.l %sp,(%a0)
Code Extract 1 - Saving context

The data are pushed onto the stack in the opposite order to when they are restored. As the data is pushed onto the stack the stack pointer is decremented. Finally the stack pointer is saved in the task control block. The address of the next instruction will already be on the stack as they will have been put on the stack before the call to taskYIELD() and so after the task is later restored the RTS instruction will jump to the next instruction in the task.

Frame Pointers

In implementing this functionality, however, we faced some difficulties. The first was due to the prologue sequence prepended by the compiler to the start of functions. When a function is called this sequence updates a frame pointer by using the LINK instruction to save the previous value of the frame pointer (register A6) on the stack, update the stack pointer and then copy the stack pointer to the frame pointer.

The purpose of this is to establish an absolute frame of reference for calculating the location of local variables and parameters to the function. Unfortunately this disrupts our context switching code as our save context code expects the top of the stack to represent the return address of the task being suspended (as saved by the ISR instruction when the task calls taskYIELD). Now, however the top of the stack contains the previous value of the frame pointer.

The MegaAVR port avoids this problem by defining the taskYIELD function with the naked attribute which instructs the compiler not to create a function prologue or epilogue sequence.

During this phase of the project we resolved this problem by modifying the makefile to pass the -fomit-frame-pointer flag to the compiler which removes the LINK instruction from the function prologue.

Stack Depth

During testing of the context-switching it was discovered that the context-switch would often cause a crash (illegal instruction) after the second context-switch. Tracing the execution with gdb revealed that the task list was being overwritten during a call to printf(). The cause of the problem was found to be the many nested calls inside the Newlib printf() implementation which were causing the stack to overflow and change the data used by the task list. Simply increasing the stack depth for the test programs solved the problem. However, it would be helpful to detect such conditions so that the programmer knows the cause.

As part of the on-board debugging functionality the MCF5272 can generate interrupts when a certain address is modified. Stack overflows could be detected by setting this address to the boundary of the allocated stack for the currently running task each time a context switch occurs. This would be a nice feature to add in the future but we considered it more important to investigate the next phase of our project than to attempt to implement this feature.

Status Register

At this stage our context-switching code did not necessarily save and restore the status register correctly. This is because prior to pushing the status register onto the stack it was first moved to register D0 (see the instruction move.w %sr,%d0) which may affect the condition code register (CCR) of the status register. Fortunately this is not of concern in cooperative multitasking as context switches will not occur mid-calculation (in which case the values of the CCR would be significant) but this does need to be addressed in the next phase: preemptive multitasking.


In order to test this increment we developed a simple test program, the body of which is shown in Program 2.

static void vTestTask( void *pvParameters );
static int task1id = 1;
static int task2id = 2;

portSHORT main( void )
{
    xTaskCreate( vTestTask, "Task 1", 1000,
    (void *) &task1id, tskIDLE_PRIORITY + 2, NULL );
    xTaskCreate( vTestTask, "Task 2", 1000,
    (void *) &task2id, tskIDLE_PRIORITY + 2, NULL );

    printf("starting scheduler...\n");

    vTaskStartScheduler( pdFALSE );

    return 0;
}
/*-----------------------------------------------*/

static void vTestTask( void *pvParameters )
{
    int taskId = *((int *)pvParameters);
    printf("Task %d yielding.\n", taskId);
    taskYIELD();
    printf("Task %d resumed.\n", taskId);
    printf("Task %d yielding.\n", taskId);
    taskYIELD();
    printf("Task %d resumed.\n", taskId);

    /* Tasks must be implemented as continuous loops */
    while(1);
}
Program 2 - Program to demonstrate cooperative multitasking

The results of this test program are shown in Figure 2.


Figure 2 - Output from the test program demonstrating cooperative multitasking.

NEXT >>> ColdFire RTOS Port - Phase 4 - Preemptive Multitasking

Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.