diff --git a/Documentation/guides/fork_vfork_migration.rst b/Documentation/guides/fork_vfork_migration.rst new file mode 100644 index 0000000000000..437fa3b2aff62 --- /dev/null +++ b/Documentation/guides/fork_vfork_migration.rst @@ -0,0 +1,210 @@ +========================================= +Migrating to separate ``fork``/``vfork`` +========================================= + +What changed +============ + +NuttX used to implement ``fork()`` and ``vfork()`` as the same function. Both +were thin libc wrappers around a single ``up_fork()`` syscall; ``vfork()`` +differed only by a trailing ``waitpid()``. Underneath, the child joined the +parent's address environment -- the same ``addrenv_join()`` that +``pthread_create()`` uses -- and got a private *copy of the stack*. So the +child shared ``.data``, ``.bss`` and the heap with its parent, and ran +concurrently with it. + +That is not ``fork()``. It is ``vfork()``-with-a-private-stack published under +``fork()``'s name. The history says so plainly: today's ``fork()`` is NuttX's +old ``vfork()``, renamed in 2023 without any change of behaviour. And the +consequence was silent: a program written against POSIX ``fork()`` compiled and +ran, and its child's writes quietly landed in the parent's variables. + +There are now three distinct primitives: + +.. list-table:: + :header-rows: 1 + :widths: 14 30 26 30 + + * - API + - Memory + - Parent + - Availability + * - ``fork()`` + - child gets **its own copy** at the same virtual addresses + - runs concurrently + - ``CONFIG_ARCH_HAVE_FORK`` -- only where an address environment can be + duplicated + * - ``vfork()`` + - child **shares** the parent's memory + - **suspended** until the child ``_exit()``\ s or ``exec()``\ s + - ``CONFIG_ARCH_HAVE_VFORK`` -- everywhere, MMU or not + * - ``task_fork()`` + - child shares memory, private stack copy + - runs concurrently + - ``CONFIG_ARCH_HAVE_TASK_FORK`` -- exactly where ``fork()`` existed + before + +``task_fork()`` is the old behaviour under an honest name. Nothing was lost. + +.. note:: + + **No architecture implements** ``up_addrenv_fork()`` **yet**, so as of this + change ``CONFIG_ARCH_HAVE_FORK`` is unset everywhere and ``fork()`` is not + available on any target. It returns architecture by architecture as that + hook lands. Until then, ``vfork()`` and ``task_fork()`` are what a NuttX + configuration offers, and ``CONFIG_FORK_IS_TASK_FORK`` keeps the spelling + ``fork()`` working for code that cannot be changed. + +This is a breaking change +========================= + +Two things break, and they break loudly rather than quietly: + +**Code calling** ``fork()`` **on a target without a duplicable address +environment no longer builds.** ``fork()`` is not declared in ``unistd.h`` +there, so you get a compile error naming the function. That is the intended +outcome: a build error is strictly better than the silent wrongness it +replaces. Today that is every target. + +**Code calling** ``fork()`` **on a target that does have real** ``fork()`` +**changes behaviour** -- from sharing to copying. Code that (perhaps +unknowingly) relied on the sharing will now see the parent and child diverge. + +Which replacement do I want? +============================ + +Answer the question "why did I call ``fork()``?". + +*I want the child to run a different program.* + Use :c:func:`posix_spawn` or ``task_spawn()``. This is the single most + common reason to call ``fork()``, NuttX has always provided a better answer + for it, and that answer does not have the pid discontinuity that + ``fork()``\ +\ ``exec()`` has. If you must keep the two-step idiom, use + ``vfork()`` + ``exec*()``: that is exactly what ``vfork()`` is for, and it + is available on every target. + +*I want a second flow of control that shares my memory.* + Use ``pthread_create()``. That is the same memory relationship, spelled + clearly, with a normal entry point instead of a function that returns twice. + If you specifically need the returns-twice shape -- for example you are + porting code and do not want to restructure it -- use ``task_fork()``. It + is a rename, not a rewrite: + + .. code-block:: c + + #include + + pid = task_fork(); /* was: pid = fork(); */ + +*I want a genuinely independent copy of this process.* + Keep calling ``fork()``, and make sure your configuration selects + ``CONFIG_ARCH_HAVE_FORK``. Be aware there is no copy-on-write: the copy is + eager, so forking a large process needs as much free memory as the process + occupies and fails with ``ENOMEM`` otherwise. + +*I cannot change the code right now.* + Set ``CONFIG_FORK_IS_TASK_FORK=y``. This aliases ``fork()`` back to + ``task_fork()``, restoring the previous behaviour **exactly** -- same + sharing, same concurrency, no new suspension. It is available on precisely + the configurations that had ``fork()`` before, and it depends on + ``!ARCH_HAVE_FORK``: on a target that can provide real ``fork()``, aliasing + it back to sharing would reintroduce the very ambiguity this change removes, + so sharing-dependent callers there must be edited. + +Configuration symbols +===================== + +``CONFIG_ARCH_HAVE_TASK_FORK`` + Hidden. The architecture can clone the calling task with a copied stack. + Inherits exactly the ``select`` lines that ``ARCH_HAVE_FORK`` used to have, + so no configuration that had ``fork()`` loses the machinery. + +``CONFIG_ARCH_HAVE_VFORK`` + Hidden. The architecture can implement POSIX ``vfork()``. + +``CONFIG_ARCH_HAVE_ADDRENV_FORK`` + Hidden. The architecture implements ``up_addrenv_fork()``, which duplicates + an address environment into freshly allocated pages mapped at the same + virtual addresses. + +``CONFIG_ARCH_HAVE_FORK`` + Hidden, derived: ``ARCH_ADDRENV && ARCH_HAVE_ADDRENV_FORK``. It no longer + means "``fork()`` exists"; it means "this configuration can provide POSIX + ``fork()`` semantics". + +``CONFIG_FORK_IS_TASK_FORK`` + Visible, default ``n``. The legacy alias described above. + +Notes for architecture maintainers +================================== + +The register/stack snapshot machinery is common to all three primitives. Each +architecture exposes three entry points -- ``up_task_fork()``, ``up_vfork()`` +and ``up_fork()`` -- which share one snapshot sequence and differ only in a +``FORK_TYPE_*`` selector (see ``include/nuttx/fork.h``) handed to +``nxtask_setup_fork()``. That is where the memory semantics are decided: +``addrenv_join()`` for ``task_fork()`` and ``vfork()``, ``addrenv_fork()`` for +``fork()``. + +Adding real ``fork()`` to an architecture +----------------------------------------- + +Two things are needed, and the second is the one that is easy to miss. + +**Implement** ``up_addrenv_fork()``. It duplicates an address environment: +allocate fresh pages, copy the parent's contents into them, and map them at the +*same* virtual addresses. ``up_addrenv_clone()`` is not this -- it copies only +the representation and leaves both processes pointing at one set of page +tables. Then give ``ARCH_HAVE_ADDRENV_FORK`` a ``default y if `` line in +``arch/Kconfig``, and ``ARCH_HAVE_FORK`` follows. + +**Build the child from the caller's saved system call frame.** In a kernel +build ``fork()`` is reached through a system call, so the return address and +stack pointer the architecture's fork entry point can observe for itself belong +to the *kernel*, not to the caller; a child built from those resumes at a +kernel address on a kernel stack. The architecture must record the caller's +exception frame when it traps -- ``xcp.sregs`` is the field that exists for +this -- and build the child from that instead, while a kernel thread that calls +the entry point directly still takes the ordinary path. + +Nothing else is required: the ``up_fork()`` entry point and the libc wrapper +are already there and become live automatically. + +Note that ``ARCH_HAVE_ADDRENV_FORK`` is about a *per-process* address +environment. A protected build has one address space carved up once at boot, +whether the boundaries are drawn by an MPU or by a fixed set of MMU mappings; +its ``up_addrenv_*()`` are stubs, and there is no mapping to duplicate at the +same virtual addresses. ``CONFIG_ARCH_ADDRENV`` being set is therefore not by +itself evidence that ``fork()`` can be provided. ``vfork()`` and +``task_fork()``, which share the parent's memory, work there as everywhere +else. + +Known gaps +========== + +**No architecture provides** ``fork()``. The generic machinery is complete -- +``addrenv_fork()``, the ``up_addrenv_fork()`` hook, the syscall, the libc +wrapper and the ``ostest`` case -- and the first ``up_addrenv_fork()`` +implementation turns it on for that architecture with no further generic work. + +**Xtensa selects none of the fork family.** Giving a child a relocated copy +of the parent's stack takes more than the copy there: the register-window save +areas embedded in the stack hold absolute stack pointers, so each one has to be +rebased along with the copy, or the child reloads a pointer into the *parent's* +stack on its very first window underflow. That rebasing is architecture- +specific and arrives with the Xtensa entry points rather than here. + +Note also on ``waitpid()`` after ``vfork()`` +============================================ + +The ``vfork()`` parent is now resumed when the child's TCB is torn down, so by +the time it runs the child is completely gone. Where the child called +``exec()`` this makes no difference -- ``exec_swap()`` has already given the +loaded program the child's pid, and that program is still running, so +``waitpid()`` behaves normally. Where the child called ``_exit()``, +``waitpid()`` can only return its status if ``CONFIG_SCHED_CHILD_STATUS`` is +enabled; otherwise it returns ``ECHILD``, because NuttX does not retain the +status of a task that no longer exists. That is a pre-existing property of +that configuration, not a change: the previous implementation blocked in a +libc ``waitpid(WNOWAIT)`` and an application's own ``waitpid()`` afterwards hit +the same wall. diff --git a/Documentation/guides/index.rst b/Documentation/guides/index.rst index acbb2a118b554..9b6114409aa00 100644 --- a/Documentation/guides/index.rst +++ b/Documentation/guides/index.rst @@ -37,6 +37,7 @@ Guides logging_rambuffer.rst ipv6.rst integrate_newlib.rst + fork_vfork_migration.rst protected_build.rst platform_directories.rst port_drivers_to_stm32f7.rst diff --git a/Documentation/implementation/memory_configurations.rst b/Documentation/implementation/memory_configurations.rst index 6ad536df5d6c3..eded4ff309669 100644 --- a/Documentation/implementation/memory_configurations.rst +++ b/Documentation/implementation/memory_configurations.rst @@ -30,7 +30,7 @@ On-Demand Paging NuttX also supports on-demand paging via ``CONFIG_PAGING``. On-demand paging is a method of virtual memory management and requires -the the CPU architecutre support a MMU. +the the CPU architecture support a MMU. In a system that uses on-demand paging, the OS responds to a page fault by copying data from some storage media into physical memory and setting up @@ -410,7 +410,7 @@ of functions that: 1. Have only one ``.text`` space in RAM, but 2. Separate ``.data`` and ``.bass`` space, and are -3. Separately linked into with the program in each address environmnet. +3. Separately linked into with the program in each address environment. (not implemented). @@ -484,7 +484,7 @@ at least in its current form. That full implementation of ``mmap()`` plus the minor changes to the NuttX ELF loader are all that are required to support fully share-able ``.text`` sections – as well as the memory savings -from not carrying aroung the relocation and symbol information +from not carrying around the relocation and symbol information (Not implemented). @@ -632,10 +632,13 @@ the contemplate in any real detail: and swap the state into physical memory as needed?(not implemented). * ``mmap()``. True shared memory and true file mapping could be supported. I am repeating myself (not implemented). -* ``fork()``. The ``fork()`` interface could be supported. NuttX currently - supports the "crippled" version, ``vfork()`` but with these process address - environments, the real ``fork()`` interface could be supported. - (not implemented). +* ``fork()``. The real ``fork()`` interface can be supported on configurations + with a duplicable process address environment: an architecture implements + ``up_addrenv_fork()``, selects ``CONFIG_ARCH_HAVE_ADDRENV_FORK``, and + ``CONFIG_ARCH_HAVE_FORK`` follows. What is not implemented is + copy-on-write: the duplication copies the parent's pages eagerly, which + needs as much free memory as the parent occupies. Demand paging would fix + that. * Dynamic Stack Allocation. Completely eliminate the need for constant tuning of static stack sizes.(not implemented). * Shared Libraries. Am I repeating myself again?(not implemented). @@ -688,8 +691,8 @@ There are two problems here: So how do you create new tasks/processes in such a context. There is only one way possible; by using an interface that takes a file name as an argument (rather than absolute address). -New processes started with ``vfork()`` and ``exec()`` or with -``posix_spawn()`` should not have any of these issues. +New processes started with ``fork()`` or ``vfork()`` and ``exec()``, or with +``posix_spawn()``, should not have any of these issues. ARM Memory Management diff --git a/Documentation/reference/user/01_task_control.rst b/Documentation/reference/user/01_task_control.rst index 163030776c35b..7b68ce27cf1b4 100644 --- a/Documentation/reference/user/01_task_control.rst +++ b/Documentation/reference/user/01_task_control.rst @@ -59,9 +59,11 @@ Standard interfaces - :c:func:`exit` - :c:func:`getpid` -Standard ``vfork`` and ``exec[v|l]`` interfaces: +Standard ``fork``/``vfork`` and ``exec[v|l]`` interfaces: + - :c:func:`fork` - :c:func:`vfork` + - :c:func:`task_fork` (non-standard) - :c:func:`exec` - :c:func:`execv` - :c:func:`execl` @@ -347,6 +349,48 @@ Functions **POSIX Compatibility:** Compatible with the POSIX interface of the same name. +.. c:function:: pid_t fork(void) + + ``fork()`` creates a new process. The child process is an exact copy of + the calling process: it receives **its own copy** of the parent's memory, + at the same virtual addresses. Writes by the child are invisible to the + parent and writes by the parent are invisible to the child. The child may + modify anything, call any function, return from the function in which + ``fork()`` was called, and run indefinitely; it runs concurrently with the + parent. None of ``vfork()``'s restrictions apply. + + NOTE: ``fork()`` requires an address environment that can be + duplicated, and so it is available only where ``CONFIG_ARCH_HAVE_FORK`` + is selected -- which in turn requires ``CONFIG_ARCH_ADDRENV`` and an + architecture that implements ``up_addrenv_fork()``. **Where it cannot + be provided, ``fork()`` is not provided at all**: the declaration is + absent from ``unistd.h`` and code that calls it fails to build. That + is deliberate. A build error naming the function is strictly better + than a ``fork()`` that silently gives the child the parent's memory. + No architecture implements ``up_addrenv_fork()`` yet, so ``fork()`` is + currently unavailable on every target; see + :doc:`/guides/fork_vfork_migration`. + + There is no copy-on-write, because NuttX has no demand paging to build + it on, so the copy is eager: forking a large process needs as much free + memory as the process occupies and fails with ``ENOMEM`` otherwise. + Spawn-heavy code should prefer :c:func:`posix_spawn` or + :c:func:`vfork`, on NuttX as anywhere. + + Applications that want the historical NuttX ``fork()`` behaviour -- + shared memory, private stack, both running -- should call + :c:func:`task_fork`. ``CONFIG_FORK_IS_TASK_FORK`` aliases ``fork()`` + back to it on configurations that cannot provide real ``fork()``, for + legacy code that cannot be changed. + + :return: 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. + + **POSIX Compatibility:** Compatible with the POSIX interface of the same + name. + .. c:function:: pid_t vfork(void) The ``vfork()`` function has the same effect as @@ -357,12 +401,18 @@ Functions function before successfully calling ``_exit()`` or one of the ``exec`` family of functions. - NOTE: ``vfork()`` is not an independent NuttX feature, but is - implemented in architecture-specific logic (using only helper - functions from the NuttX core logic). As a result, ``vfork()`` may - not be available on all architectures. The current implementation in - NuttX arm64 only guarantees that ``vfork()`` works when - CONFIG_BUILD_FLAT=y. + The child **shares** the parent's memory -- nothing is copied, which is the + entire point of ``vfork()`` and the reason a caller chooses it -- and the + parent is **suspended** until the child calls ``_exit()`` or one of the + ``exec`` family. That suspension is what makes the sharing safe, and it is + also the price: the restrictions above exist because the child is running + in the parent's address space on borrowed time. + + NOTE: ``vfork()`` is implementable with or without an MMU and is + available wherever ``CONFIG_ARCH_HAVE_VFORK`` is selected. The + suspension lives in the kernel primitive rather than in a libc + ``waitpid()``, so the parent is resumed at ``exec()`` as POSIX requires, + and ``vfork()`` does not depend on ``CONFIG_SCHED_WAITPID``. :return: Upon successful completion, ``vfork()`` returns 0 to the child process and returns the process ID of the child process to the @@ -372,6 +422,34 @@ Functions **POSIX Compatibility:** Compatible with the BSD/Linux interface of the same name. POSIX marks this interface as Obsolete. +.. c:function:: pid_t task_fork(void) + + ``task_fork()`` is a non-standard NuttX interface that clones the calling + task. The child shares the parent's ``.data``, ``.bss`` and heap -- it + joins the parent's address environment, exactly as a pthread does -- but + runs on a **private copy** of the parent's stack, and it runs concurrently + with the parent. Like ``fork()`` it returns twice. + + This is neither ``fork()`` nor ``vfork()``. It is a task cloned at the + call site with the memory relationship of a thread; the nearest precedent + is Plan 9's ``rfork(RFPROC|RFMEM)``. It is the behaviour NuttX published + under the name ``fork()`` before these three interfaces were separated, and + it is preserved here under an honest name so that nothing is lost. + + NOTE: available where ``CONFIG_ARCH_HAVE_TASK_FORK`` is selected, which + is exactly the set of configurations that had ``fork()`` before the + split. New code should prefer :c:func:`pthread_create`, which is the + same memory relationship spelled clearly, or :c:func:`posix_spawn`; + ``task_fork()`` exists to give the historical behaviour a truthful name, + not to recommend it. + + :return: Upon successful completion, ``task_fork()`` returns 0 to the child + and returns the process ID of the child to the parent. Otherwise, -1 is + returned to the parent, no child is created, and ``errno`` is set to + indicate the error. + + **POSIX Compatibility:** Non-standard. + .. c:function:: int exec(FAR const char *filename, FAR char * const *argv, FAR const struct symtab_s *exports, int nexports) This non-standard, NuttX function is similar to @@ -449,7 +527,15 @@ Functions thread, then (2) call ``execv()`` or ``execl()`` to replace the new thread with a program from the file system. Since the new thread will be terminated by the ``execv()`` or ``execl()`` call, it really served no - purpose other than to support POSIX compatibility. + purpose other than to support POSIX compatibility. :c:func:`posix_spawn` + does the same job in one step and should be preferred. + + Note also that ``exec()`` does not overlay the calling process: it starts + the new program as a separate task. ``exec_swap()`` then exchanges the two + pids, so that from the parent's point of view the pid ``vfork()`` returned + does name the running program -- but the ``vfork()`` stub itself exits, and + it is that exit which releases the suspended ``vfork()`` parent. The parent + therefore resumes at ``exec()``, as POSIX requires. The non-standard binfmt function ``exec()`` needs to have (1) a symbol table that provides the list of symbols exported by the base code, and diff --git a/Documentation/standards/posix.rst b/Documentation/standards/posix.rst index cdca6eb748ea9..47622ec7921c2 100644 --- a/Documentation/standards/posix.rst +++ b/Documentation/standards/posix.rst @@ -1421,7 +1421,7 @@ Multiple Processes: +--------------------------------+---------+ | :c:func:`exit` | Yes | +--------------------------------+---------+ -| fork() | No | +| :c:func:`fork` | Cond. | +--------------------------------+---------+ | :c:func:`getpgrp` | Yes | +--------------------------------+---------+ @@ -2364,7 +2364,7 @@ XSI Multiple Process: +--------------------------------+---------+ | :c:func:`usleep` | Yes | +--------------------------------+---------+ -| :c:func:`vfork` | Yes | +| :c:func:`vfork` | Cond. | +--------------------------------+---------+ | :c:func:`waitid` | Yes | +--------------------------------+---------+ diff --git a/arch/Kconfig b/arch/Kconfig index ab0a0c066c7e5..bda523cba47a6 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -11,7 +11,8 @@ config ARCH_ARM bool "ARM" select ARCH_HAVE_BACKTRACE select ARCH_HAVE_INTERRUPTSTACK - select ARCH_HAVE_FORK + select ARCH_HAVE_TASK_FORK if !BUILD_KERNEL + select ARCH_HAVE_VFORK if !BUILD_KERNEL select ARCH_HAVE_STACKCHECK select ARCH_HAVE_CUSTOMOPT select ARCH_HAVE_STDARG_H @@ -29,7 +30,8 @@ config ARCH_ARM64 select ARCH_64BIT select ARCH_HAVE_BACKTRACE select ARCH_HAVE_INTERRUPTSTACK - select ARCH_HAVE_FORK if !BUILD_KERNEL && !BUILD_PROTECTED + select ARCH_HAVE_TASK_FORK if !BUILD_KERNEL && !BUILD_PROTECTED + select ARCH_HAVE_VFORK if !BUILD_KERNEL && !BUILD_PROTECTED select ARCH_HAVE_STACKCHECK select ARCH_HAVE_CUSTOMOPT select ARCH_HAVE_STDARG_H @@ -87,7 +89,8 @@ config ARCH_RISCV select ARCH_HAVE_CPUINFO select ARCH_HAVE_INTERRUPTSTACK select ARCH_HAVE_STACKCHECK - select ARCH_HAVE_FORK + select ARCH_HAVE_TASK_FORK + select ARCH_HAVE_VFORK select ARCH_HAVE_CUSTOMOPT select ARCH_HAVE_SETJMP select ARCH_HAVE_STDARG_H @@ -111,7 +114,8 @@ config ARCH_SIM select ARCH_HAVE_TICKLESS select ARCH_HAVE_POWEROFF select ARCH_HAVE_TESTSET - select ARCH_HAVE_FORK if !HOST_WINDOWS + select ARCH_HAVE_TASK_FORK if !HOST_WINDOWS + select ARCH_HAVE_VFORK if !HOST_WINDOWS select ARCH_HAVE_SETJMP select ARCH_HAVE_CUSTOMOPT select ARCH_HAVE_TCBINFO @@ -147,7 +151,8 @@ config ARCH_X86_64 select PCI_LATE_DRIVERS_REGISTER if PCI select ARCH_TOOLCHAIN_GNU select ARCH_HAVE_BACKTRACE - select ARCH_HAVE_FORK if !BUILD_KERNEL + select ARCH_HAVE_TASK_FORK if !BUILD_KERNEL + select ARCH_HAVE_VFORK if !BUILD_KERNEL select ARCH_HAVE_SETJMP select ARCH_HAVE_PERF_EVENTS select ARCH_HAVE_POWEROFF @@ -482,9 +487,70 @@ config ARCH_HAVE_CPUID_MAPPING default n depends on ARCH_HAVE_MULTICPU +config ARCH_HAVE_TASK_FORK + bool + default n + ---help--- + The architecture can clone the calling task: the child shares the + parent's .data/.bss/heap and runs on a private copy of the parent's + stack, concurrently with the parent. This is the non-POSIX + task_fork() primitive; it is neither fork() nor vfork(). + +config ARCH_HAVE_VFORK + bool + default n + ---help--- + The architecture can implement POSIX vfork(): the child shares the + parent's memory and the parent is suspended until the child calls + _exit() or one of the exec family of functions. + +config ARCH_HAVE_ADDRENV_FORK + bool + default n + depends on ARCH_ADDRENV && !ARCH_STACK_DYNAMIC + ---help--- + The architecture implements up_addrenv_fork(), which duplicates an + address environment: the copy is backed by freshly allocated pages + holding a copy of the parent's contents, mapped at the same virtual + addresses. This is what POSIX fork() is built on. + + No architecture selects this yet. Two things are needed. First, + up_addrenv_fork() itself. Second, the architecture must build the + child's register context from the *user's* saved system call frame: + in a kernel build fork() is reached through a system call, so the + return address and stack pointer the architecture's fork entry point + can see for itself are the kernel's, not the caller's, and a child + built from those resumes at a kernel address. + config ARCH_HAVE_FORK bool + default y if ARCH_ADDRENV && ARCH_HAVE_ADDRENV_FORK + ---help--- + This configuration can provide POSIX fork() semantics: the child + receives its own copy of the parent's memory at the same virtual + addresses, may modify anything, may return from the function that + called fork(), and runs concurrently with the parent. + + Where this is not selected fork() is not provided at all, and code + that calls it fails to build -- see FORK_IS_TASK_FORK. + +config FORK_IS_TASK_FORK + bool "Provide fork() as an alias for task_fork() (legacy)" default n + depends on ARCH_HAVE_TASK_FORK && !ARCH_HAVE_FORK + ---help--- + Provide fork() on configurations that cannot implement POSIX fork() + semantics, by aliasing it to task_fork(). + + This preserves the historical NuttX fork() behaviour exactly: the + child shares the parent's data and heap, runs on a private copy of + the parent's stack, and runs concurrently with the parent. It does + NOT provide POSIX fork() semantics: writes by the child are visible + to the parent, and vice versa. + + Enable this only to keep legacy applications building. New code + should call task_fork() explicitly, or use pthread_create() or + posix_spawn(). config ARCH_HAVE_CRC32 bool diff --git a/arch/arm/src/common/arm_fork.c b/arch/arm/src/common/arm_fork.c index f98f8ec896a3c..ff564ae48c935 100644 --- a/arch/arm/src/common/arm_fork.c +++ b/arch/arm/src/common/arm_fork.c @@ -49,47 +49,58 @@ * Name: arm_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. + * The common ARM worker behind up_task_fork(), up_vfork() and up_fork(). + * All three snapshot the caller's registers identically; `type' -- one of + * the FORK_TYPE_* constants from include/nuttx/fork.h -- says which + * primitive was called, and is passed straight through to + * nxtask_setup_fork(), which is where the memory semantics are decided. + * + * What differs here is only the stack. Normally the child has a stack of + * its own, and this function fills it with a relocated copy of the + * parent's, rebasing the stack and frame pointers to match. When the + * child shares the parent's stack addresses -- a fork() child, inside its + * duplicated address environment -- there is nothing to relocate and the + * pointers are carried over unchanged. * * The overall sequence is: * - * 1) User code calls fork(). fork() collects context information and - * transfers control up arm_fork(). - * 2) arm_fork() and calls nxtask_setup_fork(). + * 1) User code calls task_fork(), vfork() or fork(). The libc wrapper + * enters the matching architecture entry point, which collects context + * information and transfers control to arm_fork(). + * 2) arm_fork() 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 + * - Establishing the child's address environment (for vfork() and + * task_fork(); a fork() child's is duplicated later, once its stack + * has been filled in -- see nxtask_start_fork()) + * - Allocating the stack, or inheriting the parent's for fork() * - Setup the input parameters for the task. * - Initialization of the TCB (including call to up_initial_state()) * 4) arm_fork() provides any additional operating context. arm_fork must: * - Initialize special values in any CPU registers that were not * already configured by up_initial_state() - * 5) arm_fork() then calls nxtask_start_fork() - * 6) nxtask_start_fork() then executes the child thread. + * 5) arm_fork() then calls nxtask_start_fork(), or nxtask_start_vfork() + * which additionally suspends the caller. + * 6) which executes the child thread. * * nxtask_abort_fork() may be called if an error occurs between steps 3 and * 6. * * Input Parameters: - * context - Caller context information saved by fork() + * context - Caller context information saved by the entry point + * type - One of the FORK_TYPE_* constants * * 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. + * Upon successful completion, 0 is returned to the child and the process + * ID of the child is returned to the parent. Otherwise, -1 is returned to + * the parent, no child is created, and errno is set to indicate the error. * ****************************************************************************/ -pid_t arm_fork(const struct fork_s *context) +pid_t arm_fork(const struct fork_s *context, int type) { struct tcb_s *parent = this_task(); struct tcb_s *child; @@ -115,7 +126,7 @@ pid_t arm_fork(const struct fork_s *context) /* Allocate and initialize a TCB for the child task. */ - child = nxtask_setup_fork((start_t)(context->lr & ~1)); + child = nxtask_setup_fork((start_t)(context->lr & ~1), type); if (!child) { serr("ERROR: nxtask_setup_fork failed\n"); @@ -137,43 +148,60 @@ pid_t arm_fork(const struct fork_s *context) sinfo("Parent: stackutil:%" PRIu32 "\n", stackutil); - /* Make some feeble effort to preserve the stack contents. This is - * feeble because the stack surely contains invalid pointers and other - * content that will not work in the child context. However, if the - * user follows all of the caveats of fork() usage, even this feeble - * effort is overkill. - */ + if (child->stack_base_ptr == parent->stack_base_ptr) + { + /* The child is running at the parent's stack addresses, inside its + * own duplicated address environment. There is nothing to relocate: + * every stack address the child inherits is still the address it + * names. + */ - newtop = (uint32_t)child->stack_base_ptr + - child->adj_stack_size; + newsp = oldsp; + newfp = context->fp; + } + else + { + /* Make some feeble effort to preserve the stack contents. This is + * feeble because the stack surely contains invalid pointers and other + * content that will not work in the child context. However, if the + * user follows all of the caveats of task_fork() usage, even this + * feeble effort is overkill. + * + * For a POSIX fork() child the stack contents are not merely a feeble + * effort: the child is entitled to use them, and it does. + */ - newsp = newtop - stackutil; + newtop = (uint32_t)child->stack_base_ptr + + child->adj_stack_size; - /* Move the register context to newtop. */ + newsp = newtop - stackutil; - memcpy((void *)(newsp - XCPTCONTEXT_SIZE), - child->xcp.regs, XCPTCONTEXT_SIZE); + /* Move the register context to newtop. */ - child->xcp.regs = (void *)(newsp - XCPTCONTEXT_SIZE); + memcpy((void *)(newsp - XCPTCONTEXT_SIZE), + child->xcp.regs, XCPTCONTEXT_SIZE); - memcpy((void *)newsp, (const void *)oldsp, stackutil); + child->xcp.regs = (void *)(newsp - XCPTCONTEXT_SIZE); - /* Was there a frame pointer in place before? */ + memcpy((void *)newsp, (const void *)oldsp, stackutil); - if (context->fp >= oldsp && context->fp < stacktop) - { - uint32_t frameutil = stacktop - context->fp; - newfp = newtop - frameutil; - } - else - { - newfp = context->fp; - } + /* Was there a frame pointer in place before? */ - sinfo("Old stack top:%08" PRIx32 " SP:%08" PRIx32 " FP:%08" PRIx32 "\n", - stacktop, oldsp, context->fp); - sinfo("New stack top:%08" PRIx32 " SP:%08" PRIx32 " FP:%08" PRIx32 "\n", - newtop, newsp, newfp); + if (context->fp >= oldsp && context->fp < stacktop) + { + uint32_t frameutil = stacktop - context->fp; + newfp = newtop - frameutil; + } + else + { + newfp = context->fp; + } + + sinfo("Old stack top:%08" PRIx32 " SP:%08" PRIx32 + " FP:%08" PRIx32 "\n", stacktop, oldsp, context->fp); + sinfo("New stack top:%08" PRIx32 " SP:%08" PRIx32 + " FP:%08" PRIx32 "\n", newtop, newsp, newfp); + } /* Update the stack pointer, frame pointer, and volatile registers. When * the child TCB was initialized, all of the values were set to zero. @@ -245,9 +273,9 @@ pid_t arm_fork(const struct fork_s *context) } #endif - /* And, finally, start the child task. On a failure, nxtask_start_fork() - * will discard the TCB by calling nxtask_abort_fork(). + /* And, finally, start the child task. A vfork() additionally suspends us + * until the child calls _exit() or exec(). */ - return nxtask_start_fork(child); + return nxtask_start_fork(child, type); } diff --git a/arch/arm/src/common/arm_fork.h b/arch/arm/src/common/arm_fork.h index 765f2043668d4..d666f4e515e58 100644 --- a/arch/arm/src/common/arm_fork.h +++ b/arch/arm/src/common/arm_fork.h @@ -28,6 +28,7 @@ ****************************************************************************/ #include +#include /**************************************************************************** * Pre-processor Definitions diff --git a/arch/arm/src/common/gnu/fork.S b/arch/arm/src/common/gnu/fork.S index 83e22326c26c8..6f3004304ffd0 100644 --- a/arch/arm/src/common/gnu/fork.S +++ b/arch/arm/src/common/gnu/fork.S @@ -1,5 +1,5 @@ /**************************************************************************** - * arch/arm/src/common/fork.S + * arch/arm/src/common/gnu/fork.S * * SPDX-License-Identifier: Apache-2.0 * @@ -35,49 +35,80 @@ ****************************************************************************/ /**************************************************************************** - * Name: up_fork + * Name: up_task_fork, up_vfork, up_fork * * Description: - * The up_fork() function is the base of fork() function that provided in - * libc, and fork() is implemented as a wrapper of up_fork() function. - * 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. + * These are the architecture-specific entry points of NuttX's three + * cloning primitives. All three need exactly the same thing from + * assembly -- a snapshot of the caller's callee-saved registers, stack + * pointer and return address -- and differ only in what the C code then + * does with it, so they share one snapshot sequence and are distinguished + * by a FORK_TYPE_* constant passed to arm_fork() in r1. * - * This thin layer implements fork by simply calling up_fork() with the - * fork() context as an argument. The overall sequence is: + * See include/nuttx/fork.h for what the three primitives mean. * - * 1) User code calls fork(). fork() collects context information and - * transfers control up up_fork(). - * 2) arm_fork() and calls nxtask_setup_fork(). - * 3) nxtask_setup_fork() allocates and configures the child task's TCB. - * This consists of: + * The overall sequence is: + * + * 1) User code calls task_fork(), vfork() or fork(). Each is a libc + * wrapper around the matching entry point here. + * 2) The entry point collects the context and calls arm_fork(). + * 3) arm_fork() calls nxtask_setup_fork(), which 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 + * - Establishing the child's address environment + * - Allocating the stack, or inheriting the parent's for fork() * - Setup the input parameters for the task. * - Initialization of the TCB (including call to up_initial_state()) - * 4) arm_fork() provides any additional operating context. arm_fork must: + * 4) arm_fork() provides any additional operating context: * - Initialize special values in any CPU registers that were not * already configured by up_initial_state() - * 5) arm_fork() then calls nxtask_start_fork() - * 6) nxtask_start_fork() then executes the child thread. + * - Relocate the copied stack, unless the child shares the parent's + * 5) arm_fork() then calls nxtask_start_fork() or nxtask_start_vfork() + * 6) which executes the child thread. * * Input Parameters: * None * * 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. + * Upon successful completion, 0 is returned to the child and the process + * ID of the child is returned to the parent. Otherwise, -1 is returned to + * the parent, no child is created, and errno is set to indicate the error. * ****************************************************************************/ +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + .globl up_task_fork +#ifdef __ghs__ + .type up_task_fork, $function +#else + .type up_task_fork, function +#endif + +up_task_fork: + movs r2, #FORK_TYPE_TASK + b arm_fork_common + + .size up_task_fork, .-up_task_fork +#endif + +#ifdef CONFIG_ARCH_HAVE_VFORK + .globl up_vfork +#ifdef __ghs__ + .type up_vfork, $function +#else + .type up_vfork, function +#endif + +up_vfork: + movs r2, #FORK_TYPE_VFORK + b arm_fork_common + + .size up_vfork, .-up_vfork +#endif + +#ifdef CONFIG_ARCH_HAVE_FORK .globl up_fork #ifdef __ghs__ .type up_fork, $function @@ -86,6 +117,25 @@ #endif up_fork: + movs r2, #FORK_TYPE_FORK + b arm_fork_common + + .size up_fork, .-up_fork +#endif + +/* The shared snapshot. r2 holds the FORK_TYPE_* selector on entry; it is + * call-clobbered and is not part of the snapshot, so it survives the + * sequence below untouched. lr still holds the original caller's return + * address, because the entry points above branched here rather than calling. + */ + +#ifdef __ghs__ + .type arm_fork_common, $function +#else + .type arm_fork_common, function +#endif + +arm_fork_common: /* Create a stack frame */ mov r0, sp /* Save the value of the stack on entry */ @@ -104,9 +154,12 @@ up_fork: mov r5, lr /* Copy lr to a low register */ stmia r1!, {r0,r5} /* Save sp and lr in the structure */ - /* Then, call arm_fork(), passing it a pointer to the stack structure */ + /* Then, call arm_fork(), passing it a pointer to the stack structure + * and the selector that says which primitive was called. + */ mov r0, sp + mov r1, r2 bl arm_fork /* Recover r4-r7 that were destroyed before arm_fork was called */ @@ -114,12 +167,12 @@ up_fork: mov r1, sp ldmia r1!, {r4-r7} - /* Release the stack data and return the value returned by up_fork */ + /* Release the stack data and return the value returned by arm_fork */ ldr r1, [sp, #FORK_LR_OFFSET] mov r14, r1 add sp, sp, #FORK_SIZEOF bx lr - .size up_fork, .-up_fork + .size arm_fork_common, .-arm_fork_common .end diff --git a/arch/arm/src/common/iar/fork.S b/arch/arm/src/common/iar/fork.S index 3f16484a36530..f9dc057e39283 100644 --- a/arch/arm/src/common/iar/fork.S +++ b/arch/arm/src/common/iar/fork.S @@ -28,7 +28,7 @@ #include "arm_fork.h" - MODULE up_fork + MODULE arm_fork_common SECTION .text:CODE:NOROOT(2), "ax" /**************************************************************************** @@ -39,7 +39,15 @@ * Public Symbols ****************************************************************************/ +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + PUBLIC up_task_fork +#endif +#ifdef CONFIG_ARCH_HAVE_VFORK + PUBLIC up_vfork +#endif +#ifdef CONFIG_ARCH_HAVE_FORK PUBLIC up_fork +#endif EXTERN arm_fork /**************************************************************************** @@ -47,52 +55,76 @@ ****************************************************************************/ /**************************************************************************** - * Name: up_fork + * Name: up_task_fork, up_vfork, up_fork * * Description: - * The up_fork() function is the base of fork() function that provided in - * libc, and fork() is implemented as a wrapper of up_fork() function. - * 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. + * These are the architecture-specific entry points of NuttX's three + * cloning primitives. All three need exactly the same thing from + * assembly -- a snapshot of the caller's callee-saved registers, stack + * pointer and return address -- and differ only in what the C code then + * does with it, so they share one snapshot sequence and are distinguished + * by a FORK_TYPE_* constant passed to arm_fork() in r1. * - * This thin layer implements fork by simply calling up_fork() with the - * fork() context as an argument. The overall sequence is: + * See include/nuttx/fork.h for what the three primitives mean. * - * 1) User code calls fork(). fork() collects context information and - * transfers control up up_fork(). - * 2) arm_fork() and calls nxtask_setup_fork(). - * 3) nxtask_setup_fork() allocates and configures the child task's TCB. - * This consists of: + * The overall sequence is: + * + * 1) User code calls task_fork(), vfork() or fork(). Each is a libc + * wrapper around the matching entry point here. + * 2) The entry point collects the context and calls arm_fork(). + * 3) arm_fork() calls nxtask_setup_fork(), which 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 + * - Establishing the child's address environment + * - Allocating the stack, or inheriting the parent's for fork() * - Setup the input parameters for the task. * - Initialization of the TCB (including call to up_initial_state()) * 4) arm_fork() provides any additional operating context. arm_fork must: * - Initialize special values in any CPU registers that were not * already configured by up_initial_state() - * 5) arm_fork() then calls nxtask_start_fork() - * 6) nxtask_start_fork() then executes the child thread. + * - Relocate the copied stack, unless the child shares the parent's + * 5) arm_fork() then calls nxtask_start_fork() or nxtask_start_vfork() + * 6) which executes the child thread. * * Input Parameters: * None * * 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. + * Upon successful completion, 0 is returned to the child and the process + * ID of the child is returned to the parent. Otherwise, -1 is returned to + * the parent, no child is created, and errno is set to indicate the error. * ****************************************************************************/ THUMB +#ifdef CONFIG_ARCH_HAVE_TASK_FORK +up_task_fork: + movs r2, #FORK_TYPE_TASK + b arm_fork_common +#endif + +#ifdef CONFIG_ARCH_HAVE_VFORK +up_vfork: + movs r2, #FORK_TYPE_VFORK + b arm_fork_common +#endif + +#ifdef CONFIG_ARCH_HAVE_FORK up_fork: + movs r2, #FORK_TYPE_FORK + b arm_fork_common +#endif + +/* The shared snapshot. r2 holds the FORK_TYPE_* selector on entry; it is + * call-clobbered and is not part of the snapshot, and lr still holds the + * original caller's return address because the entry points above branched + * here rather than calling. + */ + +arm_fork_common: /* Create a stack frame */ mov r0, sp /* Save the value of the stack on entry */ @@ -117,9 +149,12 @@ up_fork: /* Floating point registers (not yet) */ - /* Then, call arm_fork(), passing it a pointer to the stack structure */ + /* Then, call arm_fork(), passing it a pointer to the stack structure + * and the selector that says which primitive was called. + */ mov r0, sp + mov r1, r2 bl arm_fork /* Release the stack data and return the value returned by arm_fork */ diff --git a/arch/arm64/src/common/arm64_fork.c b/arch/arm64/src/common/arm64_fork.c index fbbf6a56da0e5..0de6fdd6ea862 100644 --- a/arch/arm64/src/common/arm64_fork.c +++ b/arch/arm64/src/common/arm64_fork.c @@ -113,7 +113,7 @@ void arm64_fork_fpureg_save(struct fork_s *context) * ****************************************************************************/ -pid_t arm64_fork(const struct fork_s *context) +pid_t arm64_fork(const struct fork_s *context, int type) { struct tcb_s *parent = this_task(); struct tcb_s *child; @@ -125,7 +125,7 @@ pid_t arm64_fork(const struct fork_s *context) /* Allocate and initialize a TCB for the child task. */ - child = nxtask_setup_fork((start_t)context->lr); + child = nxtask_setup_fork((start_t)context->lr, type); if (!child) { serr("ERROR: nxtask_setup_fork failed\n"); @@ -235,5 +235,5 @@ pid_t arm64_fork(const struct fork_s *context) * will discard the TCB by calling nxtask_abort_fork(). */ - return nxtask_start_fork(child); + return nxtask_start_fork(child, type); } diff --git a/arch/arm64/src/common/arm64_fork.h b/arch/arm64/src/common/arm64_fork.h index a2a46e7633a01..0b77ba2f093ae 100644 --- a/arch/arm64/src/common/arm64_fork.h +++ b/arch/arm64/src/common/arm64_fork.h @@ -28,6 +28,7 @@ ****************************************************************************/ #include +#include #include /**************************************************************************** diff --git a/arch/arm64/src/common/arm64_fork_func.S b/arch/arm64/src/common/arm64_fork_func.S index 79cd9566076d5..992099e39fc2e 100644 --- a/arch/arm64/src/common/arm64_fork_func.S +++ b/arch/arm64/src/common/arm64_fork_func.S @@ -41,51 +41,77 @@ ****************************************************************************/ /**************************************************************************** - * Name: fork + * Name: up_task_fork, up_vfork, up_fork * * Description: - * The up_fork() function is the base of fork() function that provided in - * libc, and fork() is implemented as a wrapper of up_fork() function. - * 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. + * These are the architecture-specific entry points of NuttX's three + * cloning primitives. All three need exactly the same thing from + * assembly -- a snapshot of the caller's registers, stack pointer and + * return address -- and differ only in what the C code then does with it, + * so they share one snapshot sequence and are distinguished by a + * FORK_TYPE_* constant passed to arm64_fork() in x1. * - * This thin layer implements fork by simply calling up_fork() with the - * fork() context as an argument. The overall sequence is: + * See include/nuttx/fork.h for what the three primitives mean. * - * 1) User code calls fork(). fork() collects context information and - * transfers control up up_fork(). - * 2) arm64_fork() and calls nxtask_setup_fork(). - * 3) nxtask_setup_fork() allocates and configures the child task's TCB. - * This consists of: + * The overall sequence is: + * + * 1) User code calls task_fork(), vfork() or fork(). Each is a libc + * wrapper around the matching entry point here. + * 2) The entry point collects the context and calls arm64_fork(). + * 3) arm64_fork() calls nxtask_setup_fork(), which 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 + * - Establishing the child's address environment + * - Allocating the stack, or inheriting the parent's for fork() * - Setup the input parameters for the task. * - Initialization of the TCB (including call to up_initial_state()) - * 4) arm64_fork() provides any additional operating context. arm64_fork must: + * 4) arm64_fork() provides any additional operating context: * - Initialize special values in any CPU registers that were not * already configured by up_initial_state() - * 5) arm64_fork() then calls nxtask_start_fork() - * 6) nxtask_start_fork() then executes the child thread. + * - Relocate the copied stack, unless the child shares the parent's + * 5) arm64_fork() then calls nxtask_start_fork() or nxtask_start_vfork() + * 6) which executes the child thread. * * Input Parameters: * None * * 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. + * Upon successful completion, 0 is returned to the child and the process + * ID of the child is returned to the parent. Otherwise, -1 is returned to + * the parent, no child is created, and errno is set to indicate the error. * ****************************************************************************/ +#ifdef CONFIG_ARCH_HAVE_TASK_FORK +GTEXT(up_task_fork) +SECTION_FUNC(text, up_task_fork) + mov x1, #FORK_TYPE_TASK + b arm64_fork_common +#endif + +#ifdef CONFIG_ARCH_HAVE_VFORK +GTEXT(up_vfork) +SECTION_FUNC(text, up_vfork) + mov x1, #FORK_TYPE_VFORK + b arm64_fork_common +#endif + +#ifdef CONFIG_ARCH_HAVE_FORK GTEXT(up_fork) SECTION_FUNC(text, up_fork) + mov x1, #FORK_TYPE_FORK + b arm64_fork_common +#endif + +/* The shared snapshot. x1 holds the FORK_TYPE_* selector on entry and is + * carried through to arm64_fork(). It is saved into the snapshot along with + * the other argument registers, which is harmless: x0-x18 are caller-saved + * and arm64_fork() does not propagate them to the child. + */ + +SECTION_FUNC(text, arm64_fork_common) /* Create a stack frame */ sub sp, sp, #8 * FORK_REGS_SIZE /* Allocate the structure on the stack */ @@ -118,14 +144,17 @@ SECTION_FUNC(text, up_fork) #ifdef CONFIG_ARCH_FPU mov x0, sp stp x0, x30, [sp, #-16]! + str x1, [sp, #-16]! /* Preserve the FORK_TYPE_* selector */ bl arm64_fork_fpureg_save + ldr x1, [sp], #16 ldp x0, x30, [sp], #16 #endif - /* Then, call arm64_fork(), passing it a pointer to the stack structure */ + /* Then, call arm64_fork(), passing it a pointer to the stack structure. + * x1 already holds the FORK_TYPE_* selector. + */ mov x0, sp - mov x1, #0 bl arm64_fork /* Release the stack data and return the value returned by arm64_fork */ diff --git a/arch/ceva/src/common/ceva_fork.c b/arch/ceva/src/common/ceva_fork.c index 2eca6db1b7620..65aca62db573a 100644 --- a/arch/ceva/src/common/ceva_fork.c +++ b/arch/ceva/src/common/ceva_fork.c @@ -40,7 +40,7 @@ ****************************************************************************/ /**************************************************************************** - * Name: ceva_fork + * Name: ceva_task_fork * * Description: * The fork() function has the same effect as posix fork(), except that the @@ -83,7 +83,7 @@ * ****************************************************************************/ -pid_t ceva_fork(const uint32_t *regs) +pid_t ceva_task_fork(const uint32_t *regs) { #ifdef CONFIG_SCHED_WAITPID struct tcb_s *parent = this_task(); @@ -97,9 +97,14 @@ pid_t ceva_fork(const uint32_t *regs) void *argv; int ret; + /* How large is the parent's stack argument area? */ + + argsize = (uintptr_t)parent->stack_base_ptr - + (uintptr_t)parent->stack_alloc_ptr; + /* Allocate and initialize a TCB for the child task. */ - child = nxtask_setup_fork(parent->start, &argsize); + child = nxtask_setup_fork(parent->start, FORK_TYPE_TASK); if (!child) { serr("ERROR: nxtask_setup_fork failed\n"); @@ -204,7 +209,7 @@ pid_t ceva_fork(const uint32_t *regs) * will discard the TCB by calling nxtask_abort_fork(). */ - return nxtask_start_fork(child); + return nxtask_start_fork(child, FORK_TYPE_TASK); #else /* CONFIG_SCHED_WAITPID */ return (pid_t)ERROR; #endif diff --git a/arch/ceva/src/xc5/fork.S b/arch/ceva/src/xc5/fork.S index ff6dc14d22fcc..9f73fb1b5cfa1 100644 --- a/arch/ceva/src/xc5/fork.S +++ b/arch/ceva/src/xc5/fork.S @@ -34,30 +34,30 @@ ****************************************************************************/ .file "fork.S" - .extern ceva_fork + .extern ceva_task_fork /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** - * Name: up_fork + * Name: up_task_fork * * Description: - * The up_fork() function is the base of fork() function that provided in - * libc, and fork() is implemented as a wrapper of up_fork() function. + * The up_task_fork() function is the base of fork() function that provided in + * libc, and fork() is implemented as a wrapper of up_task_fork() function. * 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 thin layer implements fork by simply calling up_fork() with the fork() + * This thin layer implements fork by simply calling up_task_fork() with the fork() * context as an argument. The overall sequence is: * * 1) User code calls fork(). fork() collects context information and - * transfers control up up_fork(). - * 2) ceva_fork()and calls nxtask_forksetup(). + * transfers control up up_task_fork(). + * 2) ceva_task_fork()and calls nxtask_forksetup(). * 3) task_forksetup() allocates and configures the child task's TCB. This * consists of: * - Allocation of the child task's TCB. @@ -65,11 +65,11 @@ * - Configuration of environment variables * - Setup the input parameters for the task. * - Initialization of the TCB (including call to up_initial_state() - * 4) ceva_fork() provides any additional operating context. ceva_fork must: + * 4) ceva_task_fork() provides any additional operating context. ceva_task_fork must: * - Allocate and initialize the stack * - Initialize special values in any CPU registers that were not * already configured by up_initial_state() - * 5) ceva_fork() then calls nxtask_forkstart() + * 5) ceva_task_fork() then calls nxtask_forkstart() * 6) nxtask_forkstart() then executes the child thread. * * Input Parameters: @@ -84,10 +84,10 @@ ****************************************************************************/ .text - .public up_fork - .func_start 3 up_fork + .public up_task_fork + .func_start 3 up_task_fork -up_fork: +up_task_fork: /* Create a stack frame */ subs sp, #XCPTCONTEXT_SIZE, sp @@ -98,19 +98,19 @@ up_fork: mov sp, a1 trap - /* Then, call ceva_fork(), passing it a pointer to the stack structure */ + /* Then, call ceva_task_fork(), passing it a pointer to the stack structure */ mov sp, a0 nop push {dw} retreg - callr {t} ceva_fork + callr {t} ceva_task_fork pop {dw} retreg nop - /* Release the stack data and return the value returned by ceva_fork */ + /* Release the stack data and return the value returned by ceva_task_fork */ adds sp, #XCPTCONTEXT_SIZE, sp ret - .func_end 3 up_fork + .func_end 3 up_task_fork diff --git a/arch/ceva/src/xm6/fork.S b/arch/ceva/src/xm6/fork.S index 947be2cec5e19..8068d2788be69 100644 --- a/arch/ceva/src/xm6/fork.S +++ b/arch/ceva/src/xm6/fork.S @@ -44,30 +44,30 @@ ****************************************************************************/ .file "fork.S" - .extern up_fork + .extern up_task_fork /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** - * Name: up_fork + * Name: up_task_fork * * Description: - * The up_fork() function is the base of fork() function that provided in - * libc, and fork() is implemented as a wrapper of up_fork() function. + * The up_task_fork() function is the base of fork() function that provided in + * libc, and fork() is implemented as a wrapper of up_task_fork() function. * 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 thin layer implements fork by simply calling up_fork() with the fork() + * This thin layer implements fork by simply calling up_task_fork() with the fork() * context as an argument. The overall sequence is: * * 1) User code calls fork(). fork() collects context information and - * transfers control up up_fork(). - * 2) ceva_fork()and calls nxtask_forksetup(). + * transfers control up up_task_fork(). + * 2) ceva_task_fork()and calls nxtask_forksetup(). * 3) task_forksetup() allocates and configures the child task's TCB. This * consists of: * - Allocation of the child task's TCB. @@ -75,11 +75,11 @@ * - Configuration of environment variables * - Setup the input parameters for the task. * - Initialization of the TCB (including call to up_initial_state() - * 4) ceva_fork() provides any additional operating context. ceva_fork must: + * 4) ceva_task_fork() provides any additional operating context. ceva_task_fork must: * - Allocate and initialize the stack * - Initialize special values in any CPU registers that were not * already configured by up_initial_state() - * 5) ceva_fork() then calls nxtask_forkstart() + * 5) ceva_task_fork() then calls nxtask_forkstart() * 6) nxtask_forkstart() then executes the child thread. * * Input Parameters: @@ -94,10 +94,10 @@ ****************************************************************************/ .text - .public up_fork - .func_start 3 up_fork + .public up_task_fork + .func_start 3 up_task_fork -up_fork: +up_task_fork: /* Create a stack frame */ modr (sp.ui).ui +#-XCPTCONTEXT_SIZE /* Allocate the structure on the stack */ @@ -108,18 +108,18 @@ up_fork: mov sp.ui, r1.ui trap {t0} - /* Then, call ceva_fork(), passing it a pointer to the stack structure */ + /* Then, call ceva_task_fork(), passing it a pointer to the stack structure */ mov sp.ui, r0.ui nop push retreg.ui - callr #ceva_fork, ?prx.b + callr #ceva_task_fork, ?prx.b pop retreg.ui nop - /* Release the stack data and return the value returned by ceva_fork */ + /* Release the stack data and return the value returned by ceva_task_fork */ modr (sp.ui).ui +#XCPTCONTEXT_SIZE ret ?prx.b - .func_end 3 up_fork + .func_end 3 up_task_fork diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 1d1f2a2965d87..bca1aa58f4b29 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -56,7 +56,8 @@ endchoice config ARCH_MIPS32 bool default n - select ARCH_HAVE_FORK + select ARCH_HAVE_TASK_FORK + select ARCH_HAVE_VFORK config ARCH_MIPS_M4K bool diff --git a/arch/mips/src/mips32/Kconfig b/arch/mips/src/mips32/Kconfig index 16fa348165463..eb854fe69d723 100644 --- a/arch/mips/src/mips32/Kconfig +++ b/arch/mips/src/mips32/Kconfig @@ -93,7 +93,7 @@ config MIPS32_TOOLCHAIN_MICROCHIP_XC32_LICENSED config MIPS32_FRAMEPOINTER bool "ABI Uses Frame Pointer" default n - depends on ARCH_HAVE_FORK + depends on ARCH_HAVE_TASK_FORK ---help--- Register r30 may be a frame pointer in some ABIs. Or may just be saved register s8. It makes a difference for fork handling. diff --git a/arch/mips/src/mips32/fork.S b/arch/mips/src/mips32/fork.S index 790aa8cd43f42..6bd5f22b21f6c 100644 --- a/arch/mips/src/mips32/fork.S +++ b/arch/mips/src/mips32/fork.S @@ -44,7 +44,7 @@ ************************************************************************************/ /************************************************************************************ - * Name: up_fork + * Name: up_task_fork, up_vfork, up_fork * * Description: * The up_fork() function is the base of fork() function that provided in @@ -88,15 +88,57 @@ .text .align 2 - .globl up_fork - .type up_fork, function .set nomips16 #ifdef CONFIG_MIPS_MICROMIPS .set micromips #endif - .ent up_fork +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + .globl up_task_fork + .type up_task_fork, function + .ent up_task_fork +up_task_fork: + li $a1, FORK_TYPE_TASK + b mips_fork_common + nop + .end up_task_fork + .size up_task_fork, .-up_task_fork +#endif + +#ifdef CONFIG_ARCH_HAVE_VFORK + .globl up_vfork + .type up_vfork, function + .ent up_vfork +up_vfork: + li $a1, FORK_TYPE_VFORK + b mips_fork_common + nop + .end up_vfork + .size up_vfork, .-up_vfork +#endif + +#ifdef CONFIG_ARCH_HAVE_FORK + .globl up_fork + .type up_fork, function + .ent up_fork up_fork: + li $a1, FORK_TYPE_FORK + b mips_fork_common + nop + .end up_fork + .size up_fork, .-up_fork +#endif + + /* The shared snapshot. $a1 holds the FORK_TYPE_* selector on entry and + * is carried through to mips_fork(); it is not part of the snapshot, and + * $ra still holds the original caller's return address because the entry + * points above branched here rather than calling. + */ + + .type mips_fork_common, function + .ent mips_fork_common + +mips_fork_common: /* Create a stack frame */ move $t0, $sp /* Save the value of the stack on entry */ @@ -130,7 +172,9 @@ up_fork: /* Floating point registers (not yet) */ - /* Then, call mips_fork(), passing it a pointer to the stack structure */ + /* Then, call mips_fork(), passing it a pointer to the stack structure. + * $a1 already holds the FORK_TYPE_* selector. + */ move $a0, $sp jal mips_fork @@ -142,5 +186,5 @@ up_fork: addiu $sp, $sp, FORK_SIZEOF j $ra - .end up_fork - .size up_fork, .-up_fork + .end mips_fork_common + .size mips_fork_common, .-mips_fork_common diff --git a/arch/mips/src/mips32/mips_fork.c b/arch/mips/src/mips32/mips_fork.c index 3d931d1da1d56..1f1202fd0f9d9 100644 --- a/arch/mips/src/mips32/mips_fork.c +++ b/arch/mips/src/mips32/mips_fork.c @@ -89,7 +89,7 @@ * ****************************************************************************/ -pid_t mips_fork(const struct fork_s *context) +pid_t mips_fork(const struct fork_s *context, int type) { struct tcb_s *parent = this_task(); struct tcb_s *child; @@ -113,7 +113,7 @@ pid_t mips_fork(const struct fork_s *context) context->fp, context->sp, context->ra, context->gp); #else sinfo("fp:%08" PRIx32 " sp:%08" PRIx32 " ra:%08" PRIx32 "\n", - context->fp context->sp, context->ra); + context->fp, context->sp, context->ra); #endif #else sinfo("s5:%08" PRIx32 " s6:%08" PRIx32 " s7:%08" PRIx32 @@ -130,7 +130,7 @@ pid_t mips_fork(const struct fork_s *context) /* Allocate and initialize a TCB for the child task. */ - child = nxtask_setup_fork((start_t)context->ra); + child = nxtask_setup_fork((start_t)context->ra, type); if (!child) { sinfo("nxtask_setup_fork failed\n"); @@ -217,5 +217,5 @@ pid_t mips_fork(const struct fork_s *context) * will discard the TCB by calling nxtask_abort_fork(). */ - return nxtask_start_fork(child); + return nxtask_start_fork(child, type); } diff --git a/arch/mips/src/mips32/mips_fork.h b/arch/mips/src/mips32/mips_fork.h index ab50b3eda7eff..632a2b7c57d6f 100644 --- a/arch/mips/src/mips32/mips_fork.h +++ b/arch/mips/src/mips32/mips_fork.h @@ -28,6 +28,7 @@ ****************************************************************************/ #include +#include #include /**************************************************************************** diff --git a/arch/risc-v/src/common/CMakeLists.txt b/arch/risc-v/src/common/CMakeLists.txt index 84a1d43fb47c8..2e6ccf65c0240 100644 --- a/arch/risc-v/src/common/CMakeLists.txt +++ b/arch/risc-v/src/common/CMakeLists.txt @@ -86,7 +86,9 @@ if(CONFIG_STACK_COLORATION) list(APPEND SRCS riscv_checkstack.c) endif() -if(CONFIG_ARCH_HAVE_FORK) +if(CONFIG_ARCH_HAVE_TASK_FORK + OR CONFIG_ARCH_HAVE_VFORK + OR CONFIG_ARCH_HAVE_FORK) list(APPEND SRCS fork.S riscv_fork.c) endif() diff --git a/arch/risc-v/src/common/Make.defs b/arch/risc-v/src/common/Make.defs index d98a828c508e0..aabff1ed99d3e 100644 --- a/arch/risc-v/src/common/Make.defs +++ b/arch/risc-v/src/common/Make.defs @@ -86,7 +86,7 @@ ifeq ($(CONFIG_STACK_COLORATION),y) CMN_CSRCS += riscv_checkstack.c endif -ifeq ($(CONFIG_ARCH_HAVE_FORK),y) +ifneq ($(CONFIG_ARCH_HAVE_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),) CMN_ASRCS += fork.S CMN_CSRCS += riscv_fork.c endif diff --git a/arch/risc-v/src/common/fork.S b/arch/risc-v/src/common/fork.S index 108ff00f80db1..76acd88bd5399 100644 --- a/arch/risc-v/src/common/fork.S +++ b/arch/risc-v/src/common/fork.S @@ -39,59 +39,95 @@ .file "fork.S" .globl riscv_fork +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + .globl up_task_fork +#endif +#ifdef CONFIG_ARCH_HAVE_VFORK + .globl up_vfork +#endif +#ifdef CONFIG_ARCH_HAVE_FORK .globl up_fork +#endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** - * Name: fork + * Name: up_task_fork, up_vfork, up_fork * * Description: - * The up_fork() function is the base of fork() function that provided in - * libc, and fork() is implemented as a wrapper of up_fork() function. - * 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. + * These are the architecture-specific entry points of NuttX's three + * cloning primitives. All three need exactly the same thing from + * assembly -- a snapshot of the caller's callee-saved registers, stack + * pointer and return address -- and differ only in what the C code then + * does with it, so they share one snapshot sequence and are distinguished + * by a FORK_TYPE_* constant passed to riscv_fork() in a1. * - * This thin layer implements fork by simply calling up_fork() with the - * fork() context as an argument. The overall sequence is: + * See include/nuttx/fork.h for what the three primitives mean. * - * 1) User code calls fork(). fork() collects context information and - * transfers control up up_fork(). - * 2) riscv_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) riscv_fork() provides any additional operating context. riscv_fork must: - * - Initialize special values in any CPU registers that were not - * already configured by up_initial_state() - * 5) riscv_fork() then calls nxtask_start_fork() - * 6) nxtask_start_fork() then executes the child thread. + * The overall sequence is: + * + * 1) User code calls task_fork(), vfork() or fork(). Each is a libc + * wrapper around the matching entry point here. + * 2) The entry point collects the context and calls riscv_fork(). + * 3) riscv_fork() calls nxtask_setup_fork(), which allocates and + * configures the child task's TCB. + * 4) riscv_fork() provides any additional operating context and relocates + * the copied stack. + * 5) riscv_fork() then calls nxtask_start_fork(), or nxtask_start_vfork() + * which additionally suspends the caller. + * 6) which executes the child thread. * * Input Parameters: * None * * 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. + * Upon successful completion, 0 is returned to the child and the process + * ID of the child is returned to the parent. Otherwise, -1 is returned to + * the parent, no child is created, and errno is set to indicate the error. * ****************************************************************************/ +#ifdef CONFIG_ARCH_HAVE_TASK_FORK +.type up_task_fork, function + +up_task_fork: + li a1, FORK_TYPE_TASK + j riscv_fork_common + + .size up_task_fork, .-up_task_fork +#endif + +#ifdef CONFIG_ARCH_HAVE_VFORK +.type up_vfork, function + +up_vfork: + li a1, FORK_TYPE_VFORK + j riscv_fork_common + + .size up_vfork, .-up_vfork +#endif + +#ifdef CONFIG_ARCH_HAVE_FORK .type up_fork, function up_fork: + li a1, FORK_TYPE_FORK + j riscv_fork_common + + .size up_fork, .-up_fork +#endif + +/* The shared snapshot. a1 holds the FORK_TYPE_* selector on entry and is + * carried through to riscv_fork(); it is not part of the snapshot, and ra + * still holds the original caller's return address because the entry points + * above jumped here rather than calling. + */ + +.type riscv_fork_common, function + +riscv_fork_common: #ifdef CONFIG_LIB_SYSCALL /* When coming via system call, everything is in place already */ @@ -150,7 +186,9 @@ up_fork: FSTORE fs11, FORK_FS11_OFFSET(sp) #endif - /* Then, call riscv_fork(), passing it a pointer to the stack frame */ + /* Then, call riscv_fork(), passing it a pointer to the stack frame. a1 + * already holds the FORK_TYPE_* selector. + */ mv a0, sp call riscv_fork @@ -162,5 +200,5 @@ up_fork: ret #endif - .size up_fork, .-up_fork + .size riscv_fork_common, .-riscv_fork_common .end diff --git a/arch/risc-v/src/common/riscv_fork.c b/arch/risc-v/src/common/riscv_fork.c index f25e5cf4e4062..ddcc7b2588ce5 100644 --- a/arch/risc-v/src/common/riscv_fork.c +++ b/arch/risc-v/src/common/riscv_fork.c @@ -41,7 +41,8 @@ #include "sched/sched.h" -#ifdef CONFIG_ARCH_HAVE_FORK +#if defined(CONFIG_ARCH_HAVE_TASK_FORK) || defined(CONFIG_ARCH_HAVE_VFORK) || \ + defined(CONFIG_ARCH_HAVE_FORK) /**************************************************************************** * Pre-processor Definitions @@ -102,7 +103,7 @@ #ifdef CONFIG_LIB_SYSCALL -pid_t riscv_fork(const struct fork_s *context) +pid_t riscv_fork(const struct fork_s *context, int type) { struct tcb_s *parent = this_task(); struct tcb_s *child; @@ -117,7 +118,7 @@ pid_t riscv_fork(const struct fork_s *context) /* Allocate and initialize a TCB for the child task. */ - child = nxtask_setup_fork((start_t)parent->xcp.sregs[REG_RA]); + child = nxtask_setup_fork((start_t)parent->xcp.sregs[REG_RA], type); if (!child) { sinfo("nxtask_setup_fork failed\n"); @@ -184,12 +185,12 @@ pid_t riscv_fork(const struct fork_s *context) * will discard the TCB by calling nxtask_abort_fork(). */ - return nxtask_start_fork(child); + return nxtask_start_fork(child, type); } #else -pid_t riscv_fork(const struct fork_s *context) +pid_t riscv_fork(const struct fork_s *context, int type) { struct tcb_s *parent = this_task(); struct tcb_s *child; @@ -215,7 +216,7 @@ pid_t riscv_fork(const struct fork_s *context) context->fp, context->sp, context->ra, context->gp); #else sinfo("fp:%" PRIxREG " sp:%" PRIxREG " ra:%" PRIxREG "\n", - context->fp context->sp, context->ra); + context->fp, context->sp, context->ra); #endif #else sinfo("s5:%" PRIxREG " s6:%" PRIxREG " s7:%" PRIxREG " s8:%" PRIxREG "\n", @@ -231,7 +232,7 @@ pid_t riscv_fork(const struct fork_s *context) /* Allocate and initialize a TCB for the child task. */ - child = nxtask_setup_fork((start_t)(uintptr_t)context->ra); + child = nxtask_setup_fork((start_t)(uintptr_t)context->ra, type); if (!child) { sinfo("nxtask_setup_fork failed\n"); @@ -346,8 +347,9 @@ pid_t riscv_fork(const struct fork_s *context) * will discard the TCB by calling nxtask_abort_fork(). */ - return nxtask_start_fork(child); + return nxtask_start_fork(child, type); } #endif /* CONFIG_LIB_SYSCALL */ -#endif /* CONFIG_ARCH_HAVE_FORK */ +#endif /* CONFIG_ARCH_HAVE_TASK_FORK || CONFIG_ARCH_HAVE_VFORK || + * CONFIG_ARCH_HAVE_FORK */ diff --git a/arch/risc-v/src/common/riscv_fork.h b/arch/risc-v/src/common/riscv_fork.h index 6b3a71d581854..d807d5dc499b2 100644 --- a/arch/risc-v/src/common/riscv_fork.h +++ b/arch/risc-v/src/common/riscv_fork.h @@ -28,6 +28,7 @@ ****************************************************************************/ #include +#include #include #include "riscv_internal.h" diff --git a/arch/sim/src/Makefile b/arch/sim/src/Makefile index 280c3a8b05041..c71e986845c18 100644 --- a/arch/sim/src/Makefile +++ b/arch/sim/src/Makefile @@ -95,7 +95,7 @@ ifeq ($(CONFIG_SCHED_BACKTRACE),y) CSRCS += sim_backtrace.c endif -ifeq ($(CONFIG_ARCH_HAVE_FORK),y) +ifneq ($(CONFIG_ARCH_HAVE_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),) CSRCS += sim_fork.c endif diff --git a/arch/sim/src/sim/CMakeLists.txt b/arch/sim/src/sim/CMakeLists.txt index a378448d1a324..101b94119612a 100644 --- a/arch/sim/src/sim/CMakeLists.txt +++ b/arch/sim/src/sim/CMakeLists.txt @@ -82,7 +82,9 @@ if(CONFIG_SCHED_BACKTRACE) list(APPEND SRCS sim_backtrace.c) endif() -if(CONFIG_ARCH_HAVE_FORK) +if(CONFIG_ARCH_HAVE_TASK_FORK + OR CONFIG_ARCH_HAVE_VFORK + OR CONFIG_ARCH_HAVE_FORK) list(APPEND SRCS sim_fork.c) endif() diff --git a/arch/sim/src/sim/sim_fork.c b/arch/sim/src/sim/sim_fork.c index 47ea655cbb65f..e94c6fe71a243 100644 --- a/arch/sim/src/sim/sim_fork.c +++ b/arch/sim/src/sim/sim_fork.c @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -45,7 +46,7 @@ ****************************************************************************/ /**************************************************************************** - * Name: sim_fork + * Name: sim_fork_internal * * Description: * The fork() function has the same effect as posix fork(), except that the @@ -88,7 +89,7 @@ #ifdef CONFIG_SIM_ASAN nosanitize_address #endif -pid_t sim_fork(const xcpt_reg_t *context) +static pid_t sim_fork_internal(const xcpt_reg_t *context, int type) { struct tcb_s *parent = this_task(); struct tcb_s *child; @@ -106,7 +107,7 @@ pid_t sim_fork(const xcpt_reg_t *context) /* Allocate and initialize a TCB for the child task. */ - child = nxtask_setup_fork((start_t)context[JB_PC]); + child = nxtask_setup_fork((start_t)context[JB_PC], type); if (!child) { serr("ERROR: nxtask_setup_fork failed\n"); @@ -175,5 +176,37 @@ pid_t sim_fork(const xcpt_reg_t *context) * will discard the TCB by calling nxtask_abort_fork(). */ - return nxtask_start_fork(child); + return nxtask_start_fork(child, type); +} + +/**************************************************************************** + * Name: sim_task_fork, sim_vfork, sim_fork + * + * Description: + * The three primitives, each a name for one FORK_TYPE_* value. The + * simulator's assembly entry points call these directly rather than + * carrying a selector through setjmp(), which would have to survive a call + * that is allowed to clobber every argument register. + * + ****************************************************************************/ + +#ifdef CONFIG_ARCH_HAVE_TASK_FORK +pid_t sim_task_fork(const xcpt_reg_t *context) +{ + return sim_fork_internal(context, FORK_TYPE_TASK); +} +#endif + +#ifdef CONFIG_ARCH_HAVE_VFORK +pid_t sim_vfork(const xcpt_reg_t *context) +{ + return sim_fork_internal(context, FORK_TYPE_VFORK); +} +#endif + +#ifdef CONFIG_ARCH_HAVE_FORK +pid_t sim_fork(const xcpt_reg_t *context) +{ + return sim_fork_internal(context, FORK_TYPE_FORK); } +#endif diff --git a/arch/sim/src/sim/sim_fork_arm.S b/arch/sim/src/sim/sim_fork_arm.S index ee39f54e003b5..10032044d25c7 100644 --- a/arch/sim/src/sim/sim_fork_arm.S +++ b/arch/sim/src/sim/sim_fork_arm.S @@ -43,7 +43,19 @@ ************************************************************************************/ /************************************************************************************ - * Name: up_fork + * Name: up_task_fork, up_vfork, up_fork + * + * Description: + * These are the architecture-specific entry points of NuttX's three + * cloning primitives; see include/nuttx/fork.h for what the three mean. + * They are identical apart from the C worker each calls, which is how the + * primitives are distinguished. + * + * On the simulator the caller's context is captured with setjmp() rather + * than by hand, and the child re-enters through longjmp() -- which is why + * each entry point tests setjmp()'s return value to tell which of the two + * returns it is on. + * * * Description: * The up_fork() function is the base of fork() function that provided in @@ -86,6 +98,42 @@ ************************************************************************************/ .text + +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + .globl up_task_fork + .type up_task_fork, @function +up_task_fork: + sub sp, sp, #XCPTCONTEXT_SIZE + mov r0, sp + bl setjmp + + subs r0, #1 + beq 1f + bl sim_task_fork +1: + add sp, sp, #XCPTCONTEXT_SIZE + bx lr + .size up_task_fork, . - up_task_fork +#endif /* CONFIG_ARCH_HAVE_TASK_FORK */ + +#ifdef CONFIG_ARCH_HAVE_VFORK + .globl up_vfork + .type up_vfork, @function +up_vfork: + sub sp, sp, #XCPTCONTEXT_SIZE + mov r0, sp + bl setjmp + + subs r0, #1 + beq 1f + bl sim_vfork +1: + add sp, sp, #XCPTCONTEXT_SIZE + bx lr + .size up_vfork, . - up_vfork +#endif /* CONFIG_ARCH_HAVE_VFORK */ + +#ifdef CONFIG_ARCH_HAVE_FORK .globl up_fork .type up_fork, @function up_fork: @@ -94,10 +142,11 @@ up_fork: bl setjmp subs r0, #1 - jz child + beq 1f bl sim_fork -child: +1: add sp, sp, #XCPTCONTEXT_SIZE - ret + bx lr .size up_fork, . - up_fork +#endif /* CONFIG_ARCH_HAVE_FORK */ .end diff --git a/arch/sim/src/sim/sim_fork_arm64.S b/arch/sim/src/sim/sim_fork_arm64.S index 5d813822fa620..4c8f93f449993 100644 --- a/arch/sim/src/sim/sim_fork_arm64.S +++ b/arch/sim/src/sim/sim_fork_arm64.S @@ -52,7 +52,19 @@ ***************************************************************************/ /**************************************************************************** - * Name: up_fork + * Name: up_task_fork, up_vfork, up_fork + * + * Description: + * These are the architecture-specific entry points of NuttX's three + * cloning primitives; see include/nuttx/fork.h for what the three mean. + * They are identical apart from the C worker each calls, which is how the + * primitives are distinguished. + * + * On the simulator the caller's context is captured with setjmp() rather + * than by hand, and the child re-enters through longjmp() -- which is why + * each entry point tests setjmp()'s return value to tell which of the two + * returns it is on. + * * * Description: * The up_fork() function is the base of fork() function that provided in @@ -96,9 +108,59 @@ ***************************************************************************/ .text - .globl SYMBOL(up_fork) .align 4 +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + .globl SYMBOL(up_task_fork) + +SYMBOL(up_task_fork): + + stp x29, x30, [sp] /* save FP/LR register */ + sub sp, sp, #XCPTCONTEXT_SIZE /* area from stack for setjmp() */ + + mov x0, sp /* pass stack area to setjmp() */ + bl SYMBOL(setjmp) /* save register for longjmp() */ + + subs x0, x0, #1 /* 0: parent / 1: child */ + cbz x0, 1f /* child --> return */ + + mov x0, sp /* pass stack area to the worker */ + bl SYMBOL(sim_task_fork) /* further process task creation */ + +1: + add sp, sp, #XCPTCONTEXT_SIZE /* release area from stack */ + ldp x29, x30, [sp] /* restore FP/LR register */ + + ret +#endif /* CONFIG_ARCH_HAVE_TASK_FORK */ + +#ifdef CONFIG_ARCH_HAVE_VFORK + .globl SYMBOL(up_vfork) + +SYMBOL(up_vfork): + + stp x29, x30, [sp] /* save FP/LR register */ + sub sp, sp, #XCPTCONTEXT_SIZE /* area from stack for setjmp() */ + + mov x0, sp /* pass stack area to setjmp() */ + bl SYMBOL(setjmp) /* save register for longjmp() */ + + subs x0, x0, #1 /* 0: parent / 1: child */ + cbz x0, 1f /* child --> return */ + + mov x0, sp /* pass stack area to the worker */ + bl SYMBOL(sim_vfork) /* further process task creation */ + +1: + add sp, sp, #XCPTCONTEXT_SIZE /* release area from stack */ + ldp x29, x30, [sp] /* restore FP/LR register */ + + ret +#endif /* CONFIG_ARCH_HAVE_VFORK */ + +#ifdef CONFIG_ARCH_HAVE_FORK + .globl SYMBOL(up_fork) + SYMBOL(up_fork): stp x29, x30, [sp] /* save FP/LR register */ @@ -110,7 +172,7 @@ SYMBOL(up_fork): subs x0, x0, #1 /* 0: parent / 1: child */ cbz x0, 1f /* child --> return */ - mov x0, sp /* pass stack area to sim_fork() */ + mov x0, sp /* pass stack area to the worker */ bl SYMBOL(sim_fork) /* further process task creation */ 1: @@ -118,5 +180,5 @@ SYMBOL(up_fork): ldp x29, x30, [sp] /* restore FP/LR register */ ret - +#endif /* CONFIG_ARCH_HAVE_FORK */ .end diff --git a/arch/sim/src/sim/sim_fork_x86.S b/arch/sim/src/sim/sim_fork_x86.S index ec7486664c568..ab460b03c157d 100644 --- a/arch/sim/src/sim/sim_fork_x86.S +++ b/arch/sim/src/sim/sim_fork_x86.S @@ -51,7 +51,19 @@ ************************************************************************************/ /************************************************************************************ - * Name: up_fork + * Name: up_task_fork, up_vfork, up_fork + * + * Description: + * These are the architecture-specific entry points of NuttX's three + * cloning primitives; see include/nuttx/fork.h for what the three mean. + * They are identical apart from the C worker each calls, which is how the + * primitives are distinguished. + * + * On the simulator the caller's context is captured with setjmp() rather + * than by hand, and the child re-enters through longjmp() -- which is why + * each entry point tests setjmp()'s return value to tell which of the two + * returns it is on. + * * * Description: * The up_fork() function is the base of fork() function that provided in @@ -94,6 +106,52 @@ ************************************************************************************/ .text + +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + .globl SYMBOL(up_task_fork) +#ifdef __ELF__ + .type SYMBOL(up_task_fork), @function +#endif + +SYMBOL(up_task_fork): + sub $XCPTCONTEXT_SIZE, %esp + push %esp + call SYMBOL(setjmp) + + sub $1, %eax + jz 1f + call SYMBOL(sim_task_fork) +1: + add $XCPTCONTEXT_SIZE+4, %esp + ret +#ifdef __ELF__ + .size SYMBOL(up_task_fork), . - SYMBOL(up_task_fork) +#endif +#endif /* CONFIG_ARCH_HAVE_TASK_FORK */ + +#ifdef CONFIG_ARCH_HAVE_VFORK + .globl SYMBOL(up_vfork) +#ifdef __ELF__ + .type SYMBOL(up_vfork), @function +#endif + +SYMBOL(up_vfork): + sub $XCPTCONTEXT_SIZE, %esp + push %esp + call SYMBOL(setjmp) + + sub $1, %eax + jz 1f + call SYMBOL(sim_vfork) +1: + add $XCPTCONTEXT_SIZE+4, %esp + ret +#ifdef __ELF__ + .size SYMBOL(up_vfork), . - SYMBOL(up_vfork) +#endif +#endif /* CONFIG_ARCH_HAVE_VFORK */ + +#ifdef CONFIG_ARCH_HAVE_FORK .globl SYMBOL(up_fork) #ifdef __ELF__ .type SYMBOL(up_fork), @function @@ -105,11 +163,12 @@ SYMBOL(up_fork): call SYMBOL(setjmp) sub $1, %eax - jz child + jz 1f call SYMBOL(sim_fork) -child: +1: add $XCPTCONTEXT_SIZE+4, %esp ret #ifdef __ELF__ .size SYMBOL(up_fork), . - SYMBOL(up_fork) #endif +#endif /* CONFIG_ARCH_HAVE_FORK */ diff --git a/arch/sim/src/sim/sim_fork_x86_64.S b/arch/sim/src/sim/sim_fork_x86_64.S index 85b072acf866e..92a2bc4630e2a 100644 --- a/arch/sim/src/sim/sim_fork_x86_64.S +++ b/arch/sim/src/sim/sim_fork_x86_64.S @@ -51,7 +51,19 @@ ************************************************************************************/ /************************************************************************************ - * Name: up_fork + * Name: up_task_fork, up_vfork, up_fork + * + * Description: + * These are the architecture-specific entry points of NuttX's three + * cloning primitives; see include/nuttx/fork.h for what the three mean. + * They are identical apart from the C worker each calls, which is how the + * primitives are distinguished. + * + * On the simulator the caller's context is captured with setjmp() rather + * than by hand, and the child re-enters through longjmp() -- which is why + * each entry point tests setjmp()'s return value to tell which of the two + * returns it is on. + * * * Description: * The up_fork() function is the base of fork() function that provided in @@ -94,6 +106,62 @@ ************************************************************************************/ .text + +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + .globl SYMBOL(up_task_fork) +#ifdef __ELF__ + .type SYMBOL(up_task_fork), @function +#endif + +SYMBOL(up_task_fork): + sub $XCPTCONTEXT_SIZE, %rsp +#ifdef CONFIG_SIM_X8664_MICROSOFT + mov %rsp, %rcx +#else /* if defined(CONFIG_SIM_X8664_SYSTEMV) */ + mov %rsp, %rdi +#endif + call SYMBOL(setjmp) + + sub $1, %eax + jz 1f + + call SYMBOL(sim_task_fork) +1: + add $XCPTCONTEXT_SIZE, %rsp + ret +#ifdef __ELF__ + .size SYMBOL(up_task_fork), . - SYMBOL(up_task_fork) +#endif +#endif /* CONFIG_ARCH_HAVE_TASK_FORK */ + +#ifdef CONFIG_ARCH_HAVE_VFORK + .globl SYMBOL(up_vfork) +#ifdef __ELF__ + .type SYMBOL(up_vfork), @function +#endif + +SYMBOL(up_vfork): + sub $XCPTCONTEXT_SIZE, %rsp +#ifdef CONFIG_SIM_X8664_MICROSOFT + mov %rsp, %rcx +#else /* if defined(CONFIG_SIM_X8664_SYSTEMV) */ + mov %rsp, %rdi +#endif + call SYMBOL(setjmp) + + sub $1, %eax + jz 1f + + call SYMBOL(sim_vfork) +1: + add $XCPTCONTEXT_SIZE, %rsp + ret +#ifdef __ELF__ + .size SYMBOL(up_vfork), . - SYMBOL(up_vfork) +#endif +#endif /* CONFIG_ARCH_HAVE_VFORK */ + +#ifdef CONFIG_ARCH_HAVE_FORK .globl SYMBOL(up_fork) #ifdef __ELF__ .type SYMBOL(up_fork), @function @@ -109,12 +177,13 @@ SYMBOL(up_fork): call SYMBOL(setjmp) sub $1, %eax - jz child + jz 1f call SYMBOL(sim_fork) -child: +1: add $XCPTCONTEXT_SIZE, %rsp ret #ifdef __ELF__ .size SYMBOL(up_fork), . - SYMBOL(up_fork) #endif +#endif /* CONFIG_ARCH_HAVE_FORK */ diff --git a/arch/x86_64/src/common/CMakeLists.txt b/arch/x86_64/src/common/CMakeLists.txt index 841fa13679935..e9c320af9771c 100644 --- a/arch/x86_64/src/common/CMakeLists.txt +++ b/arch/x86_64/src/common/CMakeLists.txt @@ -34,7 +34,9 @@ set(SRCS x86_64_tcbinfo.c x86_64_tlb.c) -if(CONFIG_ARCH_HAVE_FORK) +if(CONFIG_ARCH_HAVE_TASK_FORK + OR CONFIG_ARCH_HAVE_VFORK + OR CONFIG_ARCH_HAVE_FORK) list(APPEND SRCS x86_64_fork.c fork.S) endif() diff --git a/arch/x86_64/src/common/Make.defs b/arch/x86_64/src/common/Make.defs index a5a21aff0a63a..4c5c8f2bab407 100644 --- a/arch/x86_64/src/common/Make.defs +++ b/arch/x86_64/src/common/Make.defs @@ -29,7 +29,7 @@ CMN_CSRCS += x86_64_getintstack.c x86_64_initialize.c x86_64_nputs.c CMN_CSRCS += x86_64_modifyreg8.c x86_64_modifyreg16.c x86_64_modifyreg32.c CMN_CSRCS += x86_64_switchcontext.c x86_64_tlb.c -ifeq ($(CONFIG_ARCH_HAVE_FORK),y) +ifneq ($(CONFIG_ARCH_HAVE_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),) CMN_CSRCS += x86_64_fork.c CMN_ASRCS += fork.S endif diff --git a/arch/x86_64/src/common/fork.S b/arch/x86_64/src/common/fork.S index 1621b560474a1..602ded522fe24 100644 --- a/arch/x86_64/src/common/fork.S +++ b/arch/x86_64/src/common/fork.S @@ -36,7 +36,7 @@ ****************************************************************************/ /**************************************************************************** - * Name: up_fork + * Name: up_task_fork, up_vfork, up_fork * * Description: * The up_fork() function is the base of fork() function that provided in @@ -95,10 +95,43 @@ * | ......... | */ +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + .globl up_task_fork + .type up_task_fork, @function + +up_task_fork: + movq $FORK_TYPE_TASK, %rsi + jmp x86_64_fork_common +#endif + +#ifdef CONFIG_ARCH_HAVE_VFORK + .globl up_vfork + .type up_vfork, @function + +up_vfork: + movq $FORK_TYPE_VFORK, %rsi + jmp x86_64_fork_common +#endif + +#ifdef CONFIG_ARCH_HAVE_FORK .globl up_fork .type up_fork, @function up_fork: + movq $FORK_TYPE_FORK, %rsi + jmp x86_64_fork_common +#endif + +/* The shared snapshot. %rsi holds the FORK_TYPE_* selector on entry and is + * carried through to x86_64_fork() as its second argument. Note that the + * return address the entry points pushed is still on the stack -- they + * jumped here rather than calling -- so the %rsp recovered below is the + * original caller's. + */ + + .type x86_64_fork_common, @function + +x86_64_fork_common: movq %rsp, %rax addq $8, %rax movq %ss, %rdi diff --git a/arch/x86_64/src/common/x86_64_fork.c b/arch/x86_64/src/common/x86_64_fork.c index ea4d90ac044b4..31eceeff193c4 100644 --- a/arch/x86_64/src/common/x86_64_fork.c +++ b/arch/x86_64/src/common/x86_64_fork.c @@ -89,7 +89,7 @@ * ****************************************************************************/ -pid_t x86_64_fork(const struct fork_s *context) +pid_t x86_64_fork(const struct fork_s *context, int type) { struct tcb_s *parent = this_task(); struct tcb_s *child; @@ -110,7 +110,7 @@ pid_t x86_64_fork(const struct fork_s *context) /* Allocate and initialize a TCB for the child task. */ - child = nxtask_setup_fork((start_t)context->rip); + child = nxtask_setup_fork((start_t)context->rip, type); if (!child) { serr("ERROR: nxtask_setup_fork failed\n"); @@ -195,5 +195,5 @@ pid_t x86_64_fork(const struct fork_s *context) * will discard the TCB by calling nxtask_abort_fork(). */ - return nxtask_start_fork(child); + return nxtask_start_fork(child, type); } diff --git a/arch/x86_64/src/common/x86_64_fork.h b/arch/x86_64/src/common/x86_64_fork.h index f6e4c31978026..acf05285be409 100644 --- a/arch/x86_64/src/common/x86_64_fork.h +++ b/arch/x86_64/src/common/x86_64_fork.h @@ -28,6 +28,7 @@ ****************************************************************************/ #include +#include /**************************************************************************** * Pre-processor Definitions diff --git a/include/nuttx/addrenv.h b/include/nuttx/addrenv.h index 8e253c88eee1b..91c2eaa28d4d2 100644 --- a/include/nuttx/addrenv.h +++ b/include/nuttx/addrenv.h @@ -394,6 +394,31 @@ int addrenv_attach(FAR struct tcb_s *tcb, FAR struct addrenv_s *addrenv); int addrenv_join(FAR struct tcb_s *ptcb, FAR struct tcb_s *tcb); +/**************************************************************************** + * Name: addrenv_fork + * + * Description: + * Duplicate the parent's address environment for a POSIX fork() child and + * attach it: the child gets its own pages holding a copy of the parent's + * contents, mapped at the same virtual addresses. Contrast + * addrenv_join(), which gives the child the parent's memory. + * + * Input Parameters: + * ptcb - The tcb of the parent process. + * tcb - The tcb of the child process. + * + * Returned Value: + * This is a NuttX internal function so it follows the convention that + * 0 (OK) is returned on success and a negated errno is returned on + * failure. -ENOMEM is returned if there is not enough free memory to + * hold a copy of the parent. + * + ****************************************************************************/ + +#ifdef CONFIG_ARCH_HAVE_FORK +int addrenv_fork(FAR struct tcb_s *ptcb, FAR struct tcb_s *tcb); +#endif + /**************************************************************************** * Name: addrenv_leave * diff --git a/include/nuttx/arch.h b/include/nuttx/arch.h index e1a598c222b2d..01c412094ca4f 100644 --- a/include/nuttx/arch.h +++ b/include/nuttx/arch.h @@ -249,12 +249,53 @@ extern initializer_t _einit[]; * logic from architecture-specific code. ****************************************************************************/ +/**************************************************************************** + * Name: up_task_fork + * + * Description: + * Architecture-specific base of task_fork(): the child shares the + * parent's memory, runs on a private copy of the parent's stack, and runs + * concurrently. Neither fork() nor vfork(); see up_fork(), up_vfork(). + * + * Returned Value: + * Upon successful completion, up_task_fork() returns 0 to the child and + * returns the process ID of the child to the parent. Otherwise, -1 is + * returned to the parent, no child is created, and errno is set to + * indicate the error. + * + ****************************************************************************/ + +#ifdef CONFIG_ARCH_HAVE_TASK_FORK +pid_t up_task_fork(void); +#endif + +/**************************************************************************** + * Name: up_vfork + * + * Description: + * Architecture-specific base of vfork(): the child shares the parent's + * memory and the parent is suspended until the child _exit()s or exec()s. + * The child runs on a relocated copy of the parent's stack. + * + * Returned Value: + * Upon successful completion, up_vfork() returns 0 to the child and + * returns the process ID of the child to the parent. Otherwise, -1 is + * returned to the parent, no child is created, and errno is set to + * indicate the error. + * + ****************************************************************************/ + +#ifdef CONFIG_ARCH_HAVE_VFORK +pid_t up_vfork(void); +#endif + /**************************************************************************** * Name: up_fork * * Description: - * The up_fork() function is the base of fork() function that provided in - * libc, and fork() is implemented as a wrapper of up_fork() function. + * Architecture-specific base of POSIX fork(): the child receives its own + * copy of the parent's memory at the same virtual addresses and runs + * concurrently. Available only where CONFIG_ARCH_HAVE_FORK is selected. * * Returned Value: * Upon successful completion, up_fork() returns 0 to the child process @@ -264,7 +305,9 @@ extern initializer_t _einit[]; * ****************************************************************************/ +#ifdef CONFIG_ARCH_HAVE_FORK pid_t up_fork(void); +#endif /**************************************************************************** * Name: up_initialize @@ -1327,6 +1370,35 @@ int up_addrenv_clone(FAR const arch_addrenv_t *src, FAR arch_addrenv_t *dest); #endif +/**************************************************************************** + * Name: up_addrenv_fork + * + * Description: + * Duplicate an address environment for POSIX fork(): allocate fresh + * pages for the destination, copy the source's contents into them, and map + * them at the same virtual addresses. Unlike up_addrenv_clone(), which + * copies only the representation and leaves both pointing at the same page + * tables, the result is independent of the source. + * + * Implemented only where CONFIG_ARCH_HAVE_ADDRENV_FORK is selected. + * + * Input Parameters: + * src - The address environment to be duplicated. + * dest - The location to receive the duplicate. It is wiped by this + * function before anything is allocated into it. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. -ENOMEM is + * returned if there are not enough free pages to hold the copy, in which + * case nothing is left allocated. + * + ****************************************************************************/ + +#ifdef CONFIG_ARCH_HAVE_ADDRENV_FORK +int up_addrenv_fork(FAR const arch_addrenv_t *src, + FAR arch_addrenv_t *dest); +#endif + /**************************************************************************** * Name: up_addrenv_attach * diff --git a/include/nuttx/fork.h b/include/nuttx/fork.h new file mode 100644 index 0000000000000..da44bf3483b44 --- /dev/null +++ b/include/nuttx/fork.h @@ -0,0 +1,53 @@ +/**************************************************************************** + * include/nuttx/fork.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_FORK_H +#define __INCLUDE_NUTTX_FORK_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Which primitive a clone of the calling task implements. Passed to + * nxtask_setup_fork(), which is where the memory semantics are decided. + * Macros rather than an enumeration: the architecture entry points are + * assembly and load them into a register. + * + * FORK_TYPE_TASK task_fork(): shares memory, private stack copy, both + * run. Not POSIX. + * FORK_TYPE_VFORK vfork(): shares memory, parent suspended until the + * child _exit()s or exec()s. + * FORK_TYPE_FORK fork(): child gets its own copy of the parent's memory + * at the same virtual addresses, both run. + */ + +#define FORK_TYPE_TASK 0 +#define FORK_TYPE_VFORK 1 +#define FORK_TYPE_FORK 2 + +#endif /* __INCLUDE_NUTTX_FORK_H */ diff --git a/include/nuttx/sched.h b/include/nuttx/sched.h index 76630d3f10110..17b39e7bf9d79 100644 --- a/include/nuttx/sched.h +++ b/include/nuttx/sched.h @@ -420,6 +420,23 @@ struct task_join_s pthread_addr_t exit_value; /* Returned data */ }; +/* struct vfork_s ***********************************************************/ + +/* The rendezvous between a suspended vfork() parent and its child. This + * lives in the frame of nxtask_start_vfork() on the parent's stack: the + * parent is blocked in that frame for the whole lifetime of the child, so + * the storage is alive exactly as long as it is needed, and no allocation + * is required on a path that must not fail. + */ + +#ifdef CONFIG_ARCH_HAVE_VFORK +struct vfork_s +{ + sem_t sem; /* Posted when the child is done */ + bool released; /* Guards against a second post */ +}; +#endif + /* struct task_group_s ******************************************************/ /* All threads created by pthread_create belong in the same task group (along @@ -650,6 +667,14 @@ struct tcb_s /* after the frame has been */ /* removed from the stack. */ + /* vfork() Support ********************************************************/ + +#ifdef CONFIG_ARCH_HAVE_VFORK + FAR struct vfork_s *vfork_rel; /* Non-NULL in a vfork() child: */ + /* the suspended parent to release */ + /* when this task is torn down. */ +#endif + /* External Module Support ************************************************/ #ifdef CONFIG_PIC @@ -1134,34 +1159,54 @@ void nxtask_startup(main_t entrypt, int argc, FAR char *argv[]); #endif /**************************************************************************** - * Internal fork support. The overall sequence is: - * - * 1) User code calls fork(). fork() is provided in architecture-specific - * code. - * 2) fork()and calls nxtask_setup_fork(). + * Internal support for the three cloning primitives -- task_fork(), vfork() + * and fork(). See include/nuttx/fork.h for what distinguishes them; the + * sequence below is common to all three, and `type' is one of the + * FORK_TYPE_* constants defined there. + * + * 1) User code calls task_fork(), vfork() or fork(). Each is a libc wrapper + * around up_task_fork(), up_vfork() or up_fork() respectively, which are + * provided in architecture-specific code. + * 2) The architecture-specific code snapshots the caller's registers 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 + * - Establishing the child's address environment: joined to the parent's + * for task_fork() and vfork(), duplicated from it for fork() + * - Allocating the stack, or inheriting the parent's for fork() * - 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: + * 4) The architecture-specific code provides any additional operating + * context: * - Initialize special values in any CPU registers that were not * already configured by up_initial_state() - * 5) fork() then calls nxtask_start_fork() + * - Relocate the copied stack, unless the child shares the parent's + * 5) It then calls nxtask_start_fork(), or nxtask_start_vfork() which + * additionally suspends the caller. * 6) nxtask_start_fork() then executes the child thread. * * nxtask_abort_fork() may be called if an error occurs between * steps 3 and 6. * + * nxtask_vfork_resume() releases a suspended vfork() parent. It is called + * from nxsched_release_tcb(), the last point in the child's life -- by which + * time the child is off the ready-to-run list and an exec()ing child has + * already handed its pid to the program it loaded. + * ****************************************************************************/ -FAR struct tcb_s *nxtask_setup_fork(start_t retaddr); -pid_t nxtask_start_fork(FAR struct tcb_s *child); +FAR struct tcb_s *nxtask_setup_fork(start_t retaddr, int type); +pid_t nxtask_start_fork(FAR struct tcb_s *child, int type); void nxtask_abort_fork(FAR struct tcb_s *child, int errcode); +#ifdef CONFIG_ARCH_HAVE_VFORK +pid_t nxtask_start_vfork(FAR struct tcb_s *child); +void nxtask_vfork_resume(FAR struct tcb_s *child); +#endif + /**************************************************************************** * Name: nxtask_argvstr * diff --git a/include/sched.h b/include/sched.h index 9fa907af2f527..a30362f337feb 100644 --- a/include/sched.h +++ b/include/sched.h @@ -233,6 +233,16 @@ int task_create_with_stack(FAR const char *name, int priority, int task_delete(pid_t pid); int task_restart(pid_t pid); +/* Clone the calling task: the child shares the parent's memory, runs on a + * private copy of the parent's stack, and runs concurrently. Returns twice, + * like fork(). Not POSIX; new code should prefer pthread_create() or + * posix_spawn(). + */ + +#ifdef CONFIG_ARCH_HAVE_TASK_FORK +pid_t task_fork(void); +#endif + int task_setcancelstate(int state, FAR int *oldstate); int task_setcanceltype(int type, FAR int *oldtype); void task_testcancel(void); diff --git a/include/sys/syscall_lookup.h b/include/sys/syscall_lookup.h index 7a1f37284c85e..5f7b338e6dabe 100644 --- a/include/sys/syscall_lookup.h +++ b/include/sys/syscall_lookup.h @@ -112,6 +112,14 @@ SYSCALL_LOOKUP(nxsem_wait_slow, 1) /* The following can be individually enabled */ +#ifdef CONFIG_ARCH_HAVE_TASK_FORK + SYSCALL_LOOKUP(up_task_fork, 0) +#endif + +#ifdef CONFIG_ARCH_HAVE_VFORK + SYSCALL_LOOKUP(up_vfork, 0) +#endif + #ifdef CONFIG_ARCH_HAVE_FORK SYSCALL_LOOKUP(up_fork, 0) #endif diff --git a/include/unistd.h b/include/unistd.h index 63ded5444ac11..a2ea8773f392e 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -347,8 +347,17 @@ extern "C" /* Task Control Interfaces */ +/* fork() is declared only where POSIX fork() semantics can be provided, so + * that calling it elsewhere is a build error rather than a silent change of + * meaning. See CONFIG_FORK_IS_TASK_FORK, and task_fork() in sched.h. + */ + +#if defined(CONFIG_ARCH_HAVE_FORK) || defined(CONFIG_FORK_IS_TASK_FORK) pid_t fork(void); +#endif +#ifdef CONFIG_ARCH_HAVE_VFORK pid_t vfork(void); +#endif pid_t getpid(void); pid_t getpgid(pid_t pid); pid_t getpgrp(void); diff --git a/libs/libbuiltin/libgcc/gcov.c b/libs/libbuiltin/libgcc/gcov.c index c73aa620b2301..390d075d456ed 100644 --- a/libs/libbuiltin/libgcc/gcov.c +++ b/libs/libbuiltin/libgcc/gcov.c @@ -468,10 +468,16 @@ void __gcov_execle(void) { } +/* GCC redirects fork() in instrumented code to __gcov_fork(), so this is + * reachable only where unistd.h declares fork() at all. + */ + +#if defined(CONFIG_ARCH_HAVE_FORK) || defined(CONFIG_FORK_IS_TASK_FORK) pid_t __gcov_fork(void) { return fork(); } +#endif void __gcov_dump(void) { diff --git a/libs/libc/libc.csv b/libs/libc/libc.csv index 4600f5329f96a..2f4ae6faa4109 100644 --- a/libs/libc/libc.csv +++ b/libs/libc/libc.csv @@ -70,7 +70,7 @@ "flockfile","stdio.h","!defined(CONFIG_FILE_STREAM)","void","FAR FILE *" "fnmatch","fnmatch.h","","int","FAR const char *","FAR const char *","int" "fopen","stdio.h","defined(CONFIG_FILE_STREAM)","FAR FILE *","FAR const char *","FAR const char *" -"fork","unistd.h","!defined(CONFIG_BUILD_KERNEL) && defined(CONFIG_ARCH_HAVE_FORK)","pid_t" +"fork","unistd.h","!defined(CONFIG_BUILD_KERNEL) && (defined(CONFIG_ARCH_HAVE_FORK) || defined(CONFIG_FORK_IS_TASK_FORK))","pid_t" "fprintf","stdio.h","defined(CONFIG_FILE_STREAM)","int","FAR FILE *","FAR const IPTR char *","..." "fputc","stdio.h","defined(CONFIG_FILE_STREAM)","int","int","FAR FILE *" "fputs","stdio.h","defined(CONFIG_FILE_STREAM)","int","FAR const IPTR char *","FAR FILE *" @@ -326,6 +326,7 @@ "swprintf","wchar.h","","int","FAR wchar_t *","size_t","FAR const wchar_t *","..." "sysconf","unistd.h","","long","int" "syslog","syslog.h","","void","int","FAR const IPTR char *","..." +"task_fork","sched.h","!defined(CONFIG_BUILD_KERNEL) && defined(CONFIG_ARCH_HAVE_TASK_FORK)","pid_t" "task_testcancel","sched.h","defined(CONFIG_CANCELLATION_POINTS)","void" "task_tls_alloc","nuttx/tls.h","!defined(CONFIG_BUILD_KERNEL) && CONFIG_TLS_TASK_NELEM > 0","int","tls_dtor_t" "task_tls_get_value","nuttx/tls.h","CONFIG_TLS_TASK_NELEM > 0","uintptr_t","int" @@ -348,6 +349,7 @@ "usleep","unistd.h","","int","useconds_t" "vasprintf","stdio.h","","int","FAR char **","FAR const IPTR char *","va_list" "versionsort","dirent.h","","int","FAR const struct dirent **","FAR const struct dirent **" +"vfork","unistd.h","!defined(CONFIG_BUILD_KERNEL) && defined(CONFIG_ARCH_HAVE_VFORK)","pid_t" "vfprintf","stdio.h","defined(CONFIG_FILE_STREAM)","int","FAR FILE *","FAR const IPTR char *","va_list" "vprintf","stdio.h","","int","FAR const IPTR char *","va_list" "vscanf","stdio.h","defined(CONFIG_FILE_STREAM)","int","FAR const IPTR char *","va_list" diff --git a/libs/libc/unistd/CMakeLists.txt b/libs/libc/unistd/CMakeLists.txt index eb4ca8e5e937d..2e074fd65d99f 100644 --- a/libs/libc/unistd/CMakeLists.txt +++ b/libs/libc/unistd/CMakeLists.txt @@ -102,7 +102,9 @@ if(NOT CONFIG_DISABLE_MOUNTPOINTS) list(APPEND SRCS lib_truncate.c lib_posix_fallocate.c) endif() -if(CONFIG_ARCH_HAVE_FORK) +if(CONFIG_ARCH_HAVE_TASK_FORK + OR CONFIG_ARCH_HAVE_VFORK + OR CONFIG_ARCH_HAVE_FORK) list(APPEND SRCS lib_fork.c) endif() diff --git a/libs/libc/unistd/Make.defs b/libs/libc/unistd/Make.defs index e3e5ed3a2148d..8cc5e26f57d5b 100644 --- a/libs/libc/unistd/Make.defs +++ b/libs/libc/unistd/Make.defs @@ -53,7 +53,7 @@ ifneq ($(CONFIG_DISABLE_MOUNTPOINTS),y) CSRCS += lib_truncate.c lib_posix_fallocate.c endif -ifeq ($(CONFIG_ARCH_HAVE_FORK),y) +ifneq ($(CONFIG_ARCH_HAVE_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),) CSRCS += lib_fork.c endif diff --git a/libs/libc/unistd/lib_fork.c b/libs/libc/unistd/lib_fork.c index 53c2bfe0e9b2c..ebc9e2ea8b784 100644 --- a/libs/libc/unistd/lib_fork.c +++ b/libs/libc/unistd/lib_fork.c @@ -28,13 +28,15 @@ #include #include +#include #include #include #include #include #include -#if defined(CONFIG_ARCH_HAVE_FORK) +#if defined(CONFIG_ARCH_HAVE_TASK_FORK) || defined(CONFIG_ARCH_HAVE_VFORK) || \ + defined(CONFIG_ARCH_HAVE_FORK) /**************************************************************************** * Private Functions @@ -137,27 +139,30 @@ static void atfork_parent(void) ****************************************************************************/ /**************************************************************************** - * Name: fork + * Name: task_fork * * Description: - * The fork() function is a wrapper of up_fork() syscall + * Clone the calling task: the child shares the parent's memory, runs on + * a private copy of the parent's stack, and runs concurrently. Not POSIX. + * Wrapper of the up_task_fork() syscall. * * 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. + * Upon successful completion, task_fork() returns 0 to the child and + * returns the process ID of the child to the parent. Otherwise, -1 is + * returned to the parent, no child is created, and errno is set to + * indicate the error. * ****************************************************************************/ -pid_t fork(void) +#ifdef CONFIG_ARCH_HAVE_TASK_FORK +pid_t task_fork(void) { pid_t pid; #ifdef CONFIG_PTHREAD_ATFORK atfork_prepare(); #endif - pid = up_fork(); + pid = up_task_fork(); #ifdef CONFIG_PTHREAD_ATFORK if (pid == 0) @@ -172,20 +177,23 @@ pid_t fork(void) return pid; } - -#if defined(CONFIG_SCHED_WAITPID) - -/**************************************************************************** - * Public Functions - ****************************************************************************/ +#endif /* CONFIG_ARCH_HAVE_TASK_FORK */ /**************************************************************************** * Name: vfork * * Description: - * The vfork() function is implemented based on fork() function, on - * vfork(), the parent task need to wait until the child task is performing - * exec or running finished. + * The vfork() function is equivalent to fork(), except that the behavior + * is undefined if the process created by vfork() either modifies any data + * other than a variable of type pid_t used to store the return value from + * vfork(), or returns from the function in which vfork() was called, or + * calls any other function before successfully calling _exit() or one of + * the exec family of functions. + * + * The child shares the parent's memory and the parent is suspended until + * the child _exit()s or exec()s. The suspension lives in the kernel, so + * vfork() does not depend on CONFIG_SCHED_WAITPID. Wrapper of the + * up_vfork() syscall. * * Returned Value: * Upon successful completion, vfork() returns 0 to the child process and @@ -195,16 +203,15 @@ pid_t fork(void) * ****************************************************************************/ +#ifdef CONFIG_ARCH_HAVE_VFORK pid_t vfork(void) { - int status = 0; - int ret; pid_t pid; #ifdef CONFIG_PTHREAD_ATFORK atfork_prepare(); #endif - pid = up_fork(); + pid = up_vfork(); #ifdef CONFIG_PTHREAD_ATFORK if (pid == 0) @@ -217,22 +224,61 @@ pid_t vfork(void) } #endif - if (pid != 0) - { - /* we are in parent task, and we need to wait the child task - * until running finished or performing exec - */ + return pid; +} +#endif /* CONFIG_ARCH_HAVE_VFORK */ - ret = waitpid(pid, &status, WNOWAIT); - if (ret < 0) - { - serr("ERROR: waitpid failed: %d\n", get_errno()); - } +/**************************************************************************** + * Name: fork + * + * Description: + * POSIX fork(). The child receives its own copy of the parent's memory, + * at the same virtual addresses. It may modify anything, call anything, + * return from the function that called fork(), and it runs concurrently + * with the parent. None of vfork()'s restrictions apply. + * + * Provided only where CONFIG_ARCH_HAVE_FORK is selected; elsewhere fork() + * is not declared at all, so calling it is a build error. + * CONFIG_FORK_IS_TASK_FORK aliases it to task_fork() for legacy code. + * Wrapper of the up_fork() syscall. + * + * 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. + * + ****************************************************************************/ + +#if defined(CONFIG_ARCH_HAVE_FORK) +pid_t fork(void) +{ + pid_t pid; + +#ifdef CONFIG_PTHREAD_ATFORK + atfork_prepare(); +#endif + pid = up_fork(); + +#ifdef CONFIG_PTHREAD_ATFORK + if (pid == 0) + { + atfork_child(); + } + else + { + atfork_parent(); } +#endif return pid; } +#elif defined(CONFIG_FORK_IS_TASK_FORK) +pid_t fork(void) +{ + return task_fork(); +} +#endif -#endif /* CONFIG_SCHED_WAITPID */ - -#endif /* CONFIG_ARCH_HAVE_FORK */ +#endif /* CONFIG_ARCH_HAVE_TASK_FORK || CONFIG_ARCH_HAVE_VFORK || + * CONFIG_ARCH_HAVE_FORK */ diff --git a/sched/addrenv/addrenv.c b/sched/addrenv/addrenv.c index 9936a105c774c..32e6cba247c67 100644 --- a/sched/addrenv/addrenv.c +++ b/sched/addrenv/addrenv.c @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -292,6 +293,70 @@ int addrenv_join(FAR struct tcb_s *ptcb, FAR struct tcb_s *tcb) return OK; } +#ifdef CONFIG_ARCH_HAVE_FORK +/**************************************************************************** + * Name: addrenv_fork + * + * Description: + * Duplicate the parent process's address environment for a POSIX fork() + * child, and attach the duplicate to the child. + * + * This is the counterpart of addrenv_join(): where join gives the child + * the parent's memory, fork gives it a copy -- its own pages, holding a + * snapshot of the parent's contents, mapped at the same virtual addresses. + * Mapping at the same addresses is what lets the copy be exact: every + * pointer the parent held into its own memory remains valid in the child, + * including the pointers inside the copied heap's own metadata. + * + * The copy is eager -- there is no copy-on-write, because NuttX has no + * demand paging to build it on -- so forking a large process needs as much + * free memory as the process occupies, and fails with -ENOMEM if that is + * not available. That is the nature of the primitive on this class of + * system, not a defect of this implementation; spawn-heavy code should + * prefer posix_spawn() or vfork(). + * + * Input Parameters: + * ptcb - The tcb of the parent process + * tcb - The tcb of the child process + * + * Returned Value: + * This is a NuttX internal function so it follows the convention that + * 0 (OK) is returned on success and a negated errno is returned on + * failure. + * + ****************************************************************************/ + +int addrenv_fork(FAR struct tcb_s *ptcb, FAR struct tcb_s *tcb) +{ + FAR struct addrenv_s *addrenv; + int ret; + + DEBUGASSERT(ptcb->addrenv_own != NULL); + + addrenv = addrenv_allocate(); + if (addrenv == NULL) + { + return -ENOMEM; + } + + /* Duplicate the parent's regions into freshly allocated pages, mapped at + * the same virtual addresses. + */ + + ret = up_addrenv_fork(&ptcb->addrenv_own->addrenv, &addrenv->addrenv); + if (ret < 0) + { + berr("ERROR: up_addrenv_fork failed: %d\n", ret); + addrenv_drop(addrenv, false); + return ret; + } + + /* Hand the reference taken by addrenv_allocate() to the child */ + + return addrenv_attach(tcb, addrenv); +} +#endif /* CONFIG_ARCH_HAVE_FORK */ + /**************************************************************************** * Name: addrenv_leave * diff --git a/sched/sched/sched_releasetcb.c b/sched/sched/sched_releasetcb.c index 96b3e736e79cd..5e66f28d1a835 100644 --- a/sched/sched/sched_releasetcb.c +++ b/sched/sched/sched_releasetcb.c @@ -174,6 +174,15 @@ int nxsched_release_tcb(FAR struct tcb_s *tcb, uint8_t ttype) nxtask_joindestroy(tcb); #endif +#ifdef CONFIG_ARCH_HAVE_VFORK + /* Release a suspended vfork() parent here, the last point in the + * child's life: exec_swap() has already handed its pid to any + * program it loaded. + */ + + nxtask_vfork_resume(tcb); +#endif + /* And, finally, release the TCB itself */ if (tcb->flags & TCB_FLAG_FREE_TCB) diff --git a/sched/task/CMakeLists.txt b/sched/task/CMakeLists.txt index fdc19fdc589b1..bcbb24fac7a91 100644 --- a/sched/task/CMakeLists.txt +++ b/sched/task/CMakeLists.txt @@ -46,7 +46,9 @@ if(CONFIG_SCHED_HAVE_PARENT) list(APPEND SRCS task_getppid.c task_reparent.c) endif() -if(CONFIG_ARCH_HAVE_FORK) +if(CONFIG_ARCH_HAVE_TASK_FORK + OR CONFIG_ARCH_HAVE_VFORK + OR CONFIG_ARCH_HAVE_FORK) list(APPEND SRCS task_fork.c) endif() diff --git a/sched/task/Make.defs b/sched/task/Make.defs index 1fd24403b51ae..283e30f8dfd2d 100644 --- a/sched/task/Make.defs +++ b/sched/task/Make.defs @@ -30,7 +30,7 @@ ifeq ($(CONFIG_SCHED_HAVE_PARENT),y) CSRCS += task_getppid.c task_reparent.c endif -ifeq ($(CONFIG_ARCH_HAVE_FORK),y) +ifneq ($(CONFIG_ARCH_HAVE_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),) CSRCS += task_fork.c endif diff --git a/sched/task/task_exit.c b/sched/task/task_exit.c index e79c4f812504f..ab898538ad6ec 100644 --- a/sched/task/task_exit.c +++ b/sched/task/task_exit.c @@ -157,5 +157,16 @@ int nxtask_exit(void) rtcb->lockcount--; +#ifndef CONFIG_SMP + /* Publish anything woken while the TCB was being released. lockcount was + * raised directly rather than through sched_lock(), so the matching + * decrement above does not merge g_pendingtasks the way sched_unlock() + * would, and a vfork() parent released by nxsched_release_tcb() would be + * stranded there. SMP has no pending list to merge. + */ + + nxsched_merge_pending(); +#endif + return ret; } diff --git a/sched/task/task_fork.c b/sched/task/task_fork.c index 5e2b133744264..53c1bb6d8a351 100644 --- a/sched/task/task_fork.c +++ b/sched/task/task_fork.c @@ -34,7 +34,10 @@ #include #include +#include +#include #include +#include #include "sched/sched.h" #include "environ/environ.h" @@ -42,9 +45,112 @@ #include "task/task.h" #include "tls/tls.h" -/* fork() requires architecture-specific support as well as waipid(). */ +/* This file is the common core of task_fork(), vfork() and fork(); it is + * built if the architecture can provide any one of them. + */ -#ifdef CONFIG_ARCH_HAVE_FORK +#if defined(CONFIG_ARCH_HAVE_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 @@ -54,37 +160,22 @@ * 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. + * 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 - Return address - * argsize - Location to return the argument size + * 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 @@ -93,7 +184,7 @@ * ****************************************************************************/ -FAR struct tcb_s *nxtask_setup_fork(start_t retaddr) +FAR struct tcb_s *nxtask_setup_fork(start_t retaddr, int type) { FAR struct tcb_s *ptcb = this_task(); FAR struct tcb_s *parent; @@ -105,6 +196,8 @@ FAR struct tcb_s *nxtask_setup_fork(start_t retaddr) 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) */ @@ -160,16 +253,80 @@ FAR struct tcb_s *nxtask_setup_fork(start_t retaddr) } #if defined(CONFIG_ARCH_ADDRENV) - /* Join the parent address environment */ - if (ttype != TCB_FLAG_TTYPE_KERNEL) { - ret = addrenv_join(parent, child); + 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 */ @@ -193,12 +350,27 @@ FAR struct tcb_s *nxtask_setup_fork(start_t retaddr) argv = nxsched_get_stackargs(parent); nxtask_setup_name(child, argv[0]); - /* Allocate the stack for the TCB */ + /* 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; - 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); + } - ret = up_create_stack(child, stack_size, ttype); if (ret < OK) { goto errout_with_tcb; @@ -235,20 +407,35 @@ FAR struct tcb_s *nxtask_setup_fork(start_t retaddr) goto errout_with_tcb; } - /* Setup thread local storage */ - - ret = tls_dup_info(child, parent); - if (ret < OK) + /* 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) { - goto errout_with_tcb; + fork_inherit_tls(child); } - - /* Setup to pass parameters to the new task */ - - ret = nxtask_setup_stackargs(child, argv[0], &argv[1]); - if (ret < OK) + else +#endif { - goto errout_with_tcb; + 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 */ @@ -258,6 +445,18 @@ FAR struct tcb_s *nxtask_setup_fork(start_t retaddr) 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); @@ -268,58 +467,51 @@ FAR struct tcb_s *nxtask_setup_fork(start_t retaddr) * 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. + * 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 that created by - * nxtask_setup_fork() method - * wait_child - whether need to wait until the child is running finished + * child - The tcb_s struct instance created by nxtask_setup_fork() + * type - One of the FORK_TYPE_* constants * * 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. + * The process ID of the child, or ERROR on failure. * ****************************************************************************/ -pid_t nxtask_start_fork(FAR struct tcb_s *child) +pid_t nxtask_start_fork(FAR struct tcb_s *child, int type) { pid_t pid; - sinfo("Starting Child TCB=%p\n", child); + 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); @@ -327,6 +519,99 @@ pid_t nxtask_start_fork(FAR struct tcb_s *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 * @@ -340,6 +625,20 @@ pid_t nxtask_start_fork(FAR struct tcb_s *child) 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()); @@ -350,4 +649,5 @@ void nxtask_abort_fork(FAR struct tcb_s *child, int errcode) set_errno(errcode); } -#endif /* CONFIG_ARCH_HAVE_FORK */ +#endif /* CONFIG_ARCH_HAVE_TASK_FORK || CONFIG_ARCH_HAVE_VFORK || + * CONFIG_ARCH_HAVE_FORK */ diff --git a/syscall/syscall.csv b/syscall/syscall.csv index 4622a0910b817..ad5a08d97d17f 100644 --- a/syscall/syscall.csv +++ b/syscall/syscall.csv @@ -199,6 +199,8 @@ "unlink","unistd.h","!defined(CONFIG_DISABLE_MOUNTPOINT)","int","FAR const char *" "unsetenv","stdlib.h","!defined(CONFIG_DISABLE_ENVIRON)","int","FAR const char *" "up_fork","nuttx/arch.h","defined(CONFIG_ARCH_HAVE_FORK)","pid_t" +"up_task_fork","nuttx/arch.h","defined(CONFIG_ARCH_HAVE_TASK_FORK)","pid_t" +"up_vfork","nuttx/arch.h","defined(CONFIG_ARCH_HAVE_VFORK)","pid_t" "utimens","sys/stat.h","","int","FAR const char *","const struct timespec [2]|FAR const struct timespec *" "wait","sys/wait.h","defined(CONFIG_SCHED_WAITPID) && defined(CONFIG_SCHED_HAVE_PARENT)","pid_t","FAR int *" "waitid","sys/wait.h","defined(CONFIG_SCHED_WAITPID) && defined(CONFIG_SCHED_HAVE_PARENT)","int","idtype_t","id_t"," FAR siginfo_t *","int"