如何创建和写入内存映射文件?

23

编辑注:此代码示例来自 Rust 1.0 之前的版本,并且它所使用的代码在 Rust 1.0 中不存在。一些答案已经更新,以回答新版本 Rust 的核心问题。

我正在尝试使用 std::os::MemoryMap 创建一个内存映射文件。当前的方法如下:

use std::os;
use std::ptr;
use std::old_io as io;
use std::os::unix::prelude::AsRawFd;
use std::os::MapOption;

let path = Path::new("test.mmap");

let f = match io::File::open_mode(&path, io::Open, io::ReadWrite) {
    Ok(f) => f,
    Err(err) => panic!("Could not open file: {}", err),
};

let mmap_opts = &[
    MapOption::MapReadable,
    MapOption::MapWritable,
    MapOption::MapFd(f.as_raw_fd())
];

let mmap = match os::MemoryMap::new(1024*1024, mmap_opts) {
    Ok(mmap) => {
        println!("Successfully created the mmap: {}", mmap.len());
        mmap
    }
    Err(err) => panic!("Could not read the mmap: {}", err),
};

unsafe {
   let data = mmap.data();

    if data.is_null() {
        panic!("Could not access data from memory mapped file")
    }

    let src = "Hello!";
    ptr::copy_memory(data, src.as_ptr(), src.as_bytes().len());
}

这个程序出现了问题:
Process didn't exit successfully: `target/mmap` (status=4)

在调用ptr::copy_memory或其他对数据进行操作时,为什么我无法读取(或写入)MemoryMap中的数据?

  • MemoryMap在Rust中的正确使用方式是什么?

3
“fails when calling...”的意思是什么?是否有编译器错误、运行时崩溃、错误信息等? - user395760
1
您是否希望将修改保存回文件? - Shepmaster
@delnan,已更新问题并附上错误信息。 - mkhq
1
@Shepmaster,是的,那将是理想的,我看到你在答案中包含了它,非常感谢! - mkhq
3个回答

24
真正的解决办法是使用一个提供这种功能的箱子,最好是以跨平台的方式实现。
use memmap; // 0.7.0
use std::{
    fs::OpenOptions,
    io::{Seek, SeekFrom, Write},
};

const SIZE: u64 = 1024 * 1024;

fn main() {
    let src = "Hello!";

    let mut f = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("test.mmap")
        .expect("Unable to open file");

    // Allocate space in the file first
    f.seek(SeekFrom::Start(SIZE)).unwrap();
    f.write_all(&[0]).unwrap();
    f.seek(SeekFrom::Start(0)).unwrap();

    let mut data = unsafe {
        memmap::MmapOptions::new()
            .map_mut(&f)
            .expect("Could not access data from memory mapped file")
    };

    data[..src.len()].copy_from_slice(src.as_bytes());
}

请注意,这段代码仍可能导致未定义行为。由于切片由文件支持,因此文件的内容(包括切片)可能会被来自Rust程序外部的更改所改变,从而破坏了unsafe块所要维护的不变量。程序员需要确保在映射生命周期内文件不会发生更改。不幸的是,该crate本身没有提供太多防止这种情况发生甚至没有文档警告用户的帮助。
如果您希望使用较低级别的系统调用,则需要缺少两个主要部分:
  1. mmap 本身不会分配任何空间,因此您需要在文件中设置一些空间。如果没有这样做,在macOS上运行时,我会获得Illegal instruction:4

  2. MemoryMap(已)默认为私有,因此您需要将映射标记为公共的,以便更改写回文件(我假设您希望保存写入)。如果没有这样做,代码将运行,但文件永远不会更改。

这是适用于我的版本:
use libc; // 0.2.67
use std::{
    fs::OpenOptions,
    io::{Seek, SeekFrom, Write},
    os::unix::prelude::AsRawFd,
    ptr,
};

