diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2014-07-04 14:34:23 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2014-07-09 10:05:17 +0200 |
commit | 5c3d2509593476869e791111cd3d93cc1e840b3a (patch) | |
tree | 2f642fcce66748460f0f823fbeb6f292267b8cd0 /cpukit/score/include/rtems/score/schedulerimpl.h | |
parent | schedulerpriorityaffinitysmp.c: Add period at end of sentence (diff) | |
download | rtems-5c3d2509593476869e791111cd3d93cc1e840b3a.tar.bz2 |
score: Implement scheduler helping protocol
The following scheduler operations return a thread in need for help
- unblock,
- change priority, and
- yield.
A thread in need for help is a thread that encounters a scheduler state
change from scheduled to ready or a thread that cannot be scheduled in
an unblock operation. Such a thread can ask threads which depend on
resources owned by this thread for help.
Add a new ask for help scheduler operation. This operation is used by
_Scheduler_Ask_for_help() to help threads in need for help returned by
the operations mentioned above. This operation is also used by
_Scheduler_Thread_change_resource_root() in case the root of a resource
sub-tree changes. A use case is the ownership change of a resource.
In case it is not possible to schedule a thread in need for help, then
the corresponding scheduler node will be placed into the set of ready
scheduler nodes of the scheduler instance. Once a state change from
ready to scheduled happens for this scheduler node it may be used to
schedule the thread in need for help.
Diffstat (limited to 'cpukit/score/include/rtems/score/schedulerimpl.h')
-rw-r--r-- | cpukit/score/include/rtems/score/schedulerimpl.h | 586 |
1 files changed, 576 insertions, 10 deletions
diff --git a/cpukit/score/include/rtems/score/schedulerimpl.h b/cpukit/score/include/rtems/score/schedulerimpl.h index 5e4e5098d2..c41c3af3e6 100644 --- a/cpukit/score/include/rtems/score/schedulerimpl.h +++ b/cpukit/score/include/rtems/score/schedulerimpl.h @@ -10,6 +10,7 @@ /* * Copyright (C) 2010 Gedare Bloom. * Copyright (C) 2011 On-Line Applications Research Corporation (OAR). + * Copyright (c) 2014 embedded brains GmbH * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at @@ -42,6 +43,13 @@ extern "C" { */ void _Scheduler_Handler_initialization( void ); +RTEMS_INLINE_ROUTINE Scheduler_Context *_Scheduler_Get_context( + const Scheduler_Control *scheduler +) +{ + return scheduler->context; +} + RTEMS_INLINE_ROUTINE const Scheduler_Control *_Scheduler_Get( const Thread_Control *the_thread ) @@ -55,6 +63,19 @@ RTEMS_INLINE_ROUTINE const Scheduler_Control *_Scheduler_Get( #endif } +RTEMS_INLINE_ROUTINE const Scheduler_Control *_Scheduler_Get_own( + const Thread_Control *the_thread +) +{ +#if defined(RTEMS_SMP) + return the_thread->Scheduler.own_control; +#else + (void) the_thread; + + return &_Scheduler_Table[ 0 ]; +#endif +} + RTEMS_INLINE_ROUTINE const Scheduler_Control *_Scheduler_Get_by_CPU_index( uint32_t cpu_index ) @@ -78,6 +99,13 @@ RTEMS_INLINE_ROUTINE const Scheduler_Control *_Scheduler_Get_by_CPU( } #if defined(RTEMS_SMP) +RTEMS_INLINE_ROUTINE Scheduler_Node *_Scheduler_Thread_get_own_node( + const Thread_Control *the_thread +) +{ + return the_thread->Scheduler.own_node; +} + RTEMS_INLINE_ROUTINE Thread_Control *_Scheduler_Node_get_user( const Scheduler_Node *node ) @@ -117,6 +145,39 @@ RTEMS_INLINE_ROUTINE void _Scheduler_Schedule( Thread_Control *the_thread ) } #if defined(RTEMS_SMP) +typedef struct { + Thread_Control *needs_help; + Thread_Control *next_needs_help; +} Scheduler_Ask_for_help_context ; + +RTEMS_INLINE_ROUTINE bool _Scheduler_Ask_for_help_visitor( + Resource_Node *resource_node, + void *arg +) +{ + bool done; + Scheduler_Ask_for_help_context *help_context = arg; + Thread_Control *previous_needs_help = help_context->needs_help; + Thread_Control *next_needs_help; + Thread_Control *offers_help = + _Thread_Resource_node_to_thread( resource_node ); + const Scheduler_Control *scheduler = _Scheduler_Get_own( offers_help ); + + next_needs_help = ( *scheduler->Operations.ask_for_help )( + scheduler, + offers_help, + previous_needs_help + ); + + done = next_needs_help != previous_needs_help; + + if ( done ) { + help_context->next_needs_help = next_needs_help; + } + + return done; +} + /** * @brief Ask threads depending on resources owned by the thread for help. * @@ -124,13 +185,56 @@ RTEMS_INLINE_ROUTINE void _Scheduler_Schedule( Thread_Control *the_thread ) * pre-emption by a higher priority thread or it was not possible to assign it * a processor since its priority is to low on its current scheduler instance. * + * The run-time of this function depends on the size of the resource tree of + * the thread needing help and other resource trees in case threads in need for + * help are produced during this operation. + * * @param[in] needs_help The thread needing help. */ +RTEMS_INLINE_ROUTINE void _Scheduler_Ask_for_help( + Thread_Control *needs_help +) +{ + do { + const Scheduler_Control *scheduler = _Scheduler_Get_own( needs_help ); + + needs_help = ( *scheduler->Operations.ask_for_help )( + scheduler, + needs_help, + needs_help + ); + + if ( needs_help != NULL ) { + Scheduler_Ask_for_help_context help_context = { needs_help, NULL }; + + _Resource_Iterate( + &needs_help->Resource_node, + _Scheduler_Ask_for_help_visitor, + &help_context + ); + + needs_help = help_context.next_needs_help; + } + } while ( needs_help != NULL ); +} + RTEMS_INLINE_ROUTINE void _Scheduler_Ask_for_help_if_necessary( Thread_Control *needs_help ) { - (void) needs_help; + if ( + needs_help != NULL + && _Resource_Node_owns_resources( &needs_help->Resource_node ) + ) { + Scheduler_Node *node = _Scheduler_Thread_get_own_node( needs_help ); + + if ( + node->help_state != SCHEDULER_HELP_ACTIVE_RIVAL + || _Scheduler_Node_get_user( node ) != needs_help + ) { + _Scheduler_Ask_for_help( needs_help ); + } + } } #endif @@ -218,7 +322,7 @@ RTEMS_INLINE_ROUTINE void _Scheduler_Change_priority( bool prepend_it ) { - const Scheduler_Control *scheduler = _Scheduler_Get( the_thread ); + const Scheduler_Control *scheduler = _Scheduler_Get_own( the_thread ); #if defined(RTEMS_SMP) Thread_Control *needs_help; @@ -426,6 +530,7 @@ RTEMS_INLINE_ROUTINE void _Scheduler_Set( if ( current_scheduler != scheduler ) { _Thread_Set_state( the_thread, STATES_MIGRATING ); _Scheduler_Node_destroy( current_scheduler, the_thread ); + the_thread->Scheduler.own_control = scheduler; the_thread->Scheduler.control = scheduler; _Scheduler_Node_initialize( scheduler, the_thread ); _Scheduler_Update_priority( the_thread, the_thread->current_priority ); @@ -628,13 +733,6 @@ RTEMS_INLINE_ROUTINE void _Scheduler_Change_priority_if_higher( } } -RTEMS_INLINE_ROUTINE Scheduler_Context *_Scheduler_Get_context( - const Scheduler_Control *scheduler -) -{ - return scheduler->context; -} - RTEMS_INLINE_ROUTINE uint32_t _Scheduler_Get_processor_count( const Scheduler_Control *scheduler ) @@ -721,6 +819,29 @@ RTEMS_INLINE_ROUTINE void _Scheduler_Node_do_initialize( } #if defined(RTEMS_SMP) +/** + * @brief Gets an idle thread from the scheduler instance. + * + * @param[in] context The scheduler instance context. + * + * @retval idle An idle thread for use. This function must always return an + * idle thread. If none is available, then this is a fatal error. + */ +typedef Thread_Control *( *Scheduler_Get_idle_thread )( + Scheduler_Context *context +); + +/** + * @brief Releases an idle thread to the scheduler instance for reuse. + * + * @param[in] context The scheduler instance context. + * @param[in] idle The idle thread to release + */ +typedef void ( *Scheduler_Release_idle_thread )( + Scheduler_Context *context, + Thread_Control *idle +); + RTEMS_INLINE_ROUTINE Thread_Control *_Scheduler_Node_get_owner( const Scheduler_Node *node ) @@ -735,6 +856,50 @@ RTEMS_INLINE_ROUTINE Thread_Control *_Scheduler_Node_get_idle( return node->idle; } +RTEMS_INLINE_ROUTINE void _Scheduler_Node_set_user( + Scheduler_Node *node, + Thread_Control *user +) +{ + node->user = user; +} + +RTEMS_INLINE_ROUTINE void _Scheduler_Thread_set_node( + Thread_Control *the_thread, + Scheduler_Node *node +) +{ + the_thread->Scheduler.node = node; +} + +RTEMS_INLINE_ROUTINE void _Scheduler_Thread_set_scheduler_and_node( + Thread_Control *the_thread, + Scheduler_Node *node, + const Thread_Control *previous_user_of_node +) +{ + const Scheduler_Control *scheduler = + _Scheduler_Get_own( previous_user_of_node ); + + the_thread->Scheduler.control = scheduler; + _Scheduler_Thread_set_node( the_thread, node ); +} + +extern const bool _Scheduler_Thread_state_valid_state_changes[ 3 ][ 3 ]; + +RTEMS_INLINE_ROUTINE void _Scheduler_Thread_change_state( + Thread_Control *the_thread, + Thread_Scheduler_state new_state +) +{ + _Assert( + _Scheduler_Thread_state_valid_state_changes + [ the_thread->Scheduler.state ][ new_state ] + ); + + the_thread->Scheduler.state = new_state; +} + /** * @brief Changes the scheduler help state of a thread. * @@ -748,13 +913,414 @@ RTEMS_INLINE_ROUTINE Scheduler_Help_state _Scheduler_Thread_change_help_state( Scheduler_Help_state new_help_state ) { - Scheduler_Node *node = _Scheduler_Thread_get_node( the_thread ); + Scheduler_Node *node = _Scheduler_Thread_get_own_node( the_thread ); Scheduler_Help_state previous_help_state = node->help_state; node->help_state = new_help_state; return previous_help_state; } + +/** + * @brief Changes the resource tree root of a thread. + * + * For each node of the resource sub-tree specified by the top thread the + * scheduler asks for help. So the root thread gains access to all scheduler + * nodes corresponding to the resource sub-tree. In case a thread previously + * granted help is displaced by this operation, then the scheduler asks for + * help using its remaining resource tree. + * + * The run-time of this function depends on the size of the resource sub-tree + * and other resource trees in case threads in need for help are produced + * during this operation. + * + * @param[in] top The thread specifying the resource sub-tree top. + * @param[in] root The thread specifying the new resource sub-tree root. + */ +void _Scheduler_Thread_change_resource_root( + Thread_Control *top, + Thread_Control *root +); + +/** + * @brief Use an idle thread for this scheduler node. + * + * A thread in the SCHEDULER_HELP_ACTIVE_OWNER owner state may use an idle + * thread for the scheduler node owned by itself in case it executes currently + * using another scheduler node or in case it is in a blocking state. + * + * @param[in] context The scheduler instance context. + * @param[in] node The node which wants to use the idle thread. + * @param[in] get_idle_thread Function to get an idle thread. + */ +RTEMS_INLINE_ROUTINE Thread_Control *_Scheduler_Use_idle_thread( + Scheduler_Context *context, + Scheduler_Node *node, + Scheduler_Get_idle_thread get_idle_thread +) +{ + Thread_Control *idle = ( *get_idle_thread )( context ); + + _Assert( node->help_state == SCHEDULER_HELP_ACTIVE_OWNER ); + _Assert( _Scheduler_Node_get_idle( node ) == NULL ); + _Assert( + _Scheduler_Node_get_owner( node ) == _Scheduler_Node_get_user( node ) + ); + + _Scheduler_Thread_set_node( idle, node ); + + _Scheduler_Node_set_user( node, idle ); + node->idle = idle; + + return idle; +} + +/** + * @brief Try to schedule this scheduler node. + * + * @param[in] context The scheduler instance context. + * @param[in] node The node which wants to get scheduled. + * @param[in] get_idle_thread Function to get an idle thread. + * + * @retval true This node can be scheduled. + * @retval false Otherwise. + */ +RTEMS_INLINE_ROUTINE bool _Scheduler_Try_to_schedule_node( + Scheduler_Context *context, + Scheduler_Node *node, + Scheduler_Get_idle_thread get_idle_thread +) +{ + bool schedule; + Thread_Control *owner; + Thread_Control *user; + + if ( node->help_state == SCHEDULER_HELP_YOURSELF ) { + return true; + } + + owner = _Scheduler_Node_get_owner( node ); + user = _Scheduler_Node_get_user( node ); + + if ( node->help_state == SCHEDULER_HELP_ACTIVE_RIVAL) { + if ( user->Scheduler.state == THREAD_SCHEDULER_READY ) { + _Scheduler_Thread_set_scheduler_and_node( user, node, owner ); + } else { + _Scheduler_Node_set_user( node, owner ); + } + + schedule = true; + } else if ( node->help_state == SCHEDULER_HELP_ACTIVE_OWNER ) { + if ( user->Scheduler.state == THREAD_SCHEDULER_READY ) { + _Scheduler_Thread_set_scheduler_and_node( user, node, owner ); + } else { + _Scheduler_Use_idle_thread( context, node, get_idle_thread ); + } + + schedule = true; + } else { + _Assert( node->help_state == SCHEDULER_HELP_PASSIVE ); + + if ( user->Scheduler.state == THREAD_SCHEDULER_READY ) { + _Scheduler_Thread_set_scheduler_and_node( user, node, owner ); + schedule = true; + } else { + schedule = false; + } + } + + return schedule; +} + +/** + * @brief Release an idle thread using this scheduler node. + * + * @param[in] context The scheduler instance context. + * @param[in] node The node which may have an idle thread as user. + * @param[in] release_idle_thread Function to release an idle thread. + * + * @retval idle The idle thread which used this node. + * @retval NULL This node had no idle thread as an user. + */ +RTEMS_INLINE_ROUTINE Thread_Control *_Scheduler_Release_idle_thread( + Scheduler_Context *context, + Scheduler_Node *node, + Scheduler_Release_idle_thread release_idle_thread +) +{ + Thread_Control *idle = _Scheduler_Node_get_idle( node ); + + if ( idle != NULL ) { + Thread_Control *owner = _Scheduler_Node_get_owner( node ); + + node->idle = NULL; + _Scheduler_Node_set_user( node, owner ); + _Scheduler_Thread_change_state( idle, THREAD_SCHEDULER_READY ); + _Scheduler_Thread_set_node( idle, idle->Scheduler.own_node ); + + ( *release_idle_thread )( context, idle ); + } + + return idle; +} + +/** + * @brief Block this scheduler node. + * + * @param[in] context The scheduler instance context. + * @param[in] node The node which wants to get blocked. + * @param[in] is_scheduled This node is scheduled. + * @param[in] get_idle_thread Function to get an idle thread. + * + * @retval true Continue with the blocking operation. + * @retval false Otherwise. + */ +RTEMS_INLINE_ROUTINE bool _Scheduler_Block_node( + Scheduler_Context *context, + Scheduler_Node *node, + bool is_scheduled, + Scheduler_Get_idle_thread get_idle_thread +) +{ + bool block; + Thread_Control *old_user = _Scheduler_Node_get_user( node ); + Thread_Control *new_user; + + _Scheduler_Thread_change_state( old_user, THREAD_SCHEDULER_BLOCKED ); + + if ( node->help_state == SCHEDULER_HELP_ACTIVE_RIVAL ) { + new_user = _Scheduler_Node_get_owner( node ); + + _Assert( new_user != old_user ); + _Scheduler_Node_set_user( node, new_user ); + } else if ( + node->help_state == SCHEDULER_HELP_ACTIVE_OWNER + && is_scheduled + ) { + new_user = _Scheduler_Use_idle_thread( context, node, get_idle_thread ); + } else { + new_user = NULL; + } + + if ( new_user != NULL && is_scheduled ) { + Per_CPU_Control *cpu = _Thread_Get_CPU( old_user ); + + _Scheduler_Thread_change_state( new_user, THREAD_SCHEDULER_SCHEDULED ); + _Thread_Set_CPU( new_user, cpu ); + _Thread_Dispatch_update_heir( _Per_CPU_Get(), cpu, new_user ); + + block = false; + } else { + block = true; + } + + return block; +} + +/** + * @brief Unblock this scheduler node. + * + * @param[in] context The scheduler instance context. + * @param[in] the_thread The thread which wants to get unblocked. + * @param[in] node The node which wants to get unblocked. + * @param[in] is_scheduled This node is scheduled. + * @param[in] release_idle_thread Function to release an idle thread. + * + * @retval true Continue with the unblocking operation. + * @retval false Otherwise. + */ +RTEMS_INLINE_ROUTINE bool _Scheduler_Unblock_node( + Scheduler_Context *context, + Thread_Control *the_thread, + Scheduler_Node *node, + bool is_scheduled, + Scheduler_Release_idle_thread release_idle_thread +) +{ + bool unblock; + + if ( is_scheduled ) { + Thread_Control *old_user = _Scheduler_Node_get_user( node ); + Per_CPU_Control *cpu = _Thread_Get_CPU( old_user ); + + if ( node->help_state == SCHEDULER_HELP_ACTIVE_OWNER ) { + Thread_Control *idle = _Scheduler_Release_idle_thread( + context, + node, + release_idle_thread + ); + + _Assert( idle != NULL ); + (void) idle; + } else { + _Assert( node->help_state == SCHEDULER_HELP_ACTIVE_RIVAL ); + + _Scheduler_Thread_change_state( old_user, THREAD_SCHEDULER_READY ); + _Scheduler_Node_set_user( node, the_thread ); + } + + _Scheduler_Thread_change_state( the_thread, THREAD_SCHEDULER_SCHEDULED ); + _Thread_Set_CPU( the_thread, cpu ); + _Thread_Dispatch_update_heir( _Per_CPU_Get(), cpu, the_thread ); + + unblock = false; + } else { + _Scheduler_Thread_change_state( the_thread, THREAD_SCHEDULER_READY ); + + unblock = true; + } + + return unblock; +} + +/** + * @brief Asks a ready scheduler node for help. + * + * @param[in] node The ready node offering help. + * @param[in] needs_help The thread needing help. + * + * @retval needs_help The thread needing help. + */ +RTEMS_INLINE_ROUTINE Thread_Control *_Scheduler_Ask_ready_node_for_help( + Scheduler_Node *node, + Thread_Control *needs_help +) +{ + _Scheduler_Node_set_user( node, needs_help ); + + return needs_help; +} + +/** + * @brief Asks a scheduled scheduler node for help. + * + * @param[in] context The scheduler instance context. + * @param[in] node The scheduled node offering help. + * @param[in] offers_help The thread offering help. + * @param[in] needs_help The thread needing help. + * @param[in] previous_accepts_help The previous thread accepting help by this + * scheduler node. + * @param[in] release_idle_thread Function to release an idle thread. + * + * @retval needs_help The previous thread accepting help by this scheduler node + * which was displaced by the thread needing help. + * @retval NULL There are no more threads needing help. + */ +RTEMS_INLINE_ROUTINE Thread_Control *_Scheduler_Ask_scheduled_node_for_help( + Scheduler_Context *context, + Scheduler_Node *node, + Thread_Control *offers_help, + Thread_Control *needs_help, + Thread_Control *previous_accepts_help, + Scheduler_Release_idle_thread release_idle_thread +) +{ + Thread_Control *next_needs_help = NULL; + Thread_Control *old_user = NULL; + Thread_Control *new_user = NULL; + + if ( + previous_accepts_help != needs_help + && _Scheduler_Thread_get_node( previous_accepts_help ) == node + ) { + Thread_Control *idle = _Scheduler_Release_idle_thread( + context, + node, + release_idle_thread + ); + + if ( idle != NULL ) { + old_user = idle; + } else { + _Assert( _Scheduler_Node_get_user( node ) == previous_accepts_help ); + old_user = previous_accepts_help; + } + + if ( needs_help->Scheduler.state == THREAD_SCHEDULER_READY ) { + new_user = needs_help; + } else { + _Assert( node->help_state == SCHEDULER_HELP_ACTIVE_RIVAL ); + _Assert( offers_help->Scheduler.node == offers_help->Scheduler.own_node ); + + new_user = offers_help; + } + + if ( previous_accepts_help != offers_help ) { + next_needs_help = previous_accepts_help; + } + } else if ( needs_help->Scheduler.state == THREAD_SCHEDULER_READY ) { + Thread_Control *idle = _Scheduler_Release_idle_thread( + context, + node, + release_idle_thread + ); + + if ( idle != NULL ) { + old_user = idle; + } else { + old_user = _Scheduler_Node_get_user( node ); + } + + new_user = needs_help; + } else { + _Assert( needs_help->Scheduler.state == THREAD_SCHEDULER_SCHEDULED ); + } + + if ( new_user != old_user ) { + Per_CPU_Control *cpu_self = _Per_CPU_Get(); + Per_CPU_Control *cpu = _Thread_Get_CPU( old_user ); + + _Scheduler_Thread_change_state( old_user, THREAD_SCHEDULER_READY ); + _Scheduler_Thread_set_scheduler_and_node( + old_user, + _Scheduler_Thread_get_own_node( old_user ), + old_user + ); + + _Scheduler_Thread_change_state( new_user, THREAD_SCHEDULER_SCHEDULED ); + _Scheduler_Thread_set_scheduler_and_node( new_user, node, offers_help ); + + _Scheduler_Node_set_user( node, new_user ); + _Thread_Set_CPU( new_user, cpu ); + _Thread_Dispatch_update_heir( cpu_self, cpu, new_user ); + } + + return next_needs_help; +} + +/** + * @brief Asks a blocked scheduler node for help. + * + * @param[in] context The scheduler instance context. + * @param[in] node The scheduled node offering help. + * @param[in] offers_help The thread offering help. + * @param[in] needs_help The thread needing help. + * + * @retval true Enqueue this scheduler node. + * @retval false Otherwise. + */ +RTEMS_INLINE_ROUTINE bool _Scheduler_Ask_blocked_node_for_help( + Scheduler_Context *context, + Scheduler_Node *node, + Thread_Control *offers_help, + Thread_Control *needs_help +) +{ + bool enqueue; + + _Assert( node->help_state == SCHEDULER_HELP_PASSIVE ); + + if ( needs_help->Scheduler.state == THREAD_SCHEDULER_READY ) { + _Scheduler_Node_set_user( node, needs_help ); + _Scheduler_Thread_set_scheduler_and_node( needs_help, node, offers_help ); + + enqueue = true; + } else { + enqueue = false; + } + + return enqueue; +} #endif /** @} */ |