libc/stdio: Flush streams in userspace when process exits

This fixes a long standing issue, where the kernel does flushing of file
streams, although it should not handle such streams at all.
This commit is contained in:
Ville Juven 2022-12-14 09:37:22 +02:00 committed by Xiang Xiao
parent c2fa780ed0
commit 3be81e649f
10 changed files with 42 additions and 85 deletions

View File

@ -27,6 +27,7 @@
#include <nuttx/atexit.h>
#include <nuttx/compiler.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@ -47,9 +48,15 @@ FAR void *__dso_handle = &__dso_handle;
void exit(int status)
{
/* Run the registered exit functions */
atexit_call_exitfuncs(status);
/* REVISIT: Need to flush files and streams */
/* Flush all streams */
fflush(NULL);
/* Then perform the exit */
_exit(status);
}

View File

@ -33,6 +33,7 @@
#include <nuttx/fs/fs.h>
#include <nuttx/net/net.h>
#include <nuttx/lib/lib.h>
#include <nuttx/sched.h>
#ifdef CONFIG_BINFMT_LOADABLE
# include <nuttx/binfmt/binfmt.h>

View File

@ -100,5 +100,5 @@ int pthread_cancel(pthread_t thread)
/* Then let nxtask_terminate do the real work */
return nxtask_terminate((pid_t)thread, false);
return nxtask_terminate((pid_t)thread);
}

View File

@ -90,17 +90,18 @@ void nx_pthread_exit(FAR void *exit_value)
}
/* 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).
* through logic kicked off by up_exit().
*
* REVISIT: Tt should not be necessary to call this here, but releasing the
* task group (especially the group file list) requires that it is done
* here.
*
* The reason? up_exit removes the current process from the ready-to-run
* list and trying to execute code that depends on this_task() crashes at
* once, or does something very naughty.
*/
nxtask_exithook(tcb, EXIT_SUCCESS, false);
/* Then just exit, retaining all file descriptors and without
* calling atexit() functions.
*/
nxtask_exithook(tcb, status);
up_exit(EXIT_SUCCESS);
}

View File

@ -68,12 +68,18 @@ void _exit(int status)
#endif
/* Perform common task termination logic. This will get called again later
* through logic kicked off by up_exit(). However, we need to call it here
* so that we can flush buffered I/O (both of which may required
* suspending). This will be fixed later when I/O flush is moved to libc.
* through logic kicked off by up_exit().
*
* REVISIT: Tt should not be necessary to call this here, but releasing the
* task group (especially the group file list) requires that it is done
* here.
*
* The reason? up_exit removes the current process from the ready-to-run
* list and trying to execute code that depends on this_task() crashes at
* once, or does something very naughty.
*/
nxtask_exithook(tcb, status, false);
nxtask_exithook(tcb, status);
up_exit(status);
}

View File

