[PATCH] smp: Fix timeout for MrsP semaphores

Sebastian Huber sebastian.huber at embedded-brains.de
Wed Dec 17 15:14:18 UTC 2014


The previous timeout handling was flawed.  In case a waiting thread
helped out the owner could use the scheduler node indefinitely long.
Update the resource tree in _MRSP_Timeout() to avoid this issue.

Bug reported by Luca Bonato.
---
 cpukit/score/include/rtems/score/mrsp.h     |  35 ++++++--
 cpukit/score/include/rtems/score/mrspimpl.h | 101 +++++++++++------------
 testsuites/smptests/smpmrsp01/init.c        | 120 ++++++++++++++++++++--------
 3 files changed, 167 insertions(+), 89 deletions(-)

diff --git a/cpukit/score/include/rtems/score/mrsp.h b/cpukit/score/include/rtems/score/mrsp.h
index c31d5f6..589ee9d 100644
--- a/cpukit/score/include/rtems/score/mrsp.h
+++ b/cpukit/score/include/rtems/score/mrsp.h
@@ -19,8 +19,8 @@
 
 #if defined(RTEMS_SMP)
 
-#include <rtems/score/atomic.h>
 #include <rtems/score/chain.h>
+#include <rtems/score/scheduler.h>
 #include <rtems/score/thread.h>
 
 #ifdef __cplusplus
@@ -66,7 +66,13 @@ typedef enum {
   MRSP_INCORRECT_STATE = 14,
   MRSP_INVALID_PRIORITY = 19,
   MRSP_NOT_OWNER_OF_RESOURCE = 23,
-  MRSP_NO_MEMORY = 26
+  MRSP_NO_MEMORY = 26,
+
+  /**
+   * @brief Internal state used for MRSP_Rival::status to indicate that this
+   * rival waits for resource ownership.
+   */
+  MRSP_WAIT_FOR_OWNERSHIP = 255
 } MRSP_Status;
 
 /**
@@ -89,13 +95,28 @@ typedef struct {
   Thread_Control *thread;
 
   /**
-   * @brief The rival state.
+   * @brief The initial priority of the thread.
+   *
+   * Used to restore the priority after a release of this resource or timeout.
+   */
+  Priority_Control initial_priority;
+
+  /**
+   * @brief The previous help state of the thread.
+   *
+   * Used to restore this state after a timeout.
+   */
+  Scheduler_Help_state previous_help_state;
+
+  /**
+   * @brief The rival status.
    *
-   * Initially no state bits are set (MRSP_RIVAL_STATE_WAITING).  The rival
-   * will busy wait until a state change happens.  This can be
-   * MRSP_RIVAL_STATE_NEW_OWNER or MRSP_RIVAL_STATE_TIMEOUT.
+   * Initially the status is set to MRSP_WAIT_FOR_OWNERSHIP.  The rival will
+   * busy wait until a status change happens.  This can be MRSP_SUCCESSFUL or
+   * MRSP_TIMEOUT.  State changes are protected by the Giant lock and disabled
+   * interrupts.
    */
-  Atomic_Uint state;
+  volatile MRSP_Status status;
 } MRSP_Rival;
 
 /**
diff --git a/cpukit/score/include/rtems/score/mrspimpl.h b/cpukit/score/include/rtems/score/mrspimpl.h
index 1571594..57121de 100644
--- a/cpukit/score/include/rtems/score/mrspimpl.h
+++ b/cpukit/score/include/rtems/score/mrspimpl.h
@@ -36,12 +36,6 @@ extern "C" {
  * @{
  */
 
