Skip to content

arch/risc-v: Implement up_addrenv_fork() and provide POSIX fork() - #19774

Draft
casaroli wants to merge 2 commits into
apache:masterfrom
casaroli:riscv-fork
Draft

arch/risc-v: Implement up_addrenv_fork() and provide POSIX fork()#19774
casaroli wants to merge 2 commits into
apache:masterfrom
casaroli:riscv-fork

Conversation

@casaroli

Copy link
Copy Markdown
Contributor

Draft. The evidence is emulation only. I keep this as a draft until fork() runs on RISC-V hardware.

Summary

fork() was withdrawn from every architecture by #19562, and each architecture restores it with the correct behaviour. This pull request restores it for RISC-V.

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 page tables of the child.

The other half was already in the tree before #19562. riscv_swint.c stores the exception frame of the caller in xcp.sregs, and riscv_fork.c builds the child from it. RISC-V was the architecture the other ports copied for that.

riscv_fork.c also loses a duplicate branch. The review round of #19562 added the same-virtual-address shortcut to that file, and this commit added its own copy of it. One remains.

Impact

The change is specific to RISC-V. It adds capability and removes none.

ARCH_HAVE_FORK takes default y if ARCH_RISCV. That is broader than the condition the other architectures use, so the guard matters: depends on ARCH_ADDRENV && !ARCH_STACK_DYNAMIC. A configuration without address environments is unaffected, and a protected build is excluded, MPU and MMU alike. A protected build has one address space, protected by a fixed set of regions, and a second copy of a process at the same virtual addresses has no meaning there.

vfork() does not change. No board configuration changes.

Nine RISC-V chips select ARCH_HAVE_ADDRENV today: MPFS, QEMU_RV, JH7110, BL808, K230, SG2000, EIC7700X and two LiteX cores. fork() becomes available on those, in a kernel build.

Testing

Emulation only. QEMU 11.0.3 on macOS 15 with Apple Silicon, xPack riscv-none-elf-gcc 14.2.0-3.

configuration width result
rv-virt:knsh_romfs 32-bit vfork() and fork() pass, status 0
rv-virt:knsh64 64-bit vfork() and fork() pass, status 0
qemu-system-riscv32 -M virt,aclint=on -cpu rv32 -kernel nuttx -nographic
qemu-system-riscv64 -semihosting -M virt,aclint=on -cpu rv64 -kernel nuttx -nographic

Do not add -bios none to the 32-bit command. Without OpenSBI the guest prints nothing at all, which reads as a boot failure and is not one. The 64-bit configuration loads its applications over hostfs and needs -semihosting.

user_main: vfork() test
vfork_test: Child 6 ran and exited before the parent resumed
user_main: fork() test
fork_test: Child running independently (child)
fork_test: Parent and child had independent memory
ostest_main: Exiting with status 0

tools/checkpatch.sh -c -u -m -g gives no errors.

What is not tested

No RISC-V hardware. This is the reason for the draft status.

rv-virt:knsh does not boot here, and it does not boot at the merge base either, so I used rv-virt:knsh_romfs for the 32-bit row.

A report from a RISC-V board is welcome. Take this branch with apache/nuttx-apps#3685, build a kernel configuration and run ostest. The fork tests are the first output.

Duplicate an address environment into freshly allocated pages mapped at the
same virtual addresses, which is what POSIX fork() is built on.  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, so
the cost is the size of the process rather than the size of its address
space.  Shared memory stays shared -- the very same page is mapped, and
up_addrenv_destroy() already knows not to free SHM pages.

riscv_fork() 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.

RISC-V already builds the child's register context from the caller's saved
syscall frame, so nothing else is needed and ARCH_HAVE_FORK follows.  It is
the first architecture with real fork().

Verified on rv-virt:knsh64 under qemu-system-riscv64:  ostest's fork_test
reports "Parent and child had independent memory", and vfork_test passes.
rv-virt:nsh64 is unchanged, with vfork() passing and fork() correctly absent.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@github-actions github-actions Bot added Area: Documentation Improvements or additions to documentation Arch: risc-v Issues related to the RISC-V (32-bit or 64-bit) architecture Size: M The size of the change in this PR is medium labels Aug 10, 2026
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

qemu-armv8a

  • Code: .text.romfs_cachenode +4 B (+0.0%, 316,720 B)

s698pm-dkit

Review of apache#19772 asked for this shape, and it applies to every architecture in
the series.

ARCH_HAVE_FORK described when it was available from inside its own definition,
which put the per-architecture condition somewhere nobody looks.  The
architecture now says so itself.

The condition repeats both dependencies rather than relying on them, because a
select bypasses depends on.  RISC-V also excludes dynamic stacks, so that is
named here too.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: risc-v Issues related to the RISC-V (32-bit or 64-bit) architecture Area: Documentation Improvements or additions to documentation Size: M The size of the change in this PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant