Reviewed all task exit logic. For pthread_exit() moved some logic higher in the exit sequence that could be required to block. For lower level logic kicked off by _exit(), add logic to prevent blocking when the task is not in a healthy state.

This commit is contained in:
Gregory Nutt 2013-04-23 16:41:43 -06:00
parent 2072e6be94
commit 927dd57ef2
8 changed files with 174 additions and 199 deletions

View File

@ -4599,4 +4599,10 @@
* tools/kconfig2html.c: Improve behavior of Expand/Collapse
Table of Contents; Handle errors in parsing of strings and in
some uninitialized variables. Add an option to use jQuery.
* tools/mkconfigvar.sh: Fix make target.
* tools/mkconfigvar.sh: Fix make target (2014-4-23).
* sched/exit.c, pthread_exit.c, task_exit.c, task_delete,c and
task_exithook.c: For pthread_exit(), move some logic to an early
point in the exit sequence where the task may need to block. Add
conditional logic in the lower end of the eixt logic kicked off by
_exit() to prohibit blocking after the task has been torn down and is
no longer cabable of blocking (2014-4-23).

View File

@ -100,10 +100,11 @@ void exit(int status)
/* Perform common task termination logic. This will get called again later
* through logic kicked off by _exit(). However, we need to call it before
* calling _exit() in order to handle atexit() and on_exit() callbacks.
* calling _exit() in order to handle atexit() and on_exit() callbacks and
* so that we can flush buffered I/O (both of which may required suspending).
*/
task_exithook(tcb, status);
task_exithook(tcb, status, false);
/* Then "really" exit. Only the lower 8 bits of the exit status are used. */

View File

@ -265,7 +265,8 @@ int task_schedsetup(FAR struct task_tcb_s *tcb, int priority, start_t start,
main_t main, uint8_t ttype);
int task_argsetup(FAR struct task_tcb_s *tcb, FAR const char *name, FAR char * const argv[]);
int task_exit(void);
void task_exithook(FAR struct tcb_s *tcb, int status);
int task_terminate(pid_t pid, bool nonblocking);
void task_exithook(FAR struct tcb_s *tcb, int status, bool nonblocking);
void task_recover(FAR struct tcb_s *tcb);
#ifndef CONFIG_CUSTOM_STACK

View File

@ -1,7 +1,7 @@
/************************************************************************
* sched/pthread_exit.c
*
* Copyright (C) 2007, 2009, 2011-2012 Gregory Nutt. All rights reserved.
* Copyright (C) 2007, 2009, 2011-2013 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
@ -49,6 +49,7 @@
#include <nuttx/arch.h>
#include "os_internal.h"
#include "pthread_internal.h"
/************************************************************************
@ -93,7 +94,7 @@
void pthread_exit(FAR void *exit_value)
{
int error_code = (int)((intptr_t)exit_value);
struct tcb_s *tcb = (struct tcb_s*)g_readytorun.head;
int status;
sdbg("exit_value=%p\n", exit_value);
@ -115,23 +116,25 @@ void pthread_exit(FAR void *exit_value)
if (status != OK)
{
/* Assume that the join completion failured because this
* not really a pthread. Exit by calling exit() to flush
* and close all file descriptors and calling atexit()
* functions.
* not really a pthread. Exit by calling exit().
*/
if (error_code == EXIT_SUCCESS)
{
error_code = EXIT_FAILURE;
exit(EXIT_FAILURE);
}
exit(error_code);
}
/* Perform common task termination logic. This will get called again later
* through logic kicked off by _exit(). However, we need to call it before
* calling _exit() in order certain operations if this is the last thread
* of a task group: (2) To handle atexit() and on_exit() callbacks and
* (2) so that we can flush buffered I/O (which may required suspending).
*/
task_exithook(tcb, EXIT_SUCCESS, false);
/* Then just exit, retaining all file descriptors and without
* calling atexit() functions.
*/
_exit(error_code);
_exit(EXIT_SUCCESS);
}

View File

