memory: allow concurrent backing reads

This commit is contained in:
nmzik
2026-10-07 23:10:14 +04:00
parent 4d10ea17a0
commit 7e011fa565
4 changed files with 202 additions and 49 deletions
+6
View File
@@ -21,6 +21,7 @@
#include <map>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -168,6 +169,7 @@ static bool VirtualRangesOverlap(uint64_t left_start, uint64_t left_size, uint64
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
static uint32_t g_test_backing_store_unmaps_before_failure = UINT32_MAX;
static callback_func_t g_test_before_backing_map = nullptr;
static callback_func_t g_test_backing_read = nullptr;
#endif
#include "memoryAddressSpace.inc"
@@ -3569,6 +3571,10 @@ void TestBeforeNextBackingMap(callback_func_t callback) {
g_test_before_backing_map = callback;
}
void TestSetBackingReadCallback(callback_func_t callback) {
g_test_backing_read = callback;
}
void TestFailNextPhysicalMemoryUnmap() {
TestFailPhysicalMemoryUnmapAfter(0);
}
+1
View File
@@ -192,6 +192,7 @@ bool FreeGuestMemory(uint64_t vaddr, uint64_t size);
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
void TestBeforeNextBackingMap(callback_func_t callback);
void TestSetBackingReadCallback(callback_func_t callback);
void TestFailNextPhysicalMemoryUnmap();
void TestFailPhysicalMemoryUnmapAfter(uint32_t successful_unmaps);
void TestFailGuestBackingStoreUnmapAfter(uint32_t successful_unmaps);
+81 -49
View File
@@ -161,30 +161,90 @@ public:
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
return false;
}
std::lock_guard lock(m_mutex);
std::memset(m_backing_base + backing_offset, 0, size);
return true;
}
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) {
if (data == nullptr) {
if (!IsAvailable() || data == nullptr || size == 0 || UINT64_MAX - vaddr < size) {
return false;
}
std::lock_guard lock(m_mutex);
const auto end = vaddr + size;
auto first = m_maps.upper_bound(vaddr);
if (first == m_maps.begin()) {
return false;
}
--first;
auto current = vaddr;
for (auto mapping = first; current < end; ++mapping) {
if (mapping == m_maps.end() || current < mapping->second.vaddr ||
current >= mapping->second.vaddr + mapping->second.size) {
return false;
}
const auto& entry = mapping->second;
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) {
return false;
}
current += bytes;
}
const auto* source = static_cast<const uint8_t*>(data);
return TryTransferBacking(vaddr, size,
[source](uint8_t* backing, uint64_t data_offset, size_t bytes) {
std::memcpy(backing, source + data_offset, bytes);
});
current = vaddr;
for (auto mapping = first; current < end; ++mapping) {
const auto& entry = mapping->second;
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
const auto offset = entry.backing_offset + current - entry.vaddr;
std::memcpy(m_backing_base + offset, source + (current - vaddr), bytes);
current += bytes;
}
return true;
}
bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) {
if (data == nullptr) {
if (!IsAvailable() || data == nullptr || size == 0 || UINT64_MAX - vaddr < size) {
return false;
}
std::shared_lock lock(m_mutex);
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
if (g_test_backing_read != nullptr) {
g_test_backing_read(vaddr, size);
}
#endif
const auto end = vaddr + size;
auto first = m_maps.upper_bound(vaddr);
if (first == m_maps.begin()) {
return false;
}
--first;
auto current = vaddr;
for (auto mapping = first; current < end; ++mapping) {
if (mapping == m_maps.end() || current < mapping->second.vaddr ||
current >= mapping->second.vaddr + mapping->second.size) {
return false;
}
const auto& entry = mapping->second;
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) {
return false;
}
current += bytes;
}
auto* destination = static_cast<uint8_t*>(data);
return TryTransferBacking(
vaddr, size, [destination](uint8_t* backing, uint64_t data_offset, size_t bytes) {
std::memcpy(destination + data_offset, backing, bytes);
});
current = vaddr;
for (auto mapping = first; current < end; ++mapping) {
const auto& entry = mapping->second;
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
const auto offset = entry.backing_offset + current - entry.vaddr;
std::memcpy(destination + (current - vaddr), m_backing_base + offset, bytes);
current += bytes;
}
return true;
}
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) {
@@ -192,7 +252,12 @@ public:
return false;
}
std::lock_guard lock(m_mutex);
std::shared_lock lock(m_mutex);
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
if (g_test_backing_read != nullptr) {
g_test_backing_read(vaddr, size);
}
#endif
const auto end = vaddr + size;
auto first = m_maps.lower_bound(vaddr);
if (first != m_maps.begin()) {
@@ -437,7 +502,7 @@ public:
return false;
}
std::lock_guard lock(m_mutex);
std::shared_lock lock(m_mutex);
const auto end = vaddr + size;
auto current = vaddr;
@@ -457,39 +522,6 @@ public:
}
private:
template <typename Transfer>
bool TryTransferBacking(uint64_t vaddr, uint64_t size, Transfer transfer) {
if (!IsAvailable() || size == 0 || UINT64_MAX - vaddr < size) {
return false;
}
std::lock_guard lock(m_mutex);
const auto end = vaddr + size;
auto current = vaddr;
while (current < end) {
MapEntry entry {};
if (!FindContainingUnlocked(current, 1, &entry)) {
return false;
}
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) {
return false;
}
current += bytes;
}
current = vaddr;
while (current < end) {
MapEntry entry {};
EXIT_IF(!FindContainingUnlocked(current, 1, &entry));
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
const auto offset = entry.backing_offset + current - entry.vaddr;
transfer(m_backing_base + offset, current - vaddr, static_cast<size_t>(bytes));
current += bytes;
}
return true;
}
struct MapEntry {
uint64_t vaddr = 0;
uint64_t size = 0;
@@ -656,7 +688,7 @@ private:
uint8_t* m_backing_base = nullptr;
uint64_t m_size = 0;
std::mutex m_mutex;
std::shared_mutex m_mutex;
std::map<uint64_t, MapEntry> m_maps;
};
@@ -727,7 +759,7 @@ public:
if (size == 0 || UINT64_MAX - vaddr < size) {
return false;
}
std::lock_guard lock(m_mutex);
std::shared_lock lock(m_mutex);
return std::any_of(m_owned.begin(), m_owned.end(), [vaddr, size](const auto& range) {
return vaddr >= range.first && vaddr + size <= range.first + range.second;
});
@@ -755,7 +787,7 @@ public:
if (!Owns(vaddr, size)) {
return false;
}
std::lock_guard lock(m_mutex);
std::shared_lock lock(m_mutex);
auto mapping = m_mapped.upper_bound(vaddr);
if (mapping != m_mapped.begin()) {
--mapping;
@@ -1381,7 +1413,7 @@ private:
#endif
}
std::mutex m_mutex;
std::shared_mutex m_mutex;
std::map<uint64_t, uint64_t> m_free;
std::map<uint64_t, MappedRegion> m_mapped;
std::vector<std::pair<uint64_t, uint64_t>> m_owned;