Step 4 - 二级页表 + 缺页 + FIFO

This commit is contained in:
2026-04-14 08:34:17 +08:00
parent bda18464c8
commit 38aa8e9bf4
11 changed files with 605 additions and 10 deletions
@@ -0,0 +1,74 @@
using System;
namespace VMdemo.Simulation
{
public readonly struct MemoryAccessResult
{
public MemoryAccessResult(ulong pfn, bool pageTableHit, bool pageFault, EvictionInfo eviction)
{
Pfn = pfn;
PageTableHit = pageTableHit;
PageFault = pageFault;
Eviction = eviction;
}
public ulong Pfn { get; }
public bool PageTableHit { get; }
public bool PageFault { get; }
public EvictionInfo Eviction { get; }
}
public static class MemoryAccessResolver
{
public static MemoryAccessResult Resolve(
ulong vpn,
ulong l1Index,
ulong l2Index,
TwoLevelPageTable pageTable,
PhysicalMemoryManager physicalMemoryManager,
TlbCache tlbCache)
{
if (pageTable == null)
{
throw new ArgumentNullException(nameof(pageTable));
}
if (physicalMemoryManager == null)
{
throw new ArgumentNullException(nameof(physicalMemoryManager));
}
if (tlbCache == null)
{
throw new ArgumentNullException(nameof(tlbCache));
}
if (pageTable.TryGetPresentPfn(l1Index, l2Index, out var pfn))
{
tlbCache.InsertOrUpdate(vpn, pfn);
return new MemoryAccessResult(pfn, true, false, EvictionInfo.None);
}
var allocation = physicalMemoryManager.AllocateForVpn(vpn);
if (allocation.Eviction.HasEvicted)
{
pageTable.GetIndicesFromVpn(
allocation.Eviction.EvictedVpn,
out var evictedL1Index,
out var evictedL2Index);
pageTable.MarkNotPresent(evictedL1Index, evictedL2Index);
tlbCache.Remove(allocation.Eviction.EvictedVpn);
}
pageTable.SetPresent(l1Index, l2Index, allocation.AssignedPfn);
tlbCache.InsertOrUpdate(vpn, allocation.AssignedPfn);
return new MemoryAccessResult(
allocation.AssignedPfn,
pageTableHit: false,
pageFault: true,
allocation.Eviction);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69a345783d80681488273b665f20247f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,36 @@
using System;
namespace VMdemo.Simulation
{
[Serializable]
public readonly struct PageTableEntry
{
public PageTableEntry(ulong pfn, bool present, bool dirty = false)
{
Pfn = pfn;
Present = present;
Dirty = dirty;
}
public ulong Pfn { get; }
public bool Present { get; }
public bool Dirty { get; }
public static PageTableEntry NotPresent => new PageTableEntry(0UL, false, false);
public PageTableEntry MarkPresent(ulong pfn, bool dirty = false)
{
return new PageTableEntry(pfn, true, dirty);
}
public PageTableEntry MarkNotPresent()
{
return new PageTableEntry(Pfn, false, Dirty);
}
public override string ToString()
{
return $"PFN={Pfn}, Present={Present}, Dirty={Dirty}";
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 319e4b22dc36b754db1b833626fa2681
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
namespace VMdemo.Simulation
{
public readonly struct EvictionInfo
{
public EvictionInfo(bool hasEvicted, ulong evictedVpn, ulong evictedPfn)
{
HasEvicted = hasEvicted;
EvictedVpn = evictedVpn;
EvictedPfn = evictedPfn;
}
public bool HasEvicted { get; }
public ulong EvictedVpn { get; }
public ulong EvictedPfn { get; }
public static EvictionInfo None => new EvictionInfo(false, 0UL, 0UL);
}
public readonly struct AllocationResult
{
public AllocationResult(ulong assignedPfn, bool wasAlreadyResident, EvictionInfo eviction)
{
AssignedPfn = assignedPfn;
WasAlreadyResident = wasAlreadyResident;
Eviction = eviction;
}
public ulong AssignedPfn { get; }
public bool WasAlreadyResident { get; }
public EvictionInfo Eviction { get; }
}
public class PhysicalMemoryManager
{
private readonly Queue<ResidentPage> _fifoQueue = new Queue<ResidentPage>();
private readonly Dictionary<ulong, ulong> _residentByVpn = new Dictionary<ulong, ulong>();
private ulong _nextFreeFrame;
public PhysicalMemoryManager(ulong frameCount)
{
if (frameCount < 1UL)
{
throw new ArgumentOutOfRangeException(nameof(frameCount), "frameCount 必须 >= 1。");
}
FrameCount = frameCount;
_nextFreeFrame = 0UL;
}
public ulong FrameCount { get; }
public int ResidentCount => _residentByVpn.Count;
public bool TryGetResidentPfn(ulong vpn, out ulong pfn)
{
return _residentByVpn.TryGetValue(vpn, out pfn);
}
public AllocationResult AllocateForVpn(ulong vpn)
{
if (_residentByVpn.TryGetValue(vpn, out var existingPfn))
{
return new AllocationResult(existingPfn, true, EvictionInfo.None);
}
EvictionInfo evictionInfo;
ulong assignedPfn;
if (_nextFreeFrame < FrameCount)
{
assignedPfn = _nextFreeFrame;
_nextFreeFrame++;
evictionInfo = EvictionInfo.None;
}
else
{
if (_fifoQueue.Count == 0)
{
throw new InvalidOperationException("内存已满但 FIFO 队列为空,状态异常。");
}
var victim = _fifoQueue.Dequeue();
if (!_residentByVpn.Remove(victim.Vpn))
{
throw new InvalidOperationException("FIFO 淘汰项未出现在驻留映射中,状态异常。");
}
assignedPfn = victim.Pfn;
evictionInfo = new EvictionInfo(true, victim.Vpn, victim.Pfn);
}
_residentByVpn[vpn] = assignedPfn;
_fifoQueue.Enqueue(new ResidentPage(vpn, assignedPfn));
return new AllocationResult(assignedPfn, false, evictionInfo);
}
public IReadOnlyList<ulong> GetFifoVpnOrder()
{
var order = new List<ulong>(_fifoQueue.Count);
foreach (var page in _fifoQueue)
{
order.Add(page.Vpn);
}
return order;
}
public string FormatFifoQueue()
{
var order = GetFifoVpnOrder();
if (order.Count == 0)
{
return "FIFO=[]";
}
return $"FIFO=[{string.Join(",", order)}]";
}
private readonly struct ResidentPage
{
public ResidentPage(ulong vpn, ulong pfn)
{
Vpn = vpn;
Pfn = pfn;
}
public ulong Vpn { get; }
public ulong Pfn { get; }
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 442eea92195560643874961592e5317d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
namespace VMdemo.Simulation
{
public class TwoLevelPageTable
{
private readonly Dictionary<ulong, Dictionary<ulong, PageTableEntry>> _table;
private readonly ulong _l1Mask;
private readonly ulong _l2Mask;
private readonly ulong _vpnMask;
public TwoLevelPageTable(int l1Bits, int l2Bits)
{
if (l1Bits < 0 || l1Bits > 64)
{
throw new ArgumentOutOfRangeException(nameof(l1Bits), "l1Bits 必须在 0 到 64 之间。");
}
if (l2Bits < 0 || l2Bits > 64)
{
throw new ArgumentOutOfRangeException(nameof(l2Bits), "l2Bits 必须在 0 到 64 之间。");
}
if (l1Bits + l2Bits <= 0 || l1Bits + l2Bits > 64)
{
throw new ArgumentOutOfRangeException(nameof(l2Bits), "l1Bits + l2Bits 必须在 1 到 64 之间。");
}
L1Bits = l1Bits;
L2Bits = l2Bits;
_table = new Dictionary<ulong, Dictionary<ulong, PageTableEntry>>();
_l1Mask = GetBitMask(l1Bits);
_l2Mask = GetBitMask(l2Bits);
_vpnMask = GetBitMask(l1Bits + l2Bits);
}
public int L1Bits { get; }
public int L2Bits { get; }
public bool TryGetPresentPfn(ulong l1Index, ulong l2Index, out ulong pfn)
{
ValidateIndices(l1Index, l2Index);
if (!_table.TryGetValue(l1Index, out var l2Table))
{
pfn = 0UL;
return false;
}
if (!l2Table.TryGetValue(l2Index, out var entry) || !entry.Present)
{
pfn = 0UL;
return false;
}
pfn = entry.Pfn;
return true;
}
public bool TryGetEntry(ulong l1Index, ulong l2Index, out PageTableEntry entry)
{
ValidateIndices(l1Index, l2Index);
if (_table.TryGetValue(l1Index, out var l2Table) &&
l2Table.TryGetValue(l2Index, out var value))
{
entry = value;
return true;
}
entry = PageTableEntry.NotPresent;
return false;
}
public void SetPresent(ulong l1Index, ulong l2Index, ulong pfn, bool dirty = false)
{
ValidateIndices(l1Index, l2Index);
if (!_table.TryGetValue(l1Index, out var l2Table))
{
l2Table = new Dictionary<ulong, PageTableEntry>();
_table[l1Index] = l2Table;
}
l2Table[l2Index] = new PageTableEntry(pfn, true, dirty);
}
public bool MarkNotPresent(ulong l1Index, ulong l2Index)
{
ValidateIndices(l1Index, l2Index);
if (!_table.TryGetValue(l1Index, out var l2Table) ||
!l2Table.TryGetValue(l2Index, out var entry))
{
return false;
}
if (!entry.Present)
{
return false;
}
l2Table[l2Index] = entry.MarkNotPresent();
return true;
}
public void GetIndicesFromVpn(ulong vpn, out ulong l1Index, out ulong l2Index)
{
if (L1Bits + L2Bits < 64 && (vpn & ~_vpnMask) != 0UL)
{
throw new ArgumentOutOfRangeException(nameof(vpn), "vpn 超出页表可表示范围。");
}
l2Index = vpn & _l2Mask;
l1Index = L2Bits >= 64 ? 0UL : vpn >> L2Bits;
ValidateIndices(l1Index, l2Index);
}
private void ValidateIndices(ulong l1Index, ulong l2Index)
{
if (!IsWithinBits(l1Index, L1Bits, _l1Mask))
{
throw new ArgumentOutOfRangeException(nameof(l1Index), "l1Index 超出 L1 位宽范围。");
}
if (!IsWithinBits(l2Index, L2Bits, _l2Mask))
{
throw new ArgumentOutOfRangeException(nameof(l2Index), "l2Index 超出 L2 位宽范围。");
}
}
private static bool IsWithinBits(ulong value, int bits, ulong mask)
{
if (bits == 64)
{
return true;
}
if (bits == 0)
{
return value == 0UL;
}
return (value & ~mask) == 0UL;
}
private static ulong GetBitMask(int bits)
{
if (bits <= 0)
{
return 0UL;
}
if (bits >= 64)
{
return ulong.MaxValue;
}
return (1UL << bits) - 1UL;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ee7fcf06ce017648a41c05cc07db305
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: