diff --git a/apps/examples/ostest/ostest_main.c b/apps/examples/ostest/ostest_main.c index aab1ff0457..3e4197fdc9 100644 --- a/apps/examples/ostest/ostest_main.c +++ b/apps/examples/ostest/ostest_main.c @@ -43,8 +43,11 @@ #include #include #include +#include #include #include +#include + #include #include "ostest.h" @@ -264,6 +267,31 @@ static int user_main(int argc, char *argv[]) } check_test_memory_usage(); + /* If retention of child status is enable, then suppress it for this task. + * This task may produce many, many children (especially if + * CONFIG_EXAMPLES_OSTEST_LOOPS) and it does not harvest their exit status. + * As a result, the test may fail inappropriately unless retention of + * child exit status is disabled. + * + * So basically, this tests that child status can be disabled, but cannot + * verify that status is retained correctly. + */ + +#if defined(CONFIG_SCHED_HAVE_PARENT) && defined(CONFIG_SCHED_CHILD_STATUS) + { + struct sigaction sa; + int ret; + + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDWAIT; + ret = sigaction(SIGCHLD, &sa, NULL); + if (ret < 0) + { + printf("user_main: ERROR: sigaction failed: %d\n", errno); + } + } +#endif + /* Check environment variables */ #ifndef CONFIG_DISABLE_ENVIRON show_environment(true, true, true); diff --git a/nuttx/TODO b/nuttx/TODO index ed91515ba5..d6bd18d129 100644 --- a/nuttx/TODO +++ b/nuttx/TODO @@ -7,7 +7,7 @@ standards, things that could be improved, and ideas for enhancements. nuttx/ (11) Task/Scheduler (sched/) - (1) Memory Managment (mm/) + (2) Memory Managment (mm/) (3) Signals (sched/, arch/) (2) pthreads (sched/) (2) C++ Support @@ -278,6 +278,19 @@ o Memory Managment (mm/) Priority: Medium/Low, a good feature to prevent memory leaks but would have negative impact on memory usage and code size. + Title: CONTAINER ALLOCATOR + Description: There are several places where the logic requires allocation of + a tiny structure that just contains pointers to other things or + small amounts of data that needs to be bundled together. There + are examples net/net_poll.c and numerous other places. + + I am wondering if it would not be good create a pool of generic + containers (say void *[4]). There re-use these when we need + small containers. The code in sched/task_childstatus.c might + be generalized for this purpose. + Status: Open + Priority: Very low (I am not even sure that this is a good idea yet). + o Signals (sched/, arch/) ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/nuttx/net/net_poll.c b/nuttx/net/net_poll.c index ea6f54a6be..3021ac35ec 100644 --- a/nuttx/net/net_poll.c +++ b/nuttx/net/net_poll.c @@ -367,8 +367,7 @@ int psock_poll(FAR struct socket *psock, FAR struct pollfd *fds, bool setup) if (psock->s_type != SOCK_STREAM) { - ret = -ENOSYS; - goto errout; + return -ENOSYS; } #endif @@ -387,7 +386,6 @@ int psock_poll(FAR struct socket *psock, FAR struct pollfd *fds, bool setup) ret = net_pollteardown(psock, fds); } -errout: return ret; } #endif diff --git a/nuttx/sched/Kconfig b/nuttx/sched/Kconfig index 7ea3016009..7745c2c25e 100644 --- a/nuttx/sched/Kconfig +++ b/nuttx/sched/Kconfig @@ -124,6 +124,13 @@ config PREALLOC_CHILDSTATUS sa.sa_flags = SA_NOCLDWAIT; int ret = sigaction(SIGCHLD, &sa, NULL); +config DEBUG_CHILDSTATUS + bool "Enable Child Status Debug Output" + default n + depends on SCHED_CHILD_STATUS && DEBUG + ---help--- + Very detailed... I am sure that you do not want this. + config JULIAN_TIME bool "Enables Julian time conversions" default n diff --git a/nuttx/sched/os_internal.h b/nuttx/sched/os_internal.h index 7d5095bad4..dc87cb9a40 100644 --- a/nuttx/sched/os_internal.h +++ b/nuttx/sched/os_internal.h @@ -274,6 +274,7 @@ void weak_function task_initialize(void); FAR struct child_status_s *task_allocchild(void); void task_freechild(FAR struct child_status_s *status); void task_addchild(FAR _TCB *tcb, FAR struct child_status_s *child); +FAR struct child_status_s *task_exitchild(FAR _TCB *tcb); FAR struct child_status_s *task_findchild(FAR _TCB *tcb, pid_t pid); FAR struct child_status_s *task_removechild(FAR _TCB *tcb, pid_t pid); void task_removechildren(FAR _TCB *tcb); diff --git a/nuttx/sched/sched_waitid.c b/nuttx/sched/sched_waitid.c index 37ee26ce0b..e73bc223c7 100644 --- a/nuttx/sched/sched_waitid.c +++ b/nuttx/sched/sched_waitid.c @@ -53,6 +53,36 @@ * Private Functions *****************************************************************************/ +/***************************************************************************** + * Name: exitted_child + * + * Description: + * Handle the case where a child exitted properlay was we (apparently) lost + * the detch of child signal. + * + *****************************************************************************/ + +#ifdef CONFIG_SCHED_CHILD_STATUS +static void exitted_child(FAR _TCB *rtcb, FAR struct child_status_s *child, + FAR siginfo_t *info) +{ + /* The child has exited. Return the saved exit status (and some fudged + * information. + */ + + info->si_signo = SIGCHLD; + info->si_code = CLD_EXITED; + info->si_value.sival_ptr = NULL; + info->si_pid = child->ch_pid; + info->si_status = child->ch_status; + + /* Discard the child entry */ + + (void)task_removechild(rtcb, child->ch_pid); + task_freechild(child); +} +#endif + /***************************************************************************** * Public Functions *****************************************************************************/ @@ -120,9 +150,14 @@ * *****************************************************************************/ -int waitid(idtype_t idtype, id_t id, siginfo_t *info, int options) +int waitid(idtype_t idtype, id_t id, FAR siginfo_t *info, int options) { FAR _TCB *rtcb = (FAR _TCB *)g_readytorun.head; + FAR _TCB *ctcb; +#ifdef CONFIG_SCHED_CHILD_STATUS + FAR struct child_status_s *child; + bool retains; +#endif sigset_t sigset; int err; int ret; @@ -160,7 +195,11 @@ int waitid(idtype_t idtype, id_t id, siginfo_t *info, int options) */ #ifdef CONFIG_SCHED_CHILD_STATUS - if (rtcb->children == NULL) + /* Does this task retain child status? */ + + retains = ((rtcb->flags && TCB_FLAG_NOCLDWAIT) == 0); + + if (rtcb->children == NULL && retains) { /* There are no children */ @@ -169,13 +208,29 @@ int waitid(idtype_t idtype, id_t id, siginfo_t *info, int options) } else if (idtype == P_PID) { - if (task_findchild(rtcb, (pid_t)id) == NULL) - { - /* This specific pid is not a child */ + /* Get the TCB corresponding to this PID and make sure it is our child. */ + ctcb = sched_gettcb((pid_t)id); + if (!ctcb || ctcb->parent != rtcb->pid) + { err = ECHILD; goto errout_with_errno; } + + /* Does this task retain child status? */ + + if (retains) + { + /* Check if this specific pid has allocated child status? */ + + if (task_findchild(rtcb, (pid_t)id) == NULL) + { + /* This specific pid is not a child */ + + err = ECHILD; + goto errout_with_errno; + } + } } #else if (rtcb->nchildren == 0) @@ -189,7 +244,7 @@ int waitid(idtype_t idtype, id_t id, siginfo_t *info, int options) { /* Get the TCB corresponding to this PID and make sure it is our child. */ - FAR _TCB *ctcb = sched_gettcb((pid_t)id); + ctcb = sched_gettcb((pid_t)id); if (!ctcb || ctcb->parent != rtcb->pid) { err = ECHILD; @@ -209,48 +264,61 @@ int waitid(idtype_t idtype, id_t id, siginfo_t *info, int options) * instead). */ - DEBUGASSERT(rtcb->children); - if (rtcb->children == NULL) + DEBUGASSERT(!retains || rtcb->children); + if (idtype == P_ALL) { - /* This should not happen. I am just wasting your FLASH. */ + /* We are waiting for any child to exit */ - err = ECHILD; - goto errout_with_errno; + if (retains && (child = task_exitchild(rtcb)) != NULL) + { + /* A child has exitted. Apparently we missed the signal. + * Return the exit status and break out of the loop. + */ + + exitted_child(rtcb, child, info); + break; + } } - else if (idtype == P_PID) - { - FAR struct child_status_s *child; - /* We are waiting for a specific PID. Get the current status - * of the child task. - */ + /* We are waiting for a specific PID. Does this task retain child status? */ + + else if (retains) + { + /* Yes ... Get the current status of the child task. */ child = task_findchild(rtcb, (pid_t)id); DEBUGASSERT(child); - if (!child) - { - /* Yikes! The child status entry just disappeared! */ - - err = ECHILD; - goto errout_with_errno; - } - + /* Did the child exit? */ if ((child->ch_flags & CHILD_FLAG_EXITED) != 0) { - /* The child has exited. Return the saved exit status */ + /* The child has exited. Return the exit status and break out + * of the loop. + */ - info->si_signo = SIGCHLD; - info->si_code = CLD_EXITED; - info->si_value.sival_ptr = NULL; - info->si_pid = (pid_t)id; - info->si_status = child->ch_status; + exitted_child(rtcb, child, info); + break; + } + } + else + { + /* We can use kill() with signal number 0 to determine if that + * task is still alive. + */ - /* Discard the child entry and break out of the loop */ + ret = kill((pid_t)id, 0); + if (ret < 0) + { + /* It is no longer running. We know that the child task + * was running okay when we started, so we must have lost + * the signal. In this case, we know that the task exit'ed, + * but we do not know its exit status. It would be better + * to reported ECHILD than bogus status. + */ - (void)task_removechild(rtcb, (pid_t)id); - task_freechild(child); + err = ECHILD; + goto errout_with_errno; } } #else diff --git a/nuttx/sched/sched_waitpid.c b/nuttx/sched/sched_waitpid.c index fe3f7167dc..ecdc60a2c2 100644 --- a/nuttx/sched/sched_waitpid.c +++ b/nuttx/sched/sched_waitpid.c @@ -185,7 +185,7 @@ #ifndef CONFIG_SCHED_HAVE_PARENT pid_t waitpid(pid_t pid, int *stat_loc, int options) { - _TCB *tcb; + _TCB *ctcb; bool mystat; int err; int ret; @@ -208,8 +208,8 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options) /* Get the TCB corresponding to this PID */ - tcb = sched_gettcb(pid); - if (!tcb) + ctcb = sched_gettcb(pid); + if (!ctcb) { err = ECHILD; goto errout_with_errno; @@ -221,15 +221,15 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options) * others? */ - if (stat_loc != NULL && tcb->stat_loc == NULL) + if (stat_loc != NULL && ctcb->stat_loc == NULL) { - tcb->stat_loc = stat_loc; - mystat = true; + ctcb->stat_loc = stat_loc; + mystat = true; } /* Then wait for the task to exit */ - ret = sem_wait(&tcb->exitsem); + ret = sem_wait(&ctcb->exitsem); if (ret < 0) { /* Unlock pre-emption and return the ERROR (sem_wait has already set @@ -239,7 +239,7 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options) if (mystat) { - tcb->stat_loc = NULL; + ctcb->stat_loc = NULL; } goto errout; @@ -274,8 +274,10 @@ errout: pid_t waitpid(pid_t pid, int *stat_loc, int options) { FAR _TCB *rtcb = (FAR _TCB *)g_readytorun.head; + FAR _TCB *ctcb; #ifdef CONFIG_SCHED_CHILD_STATUS FAR struct child_status_s *child; + bool retains; #endif FAR struct siginfo info; sigset_t sigset; @@ -303,27 +305,43 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options) sched_lock(); - /* Verify that this task actually has children and that the the request - * TCB is actually a child of this task. + /* Verify that this task actually has children and that the requested PID + * is actually a child of this task. */ #ifdef CONFIG_SCHED_CHILD_STATUS - if (rtcb->children == NULL) - { - /* There are no children */ + /* Does this task retain child status? */ + retains = ((rtcb->flags && TCB_FLAG_NOCLDWAIT) == 0); + + if (rtcb->children == NULL && retains) + { err = ECHILD; goto errout_with_errno; } else if (pid != (pid_t)-1) { - /* This specific pid is not a child */ + /* Get the TCB corresponding to this PID and make sure it is our child. */ - if (task_findchild(rtcb, pid) == NULL) + ctcb = sched_gettcb(pid); + if (!ctcb || ctcb->parent != rtcb->pid) { err = ECHILD; goto errout_with_errno; } + + /* Does this task retain child status? */ + + if (retains) + { + /* Check if this specific pid has allocated child status? */ + + if (task_findchild(rtcb, pid) == NULL) + { + err = ECHILD; + goto errout_with_errno; + } + } } #else if (rtcb->nchildren == 0) @@ -337,7 +355,7 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options) { /* Get the TCB corresponding to this PID and make sure it is our child. */ - FAR _TCB *ctcb = sched_gettcb(pid); + ctcb = sched_gettcb(pid); if (!ctcb || ctcb->parent != rtcb->pid) { err = ECHILD; @@ -350,6 +368,7 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options) for (;;) { +#ifdef CONFIG_SCHED_CHILD_STATUS /* Check if the task has already died. Signals are not queued in * NuttX. So a possibility is that the child has died and we * missed the death of child signal (we got some other signal @@ -362,39 +381,33 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options) * chilren. */ -#ifdef CONFIG_SCHED_CHILD_STATUS - DEBUGASSERT(rtcb->children); - if (rtcb->children == NULL) -#else - if (rtcb->nchildren == 0) -#endif + DEBUGASSERT(!retains || rtcb->children); + if (retains && (child = task_exitchild(rtcb)) != NULL) { - /* There were one or more children when we started so they - * must have exit'ed. There are just no bread crumbs left - * behind to tell us the PID(s) of the existed children. - * Reporting ECHLD is about all we can do in this case. + /* A child has exitted. Apparently we missed the signal. + * Return the saved exit status. */ - err = ECHILD; - goto errout_with_errno; + /* The child has exited. Return the saved exit status */ + + *stat_loc = child->ch_status; + + /* Discard the child entry and break out of the loop */ + + (void)task_removechild(rtcb, child->ch_pid); + task_freechild(child); + break; } } - else + + /* We are waiting for a specific PID. Does this task retain child status? */ + + else if (retains) { -#ifdef CONFIG_SCHED_CHILD_STATUS - /* We are waiting for a specific PID. Get the current status - * of the child task. - */ + /* Get the current status of the child task. */ child = task_findchild(rtcb, pid); DEBUGASSERT(child); - if (!child) - { - /* Yikes! The child status entry just disappeared! */ - - err = ECHILD; - goto errout_with_errno; - } /* Did the child exit? */ @@ -408,27 +421,48 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options) (void)task_removechild(rtcb, pid); task_freechild(child); + break; } -#else - /* We are waiting for a specific PID. We can use kill() with - * signal number 0 to determine if that task is still alive. + } + else + { + /* We can use kill() with signal number 0 to determine if that + * task is still alive. */ ret = kill(pid, 0); if (ret < 0) { - /* It is no longer running. We know that the child task was - * running okay when we started, so we must have lost the - * signal. In this case, we know that the task exit'ed, but - * we do not know its exit status. It would be better to - * reported ECHILD that bogus status. + /* It is no longer running. We know that the child task + * was running okay when we started, so we must have lost + * the signal. In this case, we know that the task exit'ed, + * but we do not know its exit status. It would be better + * to reported ECHILD than bogus status. */ err = ECHILD; goto errout_with_errno; } -#endif } +#else + /* Check if the task has already died. Signals are not queued in + * NuttX. So a possibility is that the child has died and we + * missed the death of child signal (we got some other signal + * instead). + */ + + if (rtcb->nchildren == 0 || + (pid != (pid_t)-1 && (ret = kill((pid_t)id, 0)) < 0)) + { + /* We know that the child task was running okay we stared, + * so we must have lost the signal. What can we do? + * Let's claim we were interrupted by a signal. + */ + + err = EINTR; + goto errout_with_errno; + } +#endif /* Wait for any death-of-child signal */ diff --git a/nuttx/sched/sig_action.c b/nuttx/sched/sig_action.c index 7086679939..7d84b62918 100644 --- a/nuttx/sched/sig_action.c +++ b/nuttx/sched/sig_action.c @@ -169,7 +169,6 @@ int sigaction(int signo, FAR const struct sigaction *act, FAR struct sigaction * { FAR _TCB *rtcb = (FAR _TCB*)g_readytorun.head; FAR sigactq_t *sigact; - int ret; /* Since sigactions can only be installed from the running thread of * execution, no special precautions should be necessary. @@ -251,24 +250,31 @@ int sigaction(int signo, FAR const struct sigaction *act, FAR struct sigaction * if (act->sa_u._sa_handler == SIG_IGN) { - /* If there is a old sigaction, remove it from sigactionq */ + /* Do we still have a sigaction container from the previous setting? */ - sq_rem((FAR sq_entry_t*)sigact, &rtcb->sigactionq); + if (sigact) + { + /* Yes.. Remove it from sigactionq */ - /* And deallocate it */ + sq_rem((FAR sq_entry_t*)sigact, &rtcb->sigactionq); - sig_releaseaction(sigact); + /* And deallocate it */ + + sig_releaseaction(sigact); + } } /* A sigaction has been supplied */ else { - /* Check if a sigaction was found */ + /* Do we still have a sigaction container from the previous setting? + * If so, then re-use for the new signal action. + */ if (!sigact) { - /* No sigaction was found, but one is needed. Allocate one. */ + /* No.. Then we need to allocate one for the new action. */ sigact = sig_allocateaction(); @@ -294,7 +300,7 @@ int sigaction(int signo, FAR const struct sigaction *act, FAR struct sigaction * COPY_SIGACTION(&sigact->act, act); } - return ret; + return OK; } /**************************************************************************** diff --git a/nuttx/sched/task_childstatus.c b/nuttx/sched/task_childstatus.c index 0f6d36c296..09aa48135b 100644 --- a/nuttx/sched/task_childstatus.c +++ b/nuttx/sched/task_childstatus.c @@ -297,6 +297,42 @@ FAR struct child_status_s *task_findchild(FAR _TCB *tcb, pid_t pid) return NULL; } +/***************************************************************************** + * Name: task_exitchild + * + * Description: + * Search for any child that has exitted. + * + * Parameters: + * tcb - The TCB of the parent task to containing the child status. + * + * Return Value: + * On success, a non-NULL pointer to a child status structure for the + * exited child. NULL is returned if not child has exited. + * + * Assumptions: + * Called during SIGCHLD processing in a safe context. No special precautions + * are required here. + * + *****************************************************************************/ + +FAR struct child_status_s *task_exitchild(FAR _TCB *tcb) +{ + FAR struct child_status_s *child; + + /* Find the status structure with the matching PID */ + + for (child = tcb->children; child; child = child->flink) + { + if ((child->ch_flags & CHILD_FLAG_EXITED) != 0) + { + return child; + } + } + + return NULL; +} + /***************************************************************************** * Name: task_removechild * diff --git a/nuttx/sched/task_reparent.c b/nuttx/sched/task_reparent.c index 28d371bf10..3a7ece37d0 100644 --- a/nuttx/sched/task_reparent.c +++ b/nuttx/sched/task_reparent.c @@ -138,14 +138,32 @@ int task_reparent(pid_t ppid, pid_t chpid) child = task_removechild(otcb, chpid); if (child) { - /* Add the child status entry to the new parent TCB */ + /* Has the new parent supressed child exit status? */ + + if ((ptcb->flags && TCB_FLAG_NOCLDWAIT) == 0) + { + /* No.. Add the child status entry to the new parent TCB */ + + task_addchild(ptcb, child); + } + else + { + /* Yes.. Discard the child status entry */ + + task_freechild(child); + } + + /* Either case is a success */ - task_addchild(ptcb, child); ret = OK; } else { - ret = -ENOENT; + /* This would not be an error if the original parent has + * suppressed child exit status. + */ + + ret = ((otcb->flags && TCB_FLAG_NOCLDWAIT) == 0) ? -ENOENT : OK; } #else DEBUGASSERT(otcb->nchildren > 0); diff --git a/nuttx/sched/task_setup.c b/nuttx/sched/task_setup.c index 8f54b2e938..66c948cfd3 100644 --- a/nuttx/sched/task_setup.c +++ b/nuttx/sched/task_setup.c @@ -150,7 +150,8 @@ static int task_assignpid(FAR _TCB *tcb) * Name: task_saveparent * * Description: - * Save the task ID of the parent task in the child task's TCB. + * Save the task ID of the parent task in the child task's TCB and allocate + * a child status structure to catch the child task's exit status. * * Parameters: * tcb - The TCB of the new, child task. @@ -177,11 +178,15 @@ static inline void task_saveparent(FAR _TCB *tcb, uint8_t ttype) tcb->parent = rtcb->pid; - /* Exit status only needs to be retained for the case of tasks, however */ - - if (ttype == TCB_FLAG_TTYPE_TASK) - { #ifdef CONFIG_SCHED_CHILD_STATUS + /* Exit status only needs to be retained for the case of tasks, however. + * Tasks can also suppress retention of their child status by applying + * the SA_NOCLDWAIT flag with sigaction()/ + */ + + if (ttype == TCB_FLAG_TTYPE_TASK && + (rtcb->flags && TCB_FLAG_NOCLDWAIT) == 0) + { FAR struct child_status_s *child; /* Make sure that there is not already a structure for this PID in the @@ -212,11 +217,11 @@ static inline void task_saveparent(FAR _TCB *tcb, uint8_t ttype) task_addchild(rtcb, child); } -#else - DEBUGASSERT(rtcb->nchildren < UINT16_MAX); - rtcb->nchildren++; -#endif } +#else + DEBUGASSERT(rtcb->nchildren < UINT16_MAX); + rtcb->nchildren++; +#endif } #else # define task_saveparent(tcb,ttype) @@ -318,7 +323,9 @@ int task_schedsetup(FAR _TCB *tcb, int priority, start_t start, main_t main, tcb->flags &= ~TCB_FLAG_TTYPE_MASK; tcb->flags |= ttype; - /* Save the task ID of the parent task in the TCB */ + /* Save the task ID of the parent task in the TCB and allocate + * a child status structure. + */ task_saveparent(tcb, ttype);