-#define MRSP_RIVAL_STATE_WAITING 0x0U
-
-#define MRSP_RIVAL_STATE_NEW_OWNER 0x1U
-
-#define MRSP_RIVAL_STATE_TIMEOUT 0x2U
-
 RTEMS_INLINE_ROUTINE void _MRSP_Elevate_priority(
   MRSP_Control     *mrsp,
   Thread_Control   *new_owner,
@@ -52,9 +46,8 @@ RTEMS_INLINE_ROUTINE void _MRSP_Elevate_priority(
 }
 
 RTEMS_INLINE_ROUTINE void _MRSP_Restore_priority(
-  const MRSP_Control *mrsp,
-  Thread_Control     *thread,
-  Priority_Control    initial_priority
+  Thread_Control   *thread,
+  Priority_Control  initial_priority
 )
 {
   /*
@@ -134,24 +127,34 @@ RTEMS_INLINE_ROUTINE void _MRSP_Set_ceiling_priority(
   mrsp->ceiling_priorities[ scheduler_index ] = ceiling_priority;
 }
 
-RTEMS_INLINE_ROUTINE void _MRSP_Add_state(
-  MRSP_Rival   *rival,
-  unsigned int  state
-)
-{
-  _Atomic_Fetch_or_uint( &rival->state, state, ATOMIC_ORDER_RELEASE );
-}
-
 RTEMS_INLINE_ROUTINE void _MRSP_Timeout(
   Objects_Id  id,
   void       *arg
 )
 {
   MRSP_Rival *rival = arg;
+  Thread_Control *thread = rival->thread;
+  ISR_Level level;
 
   (void) id;
 
-  _MRSP_Add_state( rival, MRSP_RIVAL_STATE_TIMEOUT );
+  _ISR_Disable( level );
+
+  if ( rival->status == MRSP_WAIT_FOR_OWNERSHIP ) {
+    _Chain_Extract_unprotected( &rival->Node );
+
+    _ISR_Enable( level );
+
+    rival->status = MRSP_TIMEOUT;
+
+    _Resource_Node_extract( &thread->Resource_node );
+    _Resource_Node_set_dependency( &thread->Resource_node, NULL );
+    _Scheduler_Thread_change_help_state( thread, rival->previous_help_state );
+    _Scheduler_Thread_change_resource_root( thread, thread );
+    _MRSP_Restore_priority( thread, rival->initial_priority );
+  } else {
+    _ISR_Enable( level );
+  }
 }
 
 RTEMS_INLINE_ROUTINE MRSP_Status _MRSP_Wait_for_ownership(
@@ -166,18 +169,18 @@ RTEMS_INLINE_ROUTINE MRSP_Status _MRSP_Wait_for_ownership(
   MRSP_Status status;
   MRSP_Rival rival;
   bool previous_life_protection;
-  unsigned int state;
-  Scheduler_Help_state previous_help_state;
+
+  rival.thread = executing;
+  rival.initial_priority = initial_priority;
+  rival.previous_help_state =
+    _Scheduler_Thread_change_help_state( executing, SCHEDULER_HELP_ACTIVE_RIVAL );
+  rival.status = MRSP_WAIT_FOR_OWNERSHIP;
 
   _MRSP_Elevate_priority( mrsp, executing, ceiling_priority );
 
-  rival.thread = executing;
-  _Atomic_Init_uint( &rival.state, MRSP_RIVAL_STATE_WAITING );
   _Chain_Append_unprotected( &mrsp->Rivals, &rival.Node );
   _Resource_Add_rival( &mrsp->Resource, &executing->Resource_node );
   _Resource_Node_set_dependency( &executing->Resource_node, &mrsp->Resource );
-  previous_help_state =
-    _Scheduler_Thread_change_help_state( executing, SCHEDULER_HELP_ACTIVE_RIVAL );
 
   _Scheduler_Thread_change_resource_root(
     executing,
@@ -199,12 +202,10 @@ RTEMS_INLINE_ROUTINE MRSP_Status _MRSP_Wait_for_ownership(
 
   _Assert( _Debug_Is_thread_dispatching_allowed() );
 
-  while (
-    _Atomic_Load_uint( &rival.state, ATOMIC_ORDER_ACQUIRE )
-      == MRSP_RIVAL_STATE_WAITING
-  ) {
-    /* Wait for state change */
-  }
+  /* Wait for state change */
+  do {
+    status = rival.status;
+  } while ( status == MRSP_WAIT_FOR_OWNERSHIP );
 
   _Thread_Disable_dispatch();
   _Thread_Set_life_protection( previous_life_protection );
@@ -213,22 +214,6 @@ RTEMS_INLINE_ROUTINE MRSP_Status _MRSP_Wait_for_ownership(
     _Watchdog_Remove( &executing->Timer );
   }
 
-  _Chain_Extract_unprotected( &rival.Node );
-  state = _Atomic_Load_uint( &rival.state, ATOMIC_ORDER_RELAXED );
-
-  if ( ( state & MRSP_RIVAL_STATE_NEW_OWNER ) != 0 ) {
-    mrsp->initial_priority_of_owner = initial_priority;
-    status = MRSP_SUCCESSFUL;
-  } else {
-    _Resource_Node_extract( &executing->Resource_node );
-    _Resource_Node_set_dependency( &executing->Resource_node, NULL );
-    _Scheduler_Thread_change_help_state( executing, previous_help_state );
-    _Scheduler_Thread_change_resource_root( executing, executing );
-    _MRSP_Restore_priority( mrsp, executing, initial_priority );
-
-    status = MRSP_TIMEOUT;
-  }
-
   return status;
 }
 
