<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>MMU on 0xc0de</title>
    <link>https://0xc0de.xyz/tags/mmu/</link>
    <description>Recent content in MMU on 0xc0de</description>
    <image>
      <title>0xc0de</title>
      <url>https://0xc0de.xyz/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</url>
      <link>https://0xc0de.xyz/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</link>
    </image>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <lastBuildDate>Thu, 27 Feb 2025 00:00:00 +0800</lastBuildDate>
    <atom:link href="https://0xc0de.xyz/tags/mmu/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 3 章 - 虛擬存儲器</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch3/</link>
      <pubDate>Thu, 27 Feb 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch3/</guid>
      <description>&lt;h1 id=&#34;32---位址轉換&#34;&gt;3.2 - 位址轉換&lt;/h1&gt;
&lt;h2 id=&#34;323---page-fault&#34;&gt;3.2.3 - Page Fault&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PTE (page table entry) 中包含：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;valid&lt;/code&gt; bit：標記這個 PTE 是否有效，當作業系統設定好 page table 後，就需要將對應 PTE 的 &lt;code&gt;valid&lt;/code&gt; bit 設成 1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirty&lt;/code&gt; bit：當一個 page 內容被更新時 (e.g. 執行 store 指令)，硬體會自動將 &lt;code&gt;dirty&lt;/code&gt; bit 設成 1，代表這個 page 如果被選中要被替換時，需要將 page 的內容 swap 回硬碟&lt;/li&gt;
&lt;li&gt;&lt;code&gt;access&lt;/code&gt; bit：當一個 page 被訪問 (load/store) 時，硬體會自動將 &lt;code&gt;access&lt;/code&gt; bit 設成 1，作業系統則會定期的將 &lt;code&gt;access&lt;/code&gt; bit 清為 0&lt;/li&gt;
&lt;li&gt;當要替換 page table 時，就可以根據 access bit 來得知最近 page 是否有被訪問過，進而實現近似 LRU (Least Recently Used) 的替換策略&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;當 page 要被 swapped out 前：
&lt;ul&gt;
&lt;li&gt;要先將 D-Cache 的內容 flush 進 memory，以確保要被 swapped out 的 page 內容是最新的&lt;/li&gt;
&lt;li&gt;將 PTE 的 &lt;code&gt;valid&lt;/code&gt; bit 設成 0，以避免其他 CPU 或 MMU 在接下來 swap out 期間誤讀這個 page
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;valid&lt;/code&gt; bit 為 0 時，代表該 page 並不存在記憶體中，當 MMU 存取時會觸發 page fault 讓作業系統從硬碟讀取 page 進記憶體&lt;/li&gt;
&lt;li&gt;&lt;code&gt;valid&lt;/code&gt; bit 是由作業系統在把 page 讀進記憶體後設為 &lt;code&gt;1&lt;/code&gt; 的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;執行如 RISC-V 的 &lt;code&gt;sfence.vma&lt;/code&gt; 指令，確保 TLB 中對應的 entries 被清空，以避免 MMU 錯誤存取到被 swapped out page 的 cache 內容&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;34---加入-tlb-和-cache&#34;&gt;3.4 - 加入 TLB 和 Cache&lt;/h1&gt;
&lt;h2 id=&#34;341---tlb-的設計&#34;&gt;3.4.1 - TLB 的設計&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;TLB entry 中也會包含：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;valid&lt;/code&gt; bit：標記這個 TLB entry 是否有效；當 TLB entry 被 swap in 時，&lt;code&gt;valid&lt;/code&gt; bit 會被設為 1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirty&lt;/code&gt; bit：當執行 store 指令時，如果 TLB hit，就不會再訪問 PTE，也就不會更新 PTE 中的 &lt;code&gt;dirty&lt;/code&gt; bit；只有當 TLB entry 要被替換時，才會同步至 PTE&lt;/li&gt;
&lt;li&gt;&lt;code&gt;access&lt;/code&gt; bit：當執行 load/store 指令時，如果 TLB hit，就不會再訪問 PTE，也就不會更新 PTE 中的 &lt;code&gt;access&lt;/code&gt; bit；只有當 TLB entry 要被替換時，才會同步至 PTE&lt;/li&gt;
&lt;li&gt;P.S. RISC-V 中，TLB entry 並沒有包含 &lt;code&gt;dirty&lt;/code&gt; 和 &lt;code&gt;access&lt;/code&gt; bits，作業系統需要自己確保 PTE 的更新
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sfence.vma&lt;/code&gt; 只會將 TLB 中對應的 entries 給 invalid 而已 (針對 TLB 的部份)&lt;/li&gt;
&lt;li&gt;如果 CPU 有支援 &lt;strong&gt;Svadu&lt;/strong&gt; extension，那麼硬體就會自動更新 PTE 中的 &lt;code&gt;dirty&lt;/code&gt; 和 &lt;code&gt;access&lt;/code&gt; bit
&lt;ul&gt;
&lt;li&gt;如果硬體支援 &lt;code&gt;access&lt;/code&gt; 和 &lt;code&gt;dirty&lt;/code&gt; bits 自動更新， 作業系統只需要讀取 PTE 即可獲得最新的 &lt;code&gt;access&lt;/code&gt; 和 &lt;code&gt;dirty&lt;/code&gt; bits 的狀態&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果硬體不支援 &lt;code&gt;access&lt;/code&gt; 和 &lt;code&gt;dirty&lt;/code&gt; bits 自動更新，當 &lt;code&gt;access=0&lt;/code&gt; 或 &lt;code&gt;dirty=0&lt;/code&gt; 時：
&lt;ol&gt;
&lt;li&gt;MMU 會觸發 page fault&lt;/li&gt;
&lt;li&gt;作業系統在 page fault handler 中設置 &lt;code&gt;access=1&lt;/code&gt; 或 &lt;code&gt;dirty=1&lt;/code&gt;
&lt;ol&gt;
&lt;li&gt;作業系統得先透過設定 PTE 的 &lt;code&gt;valid&lt;/code&gt; bit (追蹤 page 是否有被 accessed) 和 disable &lt;code&gt;write&lt;/code&gt; permission (追蹤 page 是否有被 stored) 來當 page fault 發生時，在 page fault handler 更新 &lt;code&gt;access&lt;/code&gt; 或 &lt;code&gt;dirty&lt;/code&gt; bit&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;重新執行造成 page fault 的那道指令&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;也就是讓作業系統來自行管理 PTE 中 &lt;code&gt;access&lt;/code&gt; 和 &lt;code&gt;dirty&lt;/code&gt; bits 的內容，效能較差，但硬體設計較簡單&lt;/li&gt;
&lt;li&gt;RISC-V privilege spec:
&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;pte.a=0&lt;/code&gt;, or if the original memory access is a store and &lt;code&gt;pte.d=0&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;If the &lt;strong&gt;Svade&lt;/strong&gt; extension is implemented, stop and raise a page-fault exception corresponding to the original access type.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;一般為了減少 TLB miss rate，會使用 &lt;code&gt;fully-associative&lt;/code&gt; 來設計 TLB
&lt;ul&gt;
&lt;li&gt;缺點：TLB 容量不能太大，不然會增加查找的時間&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;因此，有些架構也會使用 &lt;code&gt;set-associative&lt;/code&gt; 來設計容量比較大的 TLB&lt;/li&gt;
&lt;li&gt;因為是 &lt;code&gt;fully-associative&lt;/code&gt; 或 &lt;code&gt;set-associative&lt;/code&gt;，因此 TLB entry 中，還會包含 &lt;code&gt;tag&lt;/code&gt; 欄位 (VPN，Virtual Page Number)，用來比對 TLB 是否 hit
&lt;ul&gt;
&lt;li&gt;P.S. TLB entry 中的 data 是 PFN (Page Frame Number)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;現代處理器架構，通常都使用 2-level TLB
&lt;ul&gt;
&lt;li&gt;1st level 採用 Harvard 架構，分為 I-TLB (指令) 和 D-TLB (數據)，一般採用 &lt;code&gt;fully-associative&lt;/code&gt; 設計&lt;/li&gt;
&lt;li&gt;2nd level 採用 Von Neumann 架構，指令和數據共用，一般採用 &lt;code&gt;set-associative&lt;/code&gt; 設計&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;現代處理器中因應程式的 size 越來越大，因此還會支持容量更大的 page
&lt;ul&gt;
&lt;li&gt;E.g. 128 entries 的 TLB，只能映射到 128 * 4KB = 512 MB 大小的程式，顯然不夠用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;更大的 page：
&lt;ul&gt;
&lt;li&gt;優點：
&lt;ul&gt;
&lt;li&gt;降低 TLB miss rate&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺點：
&lt;ul&gt;
&lt;li&gt;當發生 page fault 的時候，需要花更多的時間才能將更大的 page 內容從硬碟搬至記憶體&lt;/li&gt;
&lt;li&gt;如果程式用不到這麼大的 page，那麼空間就被浪費了，且也會造成 page fragment，降低 page 的使用效率&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;總和以上原因，現代處理器都支持大小可變的 page，由作業系統負責管理，根據程式的特點選用不同大小的 page，最大程度地利用 TLB 有限的空間，並降低 page fragment
&lt;ul&gt;
&lt;li&gt;在 TLB 中會有相對應的設定可以調整映射的 page 大小&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;因為記憶體的存取速度相對於 CPU 的執行速度來說非常慢，因此 TLB 通常只會採用 &lt;strong&gt;Write-back&lt;/strong&gt; 的方式設計，且因為 TLB entry 中 &lt;code&gt;tag&lt;/code&gt; 和 &lt;code&gt;data&lt;/code&gt; 等欄位是不會變動的，因此當發生 TLB miss，需要將 TLB entry swap out 時，只需要將 &lt;code&gt;dirty&lt;/code&gt; bit (執行 store 指令時會被更新) 和 &lt;code&gt;access&lt;/code&gt; bit (執行 load/store 指令時會被更新) 寫回 PTE 中即可&lt;/li&gt;
&lt;li&gt;TLB miss 發生的情況：
&lt;ol&gt;
&lt;li&gt;Page 並不在記憶體中，因此也不在 TLB 中&lt;/li&gt;
&lt;li&gt;Page 在記憶體中，page table 中也有對應的 PTE，但這個 PTE 並沒有被 cache 在 TLB 中&lt;/li&gt;
&lt;li&gt;Page 在記憶體中，page table 中也有對應的 PTE，這個 PTE 也曾經存在 TLB 中，但是因為先前的 TLB miss，因此被替換出來了&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;TLB miss 時，TLB entry 的替換策略：
&lt;ul&gt;
&lt;li&gt;LRU (Least Recently Used)&lt;/li&gt;
&lt;li&gt;Random：如同 cache，使用一個 counter，每個 cycle 都會 + 1，每次 TLB miss 要替換 TLB entry 時，就根據當下 counter 的值決定是哪個 entry 要被替換&lt;/li&gt;
&lt;li&gt;LRU 比較難實現，所以通常會採用 Random 的替換策略，實做也比較簡單&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果 TLB 採用 &lt;strong&gt;Write-back&lt;/strong&gt;，TLB entry 中的 &lt;code&gt;dirty&lt;/code&gt; 和 &lt;code&gt;access&lt;/code&gt; bits 就有可能跟 PTE 的內容不同步，如果 page 要被 swap out 的時候，作業系統會無法即時得知究竟 page 是否 dirty，以及是否有被 accessed 過
&lt;ul&gt;
&lt;li&gt;直觀解法：每次發生 page fault 要 swap page 的時候，先將 TLB entries flush 至 PTE
&lt;ul&gt;
&lt;li&gt;缺點：需要耗費額外的時間 flush TLB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;另類解法：作業系統可以認為，有在 TLB 中被 cached 對應的 pages 都是正在使用的，因此不能將其 swap out
&lt;ul&gt;
&lt;li&gt;需要作業系統自行維護一張表，紀錄哪些 PTE 有被 cached 在 TLB 中，且為 valid 的
&lt;ul&gt;
&lt;li&gt;不過這種設計並不常見&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果系統中有 D-Cache，page 也是 dirt 的，那麼 page 被更動的內容也有可能還存在 D-Cache 中，因此在 page swap out 前也必須先 flush D-Cache&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;342---cache-的設計&#34;&gt;3.4.2 - Cache 的設計&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;兩種 caches：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="32---位址轉換">3.2 - 位址轉換</h1>
<h2 id="323---page-fault">3.2.3 - Page Fault</h2>
<ul>
<li>PTE (page table entry) 中包含：
<ul>
<li><code>valid</code> bit：標記這個 PTE 是否有效，當作業系統設定好 page table 後，就需要將對應 PTE 的 <code>valid</code> bit 設成 1</li>
<li><code>dirty</code> bit：當一個 page 內容被更新時 (e.g. 執行 store 指令)，硬體會自動將 <code>dirty</code> bit 設成 1，代表這個 page 如果被選中要被替換時，需要將 page 的內容 swap 回硬碟</li>
<li><code>access</code> bit：當一個 page 被訪問 (load/store) 時，硬體會自動將 <code>access</code> bit 設成 1，作業系統則會定期的將 <code>access</code> bit 清為 0</li>
<li>當要替換 page table 時，就可以根據 access bit 來得知最近 page 是否有被訪問過，進而實現近似 LRU (Least Recently Used) 的替換策略</li>
</ul>
</li>
<li>當 page 要被 swapped out 前：
<ul>
<li>要先將 D-Cache 的內容 flush 進 memory，以確保要被 swapped out 的 page 內容是最新的</li>
<li>將 PTE 的 <code>valid</code> bit 設成 0，以避免其他 CPU 或 MMU 在接下來 swap out 期間誤讀這個 page
<ul>
<li><code>valid</code> bit 為 0 時，代表該 page 並不存在記憶體中，當 MMU 存取時會觸發 page fault 讓作業系統從硬碟讀取 page 進記憶體</li>
<li><code>valid</code> bit 是由作業系統在把 page 讀進記憶體後設為 <code>1</code> 的</li>
</ul>
</li>
<li>執行如 RISC-V 的 <code>sfence.vma</code> 指令，確保 TLB 中對應的 entries 被清空，以避免 MMU 錯誤存取到被 swapped out page 的 cache 內容</li>
</ul>
</li>
</ul>
<h1 id="34---加入-tlb-和-cache">3.4 - 加入 TLB 和 Cache</h1>
<h2 id="341---tlb-的設計">3.4.1 - TLB 的設計</h2>
<ul>
<li>TLB entry 中也會包含：
<ul>
<li><code>valid</code> bit：標記這個 TLB entry 是否有效；當 TLB entry 被 swap in 時，<code>valid</code> bit 會被設為 1</li>
<li><code>dirty</code> bit：當執行 store 指令時，如果 TLB hit，就不會再訪問 PTE，也就不會更新 PTE 中的 <code>dirty</code> bit；只有當 TLB entry 要被替換時，才會同步至 PTE</li>
<li><code>access</code> bit：當執行 load/store 指令時，如果 TLB hit，就不會再訪問 PTE，也就不會更新 PTE 中的 <code>access</code> bit；只有當 TLB entry 要被替換時，才會同步至 PTE</li>
<li>P.S. RISC-V 中，TLB entry 並沒有包含 <code>dirty</code> 和 <code>access</code> bits，作業系統需要自己確保 PTE 的更新
<ul>
<li><code>sfence.vma</code> 只會將 TLB 中對應的 entries 給 invalid 而已 (針對 TLB 的部份)</li>
<li>如果 CPU 有支援 <strong>Svadu</strong> extension，那麼硬體就會自動更新 PTE 中的 <code>dirty</code> 和 <code>access</code> bit
<ul>
<li>如果硬體支援 <code>access</code> 和 <code>dirty</code> bits 自動更新， 作業系統只需要讀取 PTE 即可獲得最新的 <code>access</code> 和 <code>dirty</code> bits 的狀態</li>
</ul>
</li>
<li>如果硬體不支援 <code>access</code> 和 <code>dirty</code> bits 自動更新，當 <code>access=0</code> 或 <code>dirty=0</code> 時：
<ol>
<li>MMU 會觸發 page fault</li>
<li>作業系統在 page fault handler 中設置 <code>access=1</code> 或 <code>dirty=1</code>
<ol>
<li>作業系統得先透過設定 PTE 的 <code>valid</code> bit (追蹤 page 是否有被 accessed) 和 disable <code>write</code> permission (追蹤 page 是否有被 stored) 來當 page fault 發生時，在 page fault handler 更新 <code>access</code> 或 <code>dirty</code> bit</li>
</ol>
</li>
<li>重新執行造成 page fault 的那道指令</li>
</ol>
<ul>
<li>也就是讓作業系統來自行管理 PTE 中 <code>access</code> 和 <code>dirty</code> bits 的內容，效能較差，但硬體設計較簡單</li>
<li>RISC-V privilege spec:
<ul>
<li>If <code>pte.a=0</code>, or if the original memory access is a store and <code>pte.d=0</code>:
<ul>
<li>If the <strong>Svade</strong> extension is implemented, stop and raise a page-fault exception corresponding to the original access type.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>一般為了減少 TLB miss rate，會使用 <code>fully-associative</code> 來設計 TLB
<ul>
<li>缺點：TLB 容量不能太大，不然會增加查找的時間</li>
</ul>
</li>
<li>因此，有些架構也會使用 <code>set-associative</code> 來設計容量比較大的 TLB</li>
<li>因為是 <code>fully-associative</code> 或 <code>set-associative</code>，因此 TLB entry 中，還會包含 <code>tag</code> 欄位 (VPN，Virtual Page Number)，用來比對 TLB 是否 hit
<ul>
<li>P.S. TLB entry 中的 data 是 PFN (Page Frame Number)</li>
</ul>
</li>
<li>現代處理器架構，通常都使用 2-level TLB
<ul>
<li>1st level 採用 Harvard 架構，分為 I-TLB (指令) 和 D-TLB (數據)，一般採用 <code>fully-associative</code> 設計</li>
<li>2nd level 採用 Von Neumann 架構，指令和數據共用，一般採用 <code>set-associative</code> 設計</li>
</ul>
</li>
<li>現代處理器中因應程式的 size 越來越大，因此還會支持容量更大的 page
<ul>
<li>E.g. 128 entries 的 TLB，只能映射到 128 * 4KB = 512 MB 大小的程式，顯然不夠用</li>
</ul>
</li>
<li>更大的 page：
<ul>
<li>優點：
<ul>
<li>降低 TLB miss rate</li>
</ul>
</li>
<li>缺點：
<ul>
<li>當發生 page fault 的時候，需要花更多的時間才能將更大的 page 內容從硬碟搬至記憶體</li>
<li>如果程式用不到這麼大的 page，那麼空間就被浪費了，且也會造成 page fragment，降低 page 的使用效率</li>
</ul>
</li>
</ul>
</li>
<li>總和以上原因，現代處理器都支持大小可變的 page，由作業系統負責管理，根據程式的特點選用不同大小的 page，最大程度地利用 TLB 有限的空間，並降低 page fragment
<ul>
<li>在 TLB 中會有相對應的設定可以調整映射的 page 大小</li>
</ul>
</li>
<li>因為記憶體的存取速度相對於 CPU 的執行速度來說非常慢，因此 TLB 通常只會採用 <strong>Write-back</strong> 的方式設計，且因為 TLB entry 中 <code>tag</code> 和 <code>data</code> 等欄位是不會變動的，因此當發生 TLB miss，需要將 TLB entry swap out 時，只需要將 <code>dirty</code> bit (執行 store 指令時會被更新) 和 <code>access</code> bit (執行 load/store 指令時會被更新) 寫回 PTE 中即可</li>
<li>TLB miss 發生的情況：
<ol>
<li>Page 並不在記憶體中，因此也不在 TLB 中</li>
<li>Page 在記憶體中，page table 中也有對應的 PTE，但這個 PTE 並沒有被 cache 在 TLB 中</li>
<li>Page 在記憶體中，page table 中也有對應的 PTE，這個 PTE 也曾經存在 TLB 中，但是因為先前的 TLB miss，因此被替換出來了</li>
</ol>
</li>
<li>TLB miss 時，TLB entry 的替換策略：
<ul>
<li>LRU (Least Recently Used)</li>
<li>Random：如同 cache，使用一個 counter，每個 cycle 都會 + 1，每次 TLB miss 要替換 TLB entry 時，就根據當下 counter 的值決定是哪個 entry 要被替換</li>
<li>LRU 比較難實現，所以通常會採用 Random 的替換策略，實做也比較簡單</li>
</ul>
</li>
<li>如果 TLB 採用 <strong>Write-back</strong>，TLB entry 中的 <code>dirty</code> 和 <code>access</code> bits 就有可能跟 PTE 的內容不同步，如果 page 要被 swap out 的時候，作業系統會無法即時得知究竟 page 是否 dirty，以及是否有被 accessed 過
<ul>
<li>直觀解法：每次發生 page fault 要 swap page 的時候，先將 TLB entries flush 至 PTE
<ul>
<li>缺點：需要耗費額外的時間 flush TLB</li>
</ul>
</li>
<li>另類解法：作業系統可以認為，有在 TLB 中被 cached 對應的 pages 都是正在使用的，因此不能將其 swap out
<ul>
<li>需要作業系統自行維護一張表，紀錄哪些 PTE 有被 cached 在 TLB 中，且為 valid 的
<ul>
<li>不過這種設計並不常見</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>如果系統中有 D-Cache，page 也是 dirt 的，那麼 page 被更動的內容也有可能還存在 D-Cache 中，因此在 page swap out 前也必須先 flush D-Cache</li>
</ul>
<h2 id="342---cache-的設計">3.4.2 - Cache 的設計</h2>
<ul>
<li>
<p>兩種 caches：</p>
<ul>
<li>
<p>Physical cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image.png"></p>
</li>
<li>
<p>Virtual cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%201.png"></p>
</li>
</ul>
</li>
<li>
<p>Virtual cache 會有 aliasing (synonyms，同義) 問題：</p>
<ul>
<li>
<p>多個 VA 對應到同一個 PA</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%202.png"></p>
</li>
<li>
<p>缺點：</p>
<ul>
<li>造成 cache 空間的浪費，因為可能會有兩個 cache sets (不同 VA) 都是對應到同一個 PA 的資料</li>
<li>當執行 store 指令更新 cache 的內容時，只有一個 cache set 的內容會被更新，但實際上，所有對應到同一個 PA 的 cache sets 內容都應該被更新，否則其他 CPU 讀到的 cache 內容就會不同步</li>
</ul>
</li>
<li>
<p>並不是所有情況都會有 aliasing (synonyms) 問題，例如：</p>
<ul>
<li>當 page 為 4 KB，VA 轉成 PA 時，low 12 bits 的位址是不會發生變化的，因此當一個 direct-mapped 的 cache 容量 ≤ 4KB 時，由於 cache index 最多只會用到 page offset 內的 12 bits (i.e. cache sets 數量 ≤ page offset 可表達的範圍)，即使兩個不同 VA 對應到同一個 PA，還是會 mapping 到同一個 cache set，也就沒有 aliasing (synonyms) 問題
<ul>
<li>
<p>只有在 cache 容量 &gt; 4KB 時，才會導致 cache index bits &gt; 12 bits (i.e. cache sets 的數量 &gt; page offset 可表達的範圍，也就是 cache index bits 包含了 VPN 的部份)，造成兩個不同 VA 對應到同一個 PA，有可能會 mapping 到不同個的 cache sets，導致 aliasing (synonyms) 問題</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%203.png"></p>
<ul>
<li>E.g. 即使 VA：<code>0x800</code> 和 <code>0x1800</code> 都是對應到同一個 PA，但因為 VA 的 <code>bits[12]</code> 也是 cache index bits 的一部分，因此還是會被 mapping 到不同的 cache sets</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>使用 bank 解決 aliasing 問題：</p>
<ul>
<li>
<p>最簡單的方式：在寫 cache 時，將 aliased 的 cache sets 都進行更新</p>
<ul>
<li>需要將 PA 的一部分作為 cache tag 來使用，並需要使用兩個 banks</li>
<li>缺點：浪費 cache 使用空間</li>
</ul>
</li>
<li>
<p>如何在不浪費 cache 使用空間的情況下，透過 bank 的方式解決 aliasing 問題：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%204.png"></p>
<ul>
<li>範例：8 KB 切成兩個 4 KB 的 cache banks
<ul>
<li>讀取 cache 時，使用 <code>VA[11:0]</code> 同時查詢兩個 banks，輸出的 cache sets 再透過 <code>PA[12]</code> 控制的 Mux 來選取是哪個 cache set 要被讀出
<ul>
<li>由於 PA 需要透過 TLB 轉換才能得知，處理時間較長，因此會增加 CPU 的 cycle time</li>
<li>Cache tag 也必須是 PA，才能跟 TLB 轉換出來的 PFN 做比對</li>
</ul>
</li>
<li>寫 cache 時，因為只有被 retired 的指令才會把數據寫入 cache，此時 PA 已經得到了，所以直接拿 <code>PA[12]</code> 來判斷要寫入到哪個 bank 中即可</li>
<li>缺點：
<ul>
<li>增加硬體的複雜度</li>
<li>由於讀取 cache 時必須同時查找所有的 banks，因此也會增加功耗</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Virtual cache 會有 homonyms (同名) 問題：</p>
<ul>
<li>一個 VA 對應到多個 PA
<ul>
<li>不同 processes，同一個 VA 可能對應到的 PA 不同</li>
</ul>
</li>
<li>在 context switch 時，需要把 virtual indexed cache 的 cache 內容都 invalidate</li>
<li>同樣的，在 context switch 時，也需要把所有的 TLB entries 都 invalidate，以避免存取到錯誤的 VA -&gt; PA mappings</li>
<li>可以透過 ASID (Address Space Identifier) 來只 invalidate 被 context switched 的 cache 內容和 TLB entries</li>
<li>可以再透過引入 <code>global</code> bit 來標記該 page 是被多個 processes 共享的 (e.g. Linux kernel space)</li>
</ul>
</li>
<li>
<p>DMA 搬 memory 的資料前，必須先 flush D-Cache，確保記憶體中的內容是最新的</p>
</li>
<li>
<p>CPU 在讀取 DMA 搬完的 memory 資料前，必須先 invalidate D-Cache，確保 CPU 一定會從 memory 讀取最新的資料</p>
</li>
<li>
<p>當發生 page fault，並需要將 page swap out 至硬碟前，如果該 page 是 <code>dirty</code> 的，那就需要先 flush D-Cache，確保要被 swapped out 的 page 內容是最新的</p>
</li>
<li>
<p>在執行完 self-modifying codes 的指令後，必須先 flush D-Cache，確保指令的修改有正確的寫進 memory；然後要再 invalid I-Cache，確保 CPU 可以讀取到 self-modified 完後最新的指令</p>
</li>
<li>
<p>Cache 在上電時，大部分都是關閉的，所以起始程式通常都是放在 non-cacheable 的 memory region；等到初始化完 cache 後，cache 才可以被開啟及使用</p>
</li>
</ul>
<h2 id="343---將-tlb-和-cache-放入流水線">3.4.3 - 將 TLB 和 Cache 放入流水線</h2>
<ul>
<li>
<p><strong>PIPT Cache (Physically-indexed, Physically-tagged)：</strong></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%205.png"></p>
<ul>
<li>Cache index 和 tag 都是 PA</li>
<li>缺點：
<ul>
<li>TLB lookup 需要消耗一定的時間，為了不影響 CPU 的 cycle time，可能會需要將 TLB lookup 獨立成一個獨立的 pipeline stage
<ul>
<li>對 I-Cache 來說，branch mis-prediction 的 penalty 會增加</li>
<li>對 D-Cache 來說，load 指令的 latency 會變長
<ul>
<li>且 load 指令通常都是其他指令的 dependency，因此也會影響其他指令可以被 issued 的時間</li>
</ul>
</li>
</ul>
</li>
<li>真實世界的 CPU 很少 L1 Cache 是採用 PIPT cache 的設計，因為這樣將 cache 訪問和 TLB lookup 給綁定在一起了，會影響 pipeline
<ul>
<li>實際上也沒有必要，如果 page offset 就可以作為 cache 的 index，VA 的 page offset 和 PA 的 page offset 都是一樣的，可以直接使用 VA 的 page offset，沒有必要再透過 TLB 做轉換</li>
<li>不過 L2/L3 Cache 通常就是使用 PIPT cache 的設計，因為 L2/L3 Cache 被訪問時，TLB lookup 有可能已經完成</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>VIPT Cache (Virtually-indexed, Physically-tagged)：</strong></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%206.png"></p>
<ul>
<li>Cache index 是 VA，tag 是 PA</li>
<li>VIPT 是目前最多被使用的 cache</li>
<li>優點：
<ul>
<li>Cache 訪問和 TLB lookup 可以同時進行</li>
</ul>
</li>
<li>缺點：
<ul>
<li>Cache 的大小會有所限制，因為要避免 aliasing 的問題</li>
</ul>
</li>
<li>假設一 direct-mapped cache，cache line：2^<code>b</code> bytes，cache sets 個數：2^<code>L</code> ⇒ Cache size = 2^(<code>L + b</code>)；Page size：2^<code>k</code> bytes，會有以下三種情況：
<ul>
<li>
<p>Page size (<code>k</code>) &gt; Cache size (<code>L + b</code>)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%207.png"></p>
</li>
<li>
<p>Page size (<code>k</code>) = Cache size (<code>L + b</code>)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%208.png"></p>
</li>
<li>
<p>Page size (<code>k</code>) &lt; Cache size (<code>L + b</code>)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%209.png"></p>
</li>
<li>
<p>針對 Page size (<code>k</code>) &gt; Cache size (<code>L + b</code>) 和 Page size (<code>k</code>) = Cache size (<code>L + b</code>)：</p>
<ul>
<li>
<p>有機會 cache 訪問和 TLB lookup 在同一個 stage 完成</p>
</li>
<li>
<p>為了要避免 aliasing 的問題，因此 cache size 被限制最大只能是一個 page 的大小</p>
</li>
<li>
<p>如果 cache size 要超過一個 page，就只能使用 set-associative 的設計：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%2010.png"></p>
</li>
</ul>
</li>
<li>
<p>針對 Page size (<code>k</code>) &lt; Cache size (<code>L + b</code>)：</p>
<ul>
<li>
<p>如果已經增加 ways 數 (ways 數不可能無限制的增加)，但還想再增加 cache 的 size，當 cache size &gt; page size 時，就會造成 aliasing 的問題</p>
</li>
<li>
<p>除了透過先前介紹 bank 的方式來解決 aliasing 的問題，還可以透過 inclusive L2 cache 來解決 aliasing 的問題：</p>
<ul>
<li>L2 cache 包含所有 L1 I-Cache 和 L1 D-Cache 的內容</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%2011.png"></p>
<ul>
<li>L2 Cache 中的 <code>a1</code> (VA) 是 VA1 的，所以如果跟 VA2 的 <code>a2</code> (VA) 部份不相同的話，就代表有 aliasing 的問題</li>
<li>透過 <code>{a1, offset}</code> 的組合，可以找到 VA1 在 L1 Cache 中的 cache line：
<ul>
<li>如果是 <code>dirty</code>，將其 flush 回 L2 Cache 後，再 invalidate cache line</li>
<li>如果沒有 <code>dirty</code>，直接 invalidate cache line</li>
</ul>
</li>
<li>此時就可以將 VA2 的內容從 L2 Cache 讀回 L1 Cache 中，且因為只有 VA2 在 L1 Cache 的 cache line 是 valid 的，也就不存在 aliasing 的問題</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>VIVT Cache (Virtually-indexed, Virtually-tagged)：</strong></p>
<ul>
<li>Cache index 和 tag 都是 VA</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%2012.png"></p>
<ul>
<li>優點：
<ul>
<li>Cache hit 的話，完全不用 TLB lookup</li>
</ul>
</li>
<li>缺點：
<ul>
<li>同樣會有 aliasing 的問題
<ul>
<li>可以同樣用跟 VIPT 的方式查找 VA1 在 L1 Cache 的 cache line，並將其 invalidate</li>
<li>不過此時 L2 Cache 沒辦法只存 <code>a1</code> (VA)，必須存整個 <code>VA1</code> (VA)，因為 L1 Cache 是 virtually-tagged 的，會使用 VA 的一部分作為 tag，只用 <code>{a1, offset}</code> 是不夠的
<ul>
<li>L2 Cache 中的 <code>VA1</code> (VA) 是 VA1 的，所以如果跟 VA2 的 <code>VA2</code> (VA) 部份不相同的話，就代表有 aliasing 的問題</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
