Skip to content

Commit

Permalink
feat: allow running machines directly from mmaped backing files
Browse files Browse the repository at this point in the history
  • Loading branch information
edubart committed Oct 10, 2024
1 parent 4a3040d commit a99ed4d
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 378 deletions.
1 change: 1 addition & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ LIBCARTESI_OBJS:= \
virtio-net-carrier-slirp.o \
dtb.o \
os.o \
os-mmap.o \
htif.o \
htif-factory.o \
shadow-state.o \
Expand Down
38 changes: 10 additions & 28 deletions src/machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,6 @@ const pma_entry::flags machine::m_cmio_tx_buffer_flags{
};

pma_entry machine::make_memory_range_pma_entry(const std::string &description, const memory_range_config &c) {
if (c.image_filename.empty()) {
return make_callocd_memory_pma_entry(description, c.start, c.length);
}
return make_mmapd_memory_pma_entry(description, c.start, c.length, c.image_filename, c.shared);
}

Expand All @@ -120,24 +117,14 @@ pma_entry machine::make_flash_drive_pma_entry(const std::string &description, co
}

pma_entry machine::make_cmio_rx_buffer_pma_entry(const cmio_config &c) {
const auto description = "cmio rx buffer memory range"s;
if (!c.rx_buffer.image_filename.empty()) {
return make_mmapd_memory_pma_entry(description, PMA_CMIO_RX_BUFFER_START, PMA_CMIO_RX_BUFFER_LENGTH,
c.rx_buffer.image_filename, c.rx_buffer.shared)
.set_flags(m_cmio_rx_buffer_flags);
}
return make_callocd_memory_pma_entry(description, PMA_CMIO_RX_BUFFER_START, PMA_CMIO_RX_BUFFER_LENGTH)
return make_mmapd_memory_pma_entry("cmio rx buffer memory range"s, PMA_CMIO_RX_BUFFER_START,
PMA_CMIO_RX_BUFFER_LENGTH, c.rx_buffer.image_filename, c.rx_buffer.shared)
.set_flags(m_cmio_rx_buffer_flags);
}

pma_entry machine::make_cmio_tx_buffer_pma_entry(const cmio_config &c) {
const auto description = "cmio tx buffer memory range"s;
if (!c.tx_buffer.image_filename.empty()) {
return make_mmapd_memory_pma_entry(description, PMA_CMIO_TX_BUFFER_START, PMA_CMIO_TX_BUFFER_LENGTH,
c.tx_buffer.image_filename, c.tx_buffer.shared)
.set_flags(m_cmio_tx_buffer_flags);
}
return make_callocd_memory_pma_entry(description, PMA_CMIO_TX_BUFFER_START, PMA_CMIO_TX_BUFFER_LENGTH)
return make_mmapd_memory_pma_entry("cmio tx buffer memory range"s, PMA_CMIO_TX_BUFFER_START,
PMA_CMIO_TX_BUFFER_LENGTH, c.tx_buffer.image_filename, c.tx_buffer.shared)
.set_flags(m_cmio_tx_buffer_flags);
}

Expand Down Expand Up @@ -323,18 +310,13 @@ machine::machine(const machine_config &c, const machine_runtime_config &r) :
write_reg(reg::iunrep, m_c.processor.iunrep);

// Register RAM
if (m_c.ram.image_filename.empty()) {
register_pma_entry(make_callocd_memory_pma_entry("RAM"s, PMA_RAM_START, m_c.ram.length).set_flags(m_ram_flags));
} else {
register_pma_entry(make_callocd_memory_pma_entry("RAM"s, PMA_RAM_START, m_c.ram.length, m_c.ram.image_filename)
.set_flags(m_ram_flags));
}
register_pma_entry(make_mmapd_memory_pma_entry("RAM"s, PMA_RAM_START, m_c.ram.length, m_c.ram.image_filename)
.set_flags(m_ram_flags));

