mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
Merge 9b787d42b2 into 06ca196d56
This commit is contained in:
commit
c4e4afd2b8
57 changed files with 2054 additions and 442 deletions
222
Documentation/guides/fork_vfork_migration.rst
Normal file
222
Documentation/guides/fork_vfork_migration.rst
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
=========================================
|
||||
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 was not ``fork()``. It was ``vfork()``-with-a-private-stack published
|
||||
under ``fork()``'s name. The history says so plainly: the ``fork()`` this
|
||||
replaces was 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`` -- no address environment needed
|
||||
* - ``task_fork()``
|
||||
- child shares memory, private stack copy
|
||||
- runs concurrently
|
||||
- ``CONFIG_TASK_FORK`` -- on by default exactly where ``fork()`` existed
|
||||
before
|
||||
|
||||
``task_fork()`` is the old behaviour under an honest name. Nothing was lost.
|
||||
|
||||
.. note::
|
||||
|
||||
``fork()`` is provided only where the architecture implements
|
||||
``up_addrenv_fork()`` and therefore selects
|
||||
``CONFIG_ARCH_HAVE_ADDRENV_FORK``; it becomes available architecture by
|
||||
architecture as that hook lands. Check ``CONFIG_ARCH_HAVE_FORK`` in your
|
||||
own configuration rather than assuming either way. Where it is unset,
|
||||
``vfork()`` and ``task_fork()`` are what the 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.
|
||||
|
||||
**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
|
||||
unlike ``fork()`` it needs no duplicable address environment.
|
||||
|
||||
*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 <sched.h>
|
||||
|
||||
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_TASK_FORK``
|
||||
Provide ``task_fork()``. Defaults to ``y`` wherever
|
||||
``ARCH_HAVE_TASK_FORK`` is selected, so no configuration that had ``fork()``
|
||||
loses the primitive. Turn it off to leave a NuttX extension out of a build
|
||||
that does not call it; the architecture entry point, the system call and the
|
||||
libc stub go with it. ``vfork()`` and ``fork()`` are unaffected.
|
||||
|
||||
``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 <arch>`` 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
|
||||
==========
|
||||
|
||||
``fork()`` **is gained one architecture at a time.** The generic machinery is
|
||||
complete -- ``addrenv_fork()``, the ``up_addrenv_fork()`` hook, the syscall, the
|
||||
libc wrapper and the ``ostest`` case -- so an architecture provides ``fork()``
|
||||
by implementing ``up_addrenv_fork()`` and selecting
|
||||
``CONFIG_ARCH_HAVE_ADDRENV_FORK``, with no further generic work.
|
||||
|
||||
**A windowed ABI needs its stack rebased, not just copied.** On Xtensa,
|
||||
giving a child a relocated copy of the parent's stack takes more than the copy:
|
||||
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 belongs 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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,47 @@ 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.
|
||||
See :doc:`/guides/fork_vfork_migration` for how to move code that
|
||||
relied on the previous behaviour.
|
||||
|
||||
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 +400,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 +421,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_TASK_FORK`` is enabled, which defaults to
|
||||
the set of configurations that had ``fork()`` before the split, and can
|
||||
be turned off. 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 +526,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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
+--------------------------------+---------+
|
||||
|
|
|
|||
95
arch/Kconfig
95
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,87 @@ config ARCH_HAVE_CPUID_MAPPING
|
|||
default n
|
||||
depends on ARCH_HAVE_MULTICPU
|
||||
|
||||
config ARCH_HAVE_FORK
|
||||
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 TASK_FORK
|
||||
bool "Provide task_fork()"
|
||||
default y
|
||||
depends on ARCH_HAVE_TASK_FORK
|
||||
---help---
|
||||
Provide task_fork(), which clones the calling task: the child shares
|
||||
the parent's .data/.bss/heap, runs on a private copy of the parent's
|
||||
stack, and runs concurrently with the parent. This is the historical
|
||||
NuttX fork() behaviour under its own name. It is not POSIX, and it is
|
||||
neither fork() nor vfork().
|
||||
|
||||
Disable this to leave it out where nothing calls it. The saving is
|
||||
small -- the architecture's entry point, the system call and the libc
|
||||
stub -- but the primitive is a NuttX extension, so a configuration
|
||||
that does not want it need not carry it. vfork() and fork() are
|
||||
unaffected; each is selected on its own.
|
||||
|
||||
config FORK_IS_TASK_FORK
|
||||
bool "Provide fork() as an alias for task_fork() (legacy)"
|
||||
default n
|
||||
depends on 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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
||||
newtop = (uint32_t)child->stack_base_ptr +
|
||||
child->adj_stack_size;
|
||||
|
||||
newsp = newtop - stackutil;
|
||||
|
||||
/* Move the register context to newtop. */
|
||||
|
||||
memcpy((void *)(newsp - XCPTCONTEXT_SIZE),
|
||||
child->xcp.regs, XCPTCONTEXT_SIZE);
|
||||
|
||||
child->xcp.regs = (void *)(newsp - XCPTCONTEXT_SIZE);
|
||||
|
||||
memcpy((void *)newsp, (const void *)oldsp, stackutil);
|
||||
|
||||
/* Was there a frame pointer in place before? */
|
||||
|
||||
if (context->fp >= oldsp && context->fp < stacktop)
|
||||
if (child->stack_base_ptr == parent->stack_base_ptr)
|
||||
{
|
||||
uint32_t frameutil = stacktop - context->fp;
|
||||
newfp = newtop - frameutil;
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
newsp = oldsp;
|
||||
newfp = context->fp;
|
||||
}
|
||||
else
|
||||
{
|
||||
newfp = context->fp;
|
||||
}
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
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);
|
||||
newtop = (uint32_t)child->stack_base_ptr +
|
||||
child->adj_stack_size;
|
||||
|
||||
newsp = newtop - stackutil;
|
||||
|
||||
/* Move the register context to newtop. */
|
||||
|
||||
memcpy((void *)(newsp - XCPTCONTEXT_SIZE),
|
||||
child->xcp.regs, XCPTCONTEXT_SIZE);
|
||||
|
||||
child->xcp.regs = (void *)(newsp - XCPTCONTEXT_SIZE);
|
||||
|
||||
memcpy((void *)newsp, (const void *)oldsp, stackutil);
|
||||
|
||||
/* Was there a frame pointer in place before? */
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
#include <nuttx/fork.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
|
|
|
|||
|
|
@ -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_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
|
||||
|
|
|
|||
|
|
@ -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_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_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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
#include <nuttx/fork.h>
|
||||
#include <arch/irq.h>
|
||||
|
||||
/****************************************************************************
|
||||
|
|
|
|||
|
|
@ -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_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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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_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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
#include <nuttx/fork.h>
|
||||
#include <arch/mips32/irq.h>
|
||||
|
||||
/****************************************************************************
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ if(CONFIG_STACK_COLORATION)
|
|||
list(APPEND SRCS riscv_checkstack.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ARCH_HAVE_FORK)
|
||||
if(CONFIG_TASK_FORK
|
||||
OR CONFIG_ARCH_HAVE_VFORK
|
||||
OR CONFIG_ARCH_HAVE_FORK)
|
||||
list(APPEND SRCS fork.S riscv_fork.c)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ ifeq ($(CONFIG_STACK_COLORATION),y)
|
|||
CMN_CSRCS += riscv_checkstack.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_ARCH_HAVE_FORK),y)
|
||||
ifneq ($(CONFIG_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),)
|
||||
CMN_ASRCS += fork.S
|
||||
CMN_CSRCS += riscv_fork.c
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -39,59 +39,95 @@
|
|||
|
||||
.file "fork.S"
|
||||
.globl riscv_fork
|
||||
#ifdef CONFIG_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_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
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@
|
|||
|
||||
#include "sched/sched.h"
|
||||
|
||||
#ifdef CONFIG_ARCH_HAVE_FORK
|
||||
#if defined(CONFIG_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_TASK_FORK || CONFIG_ARCH_HAVE_VFORK ||
|
||||
* CONFIG_ARCH_HAVE_FORK */
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
#include <nuttx/fork.h>
|
||||
#include <arch/irq.h>
|
||||
|
||||
#include "riscv_internal.h"
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ ifeq ($(CONFIG_SCHED_BACKTRACE),y)
|
|||
CSRCS += sim_backtrace.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_ARCH_HAVE_FORK),y)
|
||||
ifneq ($(CONFIG_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),)
|
||||
CSRCS += sim_fork.c
|
||||
endif
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,9 @@ if(CONFIG_SCHED_BACKTRACE)
|
|||
list(APPEND SRCS sim_backtrace.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ARCH_HAVE_FORK)
|
||||
if(CONFIG_TASK_FORK
|
||||
OR CONFIG_ARCH_HAVE_VFORK
|
||||
OR CONFIG_ARCH_HAVE_FORK)
|
||||
list(APPEND SRCS sim_fork.c)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include <nuttx/debug.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
#include <nuttx/fork.h>
|
||||
#include <nuttx/sched.h>
|
||||
#include <nuttx/arch.h>
|
||||
#include <arch/irq.h>
|
||||
|
|
@ -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_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
|
||||
|
|
|
|||
|
|
@ -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_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_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
|
||||
|
|
|
|||
|
|
@ -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_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_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
|
||||
|
|
|
|||
|
|
@ -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_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_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 */
|
||||
|
|
|
|||
|
|
@ -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_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_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 */
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ set(SRCS
|
|||
x86_64_tcbinfo.c
|
||||
x86_64_tlb.c)
|
||||
|
||||
if(CONFIG_ARCH_HAVE_FORK)
|
||||
if(CONFIG_TASK_FORK
|
||||
OR CONFIG_ARCH_HAVE_VFORK
|
||||
OR CONFIG_ARCH_HAVE_FORK)
|
||||
list(APPEND SRCS x86_64_fork.c fork.S)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -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_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),)
|
||||
CMN_CSRCS += x86_64_fork.c
|
||||
CMN_ASRCS += fork.S
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -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_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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
#include <nuttx/fork.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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_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
|
||||
*
|
||||
|
|
|
|||
53
include/nuttx/fork.h
Normal file
53
include/nuttx/fork.h
Normal file
|
|
@ -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 <nuttx/config.h>
|
||||
|
||||
/****************************************************************************
|
||||
* 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 */
|
||||
|
|
@ -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:
|
||||
* 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 fork(). fork() is provided in architecture-specific
|
||||
* code.
|
||||
* 2) fork()and calls nxtask_setup_fork().
|
||||
* 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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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_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);
|
||||
|
|
|
|||
|
|
@ -116,6 +116,14 @@ SYSCALL_LOOKUP(nxsem_wait_slow, 1)
|
|||
|
||||
/* The following can be individually enabled */
|
||||
|
||||
#ifdef CONFIG_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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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_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"
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 3.
|
|
|
@ -104,7 +104,9 @@ if(NOT CONFIG_DISABLE_MOUNTPOINTS)
|
|||
list(APPEND SRCS lib_truncate.c lib_posix_fallocate.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ARCH_HAVE_FORK)
|
||||
if(CONFIG_TASK_FORK
|
||||
OR CONFIG_ARCH_HAVE_VFORK
|
||||
OR CONFIG_ARCH_HAVE_FORK)
|
||||
list(APPEND SRCS lib_fork.c)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ ifneq ($(CONFIG_DISABLE_MOUNTPOINTS),y)
|
|||
CSRCS += lib_truncate.c lib_posix_fallocate.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_ARCH_HAVE_FORK),y)
|
||||
ifneq ($(CONFIG_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),)
|
||||
CSRCS += lib_fork.c
|
||||
endif
|
||||
|
||||
|
|
|
|||
|
|
@ -28,13 +28,15 @@
|
|||
#include <nuttx/arch.h>
|
||||
#include <nuttx/tls.h>
|
||||
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <errno.h>
|
||||
#include <nuttx/debug.h>
|
||||
|
||||
#if defined(CONFIG_ARCH_HAVE_FORK)
|
||||
#if defined(CONFIG_TASK_FORK) || defined(CONFIG_ARCH_HAVE_VFORK) || \
|
||||
defined(CONFIG_ARCH_HAVE_FORK)
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
|
|
@ -136,11 +138,109 @@ static void atfork_parent(void)
|
|||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: task_fork
|
||||
*
|
||||
* Description:
|
||||
* 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, 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_TASK_FORK
|
||||
pid_t task_fork(void)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
#ifdef CONFIG_PTHREAD_ATFORK
|
||||
atfork_prepare();
|
||||
#endif
|
||||
pid = up_task_fork();
|
||||
|
||||
#ifdef CONFIG_PTHREAD_ATFORK
|
||||
if (pid == 0)
|
||||
{
|
||||
atfork_child();
|
||||
}
|
||||
else
|
||||
{
|
||||
atfork_parent();
|
||||
}
|
||||
#endif
|
||||
|
||||
return pid;
|
||||
}
|
||||
#endif /* CONFIG_TASK_FORK */
|
||||
|
||||
/****************************************************************************
|
||||
* Name: vfork
|
||||
*
|
||||
* Description:
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_ARCH_HAVE_VFORK
|
||||
pid_t vfork(void)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
#ifdef CONFIG_PTHREAD_ATFORK
|
||||
atfork_prepare();
|
||||
#endif
|
||||
pid = up_vfork();
|
||||
|
||||
#ifdef CONFIG_PTHREAD_ATFORK
|
||||
if (pid == 0)
|
||||
{
|
||||
atfork_child();
|
||||
}
|
||||
else
|
||||
{
|
||||
atfork_parent();
|
||||
}
|
||||
#endif
|
||||
|
||||
return pid;
|
||||
}
|
||||
#endif /* CONFIG_ARCH_HAVE_VFORK */
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fork
|
||||
*
|
||||
* Description:
|
||||
* The fork() function is a wrapper of up_fork() syscall
|
||||
* 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
|
||||
|
|
@ -150,6 +250,7 @@ static void atfork_parent(void)
|
|||
*
|
||||
****************************************************************************/
|
||||
|
||||
#if defined(CONFIG_ARCH_HAVE_FORK)
|
||||
pid_t fork(void)
|
||||
{
|
||||
pid_t pid;
|
||||
|
|
@ -172,67 +273,12 @@ pid_t fork(void)
|
|||
|
||||
return pid;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SCHED_WAITPID)
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* 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.
|
||||
*
|
||||
* Returned Value:
|
||||
* Upon successful completion, vfork() returns 0 to the child process and
|
||||
* returns the process ID of the child process to the parent process.
|
||||
* Otherwise, -1 is returned to the parent, no child process is created,
|
||||
* and errno is set to indicate the error.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
pid_t vfork(void)
|
||||
#elif defined(CONFIG_FORK_IS_TASK_FORK)
|
||||
pid_t fork(void)
|
||||
{
|
||||
int status = 0;
|
||||
int ret;
|
||||
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
|
||||
|
||||
if (pid != 0)
|
||||
{
|
||||
/* we are in parent task, and we need to wait the child task
|
||||
* until running finished or performing exec
|
||||
*/
|
||||
|
||||
ret = waitpid(pid, &status, WNOWAIT);
|
||||
if (ret < 0)
|
||||
{
|
||||
serr("ERROR: waitpid failed: %d\n", get_errno());
|
||||
}
|
||||
}
|
||||
|
||||
return pid;
|
||||
return task_fork();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_SCHED_WAITPID */
|
||||
|
||||
#endif /* CONFIG_ARCH_HAVE_FORK */
|
||||
#endif /* CONFIG_TASK_FORK || CONFIG_ARCH_HAVE_VFORK ||
|
||||
* CONFIG_ARCH_HAVE_FORK */
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <nuttx/config.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <nuttx/debug.h>
|
||||
|
||||
#include <nuttx/addrenv.h>
|
||||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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_TASK_FORK
|
||||
OR CONFIG_ARCH_HAVE_VFORK
|
||||
OR CONFIG_ARCH_HAVE_FORK)
|
||||
list(APPEND SRCS task_fork.c)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -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_TASK_FORK)$(CONFIG_ARCH_HAVE_VFORK)$(CONFIG_ARCH_HAVE_FORK),)
|
||||
CSRCS += task_fork.c
|
||||
endif
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,10 @@
|
|||
#include <errno.h>
|
||||
#include <nuttx/debug.h>
|
||||
|
||||
#include <nuttx/fork.h>
|
||||
#include <nuttx/kmalloc.h>
|
||||
#include <nuttx/queue.h>
|
||||
#include <nuttx/semaphore.h>
|
||||
|
||||
#include "sched/sched.h"
|
||||
#include "environ/environ.h"
|
||||
|
|
@ -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_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.
|
||||
* 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.
|
||||
*
|
||||
* This function provides one step in the overall fork() sequence: It
|
||||
* Allocates and initializes the child task's TCB. The overall sequence
|
||||
* is:
|
||||
* Exactly two things depend on `type':
|
||||
*
|
||||
* 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.
|
||||
* - 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 */
|
||||
|
||||
stack_size = (uintptr_t)ptcb->stack_base_ptr -
|
||||
(uintptr_t)ptcb->stack_alloc_ptr + ptcb->adj_stack_size;
|
||||
#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_HAVE_FORK)
|
||||
if (type == FORK_TYPE_FORK && ttype != TCB_FLAG_TTYPE_KERNEL)
|
||||
{
|
||||
/* The child's copy of the parent's stack is already in place, at the
|
||||
* parent's address, courtesy of the duplication above.
|
||||
*/
|
||||
|
||||
fork_inherit_stack(parent, child);
|
||||
ret = OK;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
stack_size = (uintptr_t)ptcb->stack_base_ptr -
|
||||
(uintptr_t)ptcb->stack_alloc_ptr + ptcb->adj_stack_size;
|
||||
|
||||
ret = up_create_stack(child, stack_size, ttype);
|
||||
}
|
||||
|
||||
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 */
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
ret = tls_dup_info(child, parent);
|
||||
if (ret < OK)
|
||||
#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 @@ errout:
|
|||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* 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_TASK_FORK || CONFIG_ARCH_HAVE_VFORK ||
|
||||
* CONFIG_ARCH_HAVE_FORK */
|
||||
|
|
|
|||
|
|
@ -203,6 +203,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_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"
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 2.
|
Loading…
Add table
Add a link
Reference in a new issue