@@ -289,6 +274,8 @@ RTEMS_INLINE_ROUTINE MRSP_Status _MRSP_Release(
   Thread_Control *executing
 )
 {
+  ISR_Level level;
+
   if ( _Resource_Get_owner( &mrsp->Resource ) != &executing->Resource_node ) {
     return MRSP_NOT_OWNER_OF_RESOURCE;
   }
@@ -303,21 +290,35 @@ RTEMS_INLINE_ROUTINE MRSP_Status _MRSP_Release(
   }
 
   _Resource_Extract( &mrsp->Resource );
-  _MRSP_Restore_priority( mrsp, executing, mrsp->initial_priority_of_owner );
+  _MRSP_Restore_priority( executing, mrsp->initial_priority_of_owner );
+
+  _ISR_Disable( level );
 
   if ( _Chain_Is_empty( &mrsp->Rivals ) ) {
+    _ISR_Enable( level );
+
     _Resource_Set_owner( &mrsp->Resource, NULL );
   } else {
-    MRSP_Rival *rival = (MRSP_Rival *) _Chain_First( &mrsp->Rivals );
-    Thread_Control *new_owner = rival->thread;
+    MRSP_Rival *rival = (MRSP_Rival *)
+      _Chain_Get_first_unprotected( &mrsp->Rivals );
+    Thread_Control *new_owner;
+
+    /*
+     * This must be inside the critical section since the status prevents a
+     * potential double extraction in _MRSP_Timeout().
+     */
+    rival->status = MRSP_SUCCESSFUL;
+
+    _ISR_Enable( level );
 
+    new_owner = rival->thread;
+    mrsp->initial_priority_of_owner = rival->initial_priority;
     _Resource_Node_extract( &new_owner->Resource_node );
     _Resource_Node_set_dependency( &new_owner->Resource_node, NULL );
     _Resource_Node_add_resource( &new_owner->Resource_node, &mrsp->Resource );
     _Resource_Set_owner( &mrsp->Resource, &new_owner->Resource_node );
     _Scheduler_Thread_change_help_state( new_owner, SCHEDULER_HELP_ACTIVE_OWNER );
     _Scheduler_Thread_change_resource_root( new_owner, new_owner );
-    _MRSP_Add_state( rival, MRSP_RIVAL_STATE_NEW_OWNER );
   }
 
   if ( !_Resource_Node_owns_resources( &executing->Resource_node ) ) {
diff --git a/testsuites/smptests/smpmrsp01/init.c b/testsuites/smptests/smpmrsp01/init.c
index 71aa844..224cea3 100644
--- a/testsuites/smptests/smpmrsp01/init.c
+++ b/testsuites/smptests/smpmrsp01/init.c
@@ -208,6 +208,15 @@ static void print_switch_events(test_context *ctx)
   }
 }
 