@ -1,7 +1,7 @@
/****************************************************************************
* sched/task_delete.c
*
* Copyright (C) 2007-2009, 2011-2012 Gregory Nutt. All rights reserved.
* Copyright (C) 2007-2009, 2011-2013 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
@ -77,30 +77,36 @@
****************************************************************************/
/****************************************************************************
* Name: task_delete
* Name: task_terminate
*
* Description:
* This function causes a specified task to cease to exist. Its stack and
* TCB will be deallocated. This function is the companion to task_create().
* TCB will be deallocated. This function is the internal implementation
* of the task_delete() function. It includes and additional parameter
* to determine if blocking is permitted or not.
*
* The logic in this function only deletes non-running tasks. If the 'pid'
* parameter refers to to the currently runing task, then processing is
* redirected to exit().
* This function is the final function called all task termination
* sequences. task_terminate() is called only from task_delete() (with
* nonblocking == false) and from task_exit() (with nonblocking == true).
*
* Control will still be returned to task_delete() after the exit() logic
* finishes. In fact, this function is the final function called all task
* termination sequences. Here are all possible exit scenarios:
* The path through task_exit() supports the final stops of the exit(),
* _exit(), and pthread_exit
*
* - pthread_exit(). Calls exit()
* - pthread_exit(). Calls _exit()
* - exit(). Calls _exit()
* - _exit(). Calls task_exit() making the currently running task
* non-running then calls task_delete() to terminate the non-running
* task.
* - task_delete()
* non-running. task_exit then calls task_terminate() (with nonblocking
* == true) to terminate the non-running task.
*
* NOTE: that the state of non-blocking is irrelevant when called through
* exit() and pthread_exit(). In those cases task_exithook() has already
* been called with nonblocking == false;
*
* Inputs:
* pid - The task ID of the task to delete. A pid of zero
* signifies the calling task.
* nonblocking - True: The task is an unhealthy, partially torn down
* state and is not permitted to block.
*
* Return Value:
* OK on success; or ERROR on failure
@ -110,25 +116,12 @@
*
****************************************************************************/
int task_delete(pid_t pid)
int task_terminate(pid_t pid, bool nonblocking)
{
FAR struct tcb_s *rtcb;
FAR struct tcb_s *dtcb;
irqstate_t saved_state;
int ret = ERROR;
/* Check if the task to delete is the calling task */
rtcb = (FAR struct tcb_s*)g_readytorun.head;
if (pid == 0 || pid == rtcb->pid)
{
/* If it is, then what we really wanted to do was exit. Note that we
* don't bother to unlock the TCB since it will be going away.
*/
exit(EXIT_SUCCESS);
}
/* Make sure the task does not become ready-to-run while we are futzing with
* its TCB by locking ourselves as the executing task.
*/
@ -166,7 +159,7 @@ int task_delete(pid_t pid)
* I suppose EXIT_SUCCESS is an appropriate return value???
*/
task_exithook(dtcb, EXIT_SUCCESS);
task_exithook(dtcb, EXIT_SUCCESS, nonblocking);
/* Remove the task from the OS's tasks lists. */
@ -192,3 +185,61 @@ int task_delete(pid_t pid)
return ret;
}
/****************************************************************************
* Name: task_delete
*
* Description:
* This function causes a specified task to cease to exist. Its stack and
* TCB will be deallocated. This function is the companion to task_create().
* This is the version of the function exposed to the user; it is simply
* a wrapper around the internal, task_terminate function.
*
* The logic in this function only deletes non-running tasks. If the 'pid'
* parameter refers to to the currently runing task, then processing is
* redirected to exit(). This can only happen if a task calls task_delete()
* in order to delete itself.
*
* In fact, this function (and task_terminate) are the final functions
* called all task termination sequences. task_delete may be called
* from:
*
* - task_restart(),
* - pthread_cancel(),
* - and directly from user code.
*
* Other exit paths (exit(), _eixt(), and pthread_exit()) will go through
* task_terminate()
*
* Inputs:
* pid - The task ID of the task to delete. A pid of zero
* signifies the calling task.
*
* Return Value:
* OK on success; or ERROR on failure
*
* This function can fail if the provided pid does not correspond to a
* task (errno is not set)
*
****************************************************************************/
int task_delete(pid_t pid)
{
FAR struct tcb_s *rtcb;
/* Check if the task to delete is the calling task */
rtcb = (FAR struct tcb_s*)g_readytorun.head;
if (pid == 0 || pid == rtcb->pid)
{
/* If it is, then what we really wanted to do was exit. Note that we
* don't bother to unlock the TCB since it will be going away.
*/
DEBUGASSERT(!nonblocking);
exit(EXIT_SUCCESS);
}
/* Then let task_terminate do the heavy lifting */
return task_terminate(pid, false);
}