// Register DTB
pma_entry &dtb = register_pma_entry((m_c.dtb.image_filename.empty() ?
make_callocd_memory_pma_entry("DTB"s, PMA_DTB_START, PMA_DTB_LENGTH) :
make_callocd_memory_pma_entry("DTB"s, PMA_DTB_START, PMA_DTB_LENGTH, m_c.dtb.image_filename))
.set_flags(m_dtb_flags));
pma_entry &dtb =
register_pma_entry(make_mmapd_memory_pma_entry("DTB"s, PMA_DTB_START, PMA_DTB_LENGTH, m_c.dtb.image_filename))
.set_flags(m_dtb_flags);

// Register all flash drives
int i = 0;
Expand Down Expand Up @@ -485,7 +467,7 @@ machine::machine(const machine_config &c, const machine_runtime_config &r) :
if (!m_c.tlb.image_filename.empty()) {
// Create a temporary PMA entry just to load TLB contents from an image file
pma_entry tlb_image_pma = make_mmapd_memory_pma_entry("shadow TLB device"s, PMA_SHADOW_TLB_START,
PMA_SHADOW_TLB_LENGTH, m_c.tlb.image_filename, false);
PMA_SHADOW_TLB_LENGTH, m_c.tlb.image_filename);
unsigned char *hmem = tlb_image_pma.get_memory().get_host_memory();
for (uint64_t i = 0; i < PMA_TLB_SIZE; ++i) {
load_tlb_entry<TLB_CODE>(*this, i, hmem);
Expand Down
4 changes: 4 additions & 0 deletions src/os-features.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
#define HAVE_MMAP
#endif

#if !defined(NO_FLOCK) && !defined(_WIN32) && !defined(__wasi__)
#define HAVE_FLOCK
#endif

#if !defined(NO_MKDIR)
#define HAVE_MKDIR
#endif
Expand Down
306 changes: 306 additions & 0 deletions src/os-mmap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
// Copyright Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along
// with this program (see COPYING). If not, see <https://www.gnu.org/licenses/>.
//

#include <system_error>

#include "os-features.h"
#include "os-mmap.h"
#include "unique-c-ptr.h"

#if defined(HAVE_MMAP)
#include <fcntl.h> // open
#include <sys/mman.h> // mmap/munmap
#include <sys/stat.h> // fstat
#include <unistd.h> // write/read/close
#endif

#if defined(HAVE_FLOCK)
#include <sys/file.h> // flock
#endif

#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <io.h> // _write/_close
#include <sys/stat.h> // fstat
#include <windows.h>
#endif // _WIN32

namespace cartesi {

using namespace std::string_literals;

os_mmapd os_mmap(uint64_t length, int flags, const std::string &backing_path) {
if (backing_path.empty() &&
(flags & (OS_MMAP_LOCK_BACKING_FILE | OS_MMAP_SHARED | OS_MMAP_SHARED_TRUNCATE | OS_MMAP_READONLY))) {
throw std::runtime_error{"backing file path must be specified"s};
}

#ifdef HAVE_MMAP
unsigned char *host_memory = nullptr;
int backing_fd = -1;
uint64_t backing_length = 0;
try {
if (!backing_path.empty()) {
// Determine file open flags
const bool writeable = (flags & OS_MMAP_SHARED) && !(flags & OS_MMAP_READONLY);
int oflags = writeable ? O_RDWR : O_RDONLY;
int omode = 0;
if (flags & OS_MMAP_SHARED_TRUNCATE) {
// In case of truncate is specified we actually want to create a new file
oflags = O_CREAT | O_EXCL | O_RDWR;
omode |= (S_IRUSR | S_IWUSR) | S_IRGRP | S_IROTH;
}

// Try to open backing file
backing_fd = open(backing_path.c_str(), oflags, omode);
if (backing_fd < 0) {
throw std::system_error{errno, std::generic_category(),
"could not open backing file '"s + backing_path + "'"s};
}

// Truncate a new file with desired length if needed
if (flags & OS_MMAP_SHARED_TRUNCATE) {
if (ftruncate(backing_fd, static_cast<off_t>(length)) < 0) {
throw std::system_error{errno, std::generic_category(),
"could not lock backing file '"s + backing_path + "'"s};
}
backing_length = length;
} else { // Try to get file size
struct stat statbuf {};
if (fstat(backing_fd, &statbuf) < 0) {
throw std::system_error{errno, std::generic_category(),
"unable to obtain length of backing file '"s + backing_path + "'"s};
}
backing_length = static_cast<uint64_t>(statbuf.st_size);

// Check file length for shared mappings
if ((flags & OS_MMAP_SHARED) && backing_length != length) {
throw std::invalid_argument{"backing file '"s + backing_path + "' size ("s +
std::to_string(backing_length) + ") does not match range length ("s + std::to_string(length) +
")"s};
}
}

#ifdef HAVE_FLOCK
// Set file lock
if (flags & OS_MMAP_LOCK_BACKING_FILE) {
const int flockop = (writeable ? LOCK_EX : LOCK_SH) | LOCK_NB;
if (flock(backing_fd, flockop) < 0) {
throw std::system_error{errno, std::generic_category(),
"could not lock backing file '"s + backing_path + "'"s};
}
}
#endif
}

// Determine map memory flags
int mflags = (flags & OS_MMAP_SHARED) ? MAP_SHARED : MAP_PRIVATE;
if (backing_fd < 0) { // The mapping is not backed by any file
mflags |= MAP_ANONYMOUS;
}
if (flags & OS_MMAP_NO_RESERVE) {
mflags |= MAP_NORESERVE;
}
// Determine map protection flags
int mprot = PROT_READ;
if (!(flags & OS_MMAP_READONLY)) {
mprot |= PROT_WRITE;
}

// Try to map backing file to host memory
host_memory = static_cast<unsigned char *>(mmap(nullptr, length, mprot, mflags, backing_fd, 0));
if (host_memory == MAP_FAILED) {
if (!backing_path.empty()) {
throw std::system_error{errno, std::generic_category(),
"could not map backing file '"s + backing_path + "' to memory"s};
} else {
throw std::system_error{errno, std::generic_category(), "could not map memory"s};
}
}

if (backing_fd >= 0) {
// Retrieve system page size
const long page_size = sysconf(_SC_PAGESIZE);
if (page_size < 0) {
throw std::system_error{errno, std::generic_category(), "unable to retrieve system page size"s};
}
// Determine length of the backing file based
const uint64_t backing_mmaped_length = (backing_length + (page_size - 1)) & ~(page_size - 1);

if (backing_mmaped_length < length) {
unsigned char *
above_memory = // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr)
reinterpret_cast<unsigned char *>(reinterpret_cast<uintptr_t>(host_memory) + backing_mmaped_length);
const uint64_t above_length = length - backing_mmaped_length;
int above_mflags = MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS;
if (flags & OS_MMAP_NO_RESERVE) {
above_mflags |= MAP_NORESERVE;
}

// Overwrite mapping
auto *got_above_memory =
static_cast<unsigned char *>(mmap(above_memory, above_length, mprot, above_mflags, -1, 0));
if (got_above_memory != above_memory) {
throw std::system_error{errno, std::generic_category(),
"could not map memory space above backing file"s};
}
}
}

return os_mmapd{host_memory, length, flags, backing_fd, backing_length, backing_path};
} catch (std::exception &e) {
// Close backing file
if (backing_fd >= 0) {
close(backing_fd);
// Remove created backing file
if (flags & OS_MMAP_SHARED_TRUNCATE) {
unlink(backing_path.c_str());
}
}
// Unmap host memory
if (host_memory) {
munmap(host_memory, length);
}
throw;
}

#elif defined(_WIN32)
#error "NYI"
/*
const int oflags = (shared ? _O_RDWR : _O_RDONLY) | _O_BINARY;
// Try to open backing file
const int backing_file = _open(path, oflags);
if (backing_file < 0) {
throw std::system_error{errno, std::generic_category(), "could not open backing file '"s + path + "'"s};
}
// Try to get file size
struct __stat64 statbuf {};
if (_fstat64(backing_file, &statbuf) < 0) {
_close(backing_file);
throw std::system_error{errno, std::generic_category(),
"unable to obtain length of backing file '"s + path + "'"s};
}
// Check that it matches range length
if (static_cast<uint64_t>(statbuf.st_size) != length) {
_close(backing_file);
throw std::invalid_argument{"backing file '"s + path + "' size ("s +
std::to_string(static_cast<uint64_t>(statbuf.st_size)) + ") does not match range length ("s +
std::to_string(length) + ")"s};
}
// Try to map backing file to host memory
DWORD flProtect = shared ? PAGE_READWRITE : PAGE_READONLY;
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
HANDLE hFile = reinterpret_cast<HANDLE>(_get_osfhandle(backing_file));
HANDLE hFileMappingObject = CreateFileMapping(hFile, NULL, flProtect, length >> 32, length & 0xffffffff, NULL);
if (!hFileMappingObject) {
_close(backing_file);
throw std::system_error{errno, std::generic_category(),
"could not map backing file '"s + path + "' to memory"s};
}
DWORD dwDesiredAccess = shared ? FILE_MAP_WRITE : FILE_MAP_COPY;
auto *host_memory = static_cast<unsigned char *>(MapViewOfFile(hFileMappingObject, dwDesiredAccess, 0, 0, length));
if (!host_memory) {
_close(backing_file);
throw std::system_error{errno, std::generic_category(),
"could not map backing file '"s + path + "' to memory"s};
}
// We can close the file after mapping it, because the OS will retain a reference of the file on its own
_close(backing_file);
return host_memory;
*/

#else
#error "NYI"
/*
if (shared) {
throw std::runtime_error{"shared backing file mapping is unsupported"s};
}
auto fp = unique_fopen(path, "rb", std::nothrow_t{});
if (!fp) {
throw std::system_error{errno, std::generic_category(), "error opening backing file '"s + path + "'"s};
}
// Get file size
if (fseek(fp.get(), 0, SEEK_END)) {
throw std::system_error{errno, std::generic_category(),
"error obtaining length of backing file '"s + path + "'"s};
}
auto backing_length = ftell(fp.get());
if (fseek(fp.get(), 0, SEEK_SET)) {
throw std::system_error{errno, std::generic_category(),
"error obtaining length of backing file '"s + path + "'"s};
}
// Check against PMA range size
if (static_cast<uint64_t>(backing_length) > length) {
throw std::runtime_error{"backing file '"s + path + "' of "s + " is too large for range"s};
}
// use calloc to improve performance
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc, cppcoreguidelines-prefer-member-initializer)
auto host_memory = static_cast<unsigned char *>(std::calloc(1, length));
if (!host_memory) {
throw std::runtime_error{"error allocating memory"s};
}
// Read to host memory
auto read = fread(host_memory, 1, length, fp.get());
(void) read;
if (ferror(fp.get())) {
throw std::system_error{errno, std::generic_category(), "error reading from backing file '"s + path + "'"s};
}
return host_memory;
*/

#endif // HAVE_MMAP
}

void os_munmap(const os_mmapd &mmapd) {
#ifdef HAVE_MMAP
if (mmapd.host_memory != nullptr && mmapd.length > 0) {
munmap(mmapd.host_memory, mmapd.length);
}
if (mmapd.backing_fd != -1) {
close(mmapd.backing_fd);
}

#elif defined(_WIN32)
if (mmapd.host_memory != nullptr) {
UnmapViewOfFile(mmapd.host_memory);
}
if (mmapd.backing_fd != -1) {
_close(mmapd.backing_fd);
}

#else
if (mmapd.host_memory != nullptr) {
std::free(mmapd.host_memory);
}

#endif
}

} // namespace cartesi
Loading

0 comments on commit a99ed4d

Please sign in to comment.