This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit b63dd8923854961a13b62905a76735431e61fb30
Author: Marco Casaroli <[email protected]>
AuthorDate: Sun Aug 9 15:41:55 2026 +0200

    arch/arm64: Implement up_addrenv_fork() and provide POSIX fork().
    
    Duplicate an address environment into freshly allocated pages mapped at the
    same virtual addresses, which is what POSIX fork() is built on.  It lives in
    arm64_addrenv_mmu.c:  an MPU address environment is a set of protection
    regions over one physical address space, not a mapping that can be 
duplicated
    at the same virtual addresses.  So ARCH_ARM64 selects ARCH_HAVE_FORK only
    in a kernel build with ARCH_ADDRENV.  The condition repeats the
    ARCH_ADDRENV dependency, because a select bypasses depends on.
    
    arm64_fork_stack() then lets the child run at the parent's stack addresses. 
 A
    pointer to a stack local taken before fork() must name the same object in 
the
    child that it named in the parent, so the child adopts the parent's stack
    geometry rather than being given a relocated copy; the parent's stack is
    already in the duplicate, at the parent's address, with its contents.  With 
a
    zero offset arm64_fork_reloc() is then the identity, so the register context
    needs no further special casing.
    
    Verified on qemu-armv8a:knsh under qemu-system-aarch64:  ostest's fork_test
    reports "Parent and child had independent memory", and vfork_test passes.
    
    Assisted-by: Claude Code:claude-opus-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 .../guides/concurrency/fork_vfork_migration.rst    |   2 +-
 arch/Kconfig                                       |   1 +
 arch/arm64/src/common/arm64_addrenv_mmu.c          | 186 +++++++++++++++++++++
 arch/arm64/src/common/arm64_fork.c                 |  11 ++
 4 files changed, 199 insertions(+), 1 deletion(-)

diff --git a/Documentation/guides/concurrency/fork_vfork_migration.rst 
b/Documentation/guides/concurrency/fork_vfork_migration.rst
index 5952fd78f43..8f309304e66 100644
--- a/Documentation/guides/concurrency/fork_vfork_migration.rst
+++ b/Documentation/guides/concurrency/fork_vfork_migration.rst
@@ -211,7 +211,7 @@ Known gaps
 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_FORK``,
-with no further generic work.  x86_64 selects it today.
+with no further generic work.  x86_64 and arm64 select it today.
 
 **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:
diff --git a/arch/Kconfig b/arch/Kconfig
index c98bc643e83..fdc48c09f50 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -26,6 +26,7 @@ config ARCH_ARM
 config ARCH_ARM64
        bool "ARM64"
        select ALARM_ARCH
+       select ARCH_HAVE_FORK if BUILD_KERNEL && ARCH_ADDRENV
        select ARCH_64BIT
        select ARCH_HAVE_BACKTRACE
        select ARCH_HAVE_INTERRUPTSTACK
diff --git a/arch/arm64/src/common/arm64_addrenv_mmu.c 
b/arch/arm64/src/common/arm64_addrenv_mmu.c
index 5ea7e161874..686ebb04ede 100644
--- a/arch/arm64/src/common/arm64_addrenv_mmu.c
+++ b/arch/arm64/src/common/arm64_addrenv_mmu.c
@@ -370,6 +370,33 @@ static inline bool vaddr_is_shm(uintptr_t vaddr)
 #endif
 }
 
+#ifdef CONFIG_ARCH_HAVE_FORK
+/****************************************************************************
+ * Name: vaddr_is_text
+ *
+ * Description:
+ *   Check if a vaddr is part of the .text area, which is mapped read/execute
+ *   while everything else is mapped read/write.  The two arms mirror exactly
+ *   the two layouts up_addrenv_create() builds.
+ *
+ ****************************************************************************/
+
+static inline bool vaddr_is_text(const arch_addrenv_t *addrenv,
+                                 uintptr_t vaddr)
+{
+#if (CONFIG_ARCH_TEXT_VBASE != 0x0) && (CONFIG_ARCH_HEAP_VBASE != 0x0)
+  UNUSED(addrenv);
+  return vaddr >= CONFIG_ARCH_TEXT_VBASE && vaddr < ARCH_TEXT_VEND;
+#else
+  /* Contiguous layout:  the reserve sits below .text, and .data begins where
+   * .text ends.
+   */
+
+  return vaddr >= addrenv->textvbase && vaddr < addrenv->datavbase;
+#endif
+}
+#endif /* CONFIG_ARCH_HAVE_FORK */
+
 /****************************************************************************
  * Public Functions
  ****************************************************************************/
@@ -526,6 +553,165 @@ errout:
   return ret;
 }
 