+static void run_task(rtems_task_argument arg)
+{
+  volatile bool *run = (volatile bool *) arg;
+
+  while (true) {
+    *run = true;
+  }
+}
+
 static void obtain_and_release_worker(rtems_task_argument arg)
 {
   test_context *ctx = &test_instance;
@@ -216,7 +225,7 @@ static void obtain_and_release_worker(rtems_task_argument arg)
 
   ctx->worker_task = _Thread_Get_executing();
 
-  assert_prio(RTEMS_SELF, 3);
+  assert_prio(RTEMS_SELF, 4);
 
   /* Obtain with timeout (A) */
   barrier(ctx, &barrier_state);
@@ -224,7 +233,7 @@ static void obtain_and_release_worker(rtems_task_argument arg)
   sc = rtems_semaphore_obtain(ctx->mrsp_ids[0], RTEMS_WAIT, 4);
   rtems_test_assert(sc == RTEMS_TIMEOUT);
 
-  assert_prio(RTEMS_SELF, 3);
+  assert_prio(RTEMS_SELF, 4);
 
   /* Obtain with priority change and timeout (B) */
   barrier(ctx, &barrier_state);
@@ -232,7 +241,7 @@ static void obtain_and_release_worker(rtems_task_argument arg)
   sc = rtems_semaphore_obtain(ctx->mrsp_ids[0], RTEMS_WAIT, 4);
   rtems_test_assert(sc == RTEMS_TIMEOUT);
 
-  assert_prio(RTEMS_SELF, 1);
+  assert_prio(RTEMS_SELF, 2);
 
   /* Restore priority (C) */
   barrier(ctx, &barrier_state);
@@ -243,14 +252,25 @@ static void obtain_and_release_worker(rtems_task_argument arg)
   sc = rtems_semaphore_obtain(ctx->mrsp_ids[0], RTEMS_WAIT, RTEMS_NO_TIMEOUT);
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
 
-  assert_prio(RTEMS_SELF, 2);
+  assert_prio(RTEMS_SELF, 3);
 
   sc = rtems_semaphore_release(ctx->mrsp_ids[0]);
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
 
-  assert_prio(RTEMS_SELF, 3);
+  assert_prio(RTEMS_SELF, 4);
 
-  /* Worker done (E) */
+  /* Obtain and help with timeout (E) */
+  barrier(ctx, &barrier_state);
+
+  sc = rtems_semaphore_obtain(ctx->mrsp_ids[0], RTEMS_WAIT, 4);
+  rtems_test_assert(sc == RTEMS_TIMEOUT);
+
+  assert_prio(RTEMS_SELF, 4);
+
+  sc = rtems_task_suspend(ctx->high_task_id[0]);
+  rtems_test_assert(sc == RTEMS_SUCCESSFUL);
+
+  /* Worker done (H) */
   barrier(ctx, &barrier_state);
 
   rtems_task_suspend(RTEMS_SELF);
@@ -266,7 +286,21 @@ static void test_mrsp_obtain_and_release(test_context *ctx)
 
   puts("test MrsP obtain and release");
 
-  change_prio(RTEMS_SELF, 2);
+  change_prio(RTEMS_SELF, 3);
+
+  reset_switch_events(ctx);
+
+  ctx->high_run[0] = false;
+
+  sc = rtems_task_create(
+    rtems_build_name('H', 'I', 'G', '0'),
+    1,
+    RTEMS_MINIMUM_STACK_SIZE,
+    RTEMS_DEFAULT_MODES,
+    RTEMS_DEFAULT_ATTRIBUTES,
+    &ctx->high_task_id[0]
+  );
+  rtems_test_assert(sc == RTEMS_SUCCESSFUL);
 
   /* Check executing task parameters */
 
@@ -282,17 +316,17 @@ static void test_mrsp_obtain_and_release(test_context *ctx)
     1,
     RTEMS_MULTIPROCESSOR_RESOURCE_SHARING
       | RTEMS_BINARY_SEMAPHORE,
-    1,
+    2,
     &ctx->mrsp_ids[0]
   );
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
 
-  assert_prio(RTEMS_SELF, 2);
+  assert_prio(RTEMS_SELF, 3);
 
   sc = rtems_semaphore_obtain(ctx->mrsp_ids[0], RTEMS_WAIT, RTEMS_NO_TIMEOUT);
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
 
-  assert_prio(RTEMS_SELF, 1);
+  assert_prio(RTEMS_SELF, 2);
 
   /*
    * The ceiling priority values per scheduler are equal to the value specified
@@ -307,11 +341,11 @@ static void test_mrsp_obtain_and_release(test_context *ctx)
     &prio
   );
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
-  rtems_test_assert(prio == 1);
+  rtems_test_assert(prio == 2);
 
   /* Check the old value and set a new ceiling priority for scheduler B */
 
-  prio = 2;
+  prio = 3;
   sc = rtems_semaphore_set_priority(
     ctx->mrsp_ids[0],
     ctx->scheduler_ids[1],
@@ -319,7 +353,7 @@ static void test_mrsp_obtain_and_release(test_context *ctx)
     &prio
   );
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
-  rtems_test_assert(prio == 1);
+  rtems_test_assert(prio == 2);
 
   /* Check the ceiling priority values */
 
