mirror of
https://github.com/apache/nuttx.git
synced 2026-10-03 20:27:53 +00:00
arch/xtensa: Provide POSIX fork() on the ESP32-S3.
up_addrenv_fork() duplicates an address environment into freshly allocated pages mapped at the same virtual addresses. The text, data and heap regions of the source are walked one page at a time and copied into fresh pages hung off the child's own directory, using the two kmap slots that CONFIG_ARCH_KMAP_NPAGES reserves for exactly this. xtensa_fork.c already took both paths: a child that keeps the parent's stack addresses needs no relocation, which is what a duplicated address environment gives it. Only the hook and the Kconfig default were missing. fork() is offered on a kernel build, which is the only mode with per-process address environments. Verified on an ESP32-S3-WROOM-2 with esp32s3-devkit:kernel_oct. ostest reports "Parent and child had independent memory" and exits with status 0. Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
This commit is contained in:
parent
ce59fb6e71
commit
1bc7cfeeb1
2 changed files with 164 additions and 10 deletions
25
arch/Kconfig
25
arch/Kconfig
|
|
@ -159,6 +159,7 @@ config ARCH_XTENSA
|
|||
bool "Xtensa"
|
||||
select ARCH_HAVE_BACKTRACE
|
||||
select ARCH_HAVE_VFORK
|
||||
select ARCH_HAVE_FORK if BUILD_KERNEL && ARCH_ADDRENV
|
||||
select ARCH_HAVE_CPUINFO
|
||||
select ARCH_HAVE_INTERRUPTSTACK
|
||||
select ARCH_HAVE_STACKCHECK
|
||||
|
|
@ -1030,8 +1031,6 @@ config ARCH_PGPOOL_MAPPING
|
|||
Otherwise, a temporary mapping will have to be established each time
|
||||
it is necessary to modify the contents of a page.
|
||||
|
||||
if ARCH_PGPOOL_MAPPING
|
||||
|
||||
config ARCH_PGPOOL_PBASE
|
||||
hex "Page pool physical address"
|
||||
default 0x0
|
||||
|
|
@ -1041,14 +1040,9 @@ config ARCH_PGPOOL_PBASE
|
|||
but is required again in order to modularize the common address
|
||||
environment logic.
|
||||
|
||||
config ARCH_PGPOOL_VBASE
|
||||
hex "Page pool virtual address"
|
||||
default 0x0
|
||||
---help---
|
||||
The virtual address of the start of the page pool memory. This
|
||||
setting is probably equivalent to other platform specific definitions
|
||||
but is required again in order to modularize the common address
|
||||
environment logic.
|
||||
This applies whether or not the pool is statically mapped: the page
|
||||
allocator hands out physical addresses either way. Only
|
||||
ARCH_PGPOOL_VBASE depends on ARCH_PGPOOL_MAPPING.
|
||||
|
||||
config ARCH_PGPOOL_SIZE
|
||||
int "Page pool size (bytes)"
|
||||
|
|
@ -1058,6 +1052,17 @@ config ARCH_PGPOOL_SIZE
|
|||
equivalent to other platform specific definitions but is required again
|
||||
in order to modularize the common address environment logic.
|
||||
|
||||
if ARCH_PGPOOL_MAPPING
|
||||
|
||||
config ARCH_PGPOOL_VBASE
|
||||
hex "Page pool virtual address"
|
||||
default 0x0
|
||||
---help---
|
||||
The virtual address of the start of the page pool memory. This
|
||||
setting is probably equivalent to other platform specific definitions
|
||||
but is required again in order to modularize the common address
|
||||
environment logic.
|
||||
|
||||
endif # ARCH_PGPOOL_MAPPING
|
||||
endif # ARCH_ADDRENV && ARCH_NEED_ADDRENV_MAPPING
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <assert.h>
|
||||
#include <debug.h>
|
||||
#include <errno.h>
|
||||
#include <sched.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nuttx/addrenv.h>
|
||||
|
|
@ -102,6 +103,7 @@ static int alloc_region(uintptr_t *pages, unsigned int maxpages, size_t size,
|
|||
|
||||
if (paddr == 0)
|
||||
{
|
||||
berr("ERROR: page pool exhausted at page %u of %u\n", i, npages);
|
||||
*count = i;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
|
@ -576,6 +578,153 @@ int up_addrenv_clone(const arch_addrenv_t *src, arch_addrenv_t *dest)
|
|||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: copy_region
|
||||
*
|
||||
* Description:
|
||||
* Copy one region's pages from a parent environment into a child's. Both
|
||||
* sides are reached through the kernel's scratch region, which is why it
|
||||
* has two slots: source and destination are mapped at the same time so
|
||||
* this is one memcpy rather than a bounce through kernel memory.
|
||||
*
|
||||
* sched_lock() is held across each page pair for the reason every scratch
|
||||
* mapping is: those addresses are ordinary external memory to the
|
||||
* permission control, so no unprivileged task may run while a page of
|
||||
* somebody's memory is parked at one.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int copy_region(const uintptr_t *src, uintptr_t *dest, uint16_t count)
|
||||
{
|
||||
uint16_t i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
uintptr_t svaddr;
|
||||
uintptr_t dvaddr;
|
||||
|
||||
sched_lock();
|
||||
|
||||
svaddr = esp32s3_pgmap(src[i]);
|
||||
dvaddr = esp32s3_pgmap(dest[i]);
|
||||
|
||||
if (svaddr == 0 || dvaddr == 0)
|
||||
{
|
||||
if (svaddr != 0)
|
||||
{
|
||||
esp32s3_pgunmap(svaddr);
|
||||
}
|
||||
|
||||
if (dvaddr != 0)
|
||||
{
|
||||
esp32s3_pgunmap(dvaddr);
|
||||
}
|
||||
|
||||
sched_unlock();
|
||||
berr("ERROR: no scratch mapping for page %u\n", i);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
memcpy((void *)dvaddr, (const void *)svaddr, MM_PGSIZE);
|
||||
|
||||
esp32s3_pgunmap(dvaddr);
|
||||
esp32s3_pgunmap(svaddr);
|
||||
|
||||
sched_unlock();
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: up_addrenv_fork
|
||||
*
|
||||
* Description:
|
||||
* Duplicate an address environment for fork(): allocate the child pages
|
||||
* to match the parent's regions and copy the parent's contents into them.
|
||||
*
|
||||
* The copy is eager and complete. There is no copy-on-write and no demand
|
||||
* fill, because this chip provides no synchronous restartable write fault
|
||||
* to build them on -- proven, not assumed. So a fork costs a full copy of
|
||||
* the process image.
|
||||
*
|
||||
* The child's pages land at the same *virtual* addresses as the parent's,
|
||||
* which is the property the whole thing rests on: a copied stack is full
|
||||
* of pointers into itself, and they are only still correct because the
|
||||
* copy is addressed identically.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int up_addrenv_fork(const arch_addrenv_t *src, arch_addrenv_t *dest)
|
||||
{
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(src && dest);
|
||||
|
||||
memset(dest, 0, sizeof(arch_addrenv_t));
|
||||
|
||||
dest->textvbase = src->textvbase;
|
||||
dest->datavbase = src->datavbase;
|
||||
dest->heapvbase = src->heapvbase;
|
||||
dest->heapsize = src->heapsize;
|
||||
|
||||
/* Allocate the child's pages. alloc_region() takes a size, and the
|
||||
* parent's page counts are the exact sizes wanted.
|
||||
*/
|
||||
|
||||
ret = alloc_region(dest->textpages, CONFIG_ARCH_TEXT_NPAGES,
|
||||
(size_t)src->ntext * MM_PGSIZE, &dest->ntext);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = alloc_region(dest->datapages, CONFIG_ARCH_DATA_NPAGES,
|
||||
(size_t)src->ndata * MM_PGSIZE, &dest->ndata);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = alloc_region(dest->heappages, CONFIG_ARCH_HEAP_NPAGES,
|
||||
(size_t)src->nheap * MM_PGSIZE, &dest->nheap);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout;
|
||||
}
|
||||
|
||||
/* Then fill them from the parent */
|
||||
|
||||
ret = copy_region(src->textpages, dest->textpages, dest->ntext);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = copy_region(src->datapages, dest->datapages, dest->ndata);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ret = copy_region(src->heappages, dest->heappages, dest->nheap);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout;
|
||||
}
|
||||
|
||||
/* The text pages were written through the data bus. Make them visible to
|
||||
* instruction fetch before anything runs from them.
|
||||
*/
|
||||
|
||||
esp32s3_addrenv_coherent();
|
||||
return OK;
|
||||
|
||||
errout:
|
||||
up_addrenv_destroy(dest);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: up_addrenv_attach
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue