diff --git a/Documentation/guides/fork_vfork_migration.rst b/Documentation/guides/fork_vfork_migration.rst index fa9a305d3c8e6..4c49652024ab1 100644 --- a/Documentation/guides/fork_vfork_migration.rst +++ b/Documentation/guides/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. +with no further generic work. arm64 selects 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/Documentation/platforms/arm64/qemu/boards/qemu-armv8a/index.rst b/Documentation/platforms/arm64/qemu/boards/qemu-armv8a/index.rst index c0dbb8a7fdd08..64f07a378a2df 100644 --- a/Documentation/platforms/arm64/qemu/boards/qemu-armv8a/index.rst +++ b/Documentation/platforms/arm64/qemu/boards/qemu-armv8a/index.rst @@ -424,6 +424,16 @@ Running with QEMU: -net none -chardev stdio,id=con,mux=on -serial chardev:con \ -mon chardev=con,mode=readline -kernel ./nuttx +``-semihosting`` is necessary. A kernel build loads its applications over +hostfs. Without the flag the guest traps in ``smh_call`` and stops in +``AppBringUp``, which looks like a kernel defect and is not one. + +A kernel build gives each process its own address environment. This is the +only build mode on this board that provides POSIX ``fork()``: the child +receives its own copy of the memory of the parent, at the same virtual +addresses. ``vfork()`` is available in every build mode. + + Inter-VM share memory Device (ivshmem) -------------------------------------- diff --git a/arch/Kconfig b/arch/Kconfig index 60039f1991e4e..74c2d9d5a4a85 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 5ea7e16187479..686ebb04ede7c 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 @@ int up_addrenv_create(size_t textsize, size_t datasize, size_t heapsize, 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 3566c64ded810..f0e42d32d2ecd 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