fn main() {
    let src = "Hello!";

    let size = 1024 * 1024;

    let mut f = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("test.mmap")
        .expect("Unable to open file");

    // Allocate space in the file first
    f.seek(SeekFrom::Start(size as u64)).unwrap();
    f.write_all(&[0]).unwrap();
    f.seek(SeekFrom::Start(0)).unwrap();

    // This refers to the `File` but doesn't use lifetimes to indicate
    // that. This is very dangerous, and you need to be careful.
    unsafe {
        let data = libc::mmap(
            /* addr: */ ptr::null_mut(),
            /* len: */ size,
            /* prot: */ libc::PROT_READ | libc::PROT_WRITE,
            // Then make the mapping *public* so it is written back to the file
            /* flags: */ libc::MAP_SHARED,
            /* fd: */ f.as_raw_fd(),
            /* offset: */ 0,
        );

        if data == libc::MAP_FAILED {
            panic!("Could not access data from memory mapped file")
        }

        ptr::copy_nonoverlapping(src.as_ptr(), data as *mut u8, src.len());
    }
}

我不明白为什么更改文件内容会导致未定义行为。这是由于内存模型(非易失性读取)吗? - kbridge4096
@kbridge4096 它被声明为改变不可变值是未定义行为。如果文件发生更改,映射的内存将会改变,无论 Rust 程序是否将其标记为不可变。 - Shepmaster

11

最新版本:

use std::ptr;
use std::fs;
use std::io::{Write, SeekFrom, Seek};
use std::os::unix::prelude::AsRawFd;
use mmap::{MemoryMap, MapOption};

// from crates.io
extern crate mmap;
extern crate libc;

fn main() {
    let size: usize = 1024*1024;

    let mut f = fs::OpenOptions::new().read(true)
                                      .write(true)
                                      .create(true)
                                      .open("test.mmap")
                                      .unwrap();

    // Allocate space in the file first
    f.seek(SeekFrom::Start(size as u64)).unwrap();
    f.write_all(&[0]).unwrap();
    f.seek(SeekFrom::Start(0)).unwrap();

    let mmap_opts = &[
        // Then make the mapping *public* so it is written back to the file
        MapOption::MapNonStandardFlags(libc::consts::os::posix88::MAP_SHARED),
        MapOption::MapReadable,
        MapOption::MapWritable,
        MapOption::MapFd(f.as_raw_fd()),
    ];

    let mmap = MemoryMap::new(size, mmap_opts).unwrap();

    let data = mmap.data();

    if data.is_null() {
        panic!("Could not access data from memory mapped file")
    }

    let src = "Hello!";
    let src_data = src.as_bytes();

    unsafe {
        ptr::copy(src_data.as_ptr(), data, src_data.len());
    }
}

1
我遇到了错误[E0433]: 解析失败。尝试编译时在'libc'中找不到'consts'。通过将libc::consts::os::posix88::MAP_SHARED更改为libc::MAP_SHARED进行修复。 - Doncho Gunchev

2

2022 版本:

use memmap2::Mmap;
use std::fs::{self};
use std::io::{Seek, SeekFrom, Write};
use std::ops::DerefMut;

pub fn memmap2() {
    // How to write to a file using mmap
    // First open the file with writing option
    let mut file = fs::OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("mmap_write_example2.txt")
        .unwrap();

    // Allocate space in the file for the data to be written,
    // UTF8-encode string to get byte slice.
    let data_to_write: &[u8] =
        "Once upon a midnight dreary as I pondered weak and weary; äåößf\n".as_bytes();
    let size: usize = data_to_write.len();
    file.seek(SeekFrom::Start(size as u64 - 1)).unwrap();
    file.write_all(&[0]).unwrap();
    file.seek(SeekFrom::Start(0)).unwrap();

    // Then write to the file
    let mmap = unsafe { Mmap::map(&file).unwrap() };
    let mut mut_mmap = mmap.make_mut().unwrap();
    mut_mmap.deref_mut().write_all(data_to_write).unwrap();
}

请注意,其他答案已经过时,并使用自2015年以来未被触及的库。 - Thorkil Værge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接