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]

Reply via email to