You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在子进程中访问父进程内存映射区?共享映射访问异常排查

解决CLONE_VM共享内存映射区+ptrace写入的问题

首先得指出你代码里的核心问题:你用os/exec启动子进程的方式完全抵消了CLONE_VM的作用。exec系统调用会彻底替换子进程的地址空间——原来父进程里创建的内存映射、变量等都会被新程序的地址空间覆盖,自然子进程访问不到之前的映射区了。要实现共享地址空间,你需要直接调用syscall.Clone创建子进程,而不是走fork+exec的流程。

下面是修正后的完整代码,我会一步步解释关键细节:

package main

import (
	"fmt"
	"log"
	"syscall"
	"unsafe"
)

// 子进程要执行的函数,会访问共享内存
func childFunc(sharedMem unsafe.Pointer) {
	// 等待父进程通过ptrace写入数据
	fmt.Println("子进程:等待父进程写入共享内存...")
	syscall.Pause()

	// 读取共享内存内容
	data := (*[10]byte)(sharedMem)
	fmt.Printf("子进程:读取到共享内存数据:%s\n", string(data[:4]))
}

func main() {
	// 创建临时文件用于内存映射
	f, err := syscall.Open("/tmp/test_shared", syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0644)
	if err != nil {
		log.Fatalf("创建临时文件失败:%v", err)
	}
	defer syscall.Close(f)

	// 先截断文件到需要的大小(10字节),避免映射超出文件范围
	if err := syscall.Ftruncate(f, 10); err != nil {
		log.Fatalf("截断文件失败:%v", err)
	}

	// 创建内存映射区:PROT_READ|PROT_WRITE 表示读写权限,MAP_SHARED 表示映射同步到文件(如果不需要同步文件也可以用MAP_PRIVATE)
	sharedMem, err := syscall.Mmap(f, 0, 10, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
	if err != nil {
		log.Fatalf("内存映射失败:%v", err)
	}
	defer syscall.Munmap(sharedMem)

	// 先写入初始数据到映射区
	copy(sharedMem, []byte("4321"))
	fmt.Printf("父进程:初始共享内存数据:%s\n", string(sharedMem[:4]))

	// 准备子进程的栈空间(clone需要单独的栈)
	stack := make([]byte, 8192)
	stackTop := uintptr(unsafe.Pointer(&stack[len(stack)]))

	// 调用clone创建子进程,指定CLONE_VM标志(共享地址空间),同时指定子进程入口函数
	childPid, _, errno := syscall.RawSyscall6(
		syscall.SYS_CLONE,
		uintptr(syscall.CLONE_VM|syscall.SIGCHLD), // 标志:共享VM+子进程退出时发SIGCHLD
		stackTop, // 子进程栈顶(栈是向下增长的)
		0, 0, 0, 0,
	)
	if errno != 0 {
		log.Fatalf("clone失败:%v", errno)
	}

	if childPid == 0 {
		// 子进程执行逻辑
		childFunc(unsafe.Pointer(&sharedMem[0]))
		syscall.Exit(0)
	}

	fmt.Printf("父进程:子进程PID:%d\n", childPid)

	// 使用ptrace向共享内存写入数据
	// 1. 先attach到子进程
	if err := syscall.PtraceAttach(int(childPid)); err != nil {
		log.Fatalf("ptrace attach失败:%v", err)
	}
	defer syscall.PtraceDetach(int(childPid))

	// 等待子进程进入停止状态
	var waitStatus syscall.WaitStatus
	if _, err := syscall.Wait4(int(childPid), &waitStatus, 0, nil); err != nil {
		log.Fatalf("wait4失败:%v", err)
	}

	// 2. 向共享内存地址写入新数据(比如"1234")
	data := []byte("1234")
	for i := 0; i < len(data); i++ {
		addr := uintptr(unsafe.Pointer(&sharedMem[i]))
		if err := syscall.PtracePokeData(int(childPid), addr, uintptr(data[i])); err != nil {
			log.Fatalf("ptrace pokedata失败:%v", err)
		}
	}
	fmt.Println("父进程:已通过ptrace写入新数据到共享内存")

	// 3. 让子进程继续执行
	if err := syscall.PtraceCont(int(childPid), 0); err != nil {
		log.Fatalf("ptrace cont失败:%v", err)
	}

	// 等待子进程退出
	if _, err := syscall.Wait4(int(childPid), &waitStatus, 0, nil); err != nil {
		log.Fatalf("等待子进程退出失败:%v", err)
	}
	fmt.Println("父进程:子进程已退出")
}

关键细节解释:

  • 摒弃os/exec改用clone:exec会完全替换子进程地址空间,之前的内存映射会丢失。直接调用syscall.RawSyscall6触发clone系统调用,带上CLONE_VM标志,就能让父子进程共享同一地址空间。
  • 内存映射的正确姿势:
    • 先用Ftruncate设置文件大小,确保映射范围有效;
    • 给足PROT_READ|PROT_WRITE权限,保证读写操作正常;
    • MAP_SHARED会同步修改到磁盘文件,若不需要磁盘同步,用MAP_PRIVATE也能实现进程间内存共享(因为地址空间本身是共享的)。
  • ptrace的标准流程:
    • 先PtraceAttach到子进程,等待其进入停止状态;
    • 通过PtracePokeData向共享内存地址写入数据(因为地址空间共享,这个地址在父子进程中完全一致);
    • 最后用PtraceCont让子进程恢复执行,读取修改后的内存内容。
  • 子进程栈空间准备:clone要求为子进程提供独立栈空间,这里用8KB切片作为栈,栈顶指向切片末尾(栈是向下增长的)。

运行这段代码后,你会看到父子进程能正确共享内存,父进程通过ptrace写入的数据也能被子进程正常读取。

内容的提问来源于stack exchange,提问作者user5061907

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:39:37