Repository navigation
nshlib: Reap the background commands that exited. - #3889
Merged
Merged
Conversation
With CONFIG_SCHED_CHILD_STATUS, the kernel keeps the exit status of
each child until the parent waits for it. NSH waits only for a
foreground command. A background command ("cmd &") and each pipeline
stage before the last one are also children of NSH, but NSH never waits
for them. Each of them keeps a status entry in the kernel heap until
NSH exits, and the console NSH does not exit.
nsh_builtin() tried to prevent this for builtin applications. It set
SA_NOCLDWAIT for SIGCHLD before it started a background command, and
restored the old action after. This does not work:
- SA_NOCLDWAIT applies to the whole process. sigaction() with
SA_NOCLDWAIT also discards the status of all children that NSH did
not reap yet, and NuttX does not clear the flag when the old action
comes back. After the first background builtin, NSH keeps no child
status at all, also not for its foreground commands.
- The code was only compiled with CONFIG_ENABLE_ALL_SIGNALS.
- nsh_fileapp() had no such code, so every background program of a
kernel build kept its status.
Remove that code. Instead, nsh_parse_command() reaps all children that
exited, with waitpid(-1, WNOHANG), before it runs a command. NSH runs
one command at a time and waits for a foreground command before it
starts the next one. Thus a child that exited at this point is one
that no other code waits for. The "wait" command still gets the status
of a background command that runs, because it calls waitpid() for that
pid.
Two other solutions were not used. A SIGCHLD handler that calls
waitpid(-1) needs CONFIG_ENABLE_ALL_SIGNALS, and it can take the
status of a foreground command before NSH waits for it. A list of the
background pids needs memory for each job, and it does not reap a
foreground command when its waitpid() fails, for example with EINTR.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
xiaoxiang781216
approved these changes
Oct 20, 2026
acassis
approved these changes
Oct 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
With
CONFIG_SCHED_CHILD_STATUS, the kernel keeps the exit status of each child until the parent waits for it. NSH waits only for foreground commands. A background command (cmd &) and each pipeline stage before the last one are also children of NSH, but NSH never waits for them. Each one keeps a child status entry in the kernel heap until NSH exits, and the console NSH does not exit.nsh_builtin()tried to prevent this withSA_NOCLDWAITaround the start of a background builtin. This does not work:SA_NOCLDWAITapplies to the whole process,sigaction()with it also discards the status of all other children, and the kernel does not clear the flag when the old action comes back. So after the first background builtin, NSH keeps no child status at all, andwaitcan fail withECHILD. The code also neededCONFIG_ENABLE_ALL_SIGNALS, andnsh_fileapp()(all programs of a kernel build) had no such code.This PR removes the
SA_NOCLDWAITcode. Before it runs a command,nsh_parse_command()now reaps all children that exited, withwaitpid(-1, WNOHANG). NSH waits for a foreground command before it starts the next command, so a child that exited at this point is one that no other code waits for. The commit message explains why aSIGCHLDhandler or a list of background pids was not used.Impact
CONFIG_SCHED_CHILD_STATUSandCONFIG_SCHED_WAITPID. Others do not change.CONFIG_ENABLE_ALL_SIGNALS, a background builtin no longer turns off the child status of NSH.waitfor a background command works again in this case.waitstill gets the exit status of a background command that runs. As before,waitfor a command that already exited reports an error.Testing
qemu-armv8a:knsh (kernel build) and qemu-armv8a:nsh (flat build) under HVF, with
CONFIG_SCHED_CHILD_STATUS=y(the default in kernel builds with apache/nuttx#20837; qemu-armv8a:nsh already sets it) andCONFIG_PIPES=y. In the console NSH, a script runs a short program in the background 500 times; this is done 3 times. Then a script runs 100 two-stage pipelines; this is done 2 times.freeafter each round:The flat build on master does not grow because of
SA_NOCLDWAIT, but there the secondwaitfails:nsh: wait: wait failed: 10.Both builds, with this PR:
bgjob 7(knsh) andsh -c "exit 7"(nsh) give$?= 7.wait $!for a background command that sleeps 1 s and exits with 0 gives$?= 0, and with exit code 5 gives$?= 1, with no error.Exiting with status 0).tools/checkpatch.sh -c -u -m -gpasses.