/**************************************************************************** * 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 #include #include #include #include #include #include #include #include #include "sched/sched.h" #include "environ/environ.h" #include "group/group.h" #include "task/task.h" #include "tls/tls.h" /* fork() requires architecture-specific support as well as waipid(). */ #ifdef CONFIG_ARCH_HAVE_FORK /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: nxtask_setup_fork * * Description: * The fork() function has the same effect as posix fork(), except that the * behavior is undefined if the process created by fork() either modifies * any data other than a variable of type pid_t used to store the return * value from fork(), or returns from the function in which fork() was * called, or calls any other function before successfully calling _exit() * or one of the exec family of functions. * * This function provides one step in the overall fork() sequence: It * Allocates and initializes the child task's TCB. The overall sequence * is: * * 1) User code calls fork(). fork() is provided in * architecture-specific code. * 2) fork()and calls nxtask_setup_fork(). * 3) nxtask_setup_fork() allocates and configures the child task's TCB. * This consists of: * - Allocation of the child task's TCB. * - Initialization of file descriptors and streams * - Configuration of environment variables * - Allocate and initialize the stack * - Setup the input parameters for the task. * - Initialization of the TCB (including call to up_initial_state()) * 4) up_fork() provides any additional operating context. up_fork must: * - Initialize special values in any CPU registers that were not * already configured by up_initial_state() * 5) up_fork() then calls nxtask_start_fork() * 6) nxtask_start_fork() then executes the child thread. * * Input Parameters: * retaddr - Return address * argsize - Location to return the argument size * * 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 task_tcb_s *nxtask_setup_fork(start_t retaddr) { FAR struct tcb_s *ptcb = this_task(); FAR struct tcb_s *parent; FAR struct task_tcb_s *child; FAR char **argv; size_t stack_size; uint8_t ttype; int priority; int ret; DEBUGASSERT(retaddr != NULL); /* 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 task_tcb_s)); if (!child) { serr("ERROR: Failed to allocate TCB\n"); ret = -ENOMEM; goto errout; } child->cmn.flags |= TCB_FLAG_FREE_TCB; #if defined(CONFIG_ARCH_ADDRENV) /* Join the parent address environment (REVISIT: vfork() only) */ if (ttype != TCB_FLAG_TTYPE_KERNEL) { ret = addrenv_join(parent, &child->cmn); if (ret < 0) { goto errout_with_tcb; } } #endif /* Initialize the task join */ nxtask_joininit(&child->cmn); /* Allocate a new task group with the same privileges as the parent */ ret = group_initialize(child, ttype); if (ret < 0) { goto errout_with_tcb; } /* Duplicate the parent tasks environment */ ret = env_dup(child->cmn.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; } /* Allocate the stack for the TCB */ stack_size = (uintptr_t)ptcb->stack_base_ptr - (uintptr_t)ptcb->stack_alloc_ptr + ptcb->adj_stack_size; ret = up_create_stack(&child->cmn, 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->cmn); if (ret < 0) { goto errout_with_tcb; } } #endif /* Setup thread local storage */ ret = tls_dup_info(&child->cmn, parent); if (ret < OK) { goto errout_with_tcb; } /* 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; } /* Setup to pass parameters to the new task */ argv = nxsched_get_stackargs(parent); ret = nxtask_setup_arguments(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_postinitialize(child); sinfo("parent=%p, returning child=%p\n", parent, child); return child; errout_with_tcb: nxsched_release_tcb((FAR struct tcb_s *)child, ttype); errout: set_errno(-ret); return NULL; } /**************************************************************************** * Name: nxtask_start_fork * * Description: * The fork() function has the same effect as fork(), except that the * behavior is undefined if the process created by fork() either modifies * any data other than a variable of type pid_t used to store the return * value from fork(), or returns from the function in which fork() was * called, or calls any other function before successfully calling _exit() * or one of the exec family of functions. * * This function provides one step in the overall fork() sequence: It * starts execution of the previously initialized TCB. The overall * sequence is: * * 1) User code calls fork() * 2) Architecture-specific code provides fork()and calls * nxtask_setup_fork(). * 3) nxtask_setup_fork() allocates and configures the child task's TCB. * This consists of: * - Allocation of the child task's TCB. * - Initialization of file descriptors and streams * - Configuration of environment variables * - Allocate and initialize the stack * - Setup the input parameters for the task. * - Initialization of the TCB (including call to up_initial_state()) * 4) fork() provides any additional operating context. fork must: * - Initialize special values in any CPU registers that were not * already configured by up_initial_state() * 5) fork() then calls nxtask_start_fork() * 6) nxtask_start_fork() then executes the child thread. * * Input Parameters: * child - The task_tcb_s struct instance that created by * nxtask_setup_fork() method * wait_child - whether need to wait until the child is running finished * * Returned Value: * Upon successful completion, fork() returns 0 to the child process and * returns the process ID of the child process to the parent process. * Otherwise, -1 is returned to the parent, no child process is created, * and errno is set to indicate the error. * ****************************************************************************/ pid_t nxtask_start_fork(FAR struct task_tcb_s *child) { pid_t pid; sinfo("Starting Child TCB=%p\n", child); DEBUGASSERT(child); /* Get the assigned pid before we start the task */ pid = child->cmn.pid; /* Eliminate a race condition by disabling pre-emption. The child task * can be instantiated, but cannot run until we call waitpid(). This * assures us that we cannot miss the death-of-child signal (only * needed in the SMP case). */ sched_lock(); /* Activate the task */ nxtask_activate((FAR struct tcb_s *)child); sched_unlock(); return pid; } /**************************************************************************** * Name: nxtask_abort_fork * * Description: * Recover from any errors after nxtask_setup_fork() was called. * * Returned Value: * None * ****************************************************************************/ void nxtask_abort_fork(FAR struct task_tcb_s *child, int errcode) { /* 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((FAR struct tcb_s *)child, child->cmn.flags & TCB_FLAG_TTYPE_MASK); set_errno(errcode); } #endif /* CONFIG_ARCH_HAVE_FORK */