source: rtems/cpukit/score/src/threadrestart.c @ 01f302b

4.115
Last change on this file since 01f302b was 38b59a6, checked in by Sebastian Huber <sebastian.huber@…>, on 05/02/14 at 08:31:09

score: Implement forced thread migration

The current implementation of task migration in RTEMS has some
implications with respect to the interrupt latency. It is crucial to
preserve the system invariant that a task can execute on at most one
processor in the system at a time. This is accomplished with a boolean
indicator in the task context. The processor architecture specific
low-level task context switch code will mark that a task context is no
longer executing and waits that the heir context stopped execution
before it restores the heir context and resumes execution of the heir
task. So there is one point in time in which a processor is without a
task. This is essential to avoid cyclic dependencies in case multiple
tasks migrate at once. Otherwise some supervising entity is necessary to
prevent life-locks. Such a global supervisor would lead to scalability
problems so this approach is not used. Currently the thread dispatch is
performed with interrupts disabled. So in case the heir task is
currently executing on another processor then this prolongs the time of
disabled interrupts since one processor has to wait for another
processor to make progress.

It is difficult to avoid this issue with the interrupt latency since
interrupts normally store the context of the interrupted task on its
stack. In case a task is marked as not executing we must not use its
task stack to store such an interrupt context. We cannot use the heir
stack before it stopped execution on another processor. So if we enable
interrupts during this transition we have to provide an alternative task
independent stack for this time frame. This issue needs further
investigation.

  • Property mode set to 100644