@@ -331,7 +365,7 @@ static void test_mrsp_obtain_and_release(test_context *ctx)
     &prio
   );
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
-  rtems_test_assert(prio == 1);
+  rtems_test_assert(prio == 2);
 
   prio = RTEMS_CURRENT_PRIORITY;
   sc = rtems_semaphore_set_priority(
@@ -341,13 +375,13 @@ static void test_mrsp_obtain_and_release(test_context *ctx)
     &prio
   );
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
-  rtems_test_assert(prio == 2);
+  rtems_test_assert(prio == 3);
 
   /* Check that a thread waiting to get ownership remains executing */
 
   sc = rtems_task_create(
     rtems_build_name('W', 'O', 'R', 'K'),
-    3,
+    4,
     RTEMS_MINIMUM_STACK_SIZE,
     RTEMS_DEFAULT_MODES,
     RTEMS_DEFAULT_ATTRIBUTES,
@@ -364,37 +398,68 @@ static void test_mrsp_obtain_and_release(test_context *ctx)
   /* Obtain with timeout (A) */
   barrier_and_delay(ctx, &barrier_state);
 
-  assert_prio(ctx->worker_ids[0], 2);
+  assert_prio(ctx->worker_ids[0], 3);
   assert_executing_worker(ctx);
 
   /* Obtain with priority change and timeout (B) */
   barrier_and_delay(ctx, &barrier_state);
 
-  assert_prio(ctx->worker_ids[0], 2);
-  change_prio(ctx->worker_ids[0], 1);
+  assert_prio(ctx->worker_ids[0], 3);
+  change_prio(ctx->worker_ids[0], 2);
   assert_executing_worker(ctx);
 
   /* Restore priority (C) */
   barrier(ctx, &barrier_state);
 
-  assert_prio(ctx->worker_ids[0], 1);
-  change_prio(ctx->worker_ids[0], 3);
+  assert_prio(ctx->worker_ids[0], 2);
+  change_prio(ctx->worker_ids[0], 4);
 
   /* Obtain without timeout (D) */
   barrier_and_delay(ctx, &barrier_state);
 
-  assert_prio(ctx->worker_ids[0], 2);
+  assert_prio(ctx->worker_ids[0], 3);
   assert_executing_worker(ctx);
 
   sc = rtems_semaphore_release(ctx->mrsp_ids[0]);
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
 
-  /* Worker done (E) */
+  /* Check that a timeout works in case the waiting thread actually helps */
+
+  sc = rtems_semaphore_obtain(ctx->mrsp_ids[0], RTEMS_WAIT, RTEMS_NO_TIMEOUT);
+  rtems_test_assert(sc == RTEMS_SUCCESSFUL);
+
+  /* Obtain and help with timeout (E) */
+  barrier_and_delay(ctx, &barrier_state);
+
+  sc = rtems_task_start(
+    ctx->high_task_id[0],
+    run_task,
+    (rtems_task_argument) &ctx->high_run[0]
+  );
+  rtems_test_assert(sc == RTEMS_SUCCESSFUL);
+
+  rtems_test_assert(rtems_get_current_processor() == 1);
+
+  while (rtems_get_current_processor() != 0) {
+    /* Wait */
+  }
+
+  rtems_test_assert(ctx->high_run[0]);
+
+  sc = rtems_semaphore_release(ctx->mrsp_ids[0]);
+  rtems_test_assert(sc == RTEMS_SUCCESSFUL);
+
+  print_switch_events(ctx);
+
+  /* Worker done (H) */
   barrier(ctx, &barrier_state);
 
   sc = rtems_task_delete(ctx->worker_ids[0]);
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
 
+  sc = rtems_task_delete(ctx->high_task_id[0]);
+  rtems_test_assert(sc == RTEMS_SUCCESSFUL);
+
   sc = rtems_semaphore_delete(ctx->mrsp_ids[0]);
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
 }
@@ -727,15 +792,6 @@ static void test_mrsp_multiple_obtain(void)
   rtems_test_assert(sc == RTEMS_SUCCESSFUL);
 }
 
-static void run_task(rtems_task_argument arg)
-{
-  volatile bool *run = (volatile bool *) arg;
-
-  while (true) {
-    *run = true;
-  }
-}
-
 static void ready_unlock_worker(rtems_task_argument arg)
 {
   test_context *ctx = &test_instance;
-- 
1.8.4.5



More information about the devel mailing list