Hello,
NuttX does not have POSIX job control today. On master:
- setpgid() fails with EPERM if pgid is not the process ID. setsid()
returns the process ID and does not start a session. kill(-pgrp)
signals only the process pgrp.
- /dev/tty does not exist, and a terminal keeps one foreground process
ID.
- WIFSTOPPED(), WSTOPSIG() and WIFCONTINUED() are always 0.
waitid(P_PGID) gives ENOSYS.
- No code reads SA_RESTART, so each interrupted blocking call fails
with
EINTR.
#19184 added the terminal ioctls and these stubs for one process per
group. I propose real process groups, and that model stays when the new
options are off. (A POSIX process group is not a NuttX task group: a
task group is one POSIX process.)
What POSIX says (POSIX.1-2024):
- <unistd.h>, _POSIX_JOB_CONTROL: "The implementation supports job
control. This symbol shall always be set to a value greater than
zero."
- setpgid(), change history of Issue 6: "The setpgid() function is
mandatory since _POSIX_JOB_CONTROL is required to be defined in this
version."
- XBD chapter 10 has /dev/tty in "The following files shall exist on
conforming systems and shall be both readable and writable".
- sigaction(), SA_RESTART: "the function shall restart and shall not
fail with [EINTR] unless otherwise specified".
Our posix.rst lists POSIX_JOB_CONTROL in PSE54 only, not in PSE51 to
PSE53. A small system does not need it, so the two large parts have an
option that is off by default.
The work, in PR order:
1. Process groups and sessions: CONFIG_SCHED_PROCESS_GROUPS, default n,
needs SCHED_HAVE_PARENT. setpgid(), getpgid(), setsid() and getsid()
become system calls, at the end of the table, so no other number
moves. kill(0), kill(-pgid) and killpg() signal the whole group.
POSIX_SPAWN_SETSID works. Ctrl-C and Ctrl-Z go to the foreground
group. About +820 lines.
2. Stopped and continued children: no option. SIGCHLD with CLD_STOPPED
and CLD_CONTINUED, WUNTRACED, WCONTINUED, waitid(P_PGID). waitpid()
uses the wait loop of waitid(). About +770 -580 lines.
3. SA_RESTART: no option, in each CONFIG_ENABLE_ALL_SIGNALS build.
About
+480 lines.
4. Controlling terminals: CONFIG_TTY_CTTY, default n, needs part 1 and
TTY_SIGINT or TTY_SIGTSTP. /dev/tty, SIGHUP when the session leader
exits, SIGTTIN and SIGTTOU for a background group, on the serial
driver, pty and telnet. The TIOCSCTTY(pid) form of NSH and nxinit
stays. About +1080 lines.
5. nxinit starts a console service in a new session. Small.
With the options off, parts 1 and 4 add one small function,
nxsig_kill_foreground(), and behavior stays the same. The one exception
is killpg(1), which POSIX leaves unspecified: it now gives ENOSYS.
Parts 2 and 3 change behavior for everyone, as POSIX asks:
- NSH already waits with WUNTRACED. With SCHED_HAVE_PARENT it will
return to the prompt when the foreground command stops. Today it
keeps
waiting. This is from the source; I did not run it yet.
- examples/nanoxterm is the only program in the two repositories that
sets SA_RESTART. Its calls will restart, as it asks.
- Each task gets one more byte, and each pending signal action one more
bool. arm64 and RISC-V get a check at system call exit. Flat builds
and x86_64 retry inside read(), write(), the socket calls and
waitpid(). Other architectures with system calls (Arm, Xtensa, MIPS)
keep EINTR for now, because the handler runs only after the system
call returns. They need the same change in their dispatcher.
I did not measure the size yet. Each PR will give it.
Tests so far, on a tree from 2026-10-20, with test programs that I will
add to nuttx-apps:
- qemu-armv8a:knsh: process groups 21/21, terminals 22/22, stopped
children 8/10 (the other 2 need part 3), SA_RESTART 10/11 (the other
case is a timeout on a local socket), ostest 0.
- qemu-armv8a:nsh (flat): ostest 0. Most cases need fork() and skip:
4 process group cases ran, and no terminal case. I will change the
tests to run these cases without fork().
- With all parts and my other open work on one tree: a POSIX shell on
the console runs Ctrl-Z, jobs, bg, fg and Ctrl-C.
- Not run: protected builds, and parts 1 and 4 on other architectures.
I will rebase and test again before each PR. Part 2 has conflicts on
master now.
Not done yet, and I plan to do these too:
- Define _POSIX_JOB_CONTROL and answer sysconf(_SC_JOB_CONTROL) when
the
option is on.
- POSIX_SPAWN_SETPGROUP: posix_spawnattr_setpgroup() gives ENOSYS
today.
Builds without fork() need it, so it goes in part 1.
- Orphaned process groups: no stop, and EIO on a read of the terminal.
- SIGTTOU for tcsetattr() and similar calls from a background group.
Only tcsetpgrp() has it now.
- kill(-1), which gives ENOSYS.
- A blocked SIGCONT does not continue a process, and without
SCHED_CHILD_STATUS a stop can be lost.
WIFSIGNALED() also stays 0 in this work. A separate change covers it.
Questions:
1. Is this direction acceptable: real process groups and sessions
behind
default-off options, and the current stubs when they are off?
2. Parts 2 and 3 have no option, because they are standard behavior and
an option would keep the wrong behavior in some builds. Does anyone
want an option for them after the sizes are known?
Thanks,
Marco Casaroli