File size: 10.7 KB
Line 
1/**
2 * @file
3 *
4 * @brief Restart Thread
5 * @ingroup ScoreThread
6 */
7
8/*
9 *  COPYRIGHT (c) 1989-1999.
10 *  On-Line Applications Research Corporation (OAR).
11 *
12 *  Copyright (c) 2014 embedded brains GmbH.
13 *
14 *  The license and distribution terms for this file may be
15 *  found in the file LICENSE in this distribution or at
16 *  http://www.rtems.org/license/LICENSE.
17 */
18
19#if HAVE_CONFIG_H
20#include "config.h"
21#endif
22
23#include <rtems/score/threadimpl.h>
24#include <rtems/score/apimutex.h>
25#include <rtems/score/assert.h>
26#include <rtems/score/chainimpl.h>
27#include <rtems/score/isrlock.h>
28#include <rtems/score/schedulerimpl.h>
29#include <rtems/score/sysstate.h>
30#include <rtems/score/threadqimpl.h>
31#include <rtems/score/userextimpl.h>
32#include <rtems/score/watchdogimpl.h>
33#include <rtems/score/wkspace.h>
34
35typedef struct {
36  Chain_Control Chain;
37  ISR_lock_Control Lock;
38} Thread_Zombie_control;
39
40static Thread_Zombie_control _Thread_Zombies = {
41  .Chain = CHAIN_INITIALIZER_EMPTY( _Thread_Zombies.Chain ),
42  .Lock = ISR_LOCK_INITIALIZER( "thread zombies" )
43};
44
45static void _Thread_Make_zombie( Thread_Control *the_thread )
46{
47  ISR_lock_Context lock_context;
48  Thread_Zombie_control *zombies = &_Thread_Zombies;
49
50  if ( the_thread->resource_count != 0 ) {
51    _Terminate(
52      INTERNAL_ERROR_CORE,
53      false,
54      INTERNAL_ERROR_RESOURCE_IN_USE
55    );
56  }
57
58  _Thread_Set_state( the_thread, STATES_ZOMBIE );
59  _Thread_queue_Extract_with_proxy( the_thread );
60  _Watchdog_Remove( &the_thread->Timer );
61
62  _ISR_lock_ISR_disable_and_acquire( &zombies->Lock, &lock_context );
63  _Chain_Append_unprotected( &zombies->Chain, &the_thread->Object.Node );
64  _ISR_lock_Release_and_ISR_enable( &zombies->Lock, &lock_context );
65}
66
67static void _Thread_Free( Thread_Control *the_thread )
68{
69  _User_extensions_Thread_delete( the_thread );
70
71  /*
72   * Free the per-thread scheduling information.
73   */
74  _Scheduler_Free( _Scheduler_Get( the_thread ), the_thread );
75
76  /*
77   *  The thread might have been FP.  So deal with that.
78   */
79#if ( CPU_HARDWARE_FP == TRUE ) || ( CPU_SOFTWARE_FP == TRUE )
80#if ( CPU_USE_DEFERRED_FP_SWITCH == TRUE )
81  if ( _Thread_Is_allocated_fp( the_thread ) )
82    _Thread_Deallocate_fp();
83#endif
84
85  _Workspace_Free( the_thread->Start.fp_context );
86#endif
87
88  /*
89   *  Free the rest of the memory associated with this task
90   *  and set the associated pointers to NULL for safety.
91   */
92  _Thread_Stack_Free( the_thread );
93
94  _Workspace_Free( the_thread->Start.tls_area );
95
96  _Objects_Free(
97    _Objects_Get_information_id( the_thread->Object.id ),
98    &the_thread->Object
99  );
100}
101
102static void _Thread_Wait_for_execution_stop( Thread_Control *the_thread )
103{
104#if defined(RTEMS_SMP)
105  /*
106   * It is very unlikely that we see an executing thread here.  It can happen
107   * in case the thread termination sequence is interrupted by a slow interrupt
108   * service on a remote processor.
109   */
110  while ( _Thread_Is_executing_on_a_processor( the_thread ) ) {
111    /* Wait */
112  }
113#else
114  (void) the_thread;
115#endif
116}
117
118void _Thread_Kill_zombies( void )
119{
120  ISR_lock_Context lock_context;
121  Thread_Zombie_control *zombies = &_Thread_Zombies;
122  Thread_Control *the_thread;
123
124  _ISR_lock_ISR_disable_and_acquire( &zombies->Lock, &lock_context );
125
126  the_thread = (Thread_Control *) _Chain_Get_unprotected( &zombies->Chain );
127  while ( the_thread != NULL ) {
128    _ISR_lock_Release_and_ISR_enable( &zombies->Lock, &lock_context );
129
130    _Thread_Wait_for_execution_stop( the_thread );
131    _Thread_Free( the_thread );
132
133    _ISR_lock_ISR_disable_and_acquire( &zombies->Lock, &lock_context );
134
135    the_thread = (Thread_Control *) _Chain_Get_unprotected( &zombies->Chain );
136  }
137
138  _ISR_lock_Release_and_ISR_enable( &zombies->Lock, &lock_context );
139}
140
141static void _Thread_Start_life_change_for_executing(
142  Thread_Control *executing
143)
144{
145  _Assert( executing->Timer.state == WATCHDOG_INACTIVE );
146  _Assert(
147    executing->current_state == STATES_READY
148      || executing->current_state == STATES_SUSPENDED
149  );
150
151  _Thread_Add_post_switch_action( executing, &executing->Life.Action );
152}
153
154void _Thread_Life_action_handler(
155  Thread_Control  *executing,
156  Thread_Action   *action,
157  Per_CPU_Control *cpu,
158  ISR_Level        level
159)
160{
161  Thread_Life_state previous_life_state;
162
163  (void) action;
164
165  previous_life_state = executing->Life.state;
166  executing->Life.state = THREAD_LIFE_PROTECTED;
167
168  _Thread_Action_release_and_ISR_enable( cpu, level );
169
170  if ( _Thread_Is_life_terminating( previous_life_state ) ) {
171    _User_extensions_Thread_terminate( executing );
172  } else {
173    _Assert( _Thread_Is_life_restarting( previous_life_state ) );
174
175    _User_extensions_Thread_restart( executing );
176  }
177
178  _Thread_Disable_dispatch();
179
180  if ( _Thread_Is_life_terminating( previous_life_state ) ) {
181    _Thread_Make_zombie( executing );
182
183    if ( executing->Life.terminator != NULL ) {
184      _Thread_Clear_state(
185        executing->Life.terminator,
186        STATES_WAITING_FOR_TERMINATION
187      );
188    }
189
190    _Thread_Enable_dispatch();
191
192    _Assert_Not_reached();
193  } else {
194    _Assert( _Thread_Is_life_restarting( previous_life_state ) );
195
196    if ( _Thread_Is_life_terminating( executing->Life.state ) ) {
197      /* Someone deleted us in the mean-time */
198      _Thread_Start_life_change_for_executing( executing );
199    } else {
200      _Assert( executing->Timer.state == WATCHDOG_INACTIVE );
201      _Assert(
202        executing->current_state == STATES_READY
203          || executing->current_state == STATES_SUSPENDED
204      );
205
206      executing->Life.state = THREAD_LIFE_NORMAL;
207
208      _Thread_Load_environment( executing );
209      _Thread_Restart_self( executing );
210
211      _Assert_Not_reached();
212    }
213  }
214}
215
216static void _Thread_Start_life_change(
217  Thread_Control          *the_thread,
218  const Scheduler_Control *scheduler,
219  Priority_Control         priority
220)
221{
222  the_thread->is_preemptible   = the_thread->Start.is_preemptible;
223  the_thread->budget_algorithm = the_thread->Start.budget_algorithm;
224  the_thread->budget_callout   = the_thread->Start.budget_callout;
225  the_thread->real_priority    = priority;
226
227  _Thread_Set_transient( the_thread );
228  _Thread_queue_Extract_with_proxy( the_thread );
229  _Watchdog_Remove( &the_thread->Timer );
230  _Scheduler_Set_priority_if_higher( scheduler, the_thread, priority );
231  _Thread_Add_post_switch_action( the_thread, &the_thread->Life.Action );
232  _Thread_Ready( the_thread );
233  _Thread_Request_dispatch_if_executing( the_thread );
234}
235
236static void _Thread_Request_life_change(
237  Thread_Control    *the_thread,
238  Thread_Control    *executing,
239  Priority_Control   priority,
240  Thread_Life_state  additional_life_state
241)
242{
243  Thread_Life_state previous_life_state;
244  Per_CPU_Control *cpu;
245  ISR_Level level;
246  const Scheduler_Control *scheduler;
247
248  cpu = _Thread_Action_ISR_disable_and_acquire( the_thread, &level );
249  previous_life_state = the_thread->Life.state;
250  the_thread->Life.state = previous_life_state | additional_life_state;
251  _Thread_Action_release_and_ISR_enable( cpu, level );
252
253  scheduler = _Scheduler_Get( the_thread );
254  if ( the_thread == executing ) {
255    executing->real_priority = priority;
256
257    _Scheduler_Set_priority_if_higher( scheduler, the_thread, priority );
258    _Thread_Start_life_change_for_executing( executing );
259  } else if ( previous_life_state == THREAD_LIFE_NORMAL ) {
260    _Thread_Start_life_change( the_thread, scheduler, priority );
261  } else {
262    _Thread_Clear_state( the_thread, STATES_SUSPENDED );
263
264    if ( _Thread_Is_life_terminating( additional_life_state ) ) {
265      the_thread->real_priority = _Scheduler_Highest_priority_of_two(
266        scheduler,
267        the_thread->real_priority,
268        priority
269      );
270
271      _Scheduler_Change_priority_if_higher(
272        scheduler,
273        the_thread,
274        priority,
275        false
276      );
277    }
278  }
279}
280
281void _Thread_Close( Thread_Control *the_thread, Thread_Control *executing )
282{
283  _Assert( _Thread_Is_life_protected( executing->Life.state ) );
284
285  _Objects_Close(
286    _Objects_Get_information_id( the_thread->Object.id ),
287    &the_thread->Object
288  );
289
290  if ( _States_Is_dormant( the_thread->current_state ) ) {
291    _Thread_Make_zombie( the_thread );
292  } else {
293    if (
294      the_thread != executing
295        && !_Thread_Is_life_terminating( executing->Life.state )
296    ) {
297      /*
298       * Wait for termination of victim thread.  If the executing thread is
299       * also terminated, then do not wait.  This avoids potential cyclic
300       * dependencies and thus dead lock.
301       */
302       the_thread->Life.terminator = executing;
303       _Thread_Set_state( executing, STATES_WAITING_FOR_TERMINATION );
304    }
305
306    _Thread_Request_life_change(
307      the_thread,
308      executing,
309      executing->current_priority,
310      THREAD_LIFE_TERMINATING
311    );
312  }
313}
314
315bool _Thread_Restart(
316  Thread_Control            *the_thread,
317  Thread_Control            *executing,
318  void                      *pointer_argument,
319  Thread_Entry_numeric_type  numeric_argument
320)
321{
322  if ( !_States_Is_dormant( the_thread->current_state ) ) {
323    the_thread->Start.pointer_argument = pointer_argument;
324    the_thread->Start.numeric_argument = numeric_argument;
325
326    _Thread_Request_life_change(
327      the_thread,
328      executing,
329      the_thread->Start.initial_priority,
330      THREAD_LIFE_RESTARTING
331    );
332
333    return true;
334  }
335
336  return false;
337}
338
339bool _Thread_Set_life_protection( bool protect )
340{
341  bool previous_life_protection;
342  ISR_Level level;
343  Per_CPU_Control *cpu;
344  Thread_Control *executing;
345  Thread_Life_state previous_life_state;
346
347  cpu = _Thread_Action_ISR_disable_and_acquire_for_executing( &level );
348  executing = cpu->executing;
349
350  previous_life_state = executing->Life.state;
351  previous_life_protection = _Thread_Is_life_protected( previous_life_state );
352
353  if ( protect ) {
354    executing->Life.state = previous_life_state | THREAD_LIFE_PROTECTED;
355  } else {
356    executing->Life.state = previous_life_state & ~THREAD_LIFE_PROTECTED;
357  }
358
359  _Thread_Action_release_and_ISR_enable( cpu, level );
360
361#if defined(RTEMS_SMP)
362  /*
363   * On SMP configurations it is possible that a life change of an executing
364   * thread is requested, but this thread didn't notice it yet.  The life
365   * change is first marked in the life state field and then all scheduling and
366   * other thread state updates are performed.  The last step is to issues an
367   * inter-processor interrupt if necessary.  Since this takes some time we
368   * have to synchronize here.
369   */
370  if (
371    !_Thread_Is_life_protected( previous_life_state )
372      && _Thread_Is_life_changing( previous_life_state )
373  ) {
374    _Thread_Disable_dispatch();
375    _Thread_Enable_dispatch();
376
377    _Assert_Not_reached();
378  }
379#endif
380
381  if (
382    !protect
383      && _Thread_Is_life_changing( previous_life_state )
384  ) {
385    _Thread_Disable_dispatch();
386    _Thread_Start_life_change_for_executing( executing );
387    _Thread_Enable_dispatch();
388
389    _Assert_Not_reached();
390  }
391
392  return previous_life_protection;
393}
Note: See TracBrowser for help on using the repository browser.