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

Rust中进程B如何读取进程A创建的无文件名mmap临时文件?

进程间通过临时文件mmap传递大对象的Rust实现方案

问题描述

需要在进程间传递大对象,设计流程为:进程A通过tempfile创建匿名临时文件并映射为mmap写入数据,进程B读取该mmap内容。已知同一文件在不同进程中的fd数值可能不同,但内核打开文件表指向同一文件,当前存在以下疑问:

  • 进程B无临时文件名,如何利用进程A的fd读取对应mmap?
  • Rust中有无无需文件名的方式获取进程A的mmap?
  • C语言可通过sendmsg直接传递fd给进程B创建mmap,其内部转换逻辑是什么?

进程A代码示例:

#[cfg(test)]
mod tests {

use std::fs::File;
use std::os::unix::io::AsRawFd;
use bytes::Bytes;
use memmap::Mmap;
use tempfile::tempfile;

#[test]
fn test_tmp_file_mmap() {
    let file = tempfile().unwrap();
    file.set_len(100).unwrap();
    println!("fd = {}", file.as_raw_fd());
    let mmap = unsafe {
        Mmap::map(&file).unwrap()
    };
    let mut_mmap = mmap.make_mut().unwrap();
    // write something into mut_mmap

}

}

Cargo.toml依赖:

memmap = "0.7.0"
tempfile = "3.3.0"

解决方案:Unix域套接字传递文件描述符

在类Unix系统中,进程间传递文件描述符的标准方式是Unix域套接字 + sendmsg/recvmsg,Rust可通过nix库实现该逻辑。核心是内核会在进程间复制文件描述符的引用(而非数值本身),确保两个进程的fd指向同一内核文件对象。

实现步骤

  1. 进程A创建Unix域套接字并监听,同时创建临时文件、mmap写入数据;
  2. 进程B连接到该套接字,接收进程A发送的文件描述符;
  3. 进程B通过接收的fd打开文件,再mmap读取内容。

代码实现

补充依赖(Cargo.toml)

memmap = "0.7.0"
tempfile = "3.3.0"
nix = { version = "0.27.1", features = ["socket", "fs", "process"] }

进程A(发送fd)

use std::fs::File;
use std::os::unix::io::AsRawFd;
use memmap::MmapMut;
use tempfile::tempfile;
use nix::sys::socket::{self, AddressFamily, SockType, UnixAddr, MsgFlags, cmsg_space};
use nix::unistd::close;
use std::ffi::CString;

fn main() {
    // 创建Unix域套接字并监听
    let socket_path = CString::new("/tmp/mmap_fd_socket").unwrap();
    let sock_fd = socket::socket(AddressFamily::Unix, SockType::Stream, None, 0).unwrap();
    let addr = UnixAddr::new(&socket_path).unwrap();
    let _ = std::fs::remove_file(&socket_path); // 清理旧套接字文件
    socket::bind(sock_fd, &addr).unwrap();
    socket::listen(sock_fd, 1).unwrap();

    // 创建临时文件并写入数据到mmap
    let file = tempfile().unwrap();
    file.set_len(100).unwrap();
    let mut mmap = unsafe { MmapMut::map_mut(&file).unwrap() };
    mmap[0..11].copy_from_slice(b"Hello World");

    // 等待进程B连接
    let (conn_fd, _) = socket::accept(sock_fd).unwrap();

    // 构造包含fd的控制消息
    let mut cmsg = cmsg_space::<[i32; 1]>();
    let mut msg = socket::MsgHdr::new(
        None,
        &[],
        Some(&mut cmsg),
        MsgFlags::empty(),
    );
    socket::cmsg::set_cmsg(&mut cmsg, socket::ControlMessage::ScmRights(&[file.as_raw_fd()])).unwrap();

    // 发送空数据+控制消息(仅传递fd)
    socket::sendmsg(conn_fd, &msg, MsgFlags::empty()).unwrap();

    // 清理资源
    close(conn_fd).unwrap();
    close(sock_fd).unwrap();
    std::fs::remove_file(&socket_path).unwrap();
}

进程B(接收fd并读取mmap)

use std::os::unix::io::FromRawFd;
use std::fs::File;
use memmap::Mmap;
use nix::sys::socket::{self, AddressFamily, SockType, UnixAddr, MsgFlags, cmsg_space};
use nix::unistd::close;
use std::ffi::CString;

fn main() {
    // 连接到进程A的套接字
    let socket_path = CString::new("/tmp/mmap_fd_socket").unwrap();
    let sock_fd = socket::socket(AddressFamily::Unix, SockType::Stream, None, 0).unwrap();
    let addr = UnixAddr::new(&socket_path).unwrap();
    socket::connect(sock_fd, &addr).unwrap();

    // 准备接收控制消息
    let mut buf = [0u8; 1];
    let mut cmsg = cmsg_space::<[i32; 1]>();
    let mut msg = socket::MsgHdr::new(
        Some(&mut buf),
        &[],
        Some(&mut cmsg),
        MsgFlags::empty(),
    );

    // 接收消息并提取fd
    socket::recvmsg(sock_fd, &mut msg, MsgFlags::empty()).unwrap();
    let mut received_fd = -1;
    for cmsg in msg.cmsgs() {
        if let socket::ControlMessage::ScmRights(fds) = cmsg {
            received_fd = fds[0];
            break;
        }
    }
    assert_ne!(received_fd, -1);

    // 通过fd打开文件并读取mmap内容
    let file = unsafe { File::from_raw_fd(received_fd) };
    let mmap = unsafe { Mmap::map(&file).unwrap() };
    let content = String::from_utf8_lossy(&mmap[0..11]);
    println!("Received content: {}", content); // 输出:Hello World

    // 关闭套接字
    close(sock_fd).unwrap();
}

内部逻辑说明

当进程A通过sendmsg发送ScmRights类型的控制消息时:

  1. 内核会为进程B创建一个新的文件描述符,该fd与进程A的fd指向同一个内核打开文件对象;
  2. 新fd的数值可能与进程A的不同,但内核内部的文件引用完全一致,因此进程B可直接通过该fd映射mmap访问进程A写入的内容;
  3. 传递完成后,进程A关闭自身fd不会影响进程B的fd引用,直到进程B也关闭fd,内核才会回收对应的文件资源。

替代方案:匿名共享内存

如果不需要临时文件的持久化特性,可直接通过memfd_create创建匿名共享内存对象,后续传递fd的逻辑与上述一致。该方式不会在文件系统中留下任何痕迹,更适合纯内存级的进程间数据共享。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 12:20:24