Multicore Real-Time Operating System RTEMS (21) – Active Scheduling

Technical experience sharing, welcome to follow and provide guidance.

Based on our understanding of the scheduling scenarios in RTEMS, the first thing to note is active scheduling. Its implementation is _Scheduler_Schedule. From the context of the code, we can see that when the system sends a signal, if the thread’s context is set to non-preemptive, it will actively schedule to allow the signal to be handled by other threads. This article explores this scheduling method through experiments.

1. Signal Test Code

To test RTEMS’s active scheduling, we need the following test code:

rtems_task Init(
  rtems_task_argument argument
)
{
  rtems_status_code status;

  TEST_BEGIN();

  Task_name[ 1 ] =  rtems_build_name( 'T', 'A', '1', ' ' );
  Task_name[ 2 ] =  rtems_build_name( 'T', 'A', '2', ' ' );

status = rtems_task_create(
    Task_name[ 1 ],
    4,
    RTEMS_MINIMUM_STACK_SIZE * 2,
    RTEMS_DEFAULT_MODES,
    RTEMS_DEFAULT_ATTRIBUTES,
    &Task_id[ 1 ]
  );
  directive_failed( status, "rtems_task_create of TA1" );

status = rtems_task_create(
    Task_name[ 2 ],
    4,
    RTEMS_MINIMUM_STACK_SIZE,
    RTEMS_DEFAULT_MODES,
    RTEMS_DEFAULT_ATTRIBUTES,
    &Task_id[ 2 ]
  );
  directive_failed( status, "rtems_task_create of TA2" );

status = rtems_task_start( Task_id[ 1 ], Task_1, 0 );
  directive_failed( status, "rtems_task_start of TA1" );

status = rtems_task_start( Task_id[ 2 ], Task_2, 0 );
  directive_failed( status, "rtems_task_start of TA2" );

  Timer_name[ 1 ] = rtems_build_name( 'T', 'M', '1', ' ' );

status = rtems_timer_create( Timer_name[ 1 ], &Timer_id[ 1 ] );
  directive_failed( status, "rtems_timer_create of TM1" );

  rtems_task_exit();
}

To allow the task to run, we need to implement the Task_1 function:

rtems_task Task_1(
  rtems_task_argument argument
)
{
  rtems_mode        previous_mode;
  rtems_status_code status;

  puts( "TA1 - rtems_signal_catch - RTEMS_INTERRUPT_LEVEL( 0 )" );
status = rtems_signal_catch( Process_asr, RTEMS_INTERRUPT_LEVEL(0) );
  directive_failed( status, "rtems_signal_catch" );

status = rtems_task_mode( RTEMS_NO_PREEMPT , RTEMS_PREEMPT_MASK, &previous_mode );                                                                                                         
  printf("Kylin: previous_mode=%#x \n", previous_mode);

  puts( "TA1 - rtems_signal_send - RTEMS_SIGNAL_16 to self" );
status = rtems_signal_send( RTEMS_SELF, RTEMS_SIGNAL_16 );
  directive_failed( status, "rtems_signal_send" );


  puts( "TA1 - rtems_task_exit" );
  rtems_task_exit();
}

According to the above code example, we captured all interrupt sources through rtems_signal_catch. To ensure the test runs correctly, we set this task to be non-preemptive using rtems_task_mode.

2. Code Analysis

The code analysis consists of two parts: one is rtems_signal_catch, where we focus on the rtems_mode mode_set parameter, which can be noted as follows:

asr->mode_set = mode_set;

The other part is the rtems_signal_send function, which has the following call relationship:

rtems_signal_send--->_Thread_Add_post_switch_action--->action->handler = handler;
rtems_signal_send--->_Thread_Dispatch_enable--->_Thread_Do_dispatch--->_Thread_Run_post_switch_actions---> ( *action->handler )( executing, action, &lock_context );

The handler callback here is _Signal_Action_handler. The main function of this function is to call the ASR handler, which is the registered callback function. It is important to note that before calling the signal handling function, the following logic occurs:

normal_is_preemptible = executing->is_preemptible;
executing->is_preemptible = _Modes_Is_preempt( asr->mode_set );
  if ( executing->is_preemptible && !normal_is_preemptible ) {
    Per_CPU_Control *cpu_self;

    cpu_self = _Thread_Dispatch_disable_critical( lock_context );
    _Scheduler_Schedule( executing );
    _Thread_State_release( executing, lock_context );
    _Thread_Dispatch_direct( cpu_self );
  } else {
    _Thread_State_release( executing, lock_context );
  }
