Step 3 - TLB(全相联 + LRU)

This commit is contained in:
SepComet 2026-04-14 08:19:00 +08:00
parent 4ab93d7806
commit bda18464c8
7 changed files with 322 additions and 9 deletions

View File

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
namespace VMdemo.Simulation
{
public class TlbCache
{
private readonly Dictionary<ulong, LinkedListNode<TlbEntry>> _index;
private readonly LinkedList<TlbEntry> _lruList;
public TlbCache(int capacity)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "TLB 容量必须是正整数。");
}
Capacity = capacity;
_index = new Dictionary<ulong, LinkedListNode<TlbEntry>>(capacity);
_lruList = new LinkedList<TlbEntry>();
}
public int Capacity { get; }
public int Count => _index.Count;
public int HitCount { get; private set; }
public int MissCount { get; private set; }
public bool Lookup(ulong vpn, out ulong pfn)
{
return TryLookup(vpn, out pfn);
}
public bool TryLookup(ulong vpn, out ulong pfn)
{
if (_index.TryGetValue(vpn, out var node) && node.Value.IsValid)
{
MoveToMostRecent(node);
pfn = node.Value.Pfn;
HitCount++;
return true;
}
pfn = 0UL;
MissCount++;
return false;
}
public void InsertOrUpdate(ulong vpn, ulong pfn)
{
if (_index.TryGetValue(vpn, out var existingNode))
{
existingNode.Value = existingNode.Value.WithPfn(pfn);
MoveToMostRecent(existingNode);
return;
}
var newNode = new LinkedListNode<TlbEntry>(new TlbEntry(vpn, pfn));
_lruList.AddFirst(newNode);
_index[vpn] = newNode;
if (_index.Count > Capacity)
{
EvictLeastRecent();
}
}
public bool Remove(ulong vpn)
{
if (!_index.TryGetValue(vpn, out var node))
{
return false;
}
_lruList.Remove(node);
_index.Remove(vpn);
return true;
}
public void Clear()
{
_lruList.Clear();
_index.Clear();
HitCount = 0;
MissCount = 0;
}
public IReadOnlyList<TlbEntry> GetEntriesMostRecentFirst()
{
var snapshot = new List<TlbEntry>(_index.Count);
for (var node = _lruList.First; node != null; node = node.Next)
{
snapshot.Add(node.Value);
}
return snapshot;
}
private void MoveToMostRecent(LinkedListNode<TlbEntry> node)
{
if (node.List != _lruList || _lruList.First == node)
{
return;
}
_lruList.Remove(node);
_lruList.AddFirst(node);
}
private void EvictLeastRecent()
{
var leastRecentNode = _lruList.Last;
if (leastRecentNode == null)
{
return;
}
_lruList.RemoveLast();
_index.Remove(leastRecentNode.Value.Vpn);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 80f194356d342d24d8a3a8bfc9f22599
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
using System;
namespace VMdemo.Simulation
{
[Serializable]
public readonly struct TlbEntry
{
public TlbEntry(ulong vpn, ulong pfn, bool isValid = true)
{
Vpn = vpn;
Pfn = pfn;
IsValid = isValid;
}
public ulong Vpn { get; }
public ulong Pfn { get; }
public bool IsValid { get; }
public TlbEntry WithPfn(ulong pfn)
{
return new TlbEntry(Vpn, pfn, true);
}
public override string ToString()
{
return $"VPN={Vpn}, PFN={Pfn}, Valid={IsValid}";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fc1daa201fa9e844a83e62ff9feb5ae6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,130 @@
using NUnit.Framework;
using VMdemo.Simulation;
namespace VMdemo.Tests.EditMode
{
public class Step3TlbCacheTests
{
[Test]
public void Constructor_CapacityLessThanOne_Throws()
{
Assert.Throws<System.ArgumentOutOfRangeException>(() => new TlbCache(0));
Assert.Throws<System.ArgumentOutOfRangeException>(() => new TlbCache(-1));
}
[Test]
public void TryLookup_Miss_ReturnsFalseAndCountsMiss()
{
var cache = new TlbCache(2);
var hit = cache.TryLookup(10UL, out var pfn);
Assert.IsFalse(hit);
Assert.That(pfn, Is.EqualTo(0UL));
Assert.That(cache.HitCount, Is.EqualTo(0));
Assert.That(cache.MissCount, Is.EqualTo(1));
}
[Test]
public void InsertOrUpdate_AndLookup_HitReturnsMappedPfn()
{
var cache = new TlbCache(2);
cache.InsertOrUpdate(10UL, 100UL);
var hit = cache.TryLookup(10UL, out var pfn);
Assert.IsTrue(hit);
Assert.That(pfn, Is.EqualTo(100UL));
Assert.That(cache.HitCount, Is.EqualTo(1));
Assert.That(cache.MissCount, Is.EqualTo(0));
}
[Test]
public void InsertOrUpdate_ExistingEntry_UpdatesPfnAndMovesToMostRecent()
{
var cache = new TlbCache(3);
cache.InsertOrUpdate(1UL, 11UL);
cache.InsertOrUpdate(2UL, 22UL);
cache.InsertOrUpdate(3UL, 33UL);
cache.InsertOrUpdate(1UL, 111UL);
var entries = cache.GetEntriesMostRecentFirst();
Assert.That(entries.Count, Is.EqualTo(3));
Assert.That(entries[0].Vpn, Is.EqualTo(1UL));
Assert.That(entries[0].Pfn, Is.EqualTo(111UL));
Assert.That(entries[1].Vpn, Is.EqualTo(3UL));
Assert.That(entries[2].Vpn, Is.EqualTo(2UL));
}
[Test]
public void OverCapacity_EvictsLeastRecentlyUsedEntry()
{
var cache = new TlbCache(3);
cache.InsertOrUpdate(1UL, 11UL);
cache.InsertOrUpdate(2UL, 22UL);
cache.InsertOrUpdate(3UL, 33UL);
cache.TryLookup(1UL, out _); // refresh 1 as MRU
cache.InsertOrUpdate(4UL, 44UL); // should evict 2
Assert.That(cache.Count, Is.EqualTo(3));
Assert.IsFalse(cache.TryLookup(2UL, out _));
Assert.IsTrue(cache.TryLookup(1UL, out var pfn1));
Assert.That(pfn1, Is.EqualTo(11UL));
var entries = cache.GetEntriesMostRecentFirst();
Assert.That(entries[0].Vpn, Is.EqualTo(1UL));
Assert.That(entries[1].Vpn, Is.EqualTo(4UL));
Assert.That(entries[2].Vpn, Is.EqualTo(3UL));
}
[Test]
public void Remove_ExistingAndMissingKeys_BehaveAsExpected()
{
var cache = new TlbCache(2);
cache.InsertOrUpdate(5UL, 55UL);
var removedFirst = cache.Remove(5UL);
var removedSecond = cache.Remove(5UL);
Assert.IsTrue(removedFirst);
Assert.IsFalse(removedSecond);
Assert.That(cache.Count, Is.EqualTo(0));
}
[Test]
public void Clear_ResetsEntriesAndCounters()
{
var cache = new TlbCache(2);
cache.InsertOrUpdate(1UL, 10UL);
cache.InsertOrUpdate(2UL, 20UL);
cache.TryLookup(1UL, out _);
cache.TryLookup(99UL, out _);
cache.Clear();
Assert.That(cache.Count, Is.EqualTo(0));
Assert.That(cache.HitCount, Is.EqualTo(0));
Assert.That(cache.MissCount, Is.EqualTo(0));
Assert.That(cache.GetEntriesMostRecentFirst().Count, Is.EqualTo(0));
}
[Test]
public void ContinuousLookup_TracksHitAndMissCountsAccurately()
{
var cache = new TlbCache(2);
cache.InsertOrUpdate(1UL, 10UL);
cache.InsertOrUpdate(2UL, 20UL);
cache.TryLookup(1UL, out _); // hit
cache.TryLookup(3UL, out _); // miss
cache.TryLookup(2UL, out _); // hit
cache.TryLookup(3UL, out _); // miss
Assert.That(cache.HitCount, Is.EqualTo(2));
Assert.That(cache.MissCount, Is.EqualTo(2));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 971f2b92f0eedec46b9cfb3f60199340
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -83,20 +83,20 @@
### TODO ### TODO
- [ ] 新建 `TlbEntry.cs``VPN -> PFN` 映射及有效位 - [x] 新建 `TlbEntry.cs``VPN -> PFN` 映射及有效位
- [ ] 新建 `TlbCache.cs`:查询、更新、淘汰 - [x] 新建 `TlbCache.cs`:查询、更新、淘汰
- [ ] 使用 `Dictionary + LinkedList` 实现 O(1) 级 LRU - [x] 使用 `Dictionary + LinkedList` 实现 O(1) 级 LRU
### 行为要求 ### 行为要求
- [ ] `Lookup(vpn)`:命中返回 PFN未命中返回失败 - [x] `Lookup(vpn)`:命中返回 PFN未命中返回失败
- [ ] `InsertOrUpdate(vpn, pfn)`:已存在则更新并置为最近使用 - [x] `InsertOrUpdate(vpn, pfn)`:已存在则更新并置为最近使用
- [ ] 超出容量时淘汰最久未使用项 - [x] 超出容量时淘汰最久未使用项
### 完成标准 ### 完成标准
- [ ] 构造用例可验证 LRU 淘汰顺序正确 - [x] 构造用例可验证 LRU 淘汰顺序正确
- [ ] 连续访问时命中统计准确 - [x] 连续访问时命中统计准确
## 5. Step 4 - 二级页表 + 缺页 + FIFO ## 5. Step 4 - 二级页表 + 缺页 + FIFO
@ -207,4 +207,4 @@
- [x] 先完成 Step 1参数模型 + 校验) - [x] 先完成 Step 1参数模型 + 校验)
- [x] 紧接 Step 2地址拆分 - [x] 紧接 Step 2地址拆分
- [ ] 当天收尾前完成 Step 3TLB LRU 最小可运行) - [x] 当天收尾前完成 Step 3TLB LRU 最小可运行)