casaroli opened a new pull request, #21137:
URL: https://github.com/apache/nuttx/pull/21137
## Summary
**sched/task: Give the fork() child a copy of the task information.** The
child of `fork()` used the new task information that `group_allocate()` made
for its group. That task information was empty: no fork handlers, no exit
functions, no streams other than new stdin, stdout and stderr, no keys and no
loaded modules. Thus in the child:
- The child handlers of `pthread_atfork()` did not run. A mutex that a
prepare handler locked stayed locked, and the next lock of it in the child did
not return. For example, each `fork()` of a Perl that uses threads stopped in
the child (Perl locks its global mutexes in its prepare handler and unlocks
them in its parent and child handlers).
- `exit()` did not call the functions of `atexit()`, and `exit()` and
`fflush(NULL)` did not flush the streams that the parent opened.
- `pthread_key_create()` could give a key that the parent used, with the
value of the parent in the copy of the TLS.
POSIX says that the child has a copy of the address space of the parent. The
copy has the task information of the parent at the same address. The child now
uses that copy. `nxtask_setup_fork()` frees the new task information before the
copy, while the address environment of the parent is current, so the block is
in neither heap. This replaces the free in `nxtask_start_fork()`. `env_dup()`
is not necessary for the child, because the copy has the environment. `vfork()`
does not change.
**libs/libc/unistd: Hold the task information lock during fork().** Because
the child now uses a copy, another thread of the parent must not change the
task information during the copy. `fork()` holds the lock from the end of the
prepare handlers to the end of the copy. The child initializes its copy of the
lock again, because the holder of the copy is a thread of the parent. The
parent releases the lock before the parent handlers. No new lock order with the
mutexes of the handlers.
**libs/libc/pthread: Call the fork handlers in the order of POSIX.**
`pthread_atfork()` added each entry at the head of the list, so the prepare
handlers ran in the order of registration and the parent and child handlers in
the reverse order. POSIX requires the opposite in each case. Now each entry
goes at the tail. The comments of `atfork_child()` and `atfork_parent()` named
the wrong handlers.
## Impact
- The first two commits change only `fork()` (`CONFIG_ARCH_HAVE_FORK`:
kernel builds with address environments). `vfork()` keeps its own task
information.
- The third commit changes the order of the `pthread_atfork()` handlers for
`fork()` and for `vfork()`, so also in flat and protected builds.
- A `fork()` child that calls `exit()` now runs the exit functions of the
parent and flushes the copies of its streams, as on other systems. Buffered
output of the parent at the time of `fork()` can thus appear twice. A child
that must not do this calls `_exit()`.
- Programs with more than one handler see the handlers in the POSIX order.
- No change to a structure.
## Testing
Test program `forkinfo.c` (each case: `fork()`, the child checks one thing
and exits, the parent waits with a time limit): qemu-armv8a:knsh with
`CONFIG_PTHREAD_ATFORK`, `CONFIG_PIPES` and `CONFIG_TLS_NELEM=4` (QEMU, HVF).
| Case | master | this PR |
|---|---|---|
| the child handler runs in the child | FAIL | PASS |
| order: the child runs prepare B,A then child A,B | FAIL | PASS |
| the parent runs prepare B,A then parent A,B | FAIL ("1243") | PASS
("2134") |
| a mutex locked in prepare and unlocked in parent and child: the child can
lock it | FAIL | PASS |
| an `atexit()` function of the parent runs at `exit()` in the child | FAIL
| PASS |
| `fflush(NULL)` in the child flushes a stream that the parent opened | FAIL
| PASS |
| the parent's key value is in the child; a new key of the child is a
different key | FAIL | PASS |
| environment (`getenv()`, `environ`, `setenv()`), `exec()` after `fork()`,
`gettid()`/`getpid()` | PASS | PASS |
| total | 5 of 13 | 12 of 13 |
Test program `atforkorder.c` (two handlers, `vfork()`): qemu-armv8a:nsh
(flat) with `CONFIG_PTHREAD_ATFORK`: the prepare handlers run "AB" on master
and "BA" with this PR, the parent handlers "BA" on master and "AB" with this
PR. (The child of `vfork()` has its own task information, so no child handler
runs in it, on master as with this PR.)
ostest exits with status 0 on qemu-armv8a:knsh and qemu-armv8a:nsh before
and after.
The test also has a case that is not part of this change and fails on master
and on the branch: `SIGKILL` does not end a child that waits on a mutex
(`waitpid(WNOHANG)` still 0 after 3 s).
Build check with `CONFIG_PTHREAD_ATFORK=y`: qemu-armv8a:nsh,
qemu-armv8a:knsh (also without it), rv-virt:nsh64, rv-virt:pnsh64,
rv-virt:knsh64, qemu-intel64:knsh_romfs, sim:nsh, qemu-armv7a:knsh,
esp32s3-devkit:knsh, lm3s6965-ek:qemu-protected: all OK.
`tools/checkpatch.sh -c -u -m -g` passes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]