From 38aa8e9bf4c77ffc2fb59237fc76cf20d446fc3d Mon Sep 17 00:00:00 2001 From: SepComet <2428390463@qq.com> Date: Tue, 14 Apr 2026 08:34:17 +0800 Subject: [PATCH] =?UTF-8?q?Step=204=20-=20=E4=BA=8C=E7=BA=A7=E9=A1=B5?= =?UTF-8?q?=E8=A1=A8=20+=20=E7=BC=BA=E9=A1=B5=20+=20FIFO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Simulation/MemoryAccessResolver.cs | 74 ++++++++ .../Simulation/MemoryAccessResolver.cs.meta | 11 ++ Assets/Scripts/Simulation/PageTableEntry.cs | 36 ++++ .../Scripts/Simulation/PageTableEntry.cs.meta | 11 ++ .../Simulation/PhysicalMemoryManager.cs | 133 ++++++++++++++ .../Simulation/PhysicalMemoryManager.cs.meta | 11 ++ .../Scripts/Simulation/TwoLevelPageTable.cs | 163 ++++++++++++++++++ .../Simulation/TwoLevelPageTable.cs.meta | 11 ++ .../EditMode/Step4PageTableAndFifoTests.cs | 134 ++++++++++++++ .../Step4PageTableAndFifoTests.cs.meta | 11 ++ doc/MVP-TODO.md | 20 +-- 11 files changed, 605 insertions(+), 10 deletions(-) create mode 100644 Assets/Scripts/Simulation/MemoryAccessResolver.cs create mode 100644 Assets/Scripts/Simulation/MemoryAccessResolver.cs.meta create mode 100644 Assets/Scripts/Simulation/PageTableEntry.cs create mode 100644 Assets/Scripts/Simulation/PageTableEntry.cs.meta create mode 100644 Assets/Scripts/Simulation/PhysicalMemoryManager.cs create mode 100644 Assets/Scripts/Simulation/PhysicalMemoryManager.cs.meta create mode 100644 Assets/Scripts/Simulation/TwoLevelPageTable.cs create mode 100644 Assets/Scripts/Simulation/TwoLevelPageTable.cs.meta create mode 100644 Assets/Tests/EditMode/Step4PageTableAndFifoTests.cs create mode 100644 Assets/Tests/EditMode/Step4PageTableAndFifoTests.cs.meta diff --git a/Assets/Scripts/Simulation/MemoryAccessResolver.cs b/Assets/Scripts/Simulation/MemoryAccessResolver.cs new file mode 100644 index 0000000..5f795f6 --- /dev/null +++ b/Assets/Scripts/Simulation/MemoryAccessResolver.cs @@ -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); + } + } +} diff --git a/Assets/Scripts/Simulation/MemoryAccessResolver.cs.meta b/Assets/Scripts/Simulation/MemoryAccessResolver.cs.meta new file mode 100644 index 0000000..0168283 --- /dev/null +++ b/Assets/Scripts/Simulation/MemoryAccessResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 69a345783d80681488273b665f20247f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Simulation/PageTableEntry.cs b/Assets/Scripts/Simulation/PageTableEntry.cs new file mode 100644 index 0000000..bf55984 --- /dev/null +++ b/Assets/Scripts/Simulation/PageTableEntry.cs @@ -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}"; + } + } +} diff --git a/Assets/Scripts/Simulation/PageTableEntry.cs.meta b/Assets/Scripts/Simulation/PageTableEntry.cs.meta new file mode 100644 index 0000000..fccfdb9 --- /dev/null +++ b/Assets/Scripts/Simulation/PageTableEntry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 319e4b22dc36b754db1b833626fa2681 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Simulation/PhysicalMemoryManager.cs b/Assets/Scripts/Simulation/PhysicalMemoryManager.cs new file mode 100644 index 0000000..0f32672 --- /dev/null +++ b/Assets/Scripts/Simulation/PhysicalMemoryManager.cs @@ -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 _fifoQueue = new Queue(); + private readonly Dictionary _residentByVpn = new Dictionary(); + 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 GetFifoVpnOrder() + { + var order = new List(_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; } + } + } +} diff --git a/Assets/Scripts/Simulation/PhysicalMemoryManager.cs.meta b/Assets/Scripts/Simulation/PhysicalMemoryManager.cs.meta new file mode 100644 index 0000000..c7f9131 --- /dev/null +++ b/Assets/Scripts/Simulation/PhysicalMemoryManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 442eea92195560643874961592e5317d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Simulation/TwoLevelPageTable.cs b/Assets/Scripts/Simulation/TwoLevelPageTable.cs new file mode 100644 index 0000000..30295aa --- /dev/null +++ b/Assets/Scripts/Simulation/TwoLevelPageTable.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; + +namespace VMdemo.Simulation +{ + public class TwoLevelPageTable + { + private readonly Dictionary> _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>(); + _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(); + _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; + } + } +} diff --git a/Assets/Scripts/Simulation/TwoLevelPageTable.cs.meta b/Assets/Scripts/Simulation/TwoLevelPageTable.cs.meta new file mode 100644 index 0000000..7bdedf0 --- /dev/null +++ b/Assets/Scripts/Simulation/TwoLevelPageTable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ee7fcf06ce017648a41c05cc07db305 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/EditMode/Step4PageTableAndFifoTests.cs b/Assets/Tests/EditMode/Step4PageTableAndFifoTests.cs new file mode 100644 index 0000000..cb89c9e --- /dev/null +++ b/Assets/Tests/EditMode/Step4PageTableAndFifoTests.cs @@ -0,0 +1,134 @@ +using System; +using NUnit.Framework; +using VMdemo.Simulation; + +namespace VMdemo.Tests.EditMode +{ + public class Step4PageTableAndFifoTests + { + [Test] + public void TwoLevelPageTable_SetPresent_ThenTryGetPresentPfn_Hits() + { + var pageTable = new TwoLevelPageTable(3, 2); + pageTable.SetPresent(5UL, 3UL, 9UL, dirty: true); + + var hit = pageTable.TryGetPresentPfn(5UL, 3UL, out var pfn); + var exists = pageTable.TryGetEntry(5UL, 3UL, out var entry); + + Assert.IsTrue(hit); + Assert.That(pfn, Is.EqualTo(9UL)); + Assert.IsTrue(exists); + Assert.That(entry.Present, Is.True); + Assert.That(entry.Dirty, Is.True); + } + + [Test] + public void TwoLevelPageTable_MarkNotPresent_ThenMisses() + { + var pageTable = new TwoLevelPageTable(3, 2); + pageTable.SetPresent(1UL, 2UL, 7UL); + + var changed = pageTable.MarkNotPresent(1UL, 2UL); + var changedAgain = pageTable.MarkNotPresent(1UL, 2UL); + var hitAfterMark = pageTable.TryGetPresentPfn(1UL, 2UL, out _); + var exists = pageTable.TryGetEntry(1UL, 2UL, out var entry); + + Assert.IsTrue(changed); + Assert.IsFalse(changedAgain); + Assert.IsFalse(hitAfterMark); + Assert.IsTrue(exists); + Assert.IsFalse(entry.Present); + } + + [Test] + public void PhysicalMemoryManager_AllocateForVpn_EvictsInFifoOrder() + { + var manager = new PhysicalMemoryManager(2UL); + + var r1 = manager.AllocateForVpn(1UL); + var r2 = manager.AllocateForVpn(2UL); + var r3 = manager.AllocateForVpn(3UL); // evict 1 + var r4 = manager.AllocateForVpn(4UL); // evict 2 + + Assert.That(r1.AssignedPfn, Is.EqualTo(0UL)); + Assert.That(r2.AssignedPfn, Is.EqualTo(1UL)); + Assert.That(r3.AssignedPfn, Is.EqualTo(0UL)); + Assert.That(r4.AssignedPfn, Is.EqualTo(1UL)); + + Assert.IsFalse(r1.Eviction.HasEvicted); + Assert.IsFalse(r2.Eviction.HasEvicted); + Assert.IsTrue(r3.Eviction.HasEvicted); + Assert.That(r3.Eviction.EvictedVpn, Is.EqualTo(1UL)); + Assert.That(r3.Eviction.EvictedPfn, Is.EqualTo(0UL)); + Assert.IsTrue(r4.Eviction.HasEvicted); + Assert.That(r4.Eviction.EvictedVpn, Is.EqualTo(2UL)); + Assert.That(r4.Eviction.EvictedPfn, Is.EqualTo(1UL)); + + var fifoOrder = manager.GetFifoVpnOrder(); + Assert.That(fifoOrder.Count, Is.EqualTo(2)); + Assert.That(fifoOrder[0], Is.EqualTo(3UL)); + Assert.That(fifoOrder[1], Is.EqualTo(4UL)); + } + + [Test] + public void Resolve_WhenEvictionHappens_SyncsPageTableAndTlb() + { + var pageTable = new TwoLevelPageTable(4, 4); + var memory = new PhysicalMemoryManager(1UL); + var tlb = new TlbCache(4); + + const ulong vpn1 = 1UL; + pageTable.GetIndicesFromVpn(vpn1, out var l1_1, out var l2_1); + var first = MemoryAccessResolver.Resolve(vpn1, l1_1, l2_1, pageTable, memory, tlb); + + Assert.IsTrue(first.PageFault); + Assert.IsFalse(first.PageTableHit); + Assert.IsFalse(first.Eviction.HasEvicted); + Assert.IsTrue(tlb.Lookup(vpn1, out _)); + + var second = MemoryAccessResolver.Resolve(vpn1, l1_1, l2_1, pageTable, memory, tlb); + Assert.IsFalse(second.PageFault); + Assert.IsTrue(second.PageTableHit); + + const ulong vpn2 = 2UL; + pageTable.GetIndicesFromVpn(vpn2, out var l1_2, out var l2_2); + var third = MemoryAccessResolver.Resolve(vpn2, l1_2, l2_2, pageTable, memory, tlb); + + Assert.IsTrue(third.PageFault); + Assert.IsTrue(third.Eviction.HasEvicted); + Assert.That(third.Eviction.EvictedVpn, Is.EqualTo(vpn1)); + Assert.IsFalse(tlb.Lookup(vpn1, out _)); + Assert.IsTrue(tlb.Lookup(vpn2, out _)); + + pageTable.GetIndicesFromVpn(vpn1, out var evictedL1, out var evictedL2); + Assert.IsFalse(pageTable.TryGetPresentPfn(evictedL1, evictedL2, out _)); + Assert.IsFalse(memory.TryGetResidentPfn(vpn1, out _)); + Assert.IsTrue(memory.TryGetResidentPfn(vpn2, out _)); + } + + [Test] + public void HighVolumeAccess_N10000_RunsWithoutStateCorruption() + { + var pageTable = new TwoLevelPageTable(8, 8); + var memory = new PhysicalMemoryManager(16UL); + var tlb = new TlbCache(8); + var random = new Random(123); + + for (var i = 0; i < 10000; i++) + { + var vpn = (ulong)random.Next(0, 512); + pageTable.GetIndicesFromVpn(vpn, out var l1, out var l2); + + var result = MemoryAccessResolver.Resolve(vpn, l1, l2, pageTable, memory, tlb); + var fifoOrder = memory.GetFifoVpnOrder(); + + Assert.That(memory.ResidentCount, Is.LessThanOrEqualTo(16)); + Assert.That(fifoOrder.Count, Is.EqualTo(memory.ResidentCount)); + if (result.PageTableHit) + { + Assert.IsFalse(result.PageFault); + } + } + } + } +} diff --git a/Assets/Tests/EditMode/Step4PageTableAndFifoTests.cs.meta b/Assets/Tests/EditMode/Step4PageTableAndFifoTests.cs.meta new file mode 100644 index 0000000..b6aa446 --- /dev/null +++ b/Assets/Tests/EditMode/Step4PageTableAndFifoTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 03c7b540fc1fd394bb518328af03654b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/doc/MVP-TODO.md b/doc/MVP-TODO.md index 0070421..a9e26b9 100644 --- a/doc/MVP-TODO.md +++ b/doc/MVP-TODO.md @@ -102,22 +102,22 @@ ### TODO -- [ ] 新建 `TwoLevelPageTable.cs`:`Dictionary>` -- [ ] 新建 `PageTableEntry.cs`:`PFN / present / dirty(可选)` -- [ ] 新建 `PhysicalMemoryManager.cs`:空闲帧管理 + FIFO 队列 -- [ ] 缺页时分配空闲帧;无空闲帧时 FIFO 淘汰 -- [ ] 淘汰后同步页表项状态(`present = false`) +- [x] 新建 `TwoLevelPageTable.cs`:`Dictionary>` +- [x] 新建 `PageTableEntry.cs`:`PFN / present / dirty(可选)` +- [x] 新建 `PhysicalMemoryManager.cs`:空闲帧管理 + FIFO 队列 +- [x] 缺页时分配空闲帧;无空闲帧时 FIFO 淘汰 +- [x] 淘汰后同步页表项状态(`present = false`) ### 行为要求 -- [ ] 页表命中:TLB 未命中后,页表中有 `present = true` 映射 -- [ ] 缺页:页表不存在或 `present = false` -- [ ] 装入页面后写回页表并更新 TLB +- [x] 页表命中:TLB 未命中后,页表中有 `present = true` 映射 +- [x] 缺页:页表不存在或 `present = false` +- [x] 装入页面后写回页表并更新 TLB ### 完成标准 -- [ ] 能稳定运行大量访问(例如 `N=10000`)无崩溃 -- [ ] FIFO 次序可从日志中验证 +- [x] 能稳定运行大量访问(例如 `N=10000`)无崩溃 +- [x] FIFO 次序可从日志中验证 ## 6. Step 5 - 翻译引擎(单步状态机)