View File

@ -69,116 +69,6 @@
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: task_cancel_atexit
*
* Description:
* Cncel any registerd atexit function(s)
*
* This function is called from task_exit() which implements the processor-
* independent part of _exit(). _exit() is, in turn, used to implement
* the bottom half of exit() and pthread_exit(). These cases are
* distinguished as follows:
*
* 1) _exit() should be called by user logic only from tasks. In this
* case, atexit() calls will be canceled by this function.
* 2) If the user calls exit(), the exit() function will call task_exithook()
* which will process all pending atexit() call. In that case, this
* function will have no effect.
* 3) If the user called pthread_exit(), the logic in this function will
* do nothing. Only a task can legitimately called _exit(). atexit
* calls will not be cleared. task_exithook() will be called later (from
* task_delete()) and if this is the final thread of the group, any
* registered atexit() calls will be performed.
*
****************************************************************************/
#if defined(CONFIG_SCHED_ATEXIT) && !defined(CONFIG_SCHED_ONEXIT)
static inline void task_cancel_atexit(FAR struct tcb_s *tcb)
{
FAR struct task_group_s *group = tcb->group;
DEBUGASSERT(group);
/* This behavior applies only to tasks that call _exit() */
#ifndef CONFIG_DISABLE_PTHREAD
if ((tcb->flags & TCB_FLAG_TTYPE_MASK) != TCB_FLAG_TTYPE_PTHREAD)
#endif
{
#if defined(CONFIG_SCHED_ATEXIT_MAX) && CONFIG_SCHED_ATEXIT_MAX > 1
int index;
/* Nullify each atexit function pointer */
for (index = 0; index < CONFIG_SCHED_ATEXIT_MAX; index++)
{
group->tg_atexitfunc[index] = NULL;
}
#else
/* Nullify the atexit function to prevent its reuse. */
group->tg_atexitfunc = NULL;
#endif
}
}
#else
# define task_cancel_atexit(tcb)
#endif
/****************************************************************************
* Name: task_cancel_onexit
*
* Description:
* Cancel any registerd on)exit function(s).
*
* This function is called from task_exit() which implements the processor-
* independent part of _exit(). _exit() is, in turn, used to implement
* the bottom half of exit() and pthread_exit(). These cases are
* distinguished as follows:
*
* 1) _exit() should be called by user logic only from tasks. In this
* case, on_exit() calls will be canceled by this function.
* 2) If the user calls exit(), the exit() function will call task_exithook()
* which will process all pending on_exit() call. In that case, this
* function will have no effect.
* 3) If the user called pthread_exit(), the logic in this function will
* do nothing. Only a task can legitimately called _exit(). on_exit
* calls will not be cleared. task_exithook() will be called later (from
* task_delete()) and if this is the final thread of the group, any
* registered on_exit() calls will be performed.
*
****************************************************************************/
#ifdef CONFIG_SCHED_ONEXIT
static inline void task_cancel_onexit(FAR struct tcb_s *tcb)
{
FAR struct task_group_s *group = tcb->group;
DEBUGASSERT(group);
/* This behavior applies only to tasks that call _exit() */
#ifndef CONFIG_DISABLE_PTHREAD
if ((tcb->flags & TCB_FLAG_TTYPE_MASK) != TCB_FLAG_TTYPE_PTHREAD)
#endif
{
#if defined(CONFIG_SCHED_ONEXIT_MAX) && CONFIG_SCHED_ONEXIT_MAX > 1
int index;
/* Nullify each atexit function pointer */
for (index = 0; index < CONFIG_SCHED_ONEXIT_MAX; index++)
{
group->tg_onexitfunc[index] = NULL;
}
#else
group->tg_onexitfunc = NULL;
#endif
}
}
#else
# define task_cancel_onexit(tcb)
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -227,14 +117,6 @@ int task_exit(void)
(void)sched_removereadytorun(dtcb);
rtcb = (FAR struct tcb_s*)g_readytorun.head;
/* Cancel any pending atexit() or on_exit() calls. These are not performed
* when performing _exit(). Different implementations of _exit() may or may
* not* flush buffered I/O. This implemenation *will* flush buffered I/O.
*/
task_cancel_atexit(rtcb);
task_cancel_onexit(rtcb);
/* We are now in a bad state -- the head of the ready to run task list
* does not correspond to the thread that is running. Disabling pre-
* emption on this TCB and marking the new ready-to-run task as not
@ -247,10 +129,14 @@ int task_exit(void)
rtcb->lockcount++;
rtcb->task_state = TSTATE_TASK_READYTORUN;
/* Move the TCB to the specified blocked task list and delete it */
/* Move the TCB to the specified blocked task list and delete it. Calling
* task_terminate with non-blocking true will suppress atexit() and on-exit()
* calls and will cause buffered I/O to fail to be flushed. The former
* is required _exit() behavior; the latter is optional _exit() behavior.
*/
sched_addblocked(dtcb, TSTATE_TASK_INACTIVE);
task_delete(dtcb->pid);
task_terminate(dtcb->pid, true);
rtcb->task_state = TSTATE_TASK_RUNNING;
/* If there are any pending tasks, then add them to the ready-to-run

View File

@ -588,14 +588,18 @@ static inline void task_flushstreams(FAR struct tcb_s *tcb)
* to-run list. The following logic is safe because we will not be
* returning from the exit() call.
*
* When called from task_delete() we are operating on a different thread;
* When called from task_terminate() we are operating on a different thread;
* on the thread that called task_delete(). In this case, task_delete
* will have already removed the tcb from the ready-to-run list to prevent
* any further action on this task.
*
* nonblocking will be set true only when we are called from task_terminate()
* via _exit(). In that case, we must be careful to do nothing that can
* cause the cause the thread to block.
*
****************************************************************************/
void task_exithook(FAR struct tcb_s *tcb, int status)
void task_exithook(FAR struct tcb_s *tcb, int status, bool nonblocking)
{
/* Under certain conditions, task_exithook() can be called multiple times.
* A bit in the TCB was set the first time this function was called. If
@ -608,15 +612,28 @@ void task_exithook(FAR struct tcb_s *tcb, int status)
}
/* If exit function(s) were registered, call them now before we do any un-
* initialization. NOTE: In the case of task_delete(), the exit function
* will *not* be called on the thread execution of the task being deleted!
* initialization.
*
* NOTES:
*
* 1. In the case of task_delete(), the exit function will *not* be called
* on the thread execution of the task being deleted! That is probably
* a bug.
* 2. We cannot call the exit functions if nonblocking is requested: These
* functions might block.
* 3. This function will only be called with with non-blocking == true
* only when called through _exit(). _exit() behaviors requires that
* the exit functions *not* be called.
*/
if (!nonblocking)
{
task_atexit(tcb);
/* Call any registered on_exit function(s) */
task_onexit(tcb, status);
}
/* If the task was terminated by another task, it may be in an unknown
* state. Make some feeble effort to recover the state.
@ -634,9 +651,19 @@ void task_exithook(FAR struct tcb_s *tcb, int status)
/* If this is the last thread in the group, then flush all streams (File
* descriptors will be closed when the TCB is deallocated).
*
* NOTES:
* 1. We cannot flush the buffered I/O if nonblocking is requested.
* that might cause this logic to block.
* 2. This function will only be called with with non-blocking == true
* only when called through _exit(). _exit() behavior does not
* require that the streams be flushed
*/
if (!nonblocking)
{
task_flushstreams(tcb);
}
/* Leave the task group. Perhaps discarding any un-reaped child
* status (no zombies here!)