( *asr->handler )( signal_set );

From this code analysis, we can see that if the current thread state has ASR set to preemptible, but this thread was initialized as non-preemptive, under these two conditions, it will default to actively call _Scheduler_Schedule to yield the CPU. This allows higher priority programs to run first.

In simpler terms, when a task is set to be preemptible but is declared as non-preemptive, in the signal handling function, it will first call _Scheduler_Schedule, and then _Thread_Dispatch_direct will execute the currently highest priority task. After the highest priority task completes, the ASR handler callback will be called. This is because the ASR handler function is non-preemptive.

Here, the ASR handler function is Process_asr, which means that when Process_asr runs as a non-preemptive task, it will first allow the system’s highest priority task to run once.

3. Testing Verification

(gdb) bt                                                                                                                      
#0  _Scheduler_priority_Schedule (scheduler=0x2d268 <_Scheduler_Table>, the_thread=0x1059e0 <_RTEMS_tasks_Objects+1512>)
    at ../../../cpukit/include/rtems/score/schedulerimpl.h:108
#1  0x000000000001d5c0 in _Scheduler_Schedule (the_thread=0x1059e0 <_RTEMS_tasks_Objects+1512>)
    at ../../../cpukit/include/rtems/score/schedulerimpl.h:227
#2  _Signal_Action_handler (executing=0x1059e0 <_RTEMS_tasks_Objects+1512>, action=0x105ee8 <_RTEMS_tasks_Objects+2800>,
    lock_context=0x10eca0) at ../../../cpukit/rtems/src/signalsend.c:103
#3  0x000000000001f5f0 in _Thread_Run_post_switch_actions (executing=0x1059e0 <_RTEMS_tasks_Objects+1512>)
    at ../../../cpukit/score/src/threaddispatch.c:270
#4  _Thread_Do_dispatch (cpu_self=0x102080 <_Per_CPU_Information>, level=832)
    at ../../../cpukit/score/src/threaddispatch.c:356
#5  0x000000000001f870 in _Thread_Dispatch_enable (cpu_self=cpu_self@entry=0x102080 <_Per_CPU_Information>)
    at ../../../cpukit/score/src/threaddispatch.c:389
#6  0x000000000001d6dc in rtems_signal_send (id=id@entry=0, signal_set=signal_set@entry=65536)
    at ../../../cpukit/rtems/src/signalsend.c:205
#7  0x0000000000019728 in Task_1 (argument=<optimized out>) at ../../../testsuites/sptests/sp14/task1.c:71
#8  0x000000000001f9f0 in _Thread_Handler () at ../../../cpukit/score/src/threadhandler.c:164
#9  0x000000000001f900 in ?? ()

From the stack trace, we can see that in the rtems_signal_send function, _Thread_Dispatch_enable is called, and then the _Thread_Do_dispatch function triggers the _Signal_Action_handler function callback. In this function callback, before running the ASR handler, it defaults to actively scheduling once. This allows the system to utilize the last preemption window to reschedule before the task changes from preemptible to non-preemptive.

4. Extended Analysis

From the above analysis, we can see that we try to schedule once before the ASR handler runs. Similarly, if we set asr->mode to non-preemptive in the signal catch function, then in the _Signal_Action_handler, the thread state will be non-preemptive, and this signal will not actively send this scheduling event. As follows:

rtems_task Task_1(
  rtems_task_argument argument
)
{
  rtems_mode        previous_mode;
  rtems_status_code status;

  puts( "TA1 - rtems_signal_catch - RTEMS_INTERRUPT_LEVEL( 0 )" );
status = rtems_signal_catch( Process_asr, RTEMS_INTERRUPT_LEVEL(0) | RTEMS_NO_PREEMPT);
  directive_failed( status, "rtems_signal_catch" );

status = rtems_task_mode( RTEMS_NO_PREEMPT , RTEMS_PREEMPT_MASK, &previous_mode );                                                                                                         
  printf("Kylin: previous_mode=%#x \n", previous_mode);

  puts( "TA1 - rtems_signal_send - RTEMS_SIGNAL_16 to self" );
status = rtems_signal_send( RTEMS_SELF, RTEMS_SIGNAL_16 );
  directive_failed( status, "rtems_signal_send" );

  puts( "TA1 - rtems_task_exit" );
  rtems_task_exit();
}

We can see that by setting asr→mode to non-preemptive in rtems_signal_catch, this task will skip the active scheduling.

5. Conclusion

Thus, we have tested the active scheduling function of RTEMS through signals.

Leave a Comment