feat: add ext2 block cache
This commit is contained in:
1 parent
a51ee3339f
commit
32660ddd1a
4 files changed
+431
-1
No files matched your search
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 288
|
#define MONTAUK_BUILD_NUMBER 289
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
/*
|
||||||
|
* BlockCache.cpp
|
||||||
|
* Per-mount LRU cache of filesystem blocks
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BlockCache.hpp"
|
||||||
|
#include <Drivers/Storage/BlockDevice.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <Libraries/Memory.hpp>
|
||||||
|
#include <Memory/PageFrameAllocator.hpp>
|
||||||
|
#include <Memory/Heap.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Fs::BlockCache {
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Sizing
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// Capacity is free memory / FreeMemDivisor at creation, clamped to
|
||||||
|
// [MinEntries, MaxEntries]. 32768 4 KiB blocks = 128 MiB, enough to keep
|
||||||
|
// the whole native toolchain (cc1plus, as, ld, headers, libstdc++) hot.
|
||||||
|
static constexpr uint32_t MinEntries = 256;
|
||||||
|
static constexpr uint32_t MaxEntries = 32768;
|
||||||
|
static constexpr uint64_t FreeMemDivisor = 8;
|
||||||
|
|
||||||
|
// Every storage driver accepts at least 128 sectors per command (AHCI and
|
||||||
|
// the NVMe fallback both cap there), so prefetch runs stay within it.
|
||||||
|
static constexpr uint32_t MaxRunSectors = 128;
|
||||||
|
|
||||||
|
static constexpr uint32_t Nil = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Types
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// Entries live in one array and link by index. An entry is in exactly one
|
||||||
|
// of three states: never used (index >= nextFresh, no page), on the free
|
||||||
|
// list (has a page, not hashed), or live (hashed and on the LRU list).
|
||||||
|
struct Entry {
|
||||||
|
uint32_t block;
|
||||||
|
uint32_t hashNext; // bucket chain, or free-list link
|
||||||
|
uint32_t lruPrev;
|
||||||
|
uint32_t lruNext;
|
||||||
|
uint8_t* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Cache {
|
||||||
|
int blockDevIndex;
|
||||||
|
uint64_t partStartLba;
|
||||||
|
uint32_t blockSize;
|
||||||
|
uint32_t sectorsPerBlock;
|
||||||
|
|
||||||
|
Entry* entries;
|
||||||
|
uint32_t* buckets;
|
||||||
|
uint32_t capacity;
|
||||||
|
uint32_t hashShift;
|
||||||
|
int tablePages;
|
||||||
|
|
||||||
|
uint32_t nextFresh;
|
||||||
|
uint32_t freeHead;
|
||||||
|
uint32_t lruHead; // most recently used
|
||||||
|
uint32_t lruTail; // eviction candidate
|
||||||
|
|
||||||
|
// Staging buffer for multi-block prefetch reads (MaxRunSectors long).
|
||||||
|
uint8_t* staging;
|
||||||
|
int stagingPages;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Device I/O
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
static bool DevRead(Cache* c, uint32_t block, uint32_t count, void* buf) {
|
||||||
|
auto* dev = Drivers::Storage::GetBlockDevice(c->blockDevIndex);
|
||||||
|
if (!dev) return false;
|
||||||
|
return dev->ReadSectors(dev->Ctx,
|
||||||
|
c->partStartLba + (uint64_t)block * c->sectorsPerBlock,
|
||||||
|
count * c->sectorsPerBlock, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DevWrite(Cache* c, uint32_t block, const void* buf) {
|
||||||
|
auto* dev = Drivers::Storage::GetBlockDevice(c->blockDevIndex);
|
||||||
|
if (!dev) return false;
|
||||||
|
return dev->WriteSectors(dev->Ctx,
|
||||||
|
c->partStartLba + (uint64_t)block * c->sectorsPerBlock,
|
||||||
|
c->sectorsPerBlock, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Hash and LRU bookkeeping
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
static uint32_t Bucket(const Cache* c, uint32_t block) {
|
||||||
|
return (block * 0x9E3779B1u) >> c->hashShift;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t Lookup(Cache* c, uint32_t block) {
|
||||||
|
uint32_t i = c->buckets[Bucket(c, block)];
|
||||||
|
while (i != Nil) {
|
||||||
|
if (c->entries[i].block == block) return i;
|
||||||
|
i = c->entries[i].hashNext;
|
||||||
|
}
|
||||||
|
return Nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void LruUnlink(Cache* c, uint32_t i) {
|
||||||
|
Entry& e = c->entries[i];
|
||||||
|
if (e.lruPrev != Nil) c->entries[e.lruPrev].lruNext = e.lruNext;
|
||||||
|
else c->lruHead = e.lruNext;
|
||||||
|
if (e.lruNext != Nil) c->entries[e.lruNext].lruPrev = e.lruPrev;
|
||||||
|
else c->lruTail = e.lruPrev;
|
||||||
|
e.lruPrev = e.lruNext = Nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void LruPushFront(Cache* c, uint32_t i) {
|
||||||
|
Entry& e = c->entries[i];
|
||||||
|
e.lruPrev = Nil;
|
||||||
|
e.lruNext = c->lruHead;
|
||||||
|
if (c->lruHead != Nil) c->entries[c->lruHead].lruPrev = i;
|
||||||
|
c->lruHead = i;
|
||||||
|
if (c->lruTail == Nil) c->lruTail = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Touch(Cache* c, uint32_t i) {
|
||||||
|
if (c->lruHead == i) return;
|
||||||
|
LruUnlink(c, i);
|
||||||
|
LruPushFront(c, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HashInsert(Cache* c, uint32_t i) {
|
||||||
|
uint32_t b = Bucket(c, c->entries[i].block);
|
||||||
|
c->entries[i].hashNext = c->buckets[b];
|
||||||
|
c->buckets[b] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HashRemove(Cache* c, uint32_t i) {
|
||||||
|
uint32_t* link = &c->buckets[Bucket(c, c->entries[i].block)];
|
||||||
|
while (*link != Nil) {
|
||||||
|
if (*link == i) {
|
||||||
|
*link = c->entries[i].hashNext;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
link = &c->entries[*link].hashNext;
|
||||||
|
}
|
||||||
|
c->entries[i].hashNext = Nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take a live entry out of the cache and park it (and its page) on the
|
||||||
|
// free list.
|
||||||
|
static void Drop(Cache* c, uint32_t i) {
|
||||||
|
HashRemove(c, i);
|
||||||
|
LruUnlink(c, i);
|
||||||
|
c->entries[i].block = 0;
|
||||||
|
c->entries[i].hashNext = c->freeHead;
|
||||||
|
c->freeHead = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an entry with a page, not hashed and not on the LRU. Prefers the
|
||||||
|
// free list, then a fresh entry, then evicts the least recently used.
|
||||||
|
// Returns Nil only if the cache is empty and no page can be allocated.
|
||||||
|
static uint32_t Acquire(Cache* c) {
|
||||||
|
if (c->freeHead != Nil) {
|
||||||
|
uint32_t i = c->freeHead;
|
||||||
|
c->freeHead = c->entries[i].hashNext;
|
||||||
|
c->entries[i].hashNext = Nil;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c->nextFresh < c->capacity) {
|
||||||
|
void* page = Memory::g_pfa->Allocate();
|
||||||
|
if (page != nullptr) {
|
||||||
|
uint32_t i = c->nextFresh++;
|
||||||
|
Entry& e = c->entries[i];
|
||||||
|
e.block = 0;
|
||||||
|
e.hashNext = e.lruPrev = e.lruNext = Nil;
|
||||||
|
e.data = (uint8_t*)page;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
// Out of memory: fall through and recycle what we already hold.
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t victim = c->lruTail;
|
||||||
|
if (victim == Nil) return Nil;
|
||||||
|
HashRemove(c, victim);
|
||||||
|
LruUnlink(c, victim);
|
||||||
|
return victim;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish a freshly filled entry under `block`.
|
||||||
|
static void Insert(Cache* c, uint32_t i, uint32_t block) {
|
||||||
|
c->entries[i].block = block;
|
||||||
|
HashInsert(c, i);
|
||||||
|
LruPushFront(c, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Public API
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
Cache* Create(int blockDevIndex, uint64_t partStartLba, uint32_t blockSize) {
|
||||||
|
if (blockSize < 512 || blockSize > 0x1000 || (blockSize % 512) != 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
montauk::abi::MemStats stats{};
|
||||||
|
Memory::g_pfa->GetStats(&stats);
|
||||||
|
uint64_t want = stats.freeBytes / FreeMemDivisor / blockSize;
|
||||||
|
if (want > MaxEntries) want = MaxEntries;
|
||||||
|
if (want < MinEntries) want = MinEntries;
|
||||||
|
|
||||||
|
// Round down to a power of two so the bucket count can equal capacity.
|
||||||
|
uint32_t capacity = MinEntries;
|
||||||
|
while ((uint64_t)capacity * 2 <= want) capacity *= 2;
|
||||||
|
|
||||||
|
auto* c = (Cache*)Memory::g_heap->Request(sizeof(Cache));
|
||||||
|
if (c == nullptr) return nullptr;
|
||||||
|
memset(c, 0, sizeof(Cache));
|
||||||
|
|
||||||
|
// Entries and buckets share one allocation. Physically consecutive
|
||||||
|
// runs can be scarce on a fragmented PFA, so halve until it fits.
|
||||||
|
while (capacity >= MinEntries) {
|
||||||
|
uint64_t bytes = (uint64_t)capacity * (sizeof(Entry) + sizeof(uint32_t));
|
||||||
|
int pages = (int)((bytes + 0xFFF) / 0x1000);
|
||||||
|
void* table = Memory::g_pfa->ReallocConsecutive(nullptr, pages);
|
||||||
|
if (table != nullptr) {
|
||||||
|
c->entries = (Entry*)table;
|
||||||
|
c->buckets = (uint32_t*)((uint8_t*)table + (uint64_t)capacity * sizeof(Entry));
|
||||||
|
c->tablePages = pages;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
capacity /= 2;
|
||||||
|
}
|
||||||
|
if (c->entries == nullptr) {
|
||||||
|
Memory::g_heap->Free(c);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t log2 = 0;
|
||||||
|
while ((1u << log2) < capacity) log2++;
|
||||||
|
|
||||||
|
c->blockDevIndex = blockDevIndex;
|
||||||
|
c->partStartLba = partStartLba;
|
||||||
|
c->blockSize = blockSize;
|
||||||
|
c->sectorsPerBlock = blockSize / 512;
|
||||||
|
c->capacity = capacity;
|
||||||
|
c->hashShift = 32 - log2;
|
||||||
|
c->nextFresh = 0;
|
||||||
|
c->freeHead = Nil;
|
||||||
|
c->lruHead = Nil;
|
||||||
|
c->lruTail = Nil;
|
||||||
|
for (uint32_t b = 0; b < capacity; b++) c->buckets[b] = Nil;
|
||||||
|
|
||||||
|
// Prefetch is an optimization; without a staging buffer it is a no-op.
|
||||||
|
c->stagingPages = (int)((MaxRunSectors * 512 + 0xFFF) / 0x1000);
|
||||||
|
c->staging = (uint8_t*)Memory::g_pfa->ReallocConsecutive(nullptr, c->stagingPages);
|
||||||
|
if (c->staging == nullptr) c->stagingPages = 0;
|
||||||
|
|
||||||
|
KernelLogStream(OK, "BlockCache") << "Block cache: up to "
|
||||||
|
<< base::dec << (uint64_t)capacity << " blocks ("
|
||||||
|
<< (uint64_t)capacity * blockSize / (1024 * 1024) << " MiB)";
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Destroy(Cache* c) {
|
||||||
|
if (c == nullptr) return;
|
||||||
|
for (uint32_t i = 0; i < c->nextFresh; i++) {
|
||||||
|
if (c->entries[i].data != nullptr) Memory::g_pfa->Free(c->entries[i].data);
|
||||||
|
}
|
||||||
|
if (c->staging != nullptr) Memory::g_pfa->Free(c->staging, c->stagingPages);
|
||||||
|
Memory::g_pfa->Free(c->entries, c->tablePages);
|
||||||
|
Memory::g_heap->Free(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Read(Cache* c, uint32_t block, void* buf) {
|
||||||
|
if (block == 0) return false;
|
||||||
|
|
||||||
|
uint32_t i = Lookup(c, block);
|
||||||
|
if (i != Nil) {
|
||||||
|
Touch(c, i);
|
||||||
|
memcpy(buf, c->entries[i].data, c->blockSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = Acquire(c);
|
||||||
|
if (i == Nil) return DevRead(c, block, 1, buf);
|
||||||
|
|
||||||
|
if (!DevRead(c, block, 1, c->entries[i].data)) {
|
||||||
|
c->entries[i].hashNext = c->freeHead;
|
||||||
|
c->freeHead = i;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Insert(c, i, block);
|
||||||
|
memcpy(buf, c->entries[i].data, c->blockSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Write(Cache* c, uint32_t block, const void* buf) {
|
||||||
|
if (block == 0) return false;
|
||||||
|
|
||||||
|
uint32_t i = Lookup(c, block);
|
||||||
|
if (!DevWrite(c, block, buf)) {
|
||||||
|
// The on-disk contents are now unknown; don't serve a stale copy.
|
||||||
|
if (i != Nil) Drop(c, i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != Nil) {
|
||||||
|
Touch(c, i);
|
||||||
|
} else {
|
||||||
|
i = Acquire(c);
|
||||||
|
if (i == Nil) return true;
|
||||||
|
Insert(c, i, block);
|
||||||
|
}
|
||||||
|
memcpy(c->entries[i].data, buf, c->blockSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Contains(Cache* c, uint32_t block) {
|
||||||
|
return block != 0 && Lookup(c, block) != Nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t MaxRunBlocks(Cache* c) {
|
||||||
|
return MaxRunSectors / c->sectorsPerBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Prefetch(Cache* c, uint32_t first, uint32_t count) {
|
||||||
|
if (c->staging == nullptr || first == 0) return;
|
||||||
|
|
||||||
|
uint32_t maxRun = MaxRunBlocks(c);
|
||||||
|
uint32_t end = first + count;
|
||||||
|
if (end < first) return; // wrapped
|
||||||
|
|
||||||
|
uint32_t b = first;
|
||||||
|
while (b < end) {
|
||||||
|
if (Lookup(c, b) != Nil) { b++; continue; }
|
||||||
|
|
||||||
|
uint32_t run = 1;
|
||||||
|
while (run < maxRun && b + run < end && Lookup(c, b + run) == Nil) run++;
|
||||||
|
|
||||||
|
if (!DevRead(c, b, run, c->staging)) return;
|
||||||
|
|
||||||
|
for (uint32_t k = 0; k < run; k++) {
|
||||||
|
uint32_t i = Acquire(c);
|
||||||
|
if (i == Nil) return;
|
||||||
|
memcpy(c->entries[i].data, c->staging + (uint64_t)k * c->blockSize,
|
||||||
|
c->blockSize);
|
||||||
|
Insert(c, i, b + k);
|
||||||
|
}
|
||||||
|
b += run;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* BlockCache.hpp
|
||||||
|
* Per-mount LRU cache of filesystem blocks
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Fs::BlockCache {
|
||||||
|
|
||||||
|
// A cache over one partition of a block device, in units of the
|
||||||
|
// filesystem's block size (512..4096 bytes, addressed in 512-byte sectors
|
||||||
|
// from the partition start). Write-through: Write reaches the device
|
||||||
|
// before it returns, so the cache never holds dirty data and there is
|
||||||
|
// nothing to flush on sync or unmount.
|
||||||
|
//
|
||||||
|
// Not internally locked. Callers serialize access; the VFS already holds
|
||||||
|
// vfsLock across every driver call.
|
||||||
|
struct Cache;
|
||||||
|
|
||||||
|
// Create a cache for the partition. Capacity is sized from free memory at
|
||||||
|
// the time of the call; block pages are allocated lazily as the cache
|
||||||
|
// fills and returned on Destroy. Returns nullptr if the bookkeeping
|
||||||
|
// cannot be allocated (callers then do uncached I/O).
|
||||||
|
Cache* Create(int blockDevIndex, uint64_t partStartLba, uint32_t blockSize);
|
||||||
|
void Destroy(Cache* cache);
|
||||||
|
|
||||||
|
// Read one block, from the cache or the device. Block 0 is never cached.
|
||||||
|
bool Read(Cache* cache, uint32_t block, void* buf);
|
||||||
|
|
||||||
|
// Write one block to the device, then update the cached copy.
|
||||||
|
bool Write(Cache* cache, uint32_t block, const void* buf);
|
||||||
|
|
||||||
|
bool Contains(Cache* cache, uint32_t block);
|
||||||
|
|
||||||
|
// Pull `count` physically contiguous blocks starting at `first` into the
|
||||||
|
// cache, in as few device commands as possible. Blocks already cached are
|
||||||
|
// skipped. Best effort: a failed read just leaves those blocks uncached.
|
||||||
|
void Prefetch(Cache* cache, uint32_t first, uint32_t count);
|
||||||
|
|
||||||
|
// Largest run Prefetch reads in one device command.
|
||||||
|
uint32_t MaxRunBlocks(Cache* cache);
|
||||||
|
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "Ext2.hpp"
|
#include "Ext2.hpp"
|
||||||
#include "FsProbe.hpp"
|
#include "FsProbe.hpp"
|
||||||
|
#include "BlockCache.hpp"
|
||||||
#include <Drivers/Storage/BlockDevice.hpp>
|
#include <Drivers/Storage/BlockDevice.hpp>
|
||||||
#include <Terminal/Terminal.hpp>
|
#include <Terminal/Terminal.hpp>
|
||||||
#include <Libraries/Memory.hpp>
|
#include <Libraries/Memory.hpp>
|
||||||
@@ -171,6 +172,10 @@ namespace Fs::Ext2 {
|
|||||||
uint8_t* blockBuf;
|
uint8_t* blockBuf;
|
||||||
int blockBufPages;
|
int blockBufPages;
|
||||||
|
|
||||||
|
// Write-through cache that every ReadBlock/WriteBlock goes through.
|
||||||
|
// Null if it could not be allocated, in which case I/O is uncached.
|
||||||
|
BlockCache::Cache* cache;
|
||||||
|
|
||||||
// Open file handles
|
// Open file handles
|
||||||
Ext2File files[MaxFilesPerInstance];
|
Ext2File files[MaxFilesPerInstance];
|
||||||
|
|
||||||
@@ -238,12 +243,14 @@ namespace Fs::Ext2 {
|
|||||||
|
|
||||||
static bool ReadBlock(const Ext2Instance& inst, uint32_t blockNum, void* buf) {
|
static bool ReadBlock(const Ext2Instance& inst, uint32_t blockNum, void* buf) {
|
||||||
if (blockNum == 0) return false;
|
if (blockNum == 0) return false;
|
||||||
|
if (inst.cache) return BlockCache::Read(inst.cache, blockNum, buf);
|
||||||
return ReadPartSectors(inst, BlockToPartSector(inst, blockNum),
|
return ReadPartSectors(inst, BlockToPartSector(inst, blockNum),
|
||||||
SectorsPerBlock(inst), buf);
|
SectorsPerBlock(inst), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool WriteBlock(const Ext2Instance& inst, uint32_t blockNum, const void* buf) {
|
static bool WriteBlock(const Ext2Instance& inst, uint32_t blockNum, const void* buf) {
|
||||||
if (blockNum == 0) return false;
|
if (blockNum == 0) return false;
|
||||||
|
if (inst.cache) return BlockCache::Write(inst.cache, blockNum, buf);
|
||||||
return WritePartSectors(inst, BlockToPartSector(inst, blockNum),
|
return WritePartSectors(inst, BlockToPartSector(inst, blockNum),
|
||||||
SectorsPerBlock(inst), buf);
|
SectorsPerBlock(inst), buf);
|
||||||
}
|
}
|
||||||
@@ -1294,6 +1301,20 @@ namespace Fs::Ext2 {
|
|||||||
uint32_t physBlock = GetPhysicalBlock(self, file.inode, logicalBlock);
|
uint32_t physBlock = GetPhysicalBlock(self, file.inode, logicalBlock);
|
||||||
if (physBlock == 0) break;
|
if (physBlock == 0) break;
|
||||||
|
|
||||||
|
// On a miss, read ahead the physically contiguous run of this
|
||||||
|
// file's following blocks in one device command. Resolving the
|
||||||
|
// run is cheap: the indirect blocks it walks are cached too.
|
||||||
|
if (self.cache && !BlockCache::Contains(self.cache, physBlock)) {
|
||||||
|
uint32_t lastLogical = (fileSize - 1) / blockSize;
|
||||||
|
uint32_t maxRun = BlockCache::MaxRunBlocks(self.cache);
|
||||||
|
uint32_t run = 1;
|
||||||
|
while (run < maxRun && logicalBlock + run <= lastLogical &&
|
||||||
|
GetPhysicalBlock(self, file.inode, logicalBlock + run) == physBlock + run) {
|
||||||
|
run++;
|
||||||
|
}
|
||||||
|
BlockCache::Prefetch(self.cache, physBlock, run);
|
||||||
|
}
|
||||||
|
|
||||||
if (!ReadBlock(self, physBlock, dataBuf)) break;
|
if (!ReadBlock(self, physBlock, dataBuf)) break;
|
||||||
|
|
||||||
uint32_t available = blockSize - blockOff;
|
uint32_t available = blockSize - blockOff;
|
||||||
@@ -1882,6 +1903,7 @@ namespace Fs::Ext2 {
|
|||||||
if (self->dirNames != nullptr && self->dirNamesPages > 0) {
|
if (self->dirNames != nullptr && self->dirNamesPages > 0) {
|
||||||
Memory::g_pfa->Free(self->dirNames, self->dirNamesPages);
|
Memory::g_pfa->Free(self->dirNames, self->dirNamesPages);
|
||||||
}
|
}
|
||||||
|
BlockCache::Destroy(self->cache);
|
||||||
|
|
||||||
self->active = false;
|
self->active = false;
|
||||||
g_instanceSlots[(std::size_t)inst] = nullptr;
|
g_instanceSlots[(std::size_t)inst] = nullptr;
|
||||||
@@ -1916,6 +1938,7 @@ namespace Fs::Ext2 {
|
|||||||
if (self->bgdt != nullptr && self->bgdtPages > 0) {
|
if (self->bgdt != nullptr && self->bgdtPages > 0) {
|
||||||
Memory::g_pfa->Free(self->bgdt, self->bgdtPages);
|
Memory::g_pfa->Free(self->bgdt, self->bgdtPages);
|
||||||
}
|
}
|
||||||
|
BlockCache::Destroy(self->cache);
|
||||||
if (self->driver != nullptr) Memory::g_heap->Free(self->driver);
|
if (self->driver != nullptr) Memory::g_heap->Free(self->driver);
|
||||||
Memory::g_heap->Free(self);
|
Memory::g_heap->Free(self);
|
||||||
}
|
}
|
||||||
@@ -2025,6 +2048,12 @@ namespace Fs::Ext2 {
|
|||||||
return AbortMount(idx);
|
return AbortMount(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inst.cache = BlockCache::Create(blockDevIndex, startLba, blockSize);
|
||||||
|
if (!inst.cache) {
|
||||||
|
KernelLogStream(WARNING, "Ext2") << "No memory for a block cache; "
|
||||||
|
"volume I/O will be uncached";
|
||||||
|
}
|
||||||
|
|
||||||
// Load block group descriptor table
|
// Load block group descriptor table
|
||||||
// BGDT starts at the block after the superblock
|
// BGDT starts at the block after the superblock
|
||||||
uint32_t bgdtStartBlock = inst.firstDataBlock + 1;
|
uint32_t bgdtStartBlock = inst.firstDataBlock + 1;
|
||||||
|
|||||||
Reference in new issue
Block a user