Как создавать и записывать в файлы с отображением памяти?

Примечание редактора: Этот пример кода относится к версии 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?

Ответ 1

Реальный ответ состоит в том, чтобы использовать ящик, который обеспечивает эту функциональность, в идеале кроссплатформенным способом.

extern crate memmap;

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 блок. Программист должен убедиться, что файл не изменяется в течение жизни карты. К сожалению, сама корзина не оказывает большой помощи, чтобы предотвратить это, или даже любую документацию, предупреждающую пользователя.


Если вы хотите использовать системные вызовы более низкого уровня, вам не хватает двух основных частей:

  1. mmap не выделяет места самостоятельно, поэтому вам нужно установить некоторое место в файле. Без этого я получаю Illegal instruction: 4 при работе на macOS.

  2. MemoryMap (был) MemoryMap по умолчанию, поэтому вам нужно пометить отображение как общедоступное, чтобы изменения записывались обратно в файл (я предполагаю, что вы хотите, чтобы записи были сохранены). Без этого код выполняется, но файл никогда не изменяется.

Вот версия, которая работает для меня:

extern crate libc;

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,
        ) as *mut u8;

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

        ptr::copy_nonoverlapping(src.as_ptr(), data, src.len());
    }
}

Ответ 2

Текущая версия:

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());
    }
}