+#ifdef CONFIG_ARCH_HAVE_FORK
+/****************************************************************************
+ * Name: up_addrenv_fork
+ *
+ * Description:
+ *   Duplicate an address environment for POSIX fork().  The destination gets
+ *   its own page tables and its own physical pages, holding a copy of the
+ *   source's contents and mapped at the same virtual addresses.
+ *
+ *   The walk mirrors up_addrenv_destroy():  every final level page table the
+ *   source has under its static tables is visited, and every page it maps is
+ *   duplicated.  The copy is eager -- there is no copy-on-write -- so this
+ *   needs as much free page memory as the parent occupies.
+ *
+ * Input Parameters:
+ *   src  - The address environment to be duplicated.
+ *   dest - The location to receive the duplicate.
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int up_addrenv_fork(const arch_addrenv_t *src, arch_addrenv_t *dest)
+{
+  uintptr_t *sptprev;
+  uintptr_t *sptlast;
+  uintptr_t  dptprev;
+  uintptr_t *dptlast;
+  uintptr_t  paddr;
+  uintptr_t  vaddr;
+  uintptr_t  pgvaddr;
+  uintptr_t  l0;
+  size_t     pgsize;
+  int        i;
+  int        j;
+  int        ret;
+
+  DEBUGASSERT(src && dest);
+
+  memset(dest, 0, sizeof(arch_addrenv_t));
+
+  /* Give the child its own static page tables and kernel mappings */
+
+  ret = create_spgtables(dest);
+  if (ret < 0)
+    {
+      berr("ERROR: Failed to create static page tables\n");
+      goto errout;
+    }
+
+  ret = copy_kernel_mappings(dest);
+  if (ret < 0)
+    {
+      berr("ERROR: Failed to copy kernel mappings to new environment\n");
+      goto errout;
+    }
+
+  /* The duplicate lives at the same virtual addresses as the original */
+
+  dest->textvbase = src->textvbase;
+  dest->datavbase = src->datavbase;
+  dest->heapvbase = src->heapvbase;
+  dest->heapsize  = src->heapsize;
+
+  l0 = mmu_get_base_pgt_level();
+  dest->ttbr0 = mmu_ttbr_reg(dest->spgtables[l0], 0);
+
+  /* Make sure the source's page tables are visible before walking them */
+
+  UP_MB();
+
+  vaddr   = ARCH_ADDRENV_VBASE;
+  pgsize  = mmu_get_region_size(MMU_PGT_LEVEL_MAX - 1);
+  sptprev = (uintptr_t *)arm64_pgvaddr(src->spgtables[ARCH_SPGTS - 1]);
+  dptprev = arm64_pgvaddr(dest->spgtables[ARCH_SPGTS - 1]);
+
+  if (sptprev == NULL || dptprev == 0)
+    {
+      ret = -EINVAL;
+      goto errout;
+    }
+
+  for (i = 0; i < ENTRIES_PER_PGT; i++, vaddr += pgsize)
+    {
+      sptlast = (uintptr_t *)arm64_pgvaddr(mmu_pte_to_paddr(sptprev[i]));
+      if (sptlast == NULL)
+        {
+          continue;
+        }
+
+      /* Hook the static tables up for this address, then give the child its
+       * own final level page table here.
+       */
+
+      map_spgtables(dest, vaddr);
+
+      paddr = mm_pgalloc(1);
+      if (!paddr)
+        {
+          ret = -ENOMEM;
+          goto errout;
+        }
+
+      arm64_pgwipe(paddr);
+      mmu_ln_setentry(MMU_PGT_LEVEL_MAX - 1, dptprev, paddr, vaddr,
+                      MMU_UPGT_FLAGS);
+      dptlast = (uintptr_t *)arm64_pgvaddr(paddr);
+
+      for (j = 0; j < ENTRIES_PER_PGT; j++)
+        {
+          uintptr_t srcpage;
+          uintptr_t destpage;
+
+          srcpage = mmu_pte_to_paddr(sptlast[j]);
+          if (!srcpage)
+            {
+              continue;
+            }
+
+          pgvaddr = vaddr + ((uintptr_t)j << MM_PGSHIFT);
+
+          if (vaddr_is_shm(pgvaddr))
+            {
+              /* Shared memory stays shared across fork().  Map the very same
+               * page; up_addrenv_destroy() knows not to free SHM pages.
+               */
+
+              dptlast[j] = sptlast[j];
+              continue;
+            }
+
+          destpage = mm_pgalloc(1);
+          if (!destpage)
+            {
+              ret = -ENOMEM;
+              goto errout;
+            }
+
+          memcpy((void *)arm64_pgvaddr(destpage),
+                 (const void *)arm64_pgvaddr(srcpage), MM_PGSIZE);
+
+          mmu_ln_setentry(MMU_PGT_LEVEL_MAX, (uintptr_t)dptlast, destpage,
+                          pgvaddr,
+                          vaddr_is_text(src, pgvaddr) ? MMU_UTEXT_FLAGS
+                                                      : MMU_UDATA_FLAGS);
+        }
+    }
+
+  UP_MB();
+
+  return OK;
+
+errout:
+  up_addrenv_destroy(dest);
+  return ret;
+}
+#endif /* CONFIG_ARCH_HAVE_FORK */
+
 /****************************************************************************
  * Name: up_addrenv_destroy
  *
diff --git a/arch/arm64/src/common/arm64_fork.c 
b/arch/arm64/src/common/arm64_fork.c
index 3566c64ded8..f0e42d32d2e 100644
--- a/arch/arm64/src/common/arm64_fork.c
+++ b/arch/arm64/src/common/arm64_fork.c
@@ -86,6 +86,17 @@ static uint64_t arm64_fork_stack(struct tcb_s *parent, 
struct tcb_s *child,
   uint64_t stackutil;
   uint64_t newtop;
 
+  if (child->stack_base_ptr == parent->stack_base_ptr)
+    {
+      /* The child is running on the parent's stack addresses:  a fork()
+       * child, which inherited them and already has its own copy of the
+       * contents in its duplicated address environment.  There is nothing to
+       * copy and no offset to apply.
+       */
+
+      return 0;
+    }
+
   /* How much of the parent's stack was utilized?  The ARM uses a push-down
    * stack so that the current stack pointer should be lower than the
    * initial, adjusted stack pointer.  The stack usage should be the

Reply via email to