Skip to main content
The sector management system is the foundation of WinFsp-MemFs-Extended’s performance advantage over the original WinFsp memfs. Instead of allocating heap memory for each file individually, the system uses vectors of fixed-size sectors.

Sector structure

A Sector is a fixed-size buffer that stores file data. The size is defined by FULL_SECTOR_SIZE and the structure is explicitly packed to prevent padding:
#pragma pack(push, memefsNoPadding, 1)
struct Sector {
    byte Bytes[FULL_SECTOR_SIZE];
};
#pragma pack(pop, memefsNoPadding)
This packing ensures that sectors consume exactly the intended amount of memory, regardless of compiler alignment settings. See sectors.h:6-11 for the structure definition.

SectorNode structure

Each file owns a SectorNode that contains a vector of sector pointers:
struct SectorNode {
    SectorVector Sectors;           // std::vector<Sector*>
    std::shared_mutex SectorsMutex; // Thread-safe access
};
Key features:
  • Vector storage: Uses std::vector<Sector*> to store pointers to sectors
  • Thread safety: A shared mutex allows multiple readers or a single writer
  • Automatic cleanup: The destructor frees all sectors when the file is deleted
  • Move semantics: Supports efficient transfer of ownership
See sectors.h:15-29 for the full interface.

SectorManager class

The SectorManager coordinates all sector allocation and I/O operations. Each MemFs instance owns one SectorManager that manages the private heap.

Initialization

When you create a SectorManager, it creates a dedicated Windows heap:
SectorManager::SectorManager() {
    this->heap = HeapCreate(0, 0, 0);
    if (this->heap == nullptr || this->heap == INVALID_HANDLE_VALUE) {
        throw std::runtime_error("Could not create heap");
    }
}
This private heap provides better performance than using the default process heap for frequent allocations. See sectors.cpp:8-14 for the implementation.

Sector alignment

The AlignSize() method aligns byte sizes to sector boundaries:
size_t SectorManager::AlignSize(const size_t size, const bool alignUp) {
    const size_t remainder = size % FULL_SECTOR_SIZE;
    if (remainder == 0) {
        return size;
    }
    return size + (alignUp ? FULL_SECTOR_SIZE : 0) - remainder;
}
You can align either up (for allocation) or down (for offset calculations). See sectors.cpp:34-41 for the implementation.

Allocation and resizing

When you resize a file, the ReAllocate() method adjusts the number of sectors:
bool SectorManager::ReAllocate(SectorNode& node, const size_t size) {
    std::unique_lock writeLock(node.SectorsMutex);
    
    const SIZE_T alignedSize = AlignSize(size);
    const UINT64 wantedSectorCount = GetSectorAmount(alignedSize);
    
    if (vectorSize < wantedSectorCount) {
        // Growing: allocate new sectors
        node.Sectors.resize(wantedSectorCount);
        for (UINT64 i = vectorSize; i < wantedSectorCount; i++) {
            node.Sectors[i] = HeapAlloc(this->heap, 0, sizeof(Sector));
        }
    } else if (vectorSize > wantedSectorCount) {
        // Shrinking: free excess sectors
        for (UINT64 i = wantedSectorCount; i < vectorSize; i++) {
            HeapFree(this->heap, 0, node.Sectors[i]);
        }
        node.Sectors.resize(wantedSectorCount);
    }
    
    return true;
}
The process:
  1. Acquire write lock on the SectorNode’s mutex
  2. Calculate required sectors based on the new size
  3. Grow the vector if more sectors are needed:
    • Resize the vector to the new count
    • Allocate each new sector from the heap
  4. Shrink the vector if fewer sectors are needed:
    • Free excess sectors back to the heap
    • Resize the vector to the new count
  5. Update allocation counter to track total memory usage
If allocation fails, the method attempts to roll back to the previous size. See sectors.cpp:48-104 for the full implementation.

Read and write operations

The ReadWrite() template method handles both reading and writing:
template <bool IsReading>
bool SectorManager::ReadWrite(SectorNode& node, void* buffer, 
                              const size_t size, const size_t offset);
The algorithm:
  1. Acquire shared lock for thread-safe access
  2. Calculate sector range based on offset and size
  3. Handle first sector (may have offset within the sector)
  4. Process middle sectors (full sector copies)
  5. Handle last sector (may be partial)
The template parameter IsReading determines whether to copy from sectors to buffer (read) or from buffer to sectors (write). See sectors.cpp:118-160 for the implementation.

Vector advantages

Using vectors of sector pointers provides several benefits:

Efficient resizing

Vectors handle memory reallocation efficiently when growing or shrinking. Only the pointer array needs to be resized, not the actual sector data.

Reduced fragmentation

Fixed-size sectors reduce heap fragmentation compared to variable-size allocations. The private heap can optimize for this pattern.

Better cache locality

The vector of pointers is contiguous in memory, improving cache performance when accessing sector metadata.

Predictable performance

Sector allocation time is constant regardless of file size, unlike heap allocations that may need to search for suitable blocks.

Memory tracking

The SectorManager tracks total allocated sectors using an atomic counter:
volatile UINT64 allocatedSectors{0};
This counter is updated atomically during allocation and deallocation:
  • InterlockedExchangeAdd() when allocating sectors
  • InterlockedExchangeSubtract() when freeing sectors
You can query the current usage with GetAllocatedSectors() to monitor memory consumption. See sectors.h:54 and sectors.cpp:114-116 for the tracking mechanism.

Build docs developers (and LLMs) love