Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Documentation/guides/fork_vfork_migration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions Documentation/platforms/arm64/qemu/boards/qemu-armv8a/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
--------------------------------------

Expand Down
1 change: 1 addition & 0 deletions arch/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
186 changes: 186 additions & 0 deletions arch/arm64/src/common/arm64_addrenv_mmu.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
****************************************************************************/
Expand Down Expand Up @@ -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
*
Expand Down
11 changes: 11 additions & 0 deletions arch/arm64/src/common/arm64_fork.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading