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:
parent
2072e6be94
commit
927dd57ef2
@ -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).
|
||||
|
@ -289,8 +289,8 @@ struct usbdev_s
|
||||
{
|
||||
const struct usbdev_ops_s *ops; /* Access to hardware specific features */
|
||||
struct usbdev_ep_s *ep0; /* Endpoint zero */
|
||||
uint8_t speed; /* Current speed of the host connection */
|
||||
uint8_t dualspeed:1; /* 1:supports high and full speed operation */
|
||||
uint8_t speed; /* Current speed of the host connection */
|
||||
uint8_t dualspeed:1; /* 1:supports high and full speed operation */
|
||||
};
|
||||
|
||||
/* USB Device Class Implementations *************************************************/
|
||||
|
@ -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. */
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(error_code);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -508,8 +508,8 @@ static inline void task_exitwakeup(FAR struct tcb_s *tcb, int status)
|
||||
*
|
||||
* "If more than one thread is suspended in waitpid() awaiting
|
||||
* termination of the same process, exactly one thread will
|
||||
* return the process status at the time of the target process
|
||||
* termination."
|
||||
* return the process status at the time of the target process
|
||||
* termination."
|
||||
*
|
||||
* Hmmm.. what do we return to the others?
|
||||
*/
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
task_atexit(tcb);
|
||||
if (!nonblocking)
|
||||
{
|
||||
task_atexit(tcb);
|
||||
|
||||
/* Call any registered on_exit function(s) */
|
||||
/* Call any registered on_exit function(s) */
|
||||
|
||||
task_onexit(tcb, status);
|
||||
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
|
||||
*/
|
||||
|
||||
task_flushstreams(tcb);
|
||||
if (!nonblocking)
|
||||
{
|
||||
task_flushstreams(tcb);
|
||||
}
|
||||
|
||||
/* Leave the task group. Perhaps discarding any un-reaped child
|
||||
* status (no zombies here!)
|
||||
|
Loading…
Reference in New Issue
Block a user