@ -50,8 +50,8 @@ int nxtask_setup_arguments(FAR struct task_tcb_s *tcb,
/* Task exit */
int nxtask_exit(void);
int nxtask_terminate(pid_t pid, bool nonblocking);
void nxtask_exithook(FAR struct tcb_s *tcb, int status, bool nonblocking);
int nxtask_terminate(pid_t pid);
void nxtask_exithook(FAR struct tcb_s *tcb, int status);
void nxtask_recover(FAR struct tcb_s *tcb);
/* Cancellation points */

View File

@ -132,7 +132,7 @@ int nxtask_delete(pid_t pid)
* nxtask_terminate() do all of the heavy lifting.
*/
ret = nxtask_terminate(pid, false);
ret = nxtask_terminate(pid);
if (ret < 0)
{
return ret;

View File

@ -156,7 +156,7 @@ int nxtask_exit(void)
rtcb->irqcount++;
#endif
ret = nxtask_terminate(dtcb->pid, true);
ret = nxtask_terminate(dtcb->pid);
#ifdef CONFIG_SMP
rtcb->irqcount--;

View File

@ -378,34 +378,6 @@ static inline void nxtask_exitwakeup(FAR struct tcb_s *tcb, int status)
# define nxtask_exitwakeup(tcb, status)
#endif
/****************************************************************************
* Name: nxtask_flushstreams
*
* Description:
* Flush all streams when the final thread of a group exits.
*
****************************************************************************/
#ifdef CONFIG_FILE_STREAM
static inline void nxtask_flushstreams(FAR struct tcb_s *tcb)
{
FAR struct task_group_s *group = tcb->group;
/* Have we already left the group? Are we the last thread in the group? */
if (group && group->tg_nmembers == 1)
{
#ifdef CONFIG_MM_KERNEL_HEAP
lib_flushall(tcb->group->tg_streamlist);
#else
lib_flushall(&tcb->group->tg_streamlist);
#endif
}
}
#else
# define nxtask_flushstreams(tcb)
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -431,13 +403,9 @@ static inline void nxtask_flushstreams(FAR struct tcb_s *tcb)
* 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
* nxtask_terminate() via _exit(). In that case, we must be careful to do
* nothing that can cause the cause the thread to block.
*
****************************************************************************/
void nxtask_exithook(FAR struct tcb_s *tcb, int status, bool nonblocking)
void nxtask_exithook(FAR struct tcb_s *tcb, int status)
{
/* Under certain conditions, nxtask_exithook() can be called multiple
* times. A bit in the TCB was set the first time this function was
@ -460,22 +428,6 @@ void nxtask_exithook(FAR struct tcb_s *tcb, int status, bool nonblocking)
tcb->cpcount = 0;
#endif
if (!nonblocking)
{
/* 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 non-blocking == true
* only when called through _exit(). _exit() behavior does not
* require that the streams be flushed
*/
nxtask_flushstreams(tcb);
}
/* If the task was terminated by another task, it may be in an unknown
* state. Make some feeble effort to recover the state.
*/

View File

@ -51,8 +51,8 @@
* to determine if blocking is permitted or not.
*
* This function is the final function called all task termination
* sequences. nxtask_terminate() is called only from task_delete() (with
* nonblocking == false) and from nxtask_exit() (with nonblocking == true).
* sequences. nxtask_terminate() is called only from task_delete() and
* from nxtask_exit().
*
* The path through nxtask_exit() supports the final stops of the exit(),
* _exit(), and pthread_exit
@ -60,18 +60,12 @@
* - pthread_exit(). Calls _exit()
* - exit(). Calls _exit()
* - _exit(). Calls nxtask_exit() making the currently running task
* non-running. nxtask_exit then calls nxtask_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 nxtask_exithook() has already
* been called with nonblocking == false;
* non-running. nxtask_exit then calls nxtask_terminate() to terminate
* the non-running task.
*
* Input Parameters:
* 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.
*
* Returned Value:
* OK on success; or ERROR on failure
@ -81,7 +75,7 @@
*
*******************************************************************************/
int nxtask_terminate(pid_t pid, bool nonblocking)
int nxtask_terminate(pid_t pid)
{
FAR struct tcb_s *dtcb;
FAR dq_queue_t *tasklist;
@ -159,18 +153,14 @@ int nxtask_terminate(pid_t pid, bool nonblocking)
leave_critical_section(flags);
/* Perform common task termination logic (flushing streams, calling
* functions registered by at_exit/on_exit, etc.). We need to do
/* Perform common task termination logic. We need to do
* this as early as possible so that higher level clean-up logic
* can run in a healthy tasking environment.
*
* In the case where the task exits via exit(), nxtask_exithook()
* may be called twice.
*
* I suppose EXIT_SUCCESS is an appropriate return value???
*/
nxtask_exithook(dtcb, EXIT_SUCCESS, nonblocking);
nxtask_exithook(dtcb, EXIT_SUCCESS);
/* Since all tasks pass through this function as the final step in their
* exit sequence, this is an appropriate place to inform any instrumentation