nuttx/sched/task/task_fork.c
Marco Casaroli 9b787d42b2 sched/arch/libc: make task_fork() optional
task_fork() is a NuttX extension, not POSIX, and a configuration that does
not call it has no reason to carry it.  Add CONFIG_TASK_FORK to leave it out.

It depends on ARCH_HAVE_TASK_FORK and defaults to y, so it is enabled exactly
where fork() existed before the split and no configuration loses the primitive
by upgrading.  ARCH_HAVE_TASK_FORK keeps its meaning -- the architecture *can*
clone the calling task -- and the new symbol says whether this build wants it.
Everything that provides task_fork() moves to the new symbol:  the declaration
in sched.h, up_task_fork() in arch.h, the two .csv entries that generate the
system call stub and proxy, the syscall_lookup.h table entry, the architecture
entry points, and the build rules for the files that hold them.

FORK_IS_TASK_FORK now depends on TASK_FORK rather than ARCH_HAVE_TASK_FORK.
Aliasing fork() to a task_fork() that was not built would not link.

vfork() and fork() are untouched; each is still selected on its own, and
task_fork.c, lib_fork.c and the architecture's fork file are still built when
any one of the three is present.

Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 11:45:24 +02:00

653 lines
20 KiB
C

/****************************************************************************
* sched/task/task_fork.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/wait.h>
#include <stdint.h>
#include <sched.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/debug.h>
#include <nuttx/fork.h>
#include <nuttx/kmalloc.h>
#include <nuttx/queue.h>
#include <nuttx/semaphore.h>
#include "sched/sched.h"
#include "environ/environ.h"
#include "group/group.h"
#include "task/task.h"
#include "tls/tls.h"
/* This file is the common core of task_fork(), vfork() and fork(); it is
* built if the architecture can provide any one of them.
*/
#if defined(CONFIG_TASK_FORK) || defined(CONFIG_ARCH_HAVE_VFORK) || \
defined(CONFIG_ARCH_HAVE_FORK)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_HAVE_FORK)
static void fork_inherit_stack(FAR struct tcb_s *parent,
FAR struct tcb_s *child);
static void fork_inherit_tls(FAR struct tcb_s *child);
static void fork_restore_parent_env(void);
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_HAVE_FORK)
/****************************************************************************
* Name: fork_inherit_stack
*
* Description:
* Give the fork() child the parent's stack at the parent's virtual
* address rather than a relocated copy. The child's address environment
* is a duplicate, so the parent's stack is already there -- same contents,
* same address, its own pages -- and nothing needs allocating or copying.
*
* A relocated stack would break plain C: a pointer to a local taken
* before the fork would name the parent's copy, not the child's live
* object.
*
* TCB_FLAG_FREE_STACK is left clear: the stack belongs to the duplicated
* image and is released with it, so up_release_stack() must not free it.
*
* Input Parameters:
* parent - The parent task's TCB
* child - The child task's TCB
*
****************************************************************************/
static void fork_inherit_stack(FAR struct tcb_s *parent,
FAR struct tcb_s *child)
{
child->stack_alloc_ptr = parent->stack_alloc_ptr;
child->stack_base_ptr = parent->stack_base_ptr;
child->adj_stack_size = parent->adj_stack_size;
child->flags &= ~TCB_FLAG_FREE_STACK;
}
/****************************************************************************
* Name: fork_inherit_tls
*
* Description:
* Retarget the thread-local storage the fork() child inherited.
*
* tls_dup_info() cannot be used: it carves a fresh TLS block off the
* stack, which on an inherited stack would carve a second one and shift
* stack_base_ptr away from the parent's. The child's copy is already in
* place, so only the fields naming the task itself need correcting.
*
* The write lands in user memory at an address the parent also occupies,
* so the child's address environment must be current for it -- otherwise
* the parent's own TLS is what gets modified.
*
* Input Parameters:
* child - The child task's TCB
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
static void fork_inherit_tls(FAR struct tcb_s *child)
{
FAR struct tls_info_s *info = (FAR struct tls_info_s *)
child->stack_alloc_ptr;
info->tl_task = child->group->tg_info;
info->tl_tid = child->pid;
}
/****************************************************************************
* Name: fork_restore_parent_env
*
* Description:
* Undo the addrenv_select() that nxtask_setup_fork() made on the child's
* behalf, putting the caller back in its own address environment. The
* environment to go back to does not have to be remembered: the caller is
* the parent, and what was current before was the parent's own.
*
****************************************************************************/
static void fork_restore_parent_env(void)
{
addrenv_restore(this_task()->addrenv_own);
}
#endif /* CONFIG_ARCH_ADDRENV && CONFIG_ARCH_HAVE_FORK */
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: nxtask_setup_fork
*
* Description:
* Allocate and initialize the child task's TCB. This is one step in the
* sequence common to task_fork(), vfork() and fork(); see the comment
* above the prototype in include/nuttx/sched.h for the whole sequence, and
* include/nuttx/fork.h for what the three primitives mean.
*
* Exactly two things depend on `type':
*
* - the address environment: task_fork() and vfork() join the parent's,
* fork() duplicates it.
* - the stack: a task_fork() or vfork() child gets its own, which the
* architecture code fills with a relocated copy; a fork() child inherits
* the parent's address (fork_inherit_stack()).
*
* Input Parameters:
* retaddr - Address at which the child resumes
* type - One of the FORK_TYPE_* constants
*
* Returned Value:
* Upon successful completion, nxtask_setup_fork() returns a pointer to
* newly allocated and initialized child task's TCB. NULL is returned
* on any failure and the errno is set appropriately.
*
****************************************************************************/
FAR struct tcb_s *nxtask_setup_fork(start_t retaddr, int type)
{
FAR struct tcb_s *ptcb = this_task();
FAR struct tcb_s *parent;
FAR struct tcb_s *child;
FAR char **argv;
size_t stack_size;
uint8_t ttype;
int priority;
int ret;
DEBUGASSERT(retaddr != NULL);
DEBUGASSERT(type == FORK_TYPE_TASK || type == FORK_TYPE_VFORK ||
type == FORK_TYPE_FORK);
/* Get the type of the fork'ed task (kernel or user) */
if ((ptcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_KERNEL)
{
/* Fork'ed from a kernel thread */
ttype = TCB_FLAG_TTYPE_KERNEL;
parent = ptcb;
}
else
{
/* Fork'ed from a user task or pthread */
ttype = TCB_FLAG_TTYPE_TASK;
if ((ptcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_TASK)
{
parent = ptcb;
}
else
{
parent = nxsched_get_tcb(ptcb->group->tg_pid);
if (parent == NULL)
{
ret = -ENOENT;
goto errout;
}
}
}
/* Allocate a TCB for the child task. */
child = kmm_zalloc(sizeof(struct tcb_s));
if (!child)
{
serr("ERROR: Failed to allocate TCB\n");
ret = -ENOMEM;
goto errout;
}
child->flags |= TCB_FLAG_FREE_TCB;
/* Initialize the task join */
nxtask_joininit(child);
/* Allocate a new task group with the same privileges as the parent */
ret = group_allocate(child, ttype);
if (ret < 0)
{
goto errout_with_tcb;
}
#if defined(CONFIG_ARCH_ADDRENV)
if (ttype != TCB_FLAG_TTYPE_KERNEL)
{
if (type != FORK_TYPE_FORK)
{
/* task_fork() and vfork(): join the parent address environment,
* exactly as pthread_create() does. The child shares .data, .bss
* and the heap.
*/
ret = addrenv_join(parent, child);
}
#ifdef CONFIG_ARCH_HAVE_FORK
else
{
/* POSIX fork(): duplicate the parent's address environment now,
* before anything else is set up. The duplicate holds a copy of
* the parent's contents -- including its stack -- at the parent's
* virtual addresses, which is what lets the child go on to inherit
* the stack address rather than be given a relocated copy. See
* fork_inherit_stack().
*/
ret = addrenv_fork(parent, child);
if (ret >= 0)
{
/* Make the child's address environment current for the rest of
* the setup, and for the architecture code that runs after it.
*
* From here on, everything written on the child's behalf
* has to land in the child's image rather than the parent's,
* because
* the two occupy the same virtual addresses: its thread-local
* storage, and -- on architectures that keep the register save
* area on the user stack rather than on a kernel stack -- the
* register context the child is resumed from. Writing those
* under the parent's environment corrupts the parent and
* leaves the child reading whatever the snapshot happened to
* contain.
*
* Reads are unaffected: everything the setup reads from the
* parent -- environ, the argument vector -- is legible at the
* same address in the child, precisely because it is a copy.
*
* nxtask_start_fork() puts the parent's environment back.
*/
FAR struct addrenv_s *oldenv;
ret = addrenv_select(child->addrenv_own, &oldenv);
}
}
#else
/* An address environment without ARCH_HAVE_FORK -- a protected build
* over an MMU, for instance. There is an address environment to join,
* but no POSIX fork() to duplicate it for, so the branch above is not
* compiled and `type' can never be FORK_TYPE_FORK here.
*/
DEBUGASSERT(type != FORK_TYPE_FORK);
#endif
if (ret < 0)
{
goto errout_with_tcb;
}
}
#else
/* Without address environments there is only one address space, so
* everything except the stack is shared no matter which primitive was
* called. POSIX fork() cannot be provided at all, and CONFIG_ARCH_HAVE_
* FORK is not selected, so `type' can never be FORK_TYPE_FORK here.
*/
DEBUGASSERT(type != FORK_TYPE_FORK);
#endif
/* Duplicate the parent tasks environment */
ret = env_dup(child->group, environ);
if (ret < 0)
{
goto errout_with_tcb;
}
/* Associate file descriptors with the new task */
ret = group_setuptaskfiles(child, NULL, false);
if (ret < OK)
{
goto errout_with_tcb;
}
/* Set the task name */
argv = nxsched_get_stackargs(parent);
nxtask_setup_name(child, argv[0]);
/* Allocate the stack for the TCB, or inherit the parent's */
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_HAVE_FORK)
if (type == FORK_TYPE_FORK && ttype != TCB_FLAG_TTYPE_KERNEL)
{
/* The child's copy of the parent's stack is already in place, at the
* parent's address, courtesy of the duplication above.
*/
fork_inherit_stack(parent, child);
ret = OK;
}
else
#endif
{
stack_size = (uintptr_t)ptcb->stack_base_ptr -
(uintptr_t)ptcb->stack_alloc_ptr + ptcb->adj_stack_size;
ret = up_create_stack(child, stack_size, ttype);
}
if (ret < OK)
{
goto errout_with_tcb;
}
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_KERNEL_STACK)
/* Allocate the kernel stack */
if (ttype != TCB_FLAG_TTYPE_KERNEL)
{
ret = up_addrenv_kstackalloc(child);
if (ret < 0)
{
goto errout_with_tcb;
}
}
#endif
/* Get the priority of the parent task */
#ifdef CONFIG_PRIORITY_INHERITANCE
priority = ptcb->base_priority; /* "Normal," unboosted priority */
#else
priority = ptcb->sched_priority; /* Current priority */
#endif
/* Initialize the task control block. This calls up_initial_state() */
sinfo("Child priority=%d start=%p\n", priority, retaddr);
ret = nxtask_setup_scheduler(child, priority, retaddr,
ptcb->entry.main, ttype);
if (ret < OK)
{
goto errout_with_tcb;
}
/* Set up thread local storage and the argument vector.
*
* A fork() child that inherited its stack already has both, byte for
* byte, at the addresses the parent has them at -- they came across with
* the rest of the image. Re-creating them would carve fresh frames off a
* stack that already contains them, moving stack_base_ptr away from the
* parent's and undoing the inheritance. Only the TLS fields that name
* the task itself need correcting.
*/
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_HAVE_FORK)
if (type == FORK_TYPE_FORK && ttype != TCB_FLAG_TTYPE_KERNEL)
{
fork_inherit_tls(child);
}
else
#endif
{
ret = tls_dup_info(child, parent);
if (ret < OK)
{
goto errout_with_tcb;
}
ret = nxtask_setup_stackargs(child, argv[0], &argv[1]);
if (ret < OK)
{
goto errout_with_tcb;
}
}
/* Now we have enough in place that we can join the group */
group_initialize(child);
sinfo("parent=%p, returning child=%p\n", parent, child);
return child;
errout_with_tcb:
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_HAVE_FORK)
/* Get back into the parent's address environment before unwinding. If the
* duplication above never happened this is the environment we are already
* in, and addrenv_restore() is then a no-op.
*/
if (type == FORK_TYPE_FORK && ttype != TCB_FLAG_TTYPE_KERNEL)
{
fork_restore_parent_env();
}
#endif
nxsched_release_tcb((FAR struct tcb_s *)child, ttype);
errout:
set_errno(-ret);
return NULL;
}
/****************************************************************************
* Name: nxtask_start_fork
*
* Description:
* The last step of all three primitives: finish the child and run it.
* The architecture-specific code calls this once it has built the child's
* register context and stack.
*
* For vfork() this additionally suspends the caller.
*
* Input Parameters:
* child - The tcb_s struct instance created by nxtask_setup_fork()
* type - One of the FORK_TYPE_* constants
*
* Returned Value:
* The process ID of the child, or ERROR on failure.
*
****************************************************************************/
pid_t nxtask_start_fork(FAR struct tcb_s *child, int type)
{
pid_t pid;
sinfo("Starting Child TCB=%p type=%d\n", child, type);
DEBUGASSERT(child);
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_HAVE_FORK)
/* The architecture code has finished writing the child's image, so put the
* parent back in its own address environment. See nxtask_setup_fork().
*/
if (type == FORK_TYPE_FORK &&
(child->flags & TCB_FLAG_TTYPE_MASK) != TCB_FLAG_TTYPE_KERNEL)
{
fork_restore_parent_env();
}
#endif
/* Get the assigned pid before we start the task */
pid = child->pid;
#ifdef CONFIG_ARCH_HAVE_VFORK
if (type == FORK_TYPE_VFORK)
{
return nxtask_start_vfork(child);
}
#endif
/* Activate the task */
nxtask_activate(child);
return pid;
}
#ifdef CONFIG_ARCH_HAVE_VFORK
/****************************************************************************
* Name: nxtask_start_vfork
*
* Description:
* Start execution of a vfork() child and suspend the caller until the
* child calls _exit() or one of the exec family of functions.
*
* The suspension lives here, in the kernel primitive, rather than in a
* libc waitpid() as it once did. Two things follow from that. The parent
* is released when the child's TCB is torn down (see
* nxtask_vfork_resume()), which for an exec()ing child is immediately
* after exec_swap() has handed the child's pid to the program it loaded --
* so the parent resumes at exec(), holding a pid that names the running
* program, as POSIX requires. And vfork() no longer depends on
* CONFIG_SCHED_WAITPID.
*
* Input Parameters:
* child - The tcb_s struct instance created by nxtask_setup_fork()
*
* Returned Value:
* The process ID of the child.
*
****************************************************************************/
pid_t nxtask_start_vfork(FAR struct tcb_s *child)
{
struct vfork_s vfork;
pid_t pid;
int ret;
sinfo("Starting vfork Child TCB=%p\n", child);
DEBUGASSERT(child);
/* The rendezvous lives in this frame. We are about to block in it and
* will not leave until the child has posted, so it outlives every use.
*/
nxsem_init(&vfork.sem, 0, 0);
vfork.released = false;
child->vfork_rel = &vfork;
pid = child->pid;
nxtask_activate(child);
/* Wait for the child to _exit() or exec(). This is not a cancellation
* point and must not be interrupted by a signal: the child may be running
* on our stack, so returning early would corrupt it.
*/
do
{
ret = nxsem_wait_uninterruptible(&vfork.sem);
}
while (ret == -EINTR);
nxsem_destroy(&vfork.sem);
return pid;
}
/****************************************************************************
* Name: nxtask_vfork_resume
*
* Description:
* Release the vfork() parent suspended on this child, if there is one.
*
* Called from nxsched_release_tcb(), the last point in the child's life,
* by which time an exec()ing child has already handed its pid to the
* program it loaded. nxtask_abort_fork() reaches it too, so a fork that
* fails after the rendezvous also releases the parent.
*
* Input Parameters:
* child - The TCB being torn down
*
* Returned Value:
* None
*
****************************************************************************/
void nxtask_vfork_resume(FAR struct tcb_s *child)
{
FAR struct vfork_s *vfork = child->vfork_rel;
if (vfork != NULL && !vfork->released)
{
vfork->released = true;
child->vfork_rel = NULL;
nxsem_post(&vfork->sem);
}
}
#endif /* CONFIG_ARCH_HAVE_VFORK */
/****************************************************************************
* Name: nxtask_abort_fork
*
* Description:
* Recover from any errors after nxtask_setup_fork() was called.
*
* Returned Value:
* None
*
****************************************************************************/
void nxtask_abort_fork(FAR struct tcb_s *child, int errcode)
{
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_HAVE_FORK)
/* A child holding an address environment of its own, rather than a
* reference to the caller's, is a fork() child, and nxtask_setup_fork()
* left that environment selected. Get back into the parent's before
* unwinding. See nxtask_setup_fork().
*/
if (child->addrenv_own != NULL &&
child->addrenv_own != this_task()->addrenv_own)
{
fork_restore_parent_env();
}
#endif
/* The TCB was added to the active task list by nxtask_setup_scheduler() */
dq_rem((FAR dq_entry_t *)child, list_inactivetasks());
/* Release the TCB */
nxsched_release_tcb(child, child->flags & TCB_FLAG_TTYPE_MASK);
set_errno(errcode);
}
#endif /* CONFIG_TASK_FORK || CONFIG_ARCH_HAVE_VFORK ||
* CONFIG_ARCH_HAVE_FORK */