<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>liujunming</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>http://liujunming.github.io/</id>
  <link href="http://liujunming.github.io/" rel="alternate"/>
  <link href="http://liujunming.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, liujunming</rights>
  <subtitle>make it simple, make it happen.</subtitle>
  <title>L</title>
  <updated>2026-08-30T10:13:51.827Z</updated>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="体系结构" scheme="http://liujunming.github.io/categories/%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    <category term="体系结构" scheme="http://liujunming.github.io/tags/%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    <content>
      <![CDATA[<p>本文只讨论一个问题：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CPU 0 Store Buffer：x = 1</span><br><span class="line">CPU 1 Store Buffer：x = 2</span><br></pre></td></tr></table></figure><p>这两个 Store 能否同时存在？</p><span id="more"></span><p>答案是：</p><blockquote><p>可以同时存在于两个 CPU 各自的 Store Buffer 中；但当它们真正排出并进入共享的 coherent memory system 时，必须由 Cache Coherence 决定先后顺序。</p></blockquote><p>本文只讨论 x86-64 上自然对齐的普通 Store，以及普通 WB（Write-Back）cacheable memory。</p><p>本文示例是硬件模型，不表示普通非原子 C&#x2F;C++ 程序可以直接这样访问共享变量。</p><h2 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h2><p><a href="/2026/08/22/%E6%B5%85%E8%B0%88x86%E7%9A%84TSO-memory-ordering/">浅谈 x86 的 TSO memory ordering</a></p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>假设初始状态是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 0</span><br></pre></td></tr></table></figure><p>两个 CPU 同时执行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">CPU 0                       CPU 1</span><br><span class="line"></span><br><span class="line">x = 1                       x = 2</span><br></pre></td></tr></table></figure><p>每个 CPU 都有自己的私有 Store Buffer。Store 可以先进入本地 Store Buffer，而不需要立即对其他 CPU 可见。</p><p>因此，某个时刻完全可能出现：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">CPU 0                                      CPU 1</span><br><span class="line">┌───────────────────┐                      ┌───────────────────┐</span><br><span class="line">│ Store Buffer      │                      │ Store Buffer      │</span><br><span class="line">│                   │                      │                   │</span><br><span class="line">│ x = 1             │                      │ x = 2             │</span><br><span class="line">└───────────────────┘                      └───────────────────┘</span><br><span class="line"></span><br><span class="line">                    Shared coherent state</span><br><span class="line">                            x = 0</span><br></pre></td></tr></table></figure><p>也就是说：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CPU 0 SB：x = 1</span><br><span class="line">CPU 1 SB：x = 2</span><br></pre></td></tr></table></figure><p>可以同时存在。</p><p>x86-TSO 可以抽象成每个硬件线程拥有一个私有 FIFO Store Buffer，Store 随后再从各自的 Buffer 传播到共享内存。</p><h2 id="为什么不会冲突？"><a href="#为什么不会冲突？" class="headerlink" title="为什么不会冲突？"></a>为什么不会冲突？</h2><p>容易产生的误解是：</p><blockquote><p>Cache Coherence 只允许一个 CPU 修改某条 Cache Line，因此两个 CPU 不可能同时保存对 <code>x</code> 的 Store。</p></blockquote><p>这里混淆了两件事：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Store 位于本核 Store Buffer 中</span><br><span class="line"></span><br><span class="line">和</span><br><span class="line"></span><br><span class="line">Store 已经获得 Cache Line 的写权限，并对外可见</span><br></pre></td></tr></table></figure><p>它们不是一回事。</p><p>Store Buffer 是 CPU 的私有结构：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Store 进入 Store Buffer</span><br><span class="line">        ≠</span><br><span class="line">CPU 已经获得 Cache Line 的写权限</span><br><span class="line">        ≠</span><br><span class="line">Store 已经对其他 CPU 可见</span><br></pre></td></tr></table></figure><p>因此，两个 CPU 可以分别暂存：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">CPU 0：我准备写 x = 1</span><br><span class="line"></span><br><span class="line">CPU 1：我准备写 x = 2</span><br></pre></td></tr></table></figure><p>但是，它们不能无序地同时更新共享的 coherent Cache Line。</p><p>真正排出 Store Buffer 时，Cache Coherence 会对包含 <code>x</code> 的 Cache Line 进行所有权仲裁。</p><h2 id="基础-Example"><a href="#基础-Example" class="headerlink" title="基础 Example"></a>基础 Example</h2><p>初始状态：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 0</span><br></pre></td></tr></table></figure><p>两个 CPU 分别执行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">CPU 0                       CPU 1</span><br><span class="line"></span><br><span class="line">W(x) = 1                    W(x) = 2</span><br></pre></td></tr></table></figure><p>首先，两个 Store 都进入各自的 Store Buffer：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">时间</span><br><span class="line"> │</span><br><span class="line"> ▼</span><br><span class="line"></span><br><span class="line">CPU 0                                      CPU 1</span><br><span class="line"></span><br><span class="line">Store Buffer：x = 1                        Store Buffer：x = 2</span><br><span class="line">        │                                          │</span><br><span class="line">        │ 尚未全局可见                             │ 尚未全局可见</span><br><span class="line">        │                                          │</span><br><span class="line">        └───────────────┬──────────────────────────┘</span><br><span class="line">                        │</span><br><span class="line">                        ▼</span><br><span class="line">                Cache Coherence 仲裁</span><br></pre></td></tr></table></figure><p>此时两个 Store 可以同时处于 pending 状态。</p><p>接下来，Cache Coherence 必须决定谁先获得包含 <code>x</code> 的 Cache Line 的写权限。</p><p>最终只有两种顺序。</p><h3 id="情况一：CPU-0-先排出"><a href="#情况一：CPU-0-先排出" class="headerlink" title="情况一：CPU 0 先排出"></a>情况一：CPU 0 先排出</h3><p>假设 CPU 0 先获得写权限：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">CPU 0 的 x = 1</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">先排出 Store Buffer</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">x = 1 对外可见</span><br></pre></td></tr></table></figure><p>随后 CPU 1 获得写权限：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">CPU 1 的 x = 2</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">后排出 Store Buffer</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">x = 2 对外可见</span><br></pre></td></tr></table></figure><p>同一地址上的 coherence order 是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">CPU 0：W(x) = 1</span><br><span class="line">        │</span><br><span class="line">        │ coherence order</span><br><span class="line">        ▼</span><br><span class="line">CPU 1：W(x) = 2</span><br></pre></td></tr></table></figure><p>可以表示为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">W0(x = 1) →co W1(x = 2)</span><br></pre></td></tr></table></figure><p>最终：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 2</span><br></pre></td></tr></table></figure><h3 id="情况二：CPU-1-先排出"><a href="#情况二：CPU-1-先排出" class="headerlink" title="情况二：CPU 1 先排出"></a>情况二：CPU 1 先排出</h3><p>也可能是 CPU 1 先获得写权限：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">CPU 1：W(x) = 2</span><br><span class="line">        │</span><br><span class="line">        │ coherence order</span><br><span class="line">        ▼</span><br><span class="line">CPU 0：W(x) = 1</span><br></pre></td></tr></table></figure><p>可以表示为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">W1(x = 2) →co W0(x = 1)</span><br></pre></td></tr></table></figure><p>最终：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 1</span><br></pre></td></tr></table></figure><p>因此，两个 Store 可以同时位于各自的 Store Buffer 中，但一旦传播到共享 coherent state，同一地址上的 Store 必须形成一个确定的顺序。x86-TSO 相关模型和一致性约束都禁止同一地址上的 Store 处于相互矛盾的全局顺序中。</p><h2 id="“串行化”的含义"><a href="#“串行化”的含义" class="headerlink" title="“串行化”的含义"></a>“串行化”的含义</h2><p>这里的串行化不是说：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CPU 0 必须执行完全部指令</span><br><span class="line">然后 CPU 1 才能开始执行</span><br></pre></td></tr></table></figure><p>它只表示：</p><blockquote><p>对同一个地址 <code>x</code> 的两个 Store，在 coherent memory system 中必须存在明确的先后顺序。</p></blockquote><p>合法情况是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">x = 1</span><br><span class="line">然后</span><br><span class="line">x = 2</span><br></pre></td></tr></table></figure><p>或者：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">x = 2</span><br><span class="line">然后</span><br><span class="line">x = 1</span><br></pre></td></tr></table></figure><p>不允许存在：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">CPU 2 认为：x = 1 在 x = 2 前面</span><br><span class="line"></span><br><span class="line">CPU 3 认为：x = 2 在 x = 1 前面</span><br></pre></td></tr></table></figure><p>对于同一个内存位置，Cache Coherence 要求相关 Store 被序列化到一致的 coherence order 中。</p><p>需要注意：</p><blockquote><p>其他 CPU 不一定真的读取到中间值。</p></blockquote><p>例如实际顺序是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 0 -&gt; x = 1 -&gt; x = 2</span><br></pre></td></tr></table></figure><p>某个 CPU 可能一直没有读取 <code>x</code>，等它真正读取时只看到：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 2</span><br></pre></td></tr></table></figure><p>但在 coherence order 中，<code>x = 1</code> 仍然排在 <code>x = 2</code> 前面。</p><h2 id="排出-Store-Buffer-不等于写入-DRAM"><a href="#排出-Store-Buffer-不等于写入-DRAM" class="headerlink" title="排出 Store Buffer 不等于写入 DRAM"></a>排出 Store Buffer 不等于写入 DRAM</h2><p>这里所说的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Store 排出 Store Buffer</span><br></pre></td></tr></table></figure><p>不要简单理解成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">数据立即写入 DRAM</span><br></pre></td></tr></table></figure><p>对于普通 Write-Back Cache，Store 排出通常意味着：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">获得包含 x 的 Cache Line 的写权限</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">将 Store 合并到本核的 Cache Line</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">Store 按照 Cache Coherence 规则对外可见</span><br></pre></td></tr></table></figure><p>此时最新数据可能仍然位于某个 CPU 的 Cache 中，并没有立即写回 DRAM。</p><p>所以，更准确的理解是：</p><blockquote><p>Store 从 CPU 的私有 pending 状态，进入了由 Cache Coherence 管理的共享可见状态。</p></blockquote><h2 id="记忆图"><a href="#记忆图" class="headerlink" title="记忆图"></a>记忆图</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">CPU 0                                      CPU 1</span><br><span class="line"></span><br><span class="line">┌──────────────────┐                       ┌──────────────────┐</span><br><span class="line">│ Store Buffer     │                       │ Store Buffer     │</span><br><span class="line">│                  │                       │                  │</span><br><span class="line">│ x = 1            │                       │ x = 2            │</span><br><span class="line">└────────┬─────────┘                       └────────┬─────────┘</span><br><span class="line">         │                                          │</span><br><span class="line">         │       两个 pending Store 可以共存        │</span><br><span class="line">         │                                          │</span><br><span class="line">         └──────────────────┬───────────────────────┘</span><br><span class="line">                            │</span><br><span class="line">                            ▼</span><br><span class="line">                  Cache Coherence 仲裁</span><br><span class="line">                            │</span><br><span class="line">                 ┌──────────┴──────────┐</span><br><span class="line">                 │                     │</span><br><span class="line">                 ▼                     ▼</span><br><span class="line"></span><br><span class="line">          x = 1 →co x = 2       x = 2 →co x = 1</span><br><span class="line"></span><br><span class="line">              最终 x = 2            最终 x = 1</span><br></pre></td></tr></table></figure><p>可以简单记成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">私有 Store Buffer 中：</span><br><span class="line"></span><br><span class="line">CPU 0：x = 1</span><br><span class="line">CPU 1：x = 2</span><br><span class="line"></span><br><span class="line">可以同时存在。</span><br><span class="line"></span><br><span class="line">进入共享 coherent state 时：</span><br><span class="line"></span><br><span class="line">x = 1 → x = 2</span><br><span class="line"></span><br><span class="line">或者：</span><br><span class="line"></span><br><span class="line">x = 2 → x = 1</span><br><span class="line"></span><br><span class="line">必须二选一。</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>双 Store Buffer 场景的核心是：</p><blockquote><p>“同时存在”描述的是两个 CPU 的私有 pending 状态；”串行化”描述的是这些 Store 对共享 coherent state 生效时的全局顺序。</p></blockquote><p>因此：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CPU 0 SB：x = 1</span><br><span class="line">CPU 1 SB：x = 2</span><br></pre></td></tr></table></figure><p>可以同时存在。</p><p>但真正排出时，同一地址上的 Store 必须由 Cache Coherence 排成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 1 →co x = 2</span><br></pre></td></tr></table></figure><p>或者：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 2 →co x = 1</span><br></pre></td></tr></table></figure><p>不会存在两个互相矛盾的全局顺序。</p><p>一句话概括：</p><blockquote><p>两个 CPU 可以同时”准备写”同一个地址，但不能无序地同时”完成对外可见的写”；最终由 Cache Coherence 串行化。</p></blockquote><hr><p>参考资料:</p><ol><li><a href="https://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf">x86-TSO: A Rigorous and Usable Programmer’s Model for x86 Multiprocessors</a></li><li><a href="https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-745.pdf">A Better x86 Memory Model: x86-TSO</a></li><li>Intel SDM</li><li><a href="https://library.oapen.org/handle/20.500.12657/61248">A Primer on Memory Consistency and Cache Coherence, Second Edition</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/30/%E6%B5%85%E8%B0%88Store-Buffer%E4%B8%8ECache-Coherence/</id>
    <link href="http://liujunming.github.io/2026/08/30/%E6%B5%85%E8%B0%88Store-Buffer%E4%B8%8ECache-Coherence/"/>
    <published>2026-08-30T09:23:34.000Z</published>
    <summary>
      <![CDATA[<p>本文只讨论一个问题：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CPU 0 Store Buffer：x = 1</span><br><span class="line">CPU 1 Store Buffer：x = 2</span><br></pre></td></tr></table></figure>

<p>这两个 Store 能否同时存在？</p>]]>
    </summary>
    <title>浅谈Store Buffer与Cache Coherence</title>
    <updated>2026-08-30T10:13:51.827Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="计算机网络" scheme="http://liujunming.github.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <category term="计算机网络" scheme="http://liujunming.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <category term="linux" scheme="http://liujunming.github.io/tags/linux/"/>
    <content>
      <![CDATA[<p>本文通过一个简单的 NAT 例子，介绍 Linux 的 <code>conntrack</code> 流。<span id="more"></span></p><p>本文只讨论 IPv4、TCP 和 SNAT，不展开 conntrack 状态机、Netfilter Hook、DNAT、NAT Hairpin 等内容。</p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>先把核心概念说清楚：</p><blockquote><p>conntrack 流是 Linux 内核为一次双向网络通信维护的一条状态记录。</p></blockquote><p>假设内网客户端访问 Internet 服务器：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">内网客户端                               Internet 服务器</span><br><span class="line">10.0.0.2:50000  ----------------------&gt;  203.0.113.10:443</span><br><span class="line">10.0.0.2:50000  &lt;----------------------  203.0.113.10:443</span><br></pre></td></tr></table></figure><p>Linux 会把两个方向关联成同一条 conntrack 流：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">ORIGINAL 方向：</span><br><span class="line"></span><br><span class="line">10.0.0.2:50000 -&gt; 203.0.113.10:443</span><br><span class="line"></span><br><span class="line">REPLY 方向：</span><br><span class="line"></span><br><span class="line">203.0.113.10:443 -&gt; 10.0.0.2:50000</span><br></pre></td></tr></table></figure><p>所以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">请求方向 + 返回方向</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">一条 conntrack 流</span><br></pre></td></tr></table></figure><p>conntrack 记录中可以包含：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">原始方向</span><br><span class="line">回复方向</span><br><span class="line">协议状态</span><br><span class="line">超时时间</span><br><span class="line">NAT 映射</span><br></pre></td></tr></table></figure><p>Linux 内核的 conntrack 接口也明确区分了 <code>tuple-orig</code> 和 <code>tuple-reply</code> 两个方向。</p><p>需要注意：</p><blockquote><p>conntrack 流不是一个数据包，也不等于应用程序的 socket。</p></blockquote><p>它是 Linux 内核对一次双向通信维护的状态记录。</p><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><p>假设一台 Linux 机器作为 NAT 网关：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">内网客户端</span><br><span class="line">10.0.0.2</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">Linux NAT 网关</span><br><span class="line">公网地址：198.51.100.1</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">Internet 服务器</span><br><span class="line">203.0.113.10</span><br></pre></td></tr></table></figure><p>内网客户端发送：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">10.0.0.2:50000 -&gt; 203.0.113.10:443</span><br></pre></td></tr></table></figure><p>经过 SNAT 后，源地址被修改为网关的公网地址：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">198.51.100.1:62000 -&gt; 203.0.113.10:443</span><br></pre></td></tr></table></figure><p>服务器看到的客户端是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">198.51.100.1:62000</span><br></pre></td></tr></table></figure><p>因此服务器返回：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">203.0.113.10:443 -&gt; 198.51.100.1:62000</span><br></pre></td></tr></table></figure><p>问题来了：</p><blockquote><p>Linux 网关收到这个返回包后，怎么知道应该把它转发给哪个内网客户端？</p></blockquote><p>网关必须记住下面这组关系：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">198.51.100.1:62000</span><br><span class="line">        │</span><br><span class="line">        │ 对应</span><br><span class="line">        ▼</span><br><span class="line">10.0.0.2:50000</span><br></pre></td></tr></table></figure><p>这就是 conntrack 的作用。</p><p>它为这次通信保存 NAT 映射，使返回包可以执行反向转换：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">NAT 前：</span><br><span class="line"></span><br><span class="line">203.0.113.10:443 -&gt; 198.51.100.1:62000</span><br><span class="line"></span><br><span class="line">NAT 后：</span><br><span class="line"></span><br><span class="line">203.0.113.10:443 -&gt; 10.0.0.2:50000</span><br></pre></td></tr></table></figure><p>如果没有 conntrack，Linux 网关只看到：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">目的地址：198.51.100.1:62000</span><br></pre></td></tr></table></figure><p>却不知道它原来对应：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">10.0.0.2:50000</span><br></pre></td></tr></table></figure><p>返回包就无法正确送回内网客户端。</p><h2 id="基础-Example"><a href="#基础-Example" class="headerlink" title="基础 Example"></a>基础 Example</h2><p>完整过程如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">1. 内网客户端发送</span><br><span class="line"></span><br><span class="line">   10.0.0.2:50000</span><br><span class="line">          -&gt;</span><br><span class="line">   203.0.113.10:443</span><br><span class="line"></span><br><span class="line">2. Linux 网关执行 SNAT</span><br><span class="line"></span><br><span class="line">   198.51.100.1:62000</span><br><span class="line">          -&gt;</span><br><span class="line">   203.0.113.10:443</span><br><span class="line"></span><br><span class="line">3. 服务器返回</span><br><span class="line"></span><br><span class="line">   203.0.113.10:443</span><br><span class="line">          -&gt;</span><br><span class="line">   198.51.100.1:62000</span><br><span class="line"></span><br><span class="line">4. Linux 根据 conntrack 记录执行反向 NAT</span><br><span class="line"></span><br><span class="line">   203.0.113.10:443</span><br><span class="line">          -&gt;</span><br><span class="line">   10.0.0.2:50000</span><br></pre></td></tr></table></figure><p>conntrack 在其中保存：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">10.0.0.2:50000</span><br><span class="line">        │</span><br><span class="line">        │ SNAT 映射</span><br><span class="line">        ▼</span><br><span class="line">198.51.100.1:62000</span><br></pre></td></tr></table></figure><p>这样，正向数据包和返回数据包就能使用同一套 NAT 映射。</p><h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><p>简化地说：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">第一个数据包</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">创建 conntrack 流</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">确定并记录 NAT 映射</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">后续数据包复用这条映射</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">连接结束或超时后删除记录</span><br></pre></td></tr></table></figure><p>对于 nftables 的有状态 NAT，通常只有一条连接的第一个数据包经过 NAT 规则并建立 NAT 绑定，后续数据包根据 conntrack 中已经保存的信息完成转换。</p><h2 id="记忆图"><a href="#记忆图" class="headerlink" title="记忆图"></a>记忆图</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">内网客户端</span><br><span class="line">10.0.0.2:50000</span><br><span class="line">       │</span><br><span class="line">       │ 发送请求</span><br><span class="line">       ▼</span><br><span class="line">┌─────────────────────────────┐</span><br><span class="line">│ Linux NAT 网关              │</span><br><span class="line">│                             │</span><br><span class="line">│ conntrack 记录：            │</span><br><span class="line">│                             │</span><br><span class="line">│ 10.0.0.2:50000              │</span><br><span class="line">│          ↕                  │</span><br><span class="line">│ 198.51.100.1:62000          │</span><br><span class="line">└─────────────────────────────┘</span><br><span class="line">       │</span><br><span class="line">       │ SNAT 后发送</span><br><span class="line">       ▼</span><br><span class="line">Internet 服务器</span><br><span class="line">203.0.113.10:443</span><br></pre></td></tr></table></figure><p>可以简单记成：</p><blockquote><p>NAT 修改了地址，conntrack 记住了地址是怎么修改的。</p></blockquote><p>正向报文使用 NAT 映射：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">内网地址 -&gt; 公网地址</span><br></pre></td></tr></table></figure><p>返回报文使用反向映射：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">公网地址 -&gt; 内网地址</span><br></pre></td></tr></table></figure><p>这就是 conntrack 流在 NAT 中最核心的作用。</p><hr><p>参考资料:</p><ol><li><a href="https://docs.kernel.org/next/networking/netlink_spec/conntrack.html">Linux Kernel Documentation：conntrack Netlink Specification</a></li><li><a href="https://www.netfilter.org/projects/nftables/manpage.html">Netfilter nftables Manual：NAT chain</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/30/%E6%B5%85%E8%B0%88-Linux-%E7%9A%84-conntrack-%E6%B5%81/</id>
    <link href="http://liujunming.github.io/2026/08/30/%E6%B5%85%E8%B0%88-Linux-%E7%9A%84-conntrack-%E6%B5%81/"/>
    <published>2026-08-30T02:49:39.000Z</published>
    <summary>
      <![CDATA[<p>本文通过一个简单的 NAT 例子，介绍 Linux 的 <code>conntrack</code> 流。]]>
    </summary>
    <title>浅谈 Linux 的 conntrack 流</title>
    <updated>2026-08-30T02:59:10.896Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="体系结构" scheme="http://liujunming.github.io/categories/%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    <category term="ARM" scheme="http://liujunming.github.io/tags/ARM/"/>
    <category term="体系结构" scheme="http://liujunming.github.io/tags/%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    <category term="Intel" scheme="http://liujunming.github.io/tags/Intel/"/>
    <category term="Memory Ordering" scheme="http://liujunming.github.io/tags/Memory-Ordering/"/>
    <content>
      <![CDATA[<p>本文将比较x86和ARM的memory ordering的差异。<span id="more"></span></p><p>本文讨论 AArch64（Armv8-A&#x2F;Armv9-A）上普通 CPU 之间的硬件 Memory Ordering。示例假定访问的是可共享、cacheable 的 Normal Memory，访问自然对齐且单次访问自身不撕裂。对照表中的不同地址访问还假定没有地址依赖、数据依赖、Acquire、Release 或 Barrier。本文不讨论编译器重排、C&#x2F;C++ data race、MMIO、DMA、Device Memory、非对齐访问和 mixed-size 访问。</p><p>x86 与 ARM 的核心差异可以先浓缩成一句话：</p><blockquote><p><strong>x86 对普通内存访问提供较强的隐含顺序，主要放松不同地址的 Store→Load；Arm 对普通、独立、跨地址访问提供的隐含顺序更少，程序需要通过 Acquire、Release 或 Barrier 明确建立所需顺序。</strong></p></blockquote><h2 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h2><p><a href="/2026/08/22/%E6%B5%85%E8%B0%88x86%E7%9A%84TSO-memory-ordering/">浅谈x86的TSO memory ordering</a></p><h2 id="ARM-的基本模型：Weakly-Ordered"><a href="#ARM-的基本模型：Weakly-Ordered" class="headerlink" title="ARM 的基本模型：Weakly Ordered"></a>ARM 的基本模型：Weakly Ordered</h2><p>AArch64 对普通 <code>LDR</code>、<code>STR</code> 提供的是较弱的顺序保证。</p><p>对于不同地址、没有依赖关系的普通访问，可以先用下面这个入门模型理解：</p><table><thead><tr><th>较老操作 → 较新操作</th><th>x86</th><th>ARM 普通 <code>LDR/STR</code></th></tr></thead><tbody><tr><td>Load → Load</td><td>保持</td><td>可能表现为重排</td></tr><tr><td>Load → Store</td><td>保持</td><td>可能表现为重排</td></tr><tr><td>Store → Store</td><td>保持</td><td>可能表现为重排</td></tr><tr><td>Store → Load</td><td>不同地址时可能重排；同地址不能读回旧值</td><td>不同地址时可能表现为重排</td></tr></tbody></table><p>这个表是便于建立直觉的简化模型。</p><p>Arm对其模型的官方概括是：</p><blockquote><p>Arm 是弱顺序架构，允许内存访问以不同于程序顺序的方式被观察和完成。</p></blockquote><p>与此同时，同一位置的写仍然被序列化；弱顺序并不意味着同一变量可以毫无规则地变化。</p><h2 id="Message-Passing"><a href="#Message-Passing" class="headerlink" title="Message Passing"></a>Message Passing</h2><p>初始状态：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">data = 0 // 数据</span><br><span class="line">flag = 0 // ready flag</span><br></pre></td></tr></table></figure><p>生产者：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">CPU 0</span><br><span class="line"></span><br><span class="line">data = 42;</span><br><span class="line">flag = 1;</span><br></pre></td></tr></table></figure><p>消费者：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">CPU 1</span><br><span class="line"></span><br><span class="line">r1 = flag;</span><br><span class="line">r2 = data;</span><br></pre></td></tr></table></figure><p>我们关心：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">r1 == 1</span><br><span class="line">r2 == 0</span><br></pre></td></tr></table></figure><p>也就是：</p><blockquote><p>消费者已经看见”数据准备完成”的 flag，却仍然读到旧 data。</p></blockquote><h3 id="x86-为什么禁止这个结果？"><a href="#x86-为什么禁止这个结果？" class="headerlink" title="x86 为什么禁止这个结果？"></a>x86 为什么禁止这个结果？</h3><p>x86 保持：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Store → Store</span><br></pre></td></tr></table></figure><p>因此 CPU 0 对外不能表现为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">flag = 1</span><br><span class="line">先于</span><br><span class="line">data = 42</span><br></pre></td></tr></table></figure><p>x86 也保持：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Load → Load</span><br></pre></td></tr></table></figure><p>因此 CPU 1 对外不能表现为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">先读 data</span><br><span class="line">后读 flag</span><br></pre></td></tr></table></figure><p>所以在普通 x86 WB 内存的机器指令层面：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">flag == 1 &amp;&amp; data == 0</span><br></pre></td></tr></table></figure><p>是不允许的。</p><h3 id="ARM-为什么可能允许？"><a href="#ARM-为什么可能允许？" class="headerlink" title="ARM 为什么可能允许？"></a>ARM 为什么可能允许？</h3><p>ARM 对普通独立访问不自动保留这两条跨地址顺序：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">CPU 0：</span><br><span class="line"></span><br><span class="line">data = 42</span><br><span class="line">    ↓ ARM 普通 store 不保证这一跨地址顺序</span><br><span class="line">flag = 1</span><br><span class="line"></span><br><span class="line">CPU 1：</span><br><span class="line"></span><br><span class="line">读取 flag</span><br><span class="line">    ↓ ARM 普通 load 不保证这一跨地址顺序</span><br><span class="line">读取 data</span><br></pre></td></tr></table></figure><h2 id="Acquire-和-Release-是怎么解决问题的？"><a href="#Acquire-和-Release-是怎么解决问题的？" class="headerlink" title="Acquire 和 Release 是怎么解决问题的？"></a>Acquire 和 Release 是怎么解决问题的？</h2><p>正确的 ARM 版本可以写成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">生产者：</span><br><span class="line"></span><br><span class="line">写 data = 42</span><br><span class="line">Store-Release flag = 1</span><br><span class="line"></span><br><span class="line">消费者：</span><br><span class="line"></span><br><span class="line">Load-Acquire flag</span><br><span class="line">读取 data</span><br></pre></td></tr></table></figure><h3 id="Release：挡住前面的操作"><a href="#Release：挡住前面的操作" class="headerlink" title="Release：挡住前面的操作"></a>Release：挡住前面的操作</h3><p>Release store 的含义是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">release 之前的 load/store</span><br><span class="line">          ↓</span><br><span class="line">必须排在 release store 之前</span><br></pre></td></tr></table></figure><p>图示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">之前的访问  ──────&gt;  Store-Release</span><br><span class="line">                     │</span><br><span class="line">                     │ 后面的访问仍可能向前移动</span><br></pre></td></tr></table></figure><p>因此：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">data = 42</span><br><span class="line">STLR flag = 1</span><br></pre></td></tr></table></figure><p><code>STLR</code> 保证的是 Producer 侧此前对 <code>data</code> 的访问排在对 <code>flag</code> 的 release store 之前：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">写 data</span><br><span class="line">   ↓</span><br><span class="line">STLR flag = 1</span><br></pre></td></tr></table></figure><p>它只解决发布端的顺序，并不能单独约束 Consumer。Consumer 如果仍使用两个普通 <code>LDR</code>，对 <code>data</code> 的读取仍可能在观察 <code>flag</code> 之前就已被满足，因此 release 单独使用仍不能排除 Message Passing 的坏结果。在本文采用的 release&#x2F;acquire 方案中，Consumer 还需要通过 <code>LDAR</code> 观察这个 <code>flag</code>，再读取 <code>data</code>。</p><p>但 release 是单向的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Store-Release</span><br><span class="line">后面的访问</span><br></pre></td></tr></table></figure><p>后面的访问并不一定被 release 阻挡。</p><h3 id="Acquire：挡住后面的操作"><a href="#Acquire：挡住后面的操作" class="headerlink" title="Acquire：挡住后面的操作"></a>Acquire：挡住后面的操作</h3><p>Acquire load 的含义是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Load-Acquire</span><br><span class="line">     ↓</span><br><span class="line">后面的 load/store 必须排在 acquire 之后</span><br></pre></td></tr></table></figure><p>图示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">之前的访问可能越过</span><br><span class="line">             │</span><br><span class="line">             ▼</span><br><span class="line">       Load-Acquire ──────&gt; 后面的访问</span><br></pre></td></tr></table></figure><p>所以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">LDAR flag</span><br><span class="line">读取 data</span><br></pre></td></tr></table></figure><p>不允许对 <code>data</code> 的读取越过 acquire，跑到读取 <code>flag</code> 之前。</p><p>Acquire&#x2F;Release 提供单向排序语义，而不是 Full Barrier。可以把它们直观地理解为单向的 ordering primitive：Acquire 约束该访问之后的操作，Release 约束该访问之前的操作。</p><h3 id="要建立跨-CPU-的发布关系，LDAR-必须观察到-STLR-发布的值"><a href="#要建立跨-CPU-的发布关系，LDAR-必须观察到-STLR-发布的值" class="headerlink" title="要建立跨 CPU 的发布关系，LDAR 必须观察到 STLR 发布的值"></a>要建立跨 CPU 的发布关系，LDAR 必须观察到 STLR 发布的值</h3><p>当 Consumer 的 <code>LDAR</code> 读取到 Producer 的 <code>STLR</code> 写入的 <code>flag == 1</code> 时，Store-Release 和 Load-Acquire 会将以下顺序连接起来：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Producer 写 data</span><br><span class="line">   │</span><br><span class="line">   ▼</span><br><span class="line">Producer STLR flag = 1</span><br><span class="line">   │</span><br><span class="line">   │  Consumer 的 LDAR 观察到该值</span><br><span class="line">   ▼</span><br><span class="line">Consumer LDAR flag == 1</span><br><span class="line">   │</span><br><span class="line">   ▼</span><br><span class="line">Consumer 读 data</span><br></pre></td></tr></table></figure><p>因此，在这个 AArch64 Message Passing 测试中：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">flag == 1 &amp;&amp; data == 0</span><br></pre></td></tr></table></figure><p>的执行被禁止。</p><p>C&#x2F;C++ 原子内存模型中也有对应的 release&#x2F;acquire 发布模式，但那属于语言层内存模型，本文不展开。</p><h3 id="为什么生产者和消费者两边都要有顺序？"><a href="#为什么生产者和消费者两边都要有顺序？" class="headerlink" title="为什么生产者和消费者两边都要有顺序？"></a>为什么生产者和消费者两边都要有顺序？</h3><p>只有 release：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">生产者：data = 42; store_release(flag, 1);</span><br><span class="line">消费者：load_relaxed(flag); load_relaxed(data);</span><br></pre></td></tr></table></figure><p>生产者的发布顺序正确，但消费者仍可能把 <code>data</code> 的 load 提前。</p><p>只有 acquire：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">生产者：store_relaxed(data, 42); store_relaxed(flag, 1);</span><br><span class="line">消费者：load_acquire(flag); load_relaxed(data);</span><br></pre></td></tr></table></figure><p>消费者顺序正确，但生产者可能先让 <code>flag</code> 可见。</p><p>所以典型发布关系需要：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">生产者 release</span><br><span class="line">消费者 acquire</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><h3 id="x86-TSO"><a href="#x86-TSO" class="headerlink" title="x86 TSO"></a>x86 TSO</h3><p>可以把 x86 想成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">每个 CPU 都有一个 FIFO store buffer</span><br><span class="line"></span><br><span class="line">普通 load 可以绕过其中较老的、不同地址的 store</span><br><span class="line"></span><br><span class="line">除此之外，大部分程序顺序都保留</span><br></pre></td></tr></table></figure><p>因此最主要的硬件弱点是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Store → Load</span><br></pre></td></tr></table></figure><h3 id="ARM-Weak-Ordering"><a href="#ARM-Weak-Ordering" class="headerlink" title="ARM Weak Ordering"></a>ARM Weak Ordering</h3><p>可以把 ARM 想成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">普通 LDR/STR 对独立跨地址访问提供的隐含顺序较少</span><br><span class="line"></span><br><span class="line">跨不同地址的顺序默认不代表同步关系</span><br><span class="line"></span><br><span class="line">需要时由程序通过 acquire/release/barrier</span><br><span class="line">明确告诉CPU保留哪些顺序</span><br></pre></td></tr></table></figure><p>ARM 并不是”随机执行”，它仍然具备：</p><ul><li>单线程语义；</li><li>同地址 coherence；</li><li>原子操作；</li><li>依赖关系约束；</li><li>acquire&#x2F;release；</li><li>barrier；</li><li>正式定义的 allowed&#x2F;forbidden executions。</li></ul><p>它只是比 x86 给硬件留出了更大的优化空间。</p><h2 id="记忆图"><a href="#记忆图" class="headerlink" title="记忆图"></a>记忆图</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">x86 TSO</span><br><span class="line">│</span><br><span class="line">├── Load  → Load       保留</span><br><span class="line">├── Load  → Store      保留</span><br><span class="line">├── Store → Store      保留</span><br><span class="line">└── Store → Load       不同地址时可能放松</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">ARM 普通、独立、跨地址访问</span><br><span class="line">│</span><br><span class="line">├── Load  → Load       不保证</span><br><span class="line">├── Load  → Store      不保证</span><br><span class="line">├── Store → Store      不保证</span><br><span class="line">└── Store → Load       不同地址时不保证</span><br></pre></td></tr></table></figure><p>同地址访问仍受到单地址 coherence、读己之写和相关架构规则约束。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">Coherence：</span><br><span class="line"></span><br><span class="line">    同一个地址的写，大家按什么顺序看见</span><br><span class="line"></span><br><span class="line">Ordering：</span><br><span class="line"></span><br><span class="line">    不同地址的访问，大家按什么顺序看见</span><br><span class="line"></span><br><span class="line">Atomicity：</span><br><span class="line"></span><br><span class="line">    单次操作是否不可分割</span><br><span class="line"></span><br><span class="line">Acquire：</span><br><span class="line"></span><br><span class="line">    后面的访问不能跑到前面</span><br><span class="line"></span><br><span class="line">Release：</span><br><span class="line"></span><br><span class="line">    前面的访问不能跑到后面</span><br><span class="line"></span><br><span class="line">Full barrier：</span><br><span class="line"></span><br><span class="line">    前面的访问全部排在后面的访问之前</span><br><span class="line"></span><br><span class="line">x86：</span><br><span class="line"></span><br><span class="line">    大部分顺序默认存在，只主要放松 Store→Load</span><br><span class="line"></span><br><span class="line">ARM：</span><br><span class="line"></span><br><span class="line">    普通访问默认较弱，需要显式表达必要顺序</span><br></pre></td></tr></table></figure><hr><p>参考资料:</p><ol><li>Intel 64 and IA-32 Architectures Software Developer’s Manual，Volume 3A</li><li><a href="https://support.arm.com/documentation/ddi0487/mc/-Part-K-Appendixes/-Appendix-K11-Barrier-Litmus-Tests/-K11-2-Load-Acquire--Store-Release-and-barriers/-K11-2-1-Message-passing">Arm Architecture Reference Manual for A-profile architecture</a></li><li><a href="https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/generate-litmus-tests-automatically-diy7-tool">How to generate litmus tests automatically with the diy7 tool</a></li><li><a href="https://developer.arm.com/community/arm-community-blogs/b/tools-software-ides-blog/posts/dpdk-optimization-on-arm">DPDK Optimization on Arm</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/23/x86%E5%92%8CARM%E7%9A%84memory-ordering%E6%AF%94%E8%BE%83/</id>
    <link href="http://liujunming.github.io/2026/08/23/x86%E5%92%8CARM%E7%9A%84memory-ordering%E6%AF%94%E8%BE%83/"/>
    <published>2026-08-23T00:37:28.000Z</published>
    <summary>
      <![CDATA[<p>本文将比较x86和ARM的memory ordering的差异。]]>
    </summary>
    <title>x86和ARM的memory ordering比较</title>
    <updated>2026-08-23T12:59:07.407Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="体系结构" scheme="http://liujunming.github.io/categories/%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    <category term="体系结构" scheme="http://liujunming.github.io/tags/%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    <category term="Memory Ordering" scheme="http://liujunming.github.io/tags/Memory-Ordering/"/>
    <content>
      <![CDATA[<p>本文将基于第一性原理，浅谈x86的TSO memory ordering机制。<span id="more"></span></p><p>本文只讨论 x86-64 上自然对齐的普通 load&#x2F;store，对普通 WB（Write-Back）cacheable memory 的访问。暂不讨论编译器重排、non-temporal store、string operation、WC&#x2F;UC memory、MMIO、DMA、CLFLUSH、locked instruction 和 serializing instruction。</p><p>本文示例是硬件litmus test，不表示普通非原子C语言访问可以直接这样编写。</p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>先把最容易混淆的一点说清楚：<br><strong>Memory ordering 讨论的不是“CPU 是否按顺序执行指令”，而是多个 CPU 访问多个共享地址时，一个 CPU 的内存操作，可能以什么顺序被其他 CPU 观察到</strong>。</p><h2 id="四种“顺序”"><a href="#四种“顺序”" class="headerlink" title="四种“顺序”"></a>四种“顺序”</h2><p>假设程序写的是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">A</span><br><span class="line">B</span><br><span class="line">C</span><br></pre></td></tr></table></figure><p>这里实际上可能存在四种不同的顺序。</p><h3 id="程序顺序-Program-Order"><a href="#程序顺序-Program-Order" class="headerlink" title="程序顺序 Program Order"></a>程序顺序 Program Order</h3><p>源代码或机器指令中：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">A 在 B 前面</span><br><span class="line">B 在 C 前面</span><br></pre></td></tr></table></figure><p>通常写成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">A →po B →po C</span><br></pre></td></tr></table></figure><p>这只是”指令流中的顺序”。</p><h3 id="CPU-内部执行顺序"><a href="#CPU-内部执行顺序" class="headerlink" title="CPU 内部执行顺序"></a>CPU 内部执行顺序</h3><p>现代 CPU 可能：</p><ul><li>提前发射 load</li><li>推迟 store</li><li>猜测执行</li><li>并行执行无依赖指令</li><li>将 store 暂存在 store buffer</li><li>从 store buffer 向本核 load 转发数据</li></ul><p>所以 CPU 内部实际开始或完成某条指令的时间，不一定等于程序顺序。</p><p>不过，单线程通常看不出区别，因为 CPU 必须维持单线程的架构语义。</p><h3 id="内存操作的可见顺序"><a href="#内存操作的可见顺序" class="headerlink" title="内存操作的可见顺序"></a>内存操作的可见顺序</h3><p>这是 memory ordering 真正关注的内容。</p><p>例如 CPU 0 执行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">x = 1;</span><br><span class="line">y = 1;</span><br></pre></td></tr></table></figure><p>CPU 1 可能看到：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">y == 1</span><br><span class="line">x == 0</span><br></pre></td></tr></table></figure><p>这意味着，对 CPU 1 而言：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">对 y 的 store</span><br><span class="line">似乎先于</span><br><span class="line">对 x 的 store</span><br></pre></td></tr></table></figure><p>即使 CPU 0 的程序顺序正好相反。</p><blockquote><p>注意：这里描述的是一般弱内存模型中可能出现的可见顺序，不是 x86 TSO 下普通 WB load&#x2F;store 的合法结果。对于 x86，Store→Store 和 Load→Load 均保持顺序，因此 y&#x3D;&#x3D;1 &amp;&amp; x&#x3D;&#x3D;0 被禁止。</p></blockquote><h3 id="同一地址的-coherence-order"><a href="#同一地址的-coherence-order" class="headerlink" title="同一地址的 coherence order"></a>同一地址的 coherence order</h3><p>假设多个 CPU 依次向同一个地址写：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">x = 1</span><br><span class="line">x = 2</span><br><span class="line">x = 3</span><br></pre></td></tr></table></figure><p>缓存一致性协议通常要求：</p><blockquote><p>对同一个地址，所有 coherent observer 对这些 store 的先后顺序必须达成一致。</p></blockquote><p>但这只解决了<strong>单个地址</strong>的问题，并不自动解决不同地址之间的顺序：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">x = 1</span><br><span class="line">y = 1</span><br></pre></td></tr></table></figure><p>因此必须牢记：</p><blockquote><p><strong>粗略地说，Cache Coherence 主要解决单个地址的一致性；Memory Ordering 主要解决多个地址之间的可见顺序。</strong></p></blockquote><p>Armv8 同样要求对同一位置的写进行序列化，但它并不为不同位置的普通访问提供类似 x86 TSO 的强顺序。</p><h2 id="基础Example"><a href="#基础Example" class="headerlink" title="基础Example"></a>基础Example</h2><p>初始状态：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">x = 0</span><br><span class="line">y = 0</span><br></pre></td></tr></table></figure><p>程序：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">CPU 0                       CPU 1</span><br><span class="line"></span><br><span class="line">A: W(x)=1                   C: W(y)=1</span><br><span class="line">B: R(y)→r0                  D: R(x)→r1</span><br></pre></td></tr></table></figure><p>每个 CPU 的程序顺序是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CPU 0：A → B</span><br><span class="line">CPU 1：C → D</span><br></pre></td></tr></table></figure><p>寄存器总共有四种组合：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">r0=0, r1=0</span><br><span class="line">r0=0, r1=1</span><br><span class="line">r0=1, r1=0</span><br><span class="line">r0=1, r1=1</span><br></pre></td></tr></table></figure><h2 id="Sequential-Consistency"><a href="#Sequential-Consistency" class="headerlink" title="Sequential Consistency"></a>Sequential Consistency</h2><p>先构造一个最符合人直觉的模型。</p><p>Sequential Consistency，简称 SC，可以理解为：</p><blockquote><p>系统仿佛有一个全局开关，每次只允许一个 CPU 完成一个内存操作；所有 CPU 的操作被混合进一个全局总序，同时每个 CPU 自己的程序顺序不能被打乱。</p></blockquote><p>以基础Example为例，不能出现：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">B A C D</span><br></pre></td></tr></table></figure><p>因为它违反了 CPU 0 的程序顺序。</p><h3 id="Example的结果"><a href="#Example的结果" class="headerlink" title="Example的结果"></a>Example的结果</h3><p>两个线程各有两个操作，保持：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">A 在 B 前</span><br><span class="line">C 在 D 前</span><br></pre></td></tr></table></figure><table><thead><tr><th>全局顺序</th><th>执行过程</th><th align="right"><code>(r0,r1)</code></th></tr></thead><tbody><tr><td><code>A B C D</code></td><td>CPU 0 先写 x，再读到旧 y；随后 CPU 1 写 y、读到新 x</td><td align="right"><code>(0,1)</code></td></tr><tr><td><code>A C B D</code></td><td>两个写先发生，两个读都看到 1</td><td align="right"><code>(1,1)</code></td></tr><tr><td><code>A C D B</code></td><td>两个写先发生，两个读都看到 1</td><td align="right"><code>(1,1)</code></td></tr><tr><td><code>C A B D</code></td><td>两个写先发生，两个读都看到 1</td><td align="right"><code>(1,1)</code></td></tr><tr><td><code>C A D B</code></td><td>两个写先发生，两个读都看到 1</td><td align="right"><code>(1,1)</code></td></tr><tr><td><code>C D A B</code></td><td>CPU 1 先写 y，再读到旧 x；随后 CPU 0 写 x、读到新 y</td><td align="right"><code>(1,0)</code></td></tr></tbody></table><table><thead><tr><th>结果</th><th align="right">SC</th></tr></thead><tbody><tr><td><code>(0,0)</code></td><td align="right"><strong>禁止</strong></td></tr><tr><td><code>(0,1)</code></td><td align="right">允许</td></tr><tr><td><code>(1,0)</code></td><td align="right">允许</td></tr><tr><td><code>(1,1)</code></td><td align="right">允许</td></tr></tbody></table><h2 id="store-buffer"><a href="#store-buffer" class="headerlink" title="store buffer"></a>store buffer</h2><p>CPU 0 想执行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 1</span><br></pre></td></tr></table></figure><p>如果 <code>x</code> 对应的 cache line 当前被其他 CPU 共享，CPU 0 可能需要：</p><ol><li>发出 Read For Ownership；</li><li>通知其他 CPU invalidate 该 cache line；</li><li>等待一致性协议响应；</li><li>获得写权限；</li><li>才能更新 cache line。</li></ol><p>如果 CPU 每次 store 都等完整个过程，流水线会频繁停顿。</p><p>因此 CPU 通常先把 store 放进一个本地 store buffer：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">CPU pipeline</span><br><span class="line">     │</span><br><span class="line">     ▼</span><br><span class="line">Store Buffer：x = 1</span><br><span class="line">     │</span><br><span class="line">     ▼</span><br><span class="line">L1 cache / coherence network</span><br></pre></td></tr></table></figure><p>Store 可以先被本核接受并进入私有 Store Buffer，CPU 随后继续执行后续指令；但此时该 Store 可能还没有排出 Store Buffer，因此尚未对其他处理器全局可见。</p><h2 id="x86-TSO"><a href="#x86-TSO" class="headerlink" title="x86 TSO"></a>x86 TSO</h2><p>x86-64 普通 WB 内存的模型通常称为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TSO：Total Store Order</span><br></pre></td></tr></table></figure><p>可以用下面这个简化模型理解：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">每个 CPU：</span><br><span class="line"></span><br><span class="line">程序</span><br><span class="line">  │</span><br><span class="line">  ├── Load ───────────────&gt; cache/coherence</span><br><span class="line">  │</span><br><span class="line">  └── Store ──&gt; FIFO Store Buffer ──&gt; cache/coherence</span><br><span class="line">                         ▲</span><br><span class="line">                         │</span><br><span class="line">             本核 load 可以做 store forwarding</span><br></pre></td></tr></table></figure><p>一个 store 可以先进入本核 store buffer，而暂时不对其他核可见。后面的 load 如果访问不同地址，可以绕过这个尚未排空的 store。</p><p>因此，简化地说，x86 保持：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Load  → Load</span><br><span class="line">Load  → Store</span><br><span class="line">Store → Store</span><br></pre></td></tr></table></figure><p>但放松：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Store → Load       不同地址时</span><br></pre></td></tr></table></figure><p>Intel 手册明确规定：普通读之间不重排，写不能越过更早的读，普通写之间不重排；但读可以越过更早的、访问不同地址的写。</p><p>关键性质是：</p><ol><li>本 CPU 的 store 按程序顺序从 store buffer 对外传播；</li><li>load 通常按程序顺序被观察；</li><li>但 load 可以绕过更早的、访问不同地址的 buffered store；</li><li>对同一地址，CPU 必须处理 store forwarding 和同地址 coherence order。</li></ol><p>Intel 给出的普通 load&#x2F;store 顺序规则可以总结为：</p><table><thead><tr><th>较老操作 → 较新操作</th><th align="right">x86 普通 WB 内存能否表现为重排</th></tr></thead><tbody><tr><td>Load → Load</td><td align="right">否</td></tr><tr><td>Load → Store</td><td align="right">否</td></tr><tr><td>Store → Store</td><td align="right">否</td></tr><tr><td>Store → Load，不同地址</td><td align="right"><strong>可以</strong></td></tr><tr><td>Store → Load，同一地址</td><td align="right">不可以读回旧值</td></tr></tbody></table><p>这里的“否”表示架构不允许其他处理器观察到相反顺序。</p><h3 id="Example的结果-1"><a href="#Example的结果-1" class="headerlink" title="Example的结果"></a>Example的结果</h3><p>可以出现如下执行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">时间</span><br><span class="line"> │</span><br><span class="line"> ▼</span><br><span class="line"></span><br><span class="line">CPU 0                              CPU 1</span><br><span class="line"></span><br><span class="line">W(x)=1 进入 Store Buffer           W(y)=1 进入 Store Buffer</span><br><span class="line">    │                                  │</span><br><span class="line">    │ x 尚未对 CPU 1 可见              │ y 尚未对 CPU 0 可见</span><br><span class="line">    │                                  │</span><br><span class="line">R(y) 从 cache/memory 读到 0         R(x) 从 cache/memory 读到 0</span><br><span class="line">    │                                  │</span><br><span class="line">Store Buffer 最后排出 x=1           Store Buffer 最后排出 y=1</span><br></pre></td></tr></table></figure><p>注意：</p><ul><li>CPU 0 自己知道它写了 <code>x=1</code>；</li><li>但 CPU 1 暂时还看不到；</li><li>CPU 0 后面的 load 访问的是另一个地址 <code>y</code>，可以不等 <code>x=1</code> 排出；</li><li>CPU 1 同理。</li></ul><p>所以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">r0=0</span><br><span class="line">r1=0</span><br></pre></td></tr></table></figure><p>在 x86 上是允许的。</p><h3 id="store-forwarding"><a href="#store-forwarding" class="headerlink" title="store forwarding"></a>store forwarding</h3><p>初始：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">x = 0</span><br></pre></td></tr></table></figure><p>CPU 0 执行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">x = 1;</span><br><span class="line">r0 = x;</span><br></pre></td></tr></table></figure><p>从程序语义来看，结果必须是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">r0 = 1</span><br></pre></td></tr></table></figure><p>但是，前面说过，<code>x = 1</code> 可能先进入 Store Buffer：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">CPU 0</span><br><span class="line"></span><br><span class="line">执行 x = 1</span><br><span class="line">     │</span><br><span class="line">     ▼</span><br><span class="line">Store Buffer：</span><br><span class="line">    x = 1</span><br><span class="line"></span><br><span class="line">Cache / 内存：</span><br><span class="line">    x = 0</span><br></pre></td></tr></table></figure><p>这时，如果后面的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">r0 = x;</span><br></pre></td></tr></table></figure><p>直接去 Cache 中读取，就会读到：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">r0 = 0</span><br></pre></td></tr></table></figure><p>这显然违反单线程程序语义：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">x = 1;</span><br><span class="line">r0 = x;</span><br><span class="line"></span><br><span class="line">assert(r0 == 1);</span><br></pre></td></tr></table></figure><p>因此，CPU 执行 load 时不能只看 Cache，还需要检查：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Store Buffer 中有没有更早的、写同一地址的 store？</span><br></pre></td></tr></table></figure><p>如果有，就直接使用 Store Buffer 中的新值：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">                   ┌─────────────────────┐</span><br><span class="line">x = 1 ───────────&gt; │ Store Buffer: x = 1 │</span><br><span class="line">                   └──────────┬──────────┘</span><br><span class="line">                              │</span><br><span class="line">                              │ Store Forwarding</span><br><span class="line">                              ▼</span><br><span class="line">r0 = x  &lt;──────────────────── 1</span><br></pre></td></tr></table></figure><p>所以最终：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">r0 = 1</span><br></pre></td></tr></table></figure><p>这个机制就叫：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Store-to-Load Forwarding</span><br></pre></td></tr></table></figure><p>或者简称：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Store Forwarding</span><br></pre></td></tr></table></figure><p>Intel 的优化手册将其描述为：当较早的 store 和较新的 load 满足地址、大小等条件时，load 可以直接从 store buffer 获得数据，而不需要等待 store 写入缓存。</p><h3 id="为什么叫-Total-Store-Order"><a href="#为什么叫-Total-Store-Order" class="headerlink" title="为什么叫 Total Store Order"></a>为什么叫 Total Store Order</h3><blockquote><p>x86 TSO 可以抽象成“每核一个私有 FIFO Store Buffer，加一个共享内存”；Load 可以提前读取本核最新的 buffered Store，而各核 Store 排入共享内存时形成一致的全局 Store 顺序。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Total：</span><br><span class="line">    Store 最终进入一个一致的全局顺序</span><br><span class="line"></span><br><span class="line">Store：</span><br><span class="line">    这里强调的是 Store 的顺序</span><br><span class="line"></span><br><span class="line">Order：</span><br><span class="line">    同一核 Store 按程序顺序，跨核 Store 由系统交织</span><br></pre></td></tr></table></figure><h2 id="记忆图"><a href="#记忆图" class="headerlink" title="记忆图"></a>记忆图</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">SC</span><br><span class="line">│</span><br><span class="line">│ 所有 program-order 边都保留</span><br><span class="line">│</span><br><span class="line">├── Load  → Load</span><br><span class="line">├── Load  → Store</span><br><span class="line">├── Store → Store</span><br><span class="line">└── Store → Load</span><br><span class="line">        都保留</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">x86 TSO</span><br><span class="line">│</span><br><span class="line">├── Load  → Load       保留</span><br><span class="line">├── Load  → Store      保留</span><br><span class="line">├── Store → Store      保留</span><br><span class="line">└── Store → Load       不同地址时可能放松</span><br></pre></td></tr></table></figure><hr><p>参考资料:</p><ol><li>Intel 64 and IA-32 Architectures Software Developer’s Manual</li><li>Intel Optimization Reference Manual</li><li><a href="https://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf">x86-TSO: A Rigorous and Usable Programmer’s Model for x86 Multiprocessors</a></li><li><a href="https://zhuanlan.zhihu.com/p/141655129">内存一致性模型-TSO</a></li><li><a href="https://developer.arm.com/community/arm-community-blogs/b/tools-software-ides-blog/posts/armv8-sequential-consistency">Sequential Consistency in Armv8</a></li><li><a href="https://docs.kernel.org/dev-tools/lkmm/docs/explanation.html">Explanation of the Linux-Kernel Memory Consistency Model</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/22/%E6%B5%85%E8%B0%88x86%E7%9A%84TSO-memory-ordering/</id>
    <link href="http://liujunming.github.io/2026/08/22/%E6%B5%85%E8%B0%88x86%E7%9A%84TSO-memory-ordering/"/>
    <published>2026-08-22T00:36:27.000Z</published>
    <summary>
      <![CDATA[<p>本文将基于第一性原理，浅谈x86的TSO memory ordering机制。]]>
    </summary>
    <title>浅谈x86的TSO memory ordering</title>
    <updated>2026-08-23T11:09:19.797Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="文件系统" scheme="http://liujunming.github.io/categories/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/"/>
    <category term="文件系统" scheme="http://liujunming.github.io/tags/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/"/>
    <content>
      <![CDATA[<p>本文将mark下FUSE Passthrough的相关notes。<span id="more"></span></p><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>FUSE (Filesystem in Userspace) passthrough is a feature designed to improve the performance of FUSE filesystems for I&#x2F;O operations. Typically, FUSE operations involve communication between the kernel and a userspace FUSE daemon, which can incur overhead. Passthrough allows certain operations on a FUSE file to bypass the userspace daemon and be executed directly by the kernel on an underlying “backing file”.</p><p><img src="/images/2026/08/003.png"></p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p><strong>FUSE Passthrough 是一种为 FUSE 文件系统加速数据 I&#x2F;O 的机制。</strong></p><p>普通 FUSE 中，即使 daemon 最终只是读写本地的另一个文件，每次 <code>read()</code>、<code>write()</code> 都需要在内核和用户态 daemon 之间往返：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">应用程序</span><br><span class="line"></span><br><span class="line">   │ read/write</span><br><span class="line"></span><br><span class="line">   ▼</span><br><span class="line"></span><br><span class="line">Linux VFS / FUSE 内核模块</span><br><span class="line"></span><br><span class="line">   │ /dev/fuse 请求</span><br><span class="line"></span><br><span class="line">   ▼</span><br><span class="line"></span><br><span class="line">FUSE daemon（用户态）</span><br><span class="line"></span><br><span class="line">   │ pread/pwrite</span><br><span class="line"></span><br><span class="line">   ▼</span><br><span class="line"></span><br><span class="line">底层文件系统中的真实文件</span><br></pre></td></tr></table></figure><p>开启 FUSE Passthrough 后，daemon 可以在打开文件时告诉内核：</p><blockquote><p>这个 FUSE 文件对应底层的哪个 backing file。后续数据读写可以直接访问它，不必再把每个请求发给我。</p></blockquote><p>于是路径变成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">                 OPEN / CREATE</span><br><span class="line"></span><br><span class="line">应用程序 ─────► FUSE 内核模块 ─────► FUSE daemon</span><br><span class="line"></span><br><span class="line">                                      │</span><br><span class="line"></span><br><span class="line">                                      │ 返回 backing_id</span><br><span class="line"></span><br><span class="line">                                      ▼</span><br><span class="line"></span><br><span class="line">                              底层 backing file</span><br><span class="line"></span><br><span class="line">                READ / WRITE / MMAP</span><br><span class="line"></span><br><span class="line">应用程序 ─────► FUSE 内核模块 ─────► backing file ─────► 底层文件系统</span><br><span class="line"></span><br><span class="line">                         不再经过用户态 daemon</span><br></pre></td></tr></table></figure><p>可以把它概括为：</p><blockquote><p><strong>文件系统控制面仍在用户态 daemon，文件数据面可以下沉到内核。</strong></p></blockquote><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>FUSE Passthrough 的核心不是绕过整个 FUSE 文件系统，而是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">路径、打开、权限、映射和元数据</span><br><span class="line">              ↓</span><br><span class="line">        仍由 FUSE daemon 控制</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">read / write / splice / mmap</span><br><span class="line">              ↓</span><br><span class="line">      内核直接访问 backing file</span><br></pre></td></tr></table></figure><p>它保留了 FUSE 灵活的用户态控制能力，同时避开了高频数据 I&#x2F;O 的用户态往返开销。</p><p>个人总结: 思想类似于<a href="/2024/03/24/QEMU-Internals-vhost-architecture/#Vhost-overview">vhost</a></p><hr><p>参考资料:</p><ol><li><a href="https://source.android.com/docs/core/storage/fuse-passthrough?hl=en">AOSP: FUSE passthrough</a></li><li><a href="https://docs.kernel.org/next/filesystems/fuse-passthrough.html">https://docs.kernel.org/: FUSE Passthrough</a></li><li><a href="https://lwn.net/Articles/947931/">FUSE passthrough for file io</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/16/Notes-about-FUSE-Passthrough/</id>
    <link href="http://liujunming.github.io/2026/08/16/Notes-about-FUSE-Passthrough/"/>
    <published>2026-08-16T12:59:30.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下FUSE Passthrough的相关notes。]]>
    </summary>
    <title>Notes about FUSE Passthrough</title>
    <updated>2026-08-16T13:18:59.162Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="体系结构" scheme="http://liujunming.github.io/categories/%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    <category term="体系结构" scheme="http://liujunming.github.io/tags/%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    <category term="Intel" scheme="http://liujunming.github.io/tags/Intel/"/>
    <content>
      <![CDATA[<p>本文将mark下Intel Sub-NUMA Clustering(SNC)的相关notes。<span id="more"></span></p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p><img src="/images/2026/08/002.png"></p><p>Sub-NUMA Clustering(SNC) 是 Intel Xeon 处理器提供的一种 NUMA 拓扑配置技术。</p><p>它把一个物理 CPU Socket 内部的资源进一步划分成若干个”局部访问域”，每个域通常包含：</p><ul><li>一部分 CPU Core；</li><li>一部分 LLC Slice 和 CHA（Caching&#x2F;Home Agent）；</li><li>一部分集成内存控制器 IMC；</li><li>由这些内存控制器连接的本地 DRAM。</li></ul><p>随后，BIOS 将每个局部域作为一个独立的 NUMA Node 暴露给操作系统。这样，操作系统和应用程序就可以把线程及其内存放在同一个 SNC 域中，从而减少 CPU 芯片内部的远距离数据访问。</p><h2 id="Motivation"><a href="#Motivation" class="headerlink" title="Motivation"></a>Motivation</h2><p>假设一个 Socket 内有两组 Core 和两个内存控制器：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Socket 0</span><br><span class="line"></span><br><span class="line">Core 组 A ───── IMC 0 ───── DRAM A</span><br><span class="line"></span><br><span class="line">     │</span><br><span class="line"></span><br><span class="line">     └──────── 片内互连 ────────┐</span><br><span class="line"></span><br><span class="line">                                │</span><br><span class="line"></span><br><span class="line">Core 组 B ───── IMC 1 ───── DRAM B</span><br></pre></td></tr></table></figure><p>对于 Core 组 A 来说：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">访问 IMC 0 / DRAM A：距离较近</span><br><span class="line"></span><br><span class="line">访问 IMC 1 / DRAM B：距离较远</span><br></pre></td></tr></table></figure><p>但是，如果 SNC 关闭，操作系统通常只看到：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Socket 0 = NUMA Node 0</span><br></pre></td></tr></table></figure><p>操作系统会认为 Socket 0 内的所有 Core 和所有内存都是同一个 NUMA 域，无法明确区分：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">这块内存靠近 Core 组 A</span><br><span class="line"></span><br><span class="line">还是靠近 Core 组 B</span><br></pre></td></tr></table></figure><p>于是可能出现：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">线程运行在 Core 组 A</span><br><span class="line"></span><br><span class="line">          │</span><br><span class="line"></span><br><span class="line">          ▼</span><br><span class="line"></span><br><span class="line">访问芯片另一侧的 LLC / IMC 1</span><br><span class="line"></span><br><span class="line">          │</span><br><span class="line"></span><br><span class="line">          ▼</span><br><span class="line"></span><br><span class="line">DRAM B</span><br></pre></td></tr></table></figure><p>这种访问仍然可以正常完成，但需要经过更长的片内路径，可能带来：</p><ul><li>更高的 LLC 访问延迟；</li><li>更高的内存访问延迟；</li><li>更多片内互连流量；</li><li>内存控制器或互连局部拥塞；</li><li>多核扩展性下降。</li></ul><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><p>Intel 引入 <strong>Sub-NUMA Clustering（SNC）</strong>，是因为现代 Xeon 的单个 Socket 已经非常大：</p><ul><li>Core 数量越来越多；</li><li>LLC 被分散成很多 Slice；</li><li>多个内存控制器 IMC 分布在芯片不同位置；</li><li>Core、LLC、CHA、IMC 之间通过片内互连连接。</li></ul><p>因此，虽然这些资源都属于<strong>同一个 Socket</strong>，但一个 Core 访问”附近”的 LLC&#x2F;内存控制器，与访问芯片另一侧的 LLC&#x2F;内存控制器，实际路径和延迟并不相同。</p><p><strong>SNC 的目的，就是把这种 Socket 内部原本隐藏的距离差异暴露给操作系统，让线程尽量使用附近的缓存和内存。</strong> Intel 也明确指出：当整个 Socket 作为一个统一域时，软件无法根据最近资源优化数据流，一个 Core 可能访问位于芯片另一侧的 LLC Slice 或内存控制器。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">现代 Xeon Socket 很大</span><br><span class="line">       ↓</span><br><span class="line">Socket 内部访问已经不是完全等距离</span><br><span class="line">       ↓</span><br><span class="line">但操作系统若只看到一个 NUMA Node，</span><br><span class="line">就无法区分近端和远端资源</span><br><span class="line">       ↓</span><br><span class="line">SNC 将一个 Socket 划分为多个局部 NUMA 域</span><br><span class="line">       ↓</span><br><span class="line">让线程尽量访问本地 LLC、IMC 和 DRAM</span><br><span class="line">       ↓</span><br><span class="line">降低延迟、减少片内流量、改善内存带宽利用率</span><br></pre></td></tr></table></figure><h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><p>SNC 把一个大 Socket 划分成多个较小的”局部访问域”。</p><p>以 SNC2 为例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">                     Socket 0</span><br><span class="line"></span><br><span class="line">┌──────────────────────┬──────────────────────┐</span><br><span class="line"></span><br><span class="line">│ SNC Domain 0         │ SNC Domain 1         │</span><br><span class="line"></span><br><span class="line">│ OS NUMA Node 0       │ OS NUMA Node 1       │</span><br><span class="line"></span><br><span class="line">│                      │                      │</span><br><span class="line"></span><br><span class="line">│ Core 组 A            │ Core 组 B            │</span><br><span class="line"></span><br><span class="line">│ 本地 LLC / CHA       │ 本地 LLC / CHA       │</span><br><span class="line"></span><br><span class="line">│ IMC 0                │ IMC 1                │</span><br><span class="line"></span><br><span class="line">│ DRAM A               │ DRAM B               │</span><br><span class="line"></span><br><span class="line">└──────────────────────┴──────────────────────┘</span><br></pre></td></tr></table></figure><p>操作系统现在能看到两个 NUMA Node，于是可以进行更精确的放置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">线程运行在 Node 0 的 Core</span><br><span class="line"></span><br><span class="line">              +</span><br><span class="line"></span><br><span class="line">内存分配在 Node 0 的 DRAM</span><br><span class="line"></span><br><span class="line">              ↓</span><br><span class="line"></span><br><span class="line">Core → 本地 LLC/CHA → 本地 IMC → 本地 DRAM</span><br></pre></td></tr></table></figure><p>Intel 对 SNC2 的描述也是：每个局部域包含相关的 Core、本地 LLC Slice 和本地内存控制器；本地域内的 Core 访问本域映射的数据时，可以获得更低的 LLC 和内存延迟。</p><h2 id="本质：让软件看见-Socket-内部的局部性"><a href="#本质：让软件看见-Socket-内部的局部性" class="headerlink" title="本质：让软件看见 Socket 内部的局部性"></a>本质：让软件看见 Socket 内部的局部性</h2><p>可以把 SNC 的价值概括为一句话：</p><blockquote><p><strong>SNC 把”Socket 内部事实上存在、但操作系统原本看不见的 NUMA 差异”，变成操作系统可见的 NUMA Node。</strong></p></blockquote><h3 id="SNC-关闭"><a href="#SNC-关闭" class="headerlink" title="SNC 关闭"></a>SNC 关闭</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">物理上：</span><br><span class="line"></span><br><span class="line">Core A 靠近 IMC 0</span><br><span class="line"></span><br><span class="line">Core B 靠近 IMC 1</span><br><span class="line"></span><br><span class="line">但操作系统看到：</span><br><span class="line"></span><br><span class="line">所有 Core + 所有内存 = 一个 NUMA Node</span><br></pre></td></tr></table></figure><h3 id="SNC-开启"><a href="#SNC-开启" class="headerlink" title="SNC 开启"></a>SNC 开启</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">物理上：</span><br><span class="line"></span><br><span class="line">Core A + LLC/CHA A + IMC 0 + DRAM A</span><br><span class="line"></span><br><span class="line">Core B + LLC/CHA B + IMC 1 + DRAM B</span><br><span class="line"></span><br><span class="line">操作系统看到：</span><br><span class="line"></span><br><span class="line">NUMA Node 0</span><br><span class="line"></span><br><span class="line">NUMA Node 1</span><br></pre></td></tr></table></figure><p>这样，操作系统和应用就可以通过 NUMA 策略安排：</p><ul><li>线程运行在哪组 Core；</li><li>内存由哪个 IMC 分配；</li><li>数据放在哪个 SNC 域；</li><li>不同进程或数据分片分别使用哪些资源。</li></ul><p>所以 SNC 并不是简单地”把 CPU 切成几份”，而是为了建立：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Core</span><br><span class="line"></span><br><span class="line">  ↕</span><br><span class="line"></span><br><span class="line">附近的 LLC / CHA</span><br><span class="line"></span><br><span class="line">  ↕</span><br><span class="line"></span><br><span class="line">附近的 IMC</span><br><span class="line"></span><br><span class="line">  ↕</span><br><span class="line"></span><br><span class="line">本地 DRAM</span><br></pre></td></tr></table></figure><p>这种更明确的局部关系。</p><h2 id="收益"><a href="#收益" class="headerlink" title="收益"></a>收益</h2><h3 id="降低本地访问延迟"><a href="#降低本地访问延迟" class="headerlink" title="降低本地访问延迟"></a>降低本地访问延迟</h3><p>理想情况下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">SNC 关闭：</span><br><span class="line"></span><br><span class="line">Core → 较远 LLC/CHA → 较远 IMC → DRAM</span><br></pre></td></tr></table></figure><p>变成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">SNC 开启且放置正确：</span><br><span class="line"></span><br><span class="line">Core → 本地 LLC/CHA → 本地 IMC → 本地 DRAM</span><br></pre></td></tr></table></figure><p>Intel 将降低芯片内数据移动延迟作为 SNC 的直接设计目的。</p><h3 id="减少片内互连流量"><a href="#减少片内互连流量" class="headerlink" title="减少片内互连流量"></a>减少片内互连流量</h3><p>如果线程不断访问其他区域的 LLC Slice 和 IMC，数据会在片内互连上来回传输。</p><p>SNC 将大部分访问限制在局部域内，可以减少：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">跨 SNC 域的数据移动</span><br></pre></td></tr></table></figure><p>从而降低互连竞争。</p><h3 id="更好地利用多个内存控制器"><a href="#更好地利用多个内存控制器" class="headerlink" title="更好地利用多个内存控制器"></a>更好地利用多个内存控制器</h3><p>例如 SNC2 中：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">业务实例 A → Node 0 → IMC 0</span><br><span class="line"></span><br><span class="line">业务实例 B → Node 1 → IMC 1</span><br></pre></td></tr></table></figure><p>两个实例分别使用不同的局部资源，有助于避免所有线程集中访问同一组内存控制器。</p><p>因此，SNC 通常并不是提高 DRAM 的理论带宽，而是让应用程序更充分、更均衡地利用已有内存带宽。</p><h3 id="提高大规模多核程序的扩展性"><a href="#提高大规模多核程序的扩展性" class="headerlink" title="提高大规模多核程序的扩展性"></a>提高大规模多核程序的扩展性</h3><p>当 Core 数量很多时，如果所有线程都被视为处在一个统一域中，线程和内存可能随机分散。</p><p>SNC 允许程序按域划分工作：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">SNC Domain 0 → 数据分片 0</span><br><span class="line"></span><br><span class="line">SNC Domain 1 → 数据分片 1</span><br><span class="line"></span><br><span class="line">SNC Domain 2 → 数据分片 2</span><br><span class="line"></span><br><span class="line">SNC Domain 3 → 数据分片 3</span><br></pre></td></tr></table></figure><p>这特别适合：</p><ul><li>MPI &#x2F; MPI+OpenMP 程序；</li><li>NUMA-aware 数据库；</li><li>分片式缓存或存储服务；</li><li>多实例虚拟化；</li><li>每个 Worker 有独立工作集的程序。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Intel SNC 的核心思想可以概括为：</p><blockquote><p><strong>把一个很大的 CPU Socket 拆成多个较小的局部 NUMA 域，让 Core 尽量访问附近的 LLC、内存控制器和 DRAM。</strong></p></blockquote><hr><p>参考资料:</p><ol><li><a href="https://www.intel.com/content/www/us/en/developer/articles/technical/xeon-processor-scalable-family-technical-overview.html">Intel Xeon Processor Scalable Family Technical Overview：Sub-NUMA Clustering</a></li><li><a href="https://www.intel.com/content/www/us/en/developer/articles/technical/xeon-scalable-processor-max-series.html">Intel Xeon Scalable Processor Max Series：SNC2 与 SNC4</a></li><li><a href="https://cdrdv2-public.intel.com/845720/845720_1p0.pdf">Intel Xeon 6 BIOS NUMA Tuning Guide</a></li><li><a href="https://docs.kernel.org/filesystems/resctrl.html#notes-on-sub-numa-cluster-mode">Linux Kernel Documentation：Notes on Sub-NUMA Cluster Mode</a></li><li><a href="https://www.intel.com/content/www/us/en/developer/articles/guide/hpc-cluster-tuning-on-3rd-generation-xeon.html">Intel HPC Cluster Tuning Guide：SNC 与 NUMA 工具</a></li><li><a href="https://cdrdv2-public.intel.com/865785/vectorDB-sub-numa-whitepaper.pdf">Intel White Paper：Qdrant&#x2F;VectorDB 工作负载下 SNC 开关的性能影响</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/16/Notes-about-Intel-Sub-NUMA-Clustering-SNC/</id>
    <link href="http://liujunming.github.io/2026/08/16/Notes-about-Intel-Sub-NUMA-Clustering-SNC/"/>
    <published>2026-08-16T08:06:42.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下Intel Sub-NUMA Clustering(SNC)的相关notes。]]>
    </summary>
    <title>Notes about Intel Sub-NUMA Clustering(SNC)</title>
    <updated>2026-08-16T10:33:31.794Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="RAS" scheme="http://liujunming.github.io/categories/RAS/"/>
    <category term="RAS" scheme="http://liujunming.github.io/tags/RAS/"/>
    <content>
      <![CDATA[<p>Chipkill的ECC能力是强于Bounded Fault ECC的。那么既然已经有了Chipkill，为什么还要有Bounded Fault ECC呢？<span id="more"></span></p><h2 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h2><ul><li><a href="/2025/01/05/DRAM-components/">DRAM components</a></li><li><a href="/2026/07/25/Notes-about-DRAM-Chipkill%E6%8A%80%E6%9C%AF/">DRAM Chipkill技术</a></li><li><a href="/2026/07/26/Notes-about-DDR5-Bounded-Fault-ECC%E6%8A%80%E6%9C%AF/">DDR5 Bounded Fault ECC技术</a></li></ul><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p><strong>Bounded Fault ECC（有界故障ECC）的提出，很大程度上就是为了弥补传统 Chipkill 在 x8 DRAM 组织下无法覆盖某些 device fault 的问题。</strong></p><p>核心关系：</p><blockquote><p><strong>Chipkill 本身不是万能的，它依赖 DRAM device 的粒度（x4&#x2F;x8）。Bounded Fault ECC 的目标就是把”不可控的大故障”限制成 ECC 可以处理的有界故障，从而让 Chipkill 能覆盖更多场景。</strong></p></blockquote><p>下面结合 x4 &#x2F; x8 解释。</p><h2 id="回顾-Chipkill-的基本思想"><a href="#回顾-Chipkill-的基本思想" class="headerlink" title="回顾 Chipkill 的基本思想"></a>回顾 Chipkill 的基本思想</h2><p>Chipkill：</p><blockquote><p>将一个 DRAM chip&#x2F;device 的失效看成一个 ECC symbol 丢失，然后利用强ECC恢复。</p></blockquote><p>例如：</p><p>一个 cache line：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Data:</span><br><span class="line"></span><br><span class="line">Chip0  Chip1  Chip2  Chip3</span><br><span class="line">  |      |      |      |</span><br><span class="line">symbol symbol symbol symbol</span><br></pre></td></tr></table></figure><p>如果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Chip2 完全失效</span><br></pre></td></tr></table></figure><p>变成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">symbol2 missing</span><br></pre></td></tr></table></figure><p>ECC恢复：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">symbol0</span><br><span class="line">symbol1</span><br><span class="line">symbol2  &lt;--- lost</span><br><span class="line">symbol3</span><br><span class="line"></span><br><span class="line">        |</span><br><span class="line">        v</span><br><span class="line"></span><br><span class="line">ECC reconstruction</span><br></pre></td></tr></table></figure><p>因此：</p><p><strong>Chipkill要求：device failure必须是ECC可以建模和恢复的symbol error。</strong></p><h2 id="x4-DRAM为什么天然适合Chipkill？"><a href="#x4-DRAM为什么天然适合Chipkill？" class="headerlink" title="x4 DRAM为什么天然适合Chipkill？"></a>x4 DRAM为什么天然适合Chipkill？</h2><p>x4 DRAM：</p><p>表示：</p><blockquote><p>一个DRAM chip一次输出4 bit数据。</p></blockquote><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">x4 DRAM:</span><br><span class="line"></span><br><span class="line">Chip0 ---&gt; 4 bits</span><br><span class="line">Chip1 ---&gt; 4 bits</span><br><span class="line">Chip2 ---&gt; 4 bits</span><br><span class="line">Chip3 ---&gt; 4 bits</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>假设一个device坏：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Chip2 failure</span><br></pre></td></tr></table></figure><p>损失：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">4 bits</span><br></pre></td></tr></table></figure><p>ECC看到：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">一个symbol丢失</span><br></pre></td></tr></table></figure><p>这是比较容易处理的。</p><p>所以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">x4 DRAM + ECC DIMM</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">Chipkill-friendly</span><br></pre></td></tr></table></figure><h2 id="为什么x8-DRAM给Chipkill带来问题？"><a href="#为什么x8-DRAM给Chipkill带来问题？" class="headerlink" title="为什么x8 DRAM给Chipkill带来问题？"></a>为什么x8 DRAM给Chipkill带来问题？</h2><p>x8 DRAM：</p><p>一个chip输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">8 bits</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Chip0 ---&gt; 8 bits</span><br><span class="line">Chip1 ---&gt; 8 bits</span><br><span class="line">Chip2 ---&gt; 8 bits</span><br><span class="line">Chip3 ---&gt; 8 bits</span><br></pre></td></tr></table></figure><p>如果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Chip2 failure</span><br></pre></td></tr></table></figure><p>一次丢：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">8 bits</span><br></pre></td></tr></table></figure><p>问题来了：</p><p>ECC symbol粒度通常没有这么大。</p><p>也就是说：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">一个device failure</span><br><span class="line"></span><br><span class="line">        =</span><br><span class="line"></span><br><span class="line">多个ECC symbol同时错误</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">symbol0</span><br><span class="line">symbol1</span><br><span class="line">symbol2</span><br><span class="line">symbol3</span><br><span class="line"></span><br><span class="line">Chip2坏:</span><br><span class="line"></span><br><span class="line">symbol1 + symbol2同时丢失</span><br></pre></td></tr></table></figure><p>传统Chipkill：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">一个symbol failure</span><br></pre></td></tr></table></figure><p>可以处理。</p><p>但是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">多个symbol failure</span><br></pre></td></tr></table></figure><p>可能超出ECC能力。</p><p>所以：</p><blockquote><p><strong>传统Chipkill在x8 DRAM下无法保证覆盖整个device failure。</strong></p></blockquote><h2 id="为什么需要-Bounded-Fault-ECC？"><a href="#为什么需要-Bounded-Fault-ECC？" class="headerlink" title="为什么需要 Bounded Fault ECC？"></a>为什么需要 Bounded Fault ECC？</h2><p>问题本质：</p><p>x8 DRAM：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">device failure</span><br><span class="line">        |</span><br><span class="line">        v</span><br><span class="line">8-bit error burst</span><br></pre></td></tr></table></figure><p>这个故障太大。</p><p>Bounded Fault ECC的思想：</p><blockquote><p>不要求ECC处理任意大的device fault，而是通过限制fault范围，把错误变成ECC可纠正的形式。</p></blockquote><p>也就是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">unbounded fault</span><br><span class="line"></span><br><span class="line">     |</span><br><span class="line">     v</span><br><span class="line"></span><br><span class="line">bounded fault</span><br><span class="line"></span><br><span class="line">     |</span><br><span class="line">     v</span><br><span class="line"></span><br><span class="line">ECC correction</span><br></pre></td></tr></table></figure><h2 id="Bounded-Fault-ECC如何帮助Chipkill？"><a href="#Bounded-Fault-ECC如何帮助Chipkill？" class="headerlink" title="Bounded Fault ECC如何帮助Chipkill？"></a>Bounded Fault ECC如何帮助Chipkill？</h2><p>简单理解：</p><p>以前：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">x8 DRAM</span><br><span class="line"></span><br><span class="line">Chip failure</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">8-bit burst error</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       X</span><br><span class="line"></span><br><span class="line">Chipkill无法覆盖</span><br></pre></td></tr></table></figure><hr><p>加入Bounded Fault ECC：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">x8 DRAM</span><br><span class="line"></span><br><span class="line">Chip failure</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">fault containment</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">bounded error pattern</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">Chipkill ECC can correct</span><br></pre></td></tr></table></figure><p>也就是说：</p><p><strong>Bounded Fault ECC把x8 device failure转换成Chipkill ECC能够处理的故障模型。</strong></p><h2 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h2><h3 id="没有Bounded-Fault-ECC"><a href="#没有Bounded-Fault-ECC" class="headerlink" title="没有Bounded Fault ECC"></a>没有Bounded Fault ECC</h3><p>x8:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Chip0 Chip1 Chip2 Chip3</span><br><span class="line"></span><br><span class="line">       X</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Chip2坏：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">8 bits corrupted</span><br></pre></td></tr></table></figure><p>ECC：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">需要恢复8-bit device failure</span><br><span class="line"></span><br><span class="line">超过能力</span><br></pre></td></tr></table></figure><p>结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UE</span><br></pre></td></tr></table></figure><h3 id="有Bounded-Fault-ECC"><a href="#有Bounded-Fault-ECC" class="headerlink" title="有Bounded Fault ECC"></a>有Bounded Fault ECC</h3><p>故障：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Chip2 failure</span><br></pre></td></tr></table></figure><p>经过fault bounding：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">只暴露有限bit error</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">错误限制在一个ECC word范围内</span><br></pre></td></tr></table></figure><p>然后：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Chipkill ECC</span><br><span class="line"></span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line"></span><br><span class="line">correct</span><br></pre></td></tr></table></figure><h2 id="x4、x8与RAS演进关系"><a href="#x4、x8与RAS演进关系" class="headerlink" title="x4、x8与RAS演进关系"></a>x4、x8与RAS演进关系</h2><p>可以这样理解：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">         DRAM device fault</span><br><span class="line"></span><br><span class="line">               |</span><br><span class="line">      -----------------</span><br><span class="line">      |               |</span><br><span class="line">     x4              x8</span><br><span class="line">      |               |</span><br><span class="line">      |               |</span><br><span class="line">Chipkill容易     Chipkill困难</span><br><span class="line">      |               |</span><br><span class="line">      |               |</span><br><span class="line">      v               v</span><br><span class="line"></span><br><span class="line">传统Chipkill    Bounded Fault ECC</span><br><span class="line">                      |</span><br><span class="line">                      |</span><br><span class="line">                      v</span><br><span class="line"></span><br><span class="line">             x8也可以实现</span><br><span class="line">             device fault tolerance</span><br></pre></td></tr></table></figure><h2 id="为什么不用全部换成x4？"><a href="#为什么不用全部换成x4？" class="headerlink" title="为什么不用全部换成x4？"></a>为什么不用全部换成x4？</h2><p>这是实际工程问题。</p><p>x4优势：</p><ul><li>更容易实现Chipkill</li></ul><p>但是：</p><ul><li>DRAM颗粒组织不同</li><li>PCB布线</li><li>成本</li><li>容量密度</li><li>产品兼容性</li></ul><p>x8：</p><ul><li>成本低</li><li>常见</li><li>容量密度高</li></ul><p>所以业界希望：</p><blockquote><p><strong>保留x8 DRAM优势，同时获得类似Chipkill的可靠性。</strong></p></blockquote><p>这就是Bounded Fault ECC出现的原因。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><img src="/images/2026/08/001.png"></p><p>Chipkill依赖DRAM device故障粒度，而传统Chipkill更适合x4 DRAM。x8 DRAM由于一次device failure会产生更大的错误范围，超过传统Chipkill ECC能力。Bounded Fault ECC通过限制故障范围，将x8 device failure转换为ECC可处理的有界错误，从而让Chipkill类保护能够覆盖x8 DRAM。</p><blockquote><p>Bounded Fault ECC不是替代Chipkill，而是扩展Chipkill能够处理的fault model。</p></blockquote>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/09/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3DDR5-Bounded-Fault-ECC-motivation/</id>
    <link href="http://liujunming.github.io/2026/08/09/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3DDR5-Bounded-Fault-ECC-motivation/"/>
    <published>2026-08-09T14:20:49.000Z</published>
    <summary>
      <![CDATA[<p>Chipkill的ECC能力是强于Bounded Fault ECC的。那么既然已经有了Chipkill，为什么还要有Bounded Fault ECC呢？]]>
    </summary>
    <title>深入理解Bounded Fault ECC motivation</title>
    <updated>2026-08-09T14:53:28.875Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="RAS" scheme="http://liujunming.github.io/categories/RAS/"/>
    <category term="RAS" scheme="http://liujunming.github.io/tags/RAS/"/>
    <content>
      <![CDATA[<p>本文将mark下RAS Offload的相关notes。<span id="more"></span></p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p><strong>RAS Offload 是一种将部分硬件错误处理任务从 CPU 的 SMM（System Management Mode）环境卸载到 BMC 的技术</strong>。</p><p>Intel Xeon 6 引入<strong>BMC RAS Offload</strong>：</p><blockquote><p>将部分 RAS error handling 从 SMM 转移到 BMC 环境，从而减少 SMI（System Management Interrupt）的使用。</p></blockquote><p>传统方式：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Hardware Error</span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line"> CPU / SMM</span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line"> RAS处理</span><br></pre></td></tr></table></figure><p>RAS Offload：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Hardware Error</span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line">    BMC</span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line"> RAS处理</span><br></pre></td></tr></table></figure><p>即<strong>让BMC承担部分RAS管理任务，而不是让CPU参与处理</strong>。</p><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><h3 id="减少-SMI-对业务性能的影响"><a href="#减少-SMI-对业务性能的影响" class="headerlink" title="减少 SMI 对业务性能的影响"></a>减少 SMI 对业务性能的影响</h3><p>传统RAS处理中：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">硬件错误</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">触发SMI</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">CPU进入SMM处理</span><br></pre></td></tr></table></figure><p>问题：</p><ul><li>SMI会暂停正常CPU执行</li><li>OS和应用无法控制SMM</li><li>可能造成 latency spike 和性能抖动</li></ul><p>对于：</p><ul><li>云服务器</li><li>HPC</li><li>AI训练</li></ul><p>这种长时间运行业务，SMI影响较大。</p><p>RAS Offload后：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">硬件错误</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">BMC处理</span><br></pre></td></tr></table></figure><p>减少CPU中断和业务扰动。</p><h3 id="利用BMC独立管理能力"><a href="#利用BMC独立管理能力" class="headerlink" title="利用BMC独立管理能力"></a>利用BMC独立管理能力</h3><p>BMC本身就是服务器的管理控制器：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">      Server</span><br><span class="line"></span><br><span class="line">+-------------+</span><br><span class="line">|    CPU      |</span><br><span class="line">|  OS/APP     |</span><br><span class="line">+-------------+</span><br><span class="line"></span><br><span class="line">+-------------+</span><br><span class="line">|    BMC      |</span><br><span class="line">| Management  |</span><br><span class="line">+-------------+</span><br></pre></td></tr></table></figure><p>特点：</p><ul><li>独立运行</li><li>独立供电</li><li>即使OS异常，也可以工作</li></ul><p>因此适合承担：</p><ul><li>错误收集</li><li>故障处理</li><li>RAS管理</li></ul><h2 id="RAS-Offload与BMC的关系"><a href="#RAS-Offload与BMC的关系" class="headerlink" title="RAS Offload与BMC的关系"></a>RAS Offload与BMC的关系</h2><p>两者关系：</p><blockquote><p>BMC 是 RAS Offload 的执行平台，但 RAS Offload 不是 BMC 本身。</p></blockquote><p>BMC功能包括：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">BMC</span><br><span class="line"> |</span><br><span class="line"> +-- 电源管理</span><br><span class="line"> |</span><br><span class="line"> +-- 风扇控制</span><br><span class="line"> |</span><br><span class="line"> +-- 远程管理</span><br><span class="line"> |</span><br><span class="line"> +-- Firmware管理</span><br><span class="line"> |</span><br><span class="line"> +-- RAS Offload</span><br></pre></td></tr></table></figure><p>RAS Offload只是BMC新增的一类可靠性管理能力。</p><h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><p>简化流程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">       Hardware Error</span><br><span class="line">             |</span><br><span class="line">             v</span><br><span class="line">      Error information</span><br><span class="line">             |</span><br><span class="line">             v</span><br><span class="line">            BMC</span><br><span class="line">             |</span><br><span class="line">     +-------+-------+</span><br><span class="line">     |</span><br><span class="line">     v</span><br><span class="line">RAS decision/action</span><br></pre></td></tr></table></figure><p>BMC负责：</p><ul><li>收集错误状态</li><li>执行部分恢复动作</li><li>记录错误信息</li></ul><p>避免：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Error</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">CPU SMM</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">SMI interruption</span><br></pre></td></tr></table></figure><h2 id="Intel-Xeon-6中支持的RAS-Offload能力"><a href="#Intel-Xeon-6中支持的RAS-Offload能力" class="headerlink" title="Intel Xeon 6中支持的RAS Offload能力"></a>Intel Xeon 6中支持的RAS Offload能力</h2><h3 id="Memory-PCIe-UPI-Corrected-Error-CE-handling"><a href="#Memory-PCIe-UPI-Corrected-Error-CE-handling" class="headerlink" title="Memory &#x2F; PCIe &#x2F; UPI Corrected Error (CE) handling"></a>Memory &#x2F; PCIe &#x2F; UPI Corrected Error (CE) handling</h3><ul><li>收集和处理可纠正错误</li><li>减少CPU参与</li></ul><h3 id="Runtime-PPR"><a href="#Runtime-PPR" class="headerlink" title="Runtime PPR"></a>Runtime PPR</h3><p>运行时触发：</p><ul><li>DRAM坏Row修复</li><li>将故障Row映射到备用Row</li></ul><p>属于memory repair能力。</p><h3 id="ADDDC"><a href="#ADDDC" class="headerlink" title="ADDDC"></a>ADDDC</h3><p>当DRAM device出现失效趋势：</p><ul><li>动态增强ECC保护</li><li>避免device failure导致系统故障</li></ul><h3 id="ECS-data-collection"><a href="#ECS-data-collection" class="headerlink" title="ECS data collection"></a>ECS data collection</h3><p>DDR5 Error Check Scrub相关：</p><ul><li>收集DRAM错误信息</li><li>辅助RAS分析</li></ul><h3 id="SPD-bus-recovery"><a href="#SPD-bus-recovery" class="headerlink" title="SPD bus recovery"></a>SPD bus recovery</h3><p>恢复DIMM SPD访问通道：</p><ul><li>解决SPD&#x2F;SMBus异常</li><li>保证DIMM管理信息可获取</li></ul><h2 id="RAS-Offload-vs-传统RAS"><a href="#RAS-Offload-vs-传统RAS" class="headerlink" title="RAS Offload vs 传统RAS"></a>RAS Offload vs 传统RAS</h2><table><thead><tr><th align="center"></th><th align="center">传统RAS</th><th align="center">RAS Offload</th></tr></thead><tbody><tr><td align="center">执行位置</td><td align="center">CPU SMM</td><td align="center">BMC</td></tr><tr><td align="center">错误处理触发</td><td align="center">SMI</td><td align="center">BMC处理</td></tr><tr><td align="center">是否影响CPU业务</td><td align="center">可能影响</td><td align="center">降低影响</td></tr><tr><td align="center">管理独立性</td><td align="center">依赖CPU</td><td align="center">独立管理平面</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>RAS Offload 是Intel Xeon 6提出的服务器可靠性增强机制，通过将部分RAS错误处理任务从CPU的SMM环境迁移到BMC，使BMC承担错误管理和恢复工作，从而减少SMI中断对业务性能的影响。</p><p>简单理解：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">过去：</span><br><span class="line"></span><br><span class="line">Hardware Error</span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line">CPU/SMM处理</span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line">影响业务</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">现在：</span><br><span class="line"></span><br><span class="line">Hardware Error</span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line">BMC RAS Offload</span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line">独立处理，减少CPU干扰</span><br></pre></td></tr></table></figure><p>其中：</p><ul><li>BMC &#x3D; RAS Offload 的执行载体</li><li>PPR&#x2F;ADDDC&#x2F;ECS&#x2F;SPD recovery &#x3D; BMC可以辅助执行的具体RAS动作</li></ul><p>它代表服务器RAS架构从<strong>CPU参与故障处理</strong>，向<strong>独立管理控制器负责可靠性管理</strong>的演进。</p><hr><p>参考资料:</p><ol><li>Intel® Xeon® 6 Processors with Performance-cores (P-cores): Reliability, Availability, and Serviceability</li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/02/Notes-about-RAS-Offload/</id>
    <link href="http://liujunming.github.io/2026/08/02/Notes-about-RAS-Offload/"/>
    <published>2026-08-02T09:15:07.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下RAS Offload的相关notes。]]>
    </summary>
    <title>Notes about RAS Offload</title>
    <updated>2026-08-02T09:41:36.522Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="RAS" scheme="http://liujunming.github.io/categories/RAS/"/>
    <category term="RAS" scheme="http://liujunming.github.io/tags/RAS/"/>
    <content>
      <![CDATA[<p>本文将mark下Post Package Repair(PPR)的相关notes。<span id="more"></span></p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p><strong>PPR(Post Package Repair)是一种 DRAM内部的修复(repair)机制，通过备用 Row(spare row)替换故障 Row，使DRAM继续可靠工作</strong>。</p><p>简单理解：</p><blockquote><p>PPR &#x3D; DRAM发现某个row永久失效后，把该row映射到备用row。</p></blockquote><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">DRAM Bank</span><br><span class="line"></span><br><span class="line">Row0</span><br><span class="line">Row1</span><br><span class="line">Row2  ----&gt; faulty</span><br><span class="line">Row3</span><br><span class="line"></span><br><span class="line">Spare Row0</span><br><span class="line">Spare Row1</span><br></pre></td></tr></table></figure><p>执行PPR：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Row2  ----------+</span><br><span class="line">                |</span><br><span class="line">                v</span><br><span class="line">           Spare Row0</span><br></pre></td></tr></table></figure><p>之后：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">访问 Row2</span><br><span class="line"></span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line"></span><br><span class="line">DRAM内部repair logic</span><br><span class="line"></span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line"></span><br><span class="line">Spare Row0</span><br></pre></td></tr></table></figure><p>对上层：</p><ul><li>CPU不知道</li><li>OS不知道</li><li>应用不知道</li></ul><p>访问地址保持不变。</p><h3 id="PPR属于Memory-Sparing的一种"><a href="#PPR属于Memory-Sparing的一种" class="headerlink" title="PPR属于Memory Sparing的一种"></a>PPR属于Memory Sparing的一种</h3><p>这里需要特别强调：<br><strong>PPR不是独立于Memory Sparing之外的技术，而是Memory Sparing的一种具体实现</strong>。</p><p>层次关系：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Memory Sparing</span><br><span class="line">       |</span><br><span class="line">       +----------------+</span><br><span class="line">       |                |</span><br><span class="line"> DRAM内部Sparing    System级Sparing</span><br><span class="line">       |                |</span><br><span class="line">       |                |</span><br><span class="line">      PPR          Rank/DIMM Sparing</span><br><span class="line">       |</span><br><span class="line">   Spare Row</span><br></pre></td></tr></table></figure><p>所以：</p><ul><li>PPR：row级memory sparing</li><li>Rank sparing：rank级memory sparing</li></ul><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><h3 id="DRAM故障通常是局部永久故障"><a href="#DRAM故障通常是局部永久故障" class="headerlink" title="DRAM故障通常是局部永久故障"></a>DRAM故障通常是局部永久故障</h3><p>DRAM长期运行后可能出现：</p><ul><li>bad cell</li><li>bad row</li><li>weak row</li></ul><p>错误趋势：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">正常</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line"></span><br><span class="line">Correctable Error增加</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line"></span><br><span class="line">Row failure</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line"></span><br><span class="line">Uncorrectable Error</span><br></pre></td></tr></table></figure><p>如果不处理：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Bad Row</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">UE</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">系统故障</span><br></pre></td></tr></table></figure><p>PPR提供：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">发现坏Row</span><br><span class="line"></span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line"></span><br><span class="line">替换Row</span><br><span class="line"></span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line"></span><br><span class="line">继续运行</span><br></pre></td></tr></table></figure><h3 id="ECC无法解决所有DRAM故障"><a href="#ECC无法解决所有DRAM故障" class="headerlink" title="ECC无法解决所有DRAM故障"></a>ECC无法解决所有DRAM故障</h3><p>ECC主要解决bit error</p><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">single bit flip</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">ECC correction</span><br></pre></td></tr></table></figure><p>但是整个Row失效属于更大粒度故障：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Row failure</span><br><span class="line">     |</span><br><span class="line">     v</span><br><span class="line">ECC能力不足</span><br></pre></td></tr></table></figure><p>因此需要：</p><ul><li>ECC：纠错</li><li>PPR：修复&#x2F;替换坏资源</li></ul><h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><p>PPR依赖三个部分：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">       CPU</span><br><span class="line"></span><br><span class="line">        |</span><br><span class="line">        |</span><br><span class="line">Memory Controller</span><br><span class="line"></span><br><span class="line">        |</span><br><span class="line"> DDR Command</span><br><span class="line"></span><br><span class="line">        |</span><br><span class="line">        v</span><br><span class="line"></span><br><span class="line">      DRAM</span><br><span class="line"></span><br><span class="line"> +-------------+</span><br><span class="line"> | Repair Logic|</span><br><span class="line"> | Spare Rows  |</span><br><span class="line"> +-------------+</span><br><span class="line"></span><br><span class="line"> Bad Row</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">Spare Row</span><br></pre></td></tr></table></figure><h3 id="Step-1-发现故障Row"><a href="#Step-1-发现故障Row" class="headerlink" title="Step 1:发现故障Row"></a>Step 1:发现故障Row</h3><p>来源可能包括：</p><ul><li>ECC error统计</li><li>Memory Patrol Scrub</li><li>DRAM内部检测</li><li>RAS软件策略</li></ul><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Bank 3</span><br><span class="line">Row 100</span><br><span class="line"></span><br><span class="line">CE数量持续增加</span><br></pre></td></tr></table></figure><p>判断：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Row 100可能永久故障</span><br></pre></td></tr></table></figure><h3 id="Step-2-发送PPR命令"><a href="#Step-2-发送PPR命令" class="headerlink" title="Step 2:发送PPR命令"></a>Step 2:发送PPR命令</h3><p>Memory Controller发送repair command：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Repair(Row100)</span><br></pre></td></tr></table></figure><p>DRAM内部：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Row100</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">Spare Row</span><br></pre></td></tr></table></figure><p>建立映射关系。</p><h3 id="Step-3-后续访问自动重定向"><a href="#Step-3-后续访问自动重定向" class="headerlink" title="Step 3:后续访问自动重定向"></a>Step 3:后续访问自动重定向</h3><p>之后：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">CPU访问：</span><br><span class="line"></span><br><span class="line">Bank3 / Row100</span><br></pre></td></tr></table></figure><p>实际：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Bank3 / Spare Row</span><br></pre></td></tr></table></figure><p>软件无感知。</p><h2 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h2><p>Intel Xeon 6 RAS文档中提到：</p><blockquote><p>supports both runtime soft Post Package Repair (sPPR) and power-up hard Post Package Repair (hPPR)</p></blockquote><p>也就是说：</p><ul><li>sPPR：运行时修复</li><li>hPPR：启动阶段永久修复</li></ul><p>Hard PPR (hPPR), for a permanent row repair, and Soft PPR (sPPR), for a temporary row repair.</p><h3 id="hPPR"><a href="#hPPR" class="headerlink" title="hPPR"></a>hPPR</h3><p>特点：<strong>永久修复</strong></p><p>流程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">PPR</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line"></span><br><span class="line">Repair information写入非易失存储</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line"></span><br><span class="line">Power cycle后仍有效</span><br></pre></td></tr></table></figure><p>适合：</p><ul><li>永久坏row</li><li>长期修复</li></ul><h3 id="sPPR"><a href="#sPPR" class="headerlink" title="sPPR"></a>sPPR</h3><p>特点：<strong>临时修复</strong></p><p>流程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">运行期间发现坏row</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">建立临时repair mapping</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">继续运行</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">掉电失效</span><br></pre></td></tr></table></figure><p>适合：</p><ul><li>runtime recovery</li><li>临时避免故障</li></ul><h2 id="PPR和Memory-Sparing区别"><a href="#PPR和Memory-Sparing区别" class="headerlink" title="PPR和Memory Sparing区别"></a>PPR和Memory Sparing区别</h2><p>二者关系：</p><blockquote><p>PPR是Memory Sparing的一种。</p></blockquote><p>区别主要在粒度：</p><table><thead><tr><th align="center"></th><th align="center">PPR</th><th align="center">Rank&#x2F;DIMM Sparing</th></tr></thead><tbody><tr><td align="center">粒度</td><td align="center">Row</td><td align="center">Rank&#x2F;DIMM</td></tr><tr><td align="center">位置</td><td align="center">DRAM内部</td><td align="center">Memory Controller&#x2F;平台</td></tr><tr><td align="center">备用资源</td><td align="center">Spare Row</td><td align="center">Spare Rank&#x2F;DIMM</td></tr><tr><td align="center">控制者</td><td align="center">DRAM内部repair logic</td><td align="center">Memory Controller&#x2F;RAS firmware</td></tr></tbody></table><h2 id="PPR-vs-ADDDC-vs-Chipkill"><a href="#PPR-vs-ADDDC-vs-Chipkill" class="headerlink" title="PPR vs ADDDC vs Chipkill"></a>PPR vs ADDDC vs Chipkill</h2><p>它们解决不同问题：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">             DRAM Reliability</span><br><span class="line"></span><br><span class="line">                   |</span><br><span class="line">    --------------------------------</span><br><span class="line">    |              |              |</span><br><span class="line">   ECC          ADDDC           PPR</span><br><span class="line">    |              |              |</span><br><span class="line">bit error     device fault     row failure</span><br><span class="line">correction    correction        repair</span><br></pre></td></tr></table></figure><p>简单理解：</p><table><thead><tr><th align="center">技术</th><th align="center">解决的问题</th></tr></thead><tbody><tr><td align="center">ECC</td><td align="center">bit错误</td></tr><tr><td align="center">Chipkill</td><td align="center">chip&#x2F;device故障</td></tr><tr><td align="center">ADDDC</td><td align="center">预测device故障并增强保护</td></tr><tr><td align="center">PPR</td><td align="center">替换坏row</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Post Package Repair（PPR）是DDR5时代重要的DRAM RAS技术，本质是一种row-level Memory Sparing：当DRAM某个row出现永久故障时，利用备用row建立地址重映射，使坏row被透明替换，从而避免故障升级为系统错误。DDR5进一步支持sPPR（运行时临时修复）和hPPR（永久修复）。</p><hr><p>参考资料:</p><ol><li><a href="https://lpc.events/event/18/contributions/1874/attachments/1416/3059/RAS%20Feature%20Control%20LPC%202024%20-%20Version%200.2.pdf">Unification of RAS feature control - Enhancing EDAC</a></li><li>Intel® Xeon® 6 Processors with Performance-cores (P-cores): Reliability, Availability, and Serviceability</li><li><a href="https://assets.micron.com/adobe/assets/urn%3Aaaid%3Aaem%3A5ea148c8-e3fe-489e-8489-99b1b9cdcd3c/renditions/original/as/ddr5-new-features-white-paper.pdf">Micron® DDR5 SDRAM: New Features</a></li><li><a href="https://www.intel.com/content/www/us/en/support/articles/000100102/server-products.html">Post Package Repair (PPR) Types and SEL Events on Purley and Whitley Platforms</a></li><li><a href="https://edc.intel.com/content/www/us/en/design/ipla/software-development-platforms/client/platforms/alder-lake-desktop/12th-generation-intel-core-processors-datasheet-volume-1-of-2/010/post-package-repair-ppr/">Post Package Repair (PPR)</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/02/Notes-about-Post-Package-Repair-PPR/</id>
    <link href="http://liujunming.github.io/2026/08/02/Notes-about-Post-Package-Repair-PPR/"/>
    <published>2026-08-02T00:47:53.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下Post Package Repair(PPR)的相关notes。]]>
    </summary>
    <title>Notes about Post Package Repair(PPR)</title>
    <updated>2026-08-02T08:57:26.158Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="RAS" scheme="http://liujunming.github.io/categories/RAS/"/>
    <category term="RAS" scheme="http://liujunming.github.io/tags/RAS/"/>
    <content>
      <![CDATA[<p>本文将mark下Adaptive Double Device Data Correction(ADDDC)的相关notes。<span id="more"></span></p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>ADDDC 是一种针对 DRAM device（DRAM 芯片级故障）的自适应 ECC 保护技术。</p><p>核心思想：</p><blockquote><p>Memory Controller 监测到某个 DRAM device 存在失效风险时，动态启用更强的 ECC 保护能力，使系统能够容忍该 device 失效，避免系统崩溃。</p></blockquote><p>简单流程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">正常运行</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">普通ECC保护</span><br><span class="line">   |</span><br><span class="line">检测到device错误趋势增加</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">开启 ADDDC</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">增强ECC保护</span><br><span class="line">   |</span><br><span class="line">device最终失效</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">系统继续运行</span><br></pre></td></tr></table></figure><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><h3 id="DRAM-device-故障通常有演进过程"><a href="#DRAM-device-故障通常有演进过程" class="headerlink" title="DRAM device 故障通常有演进过程"></a>DRAM device 故障通常有演进过程</h3><p>实际服务器中：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">正常DRAM</span><br><span class="line"></span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line"></span><br><span class="line">Correctable Error (CE) 增加</span><br><span class="line"></span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line"></span><br><span class="line">Device退化</span><br><span class="line"></span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line"></span><br><span class="line">Device Failure</span><br><span class="line"></span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line"></span><br><span class="line">Uncorrectable Error (UE)</span><br></pre></td></tr></table></figure><p>如果等到 device 完全失效：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Device failure</span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line">ECC无法恢复</span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line">系统故障</span><br></pre></td></tr></table></figure><p>因此需要<strong>提前发现风险，并提升保护能力</strong>。</p><h3 id="为什么不一直使用-Chipkill？"><a href="#为什么不一直使用-Chipkill？" class="headerlink" title="为什么不一直使用 Chipkill？"></a>为什么不一直使用 Chipkill？</h3><p>Chipkill 同样可以容忍 DRAM device 故障。</p><p>但是 Chipkill 的问题：</p><h4 id="持续性能开销"><a href="#持续性能开销" class="headerlink" title="持续性能开销"></a>持续性能开销</h4><p>Chipkill 需要一直运行更强的 device-level ECC：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">普通ECC:</span><br><span class="line"></span><br><span class="line">bit error correction</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Chipkill:</span><br><span class="line"></span><br><span class="line">device failure correction</span><br></pre></td></tr></table></figure><p>因此 Memory Controller 需要：</p><ul><li>更复杂ECC decoder</li><li>更复杂的数据重构逻辑</li><li>更高的访问处理复杂度</li></ul><p>结果：</p><ul><li>ECC decode latency增加</li><li>memory access path更复杂</li><li>功耗增加</li></ul><p>即：</p><blockquote><p>即使DRAM完全健康，也一直支付高可靠性保护成本。</p></blockquote><h4 id="ECC资源-容量开销无法避免"><a href="#ECC资源-容量开销无法避免" class="headerlink" title="ECC资源&#x2F;容量开销无法避免"></a>ECC资源&#x2F;容量开销无法避免</h4><p>Chipkill需要更多ECC冗余：</p><ul><li>通常采用 x4 DRAM organization</li><li>需要更多ECC symbol</li></ul><p>因此：</p><ul><li>DIMM有效容量利用率降低</li><li>ECC开销增加</li></ul><p>这一点 <strong>ADDDC无法消除</strong>。</p><p>ADDDC主要优化的是<strong>性能开销，而不是ECC容量开销</strong>。</p><h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><p>ADDDC主要由Integrated Memory Controller(IMC)实现。</p><p>架构：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">            CPU</span><br><span class="line"></span><br><span class="line">             |</span><br><span class="line">             |</span><br><span class="line">Integrated Memory Controller</span><br><span class="line">             |</span><br><span class="line">       ECC / RAS Logic</span><br><span class="line">             |</span><br><span class="line">             |</span><br><span class="line">           DRAM DIMM</span><br></pre></td></tr></table></figure><h3 id="Step-1：监测-DRAM-device-错误"><a href="#Step-1：监测-DRAM-device-错误" class="headerlink" title="Step 1：监测 DRAM device 错误"></a>Step 1：监测 DRAM device 错误</h3><p>Memory Controller统计：</p><ul><li>Correctable Error (CE)</li><li>device错误趋势</li></ul><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Chip3:</span><br><span class="line"></span><br><span class="line">CE:</span><br><span class="line">10</span><br><span class="line">100</span><br><span class="line">10000</span><br></pre></td></tr></table></figure><p>判断：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Chip3可能即将失效</span><br></pre></td></tr></table></figure><h3 id="Step-2：动态切换保护模式"><a href="#Step-2：动态切换保护模式" class="headerlink" title="Step 2：动态切换保护模式"></a>Step 2：动态切换保护模式</h3><p>正常：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Normal ECC</span><br><span class="line"></span><br><span class="line">低开销</span><br></pre></td></tr></table></figure><p>发现风险：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Normal ECC</span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line">ADDDC mode</span><br></pre></td></tr></table></figure><p>切换到更强 ECC 保护。</p><p>Intel 文档强调：</p><blockquote><p>The dynamic nature of the operation makes the performance implications material only after the DRAM device is detected to be failing.</p></blockquote><p>含义：只有检测到 DRAM device 正在失效后，ADDDC带来的性能影响才出现。</p><h3 id="Step-3：device失效后仍可恢复数据"><a href="#Step-3：device失效后仍可恢复数据" class="headerlink" title="Step 3：device失效后仍可恢复数据"></a>Step 3：device失效后仍可恢复数据</h3><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DRAM:</span><br><span class="line"></span><br><span class="line">Chip0 Chip1 Chip2 Chip3</span><br><span class="line">                 X</span><br></pre></td></tr></table></figure><p>ADDDC：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Chip3 failure</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">ECC reconstruction</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">Correct data</span><br></pre></td></tr></table></figure><p>避免：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DRAM device failure</span><br><span class="line">        |</span><br><span class="line">        v</span><br><span class="line">System crash</span><br></pre></td></tr></table></figure><h2 id="ADDDC-vs-Chipkill"><a href="#ADDDC-vs-Chipkill" class="headerlink" title="ADDDC vs Chipkill"></a>ADDDC vs Chipkill</h2><table><thead><tr><th align="center"></th><th align="center">Chipkill</th><th align="center">ADDDC</th></tr></thead><tbody><tr><td align="center">目标</td><td align="center">容忍DRAM device故障</td><td align="center">容忍DRAM device故障</td></tr><tr><td align="center">保护方式</td><td align="center">固定开启</td><td align="center">动态开启</td></tr><tr><td align="center">正常运行成本</td><td align="center">持续存在</td><td align="center">较低</td></tr><tr><td align="center">故障风险出现后</td><td align="center">无变化</td><td align="center">提升保护</td></tr><tr><td align="center">主要优化点</td><td align="center">最高可靠性</td><td align="center">可靠性与性能平衡</td></tr></tbody></table><p>简单理解：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Chipkill:</span><br><span class="line"></span><br><span class="line">一直穿防弹衣</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">ADDDC:</span><br><span class="line"></span><br><span class="line">平时轻装运行</span><br><span class="line">发现危险再穿防弹衣</span><br></pre></td></tr></table></figure><h2 id="ADDDC-vs-Memory-Sparing"><a href="#ADDDC-vs-Memory-Sparing" class="headerlink" title="ADDDC vs Memory Sparing"></a>ADDDC vs Memory Sparing</h2><table><thead><tr><th align="center"></th><th align="center">ADDDC</th><th align="center">Memory Sparing</th></tr></thead><tbody><tr><td align="center">核心</td><td align="center">增强ECC纠错</td><td align="center">替换故障内存</td></tr><tr><td align="center">是否需要备用memory</td><td align="center">否</td><td align="center">需要</td></tr><tr><td align="center">动作</td><td align="center">继续使用device并纠错</td><td align="center">切换到spare资源</td></tr><tr><td align="center">目标</td><td align="center">容忍故障</td><td align="center">避免使用故障区域</td></tr></tbody></table><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><blockquote><p>ADDDC 是一种预测驱动的 DRAM RAS 技术：Memory Controller 通过监控 DRAM device 错误趋势，在 device 即将失效时动态提升 ECC 保护能力，使系统能够容忍 device 级故障。</p></blockquote><p>相比 Chipkill：</p><ul><li>ADDDC不能消除ECC容量开销</li><li>优势在于避免Chipkill长期开启带来的性能开销</li><li>只有检测到故障风险时才进入高保护模式</li></ul><p>演进关系：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">普通ECC</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">Chipkill</span><br><span class="line">(一直高保护，高可靠，高成本)</span><br><span class="line"></span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line"></span><br><span class="line">ADDDC</span><br><span class="line">(预测故障，按需增强保护)</span><br></pre></td></tr></table></figure><p>ADDDC 的核心价值就是：<strong>用故障预测换取更低的长期性能成本</strong>。</p><hr><p>参考资料:</p><ol><li>Intel® Xeon® 6 Processors with Performance-cores (P-cores): Reliability, Availability, and Serviceability</li><li><a href="https://www.intel.com/content/www/us/en/developer/articles/technical/new-reliability-availability-and-serviceability-ras-features-in-the-intel-xeon-processor.html">New Reliability, Availability, and Serviceability (RAS) Features in the Intel® Xeon® Processor Family</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/01/Notes-about-Adaptive-Double-Device-Data-Correction-ADDDC/</id>
    <link href="http://liujunming.github.io/2026/08/01/Notes-about-Adaptive-Double-Device-Data-Correction-ADDDC/"/>
    <published>2026-08-01T13:12:33.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下Adaptive Double Device Data Correction(ADDDC)的相关notes。]]>
    </summary>
    <title>Notes about Adaptive Double Device Data Correction(ADDDC)</title>
    <updated>2026-08-01T14:53:48.162Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="RAS" scheme="http://liujunming.github.io/categories/RAS/"/>
    <category term="RAS" scheme="http://liujunming.github.io/tags/RAS/"/>
    <content>
      <![CDATA[<p>本文将mark下Memory Sparing的相关notes。<span id="more"></span></p><h2 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h2><ul><li><a href="/2025/01/05/DRAM-components/">DRAM components</a></li></ul><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><ul><li>Memory sparing is a repair function that replaces a portion of memory (spared memory) with a portion of functional memory at that same DPA.</li><li>Cacheline&#x2F;row&#x2F;bank&#x2F;rank memory sparing replaces full cacheline&#x2F;single DDR row&#x2F;entire bank&#x2F;entire DDR rank respectively.</li></ul><h2 id="DPA"><a href="#DPA" class="headerlink" title="DPA"></a>DPA</h2><p><strong>DPA（Device Physical Address）是内存设备内部看到的物理地址</strong>。</p><p>CPU&#x2F;OS访问的是<strong>System Physical Address（SPA）</strong>，经过 Memory Controller 的地址映射后，转换成DRAM设备地址：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">CPU / OS</span><br><span class="line">   |</span><br><span class="line">   |  System Physical Address (SPA)</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">Memory Controller</span><br><span class="line">   |</span><br><span class="line">   | Address Mapping</span><br><span class="line">   | (Channel / Rank / Bank / Row / Column)</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">DRAM Device</span><br><span class="line">   |</span><br><span class="line">   | Device Physical Address (DPA)</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">Memory Cell</span><br></pre></td></tr></table></figure><p>简单理解：</p><ul><li>SPA：软件看到的地址</li><li>DPA：DRAM硬件看到的位置</li></ul><p>Memory Sparing中的：</p><blockquote><p>“replace … with functional memory at that same DPA”</p></blockquote><p>意思是：</p><blockquote><p>对外暴露的设备物理地址保持不变，只是这个DPA背后的实际存储资源发生替换</p></blockquote><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">DPA = Rank0 / Row100</span><br><span class="line"></span><br><span class="line">原来:</span><br><span class="line"></span><br><span class="line">DPA</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">坏 Row100</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Sparing后:</span><br><span class="line"></span><br><span class="line">DPA</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">Spare Row</span><br></pre></td></tr></table></figure><p>对OS和应用透明。</p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>Memory Sparing是一种利用备用内存资源替换故障内存区域的RAS技术。</p><p>核心思想：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">发现故障区域</span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line">地址重映射</span><br><span class="line">      |</span><br><span class="line">      v</span><br><span class="line">备用区域接管</span><br></pre></td></tr></table></figure><p>内存区域坏 → spare memory替代。</p><table><thead><tr><th align="center">类型</th><th align="center">替换对象</th></tr></thead><tbody><tr><td align="center">Cacheline Sparing</td><td align="center">替换一个cacheline</td></tr><tr><td align="center">Row Sparing</td><td align="center">替换一个DRAM row</td></tr><tr><td align="center">Bank Sparing</td><td align="center">替换整个bank</td></tr><tr><td align="center">Rank Sparing</td><td align="center">替换整个DRAM rank</td></tr></tbody></table><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><h3 id="DRAM故障通常会逐渐恶化"><a href="#DRAM故障通常会逐渐恶化" class="headerlink" title="DRAM故障通常会逐渐恶化"></a>DRAM故障通常会逐渐恶化</h3><p>实际field数据表明：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">正常</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line"></span><br><span class="line">Correctable Error (CE)</span><br><span class="line">增加</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line"></span><br><span class="line">Uncorrectable Error (UE)</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line"></span><br><span class="line">系统故障</span><br></pre></td></tr></table></figure><p>如果等待UE：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DRAM失效</span><br><span class="line">   |</span><br><span class="line">   v</span><br><span class="line">系统崩溃</span><br></pre></td></tr></table></figure><p>Sparing可以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">CE增加</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">预测风险</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">提前替换</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">避免故障</span><br></pre></td></tr></table></figure><h3 id="ECC无法解决所有故障"><a href="#ECC无法解决所有故障" class="headerlink" title="ECC无法解决所有故障"></a>ECC无法解决所有故障</h3><p>ECC适合：</p><ul><li>bit error</li><li>少量错误</li></ul><p>但对于：</p><ul><li>row failure</li><li>bank failure</li><li>rank failure</li></ul><p>可能无法恢复。</p><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">整个Rank失效</span><br><span class="line"></span><br><span class="line">ECC:</span><br><span class="line">  无法修复</span><br><span class="line"></span><br><span class="line">Memory Sparing:</span><br><span class="line">  使用备用Rank</span><br></pre></td></tr></table></figure><h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><p>核心：<strong>通过硬件地址重映射，将坏区域替换到备用区域</strong>。</p><p>例如 Rank Sparing：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">正常：</span><br><span class="line"></span><br><span class="line">SPA</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">Memory Controller</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">Rank0</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Rank0出现故障</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">触发Sparing:</span><br><span class="line"></span><br><span class="line">SPA</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">Memory Controller</span><br><span class="line"> |</span><br><span class="line"> v</span><br><span class="line">Spare Rank</span><br></pre></td></tr></table></figure><p>软件仍然访问同一个地址：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Application</span><br><span class="line">    |</span><br><span class="line">    v</span><br><span class="line">same memory address</span><br></pre></td></tr></table></figure><p>但实际访问：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">old memory</span><br><span class="line">       |</span><br><span class="line">       X</span><br><span class="line"></span><br><span class="line">       |</span><br><span class="line">       v</span><br><span class="line"></span><br><span class="line">spare memory</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Memory Sparing就是DRAM领域的“热备替换”技术：当某个内存区域出现失效风险时，通过DPA级别的地址重映射，用备用内存区域替换它，使系统继续运行，而无需OS或应用感知。</p><hr><p>参考资料:</p><ol><li><a href="https://lpc.events/event/18/contributions/1874/attachments/1416/3059/RAS%20Feature%20Control%20LPC%202024%20-%20Version%200.2.pdf">Unification of RAS feature control - Enhancing EDAC</a></li><li><a href="https://www.intel.com.tw/content/dam/www/public/us/en/documents/datasheets/xeon-processor-7500-series-vol-2-datasheet.pdf">Intel® Xeon® Processor 7500 Series Datasheet, Volume 2</a></li><li><a href="https://www.intel.com/content/www/us/en/support/articles/000091455/server-products/server-boards.html">Supported Memory and Memory Population Rules for the Intel® Server Board S2600ST Family</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/01/Notes-about-Memory-Sparing/</id>
    <link href="http://liujunming.github.io/2026/08/01/Notes-about-Memory-Sparing/"/>
    <published>2026-08-01T12:12:21.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下Memory Sparing的相关notes。]]>
    </summary>
    <title>Notes about Memory Sparing</title>
    <updated>2026-08-01T13:03:22.664Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="RAS" scheme="http://liujunming.github.io/categories/RAS/"/>
    <category term="RAS" scheme="http://liujunming.github.io/tags/RAS/"/>
    <content>
      <![CDATA[<p>本文将mark下Error Check Scrub (ECS)的相关notes。<span id="more"></span></p><h2 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h2><ul><li><a href="/2025/01/04/Notes-about-Memory-Scrubbing/#How">Patrol scrubbing</a></li><li><a href="/2026/07/25/Notes-about-DDR5-On-Die-ECC%E6%8A%80%E6%9C%AF/">DDR5 On-Die ECC</a></li></ul><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>ECS 会在 DRAM 内部依次检查存储阵列中的数据：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">从 DRAM 阵列读出一个内部数据块</span><br><span class="line">             ↓</span><br><span class="line">       On-Die ECC 检查</span><br><span class="line">             ↓</span><br><span class="line">      是否有单比特错误？</span><br><span class="line">        /             \</span><br><span class="line">      没有             有</span><br><span class="line">       |               |</span><br><span class="line">   继续检查       纠正错误数据</span><br><span class="line">                       ↓</span><br><span class="line">              把正确数据写回阵列</span><br><span class="line">                       ↓</span><br><span class="line">                 更新错误计数</span><br></pre></td></tr></table></figure><p>ECS 读取内部数据；若发现可纠正错误，就把纠正后的数据重新写回。它可以由命令手动启动，也可以由 DRAM 自动调度执行。</p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>可以把ECS理解成：</p><blockquote><p>DDR5 DRAM 芯片内部自带的一套“巡检 + 纠错 + 回写 + 记账”机制。</p></blockquote><p>ECS有点像内存控制器的Patrol Scrub，但 ECS 运行在 <strong>DRAM 芯片内部</strong>，使用的是 DDR5 的 <strong>On-Die ECC</strong>。</p><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><p>假设 DRAM 中保存的是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">原始正确数据：10110010</span><br><span class="line">实际阵列数据：10100010</span><br><span class="line">                    ↑</span><br><span class="line">                  一位翻转</span><br></pre></td></tr></table></figure><p>普通读取时，On-Die ECC 可以临时纠正：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">阵列中的错误数据</span><br><span class="line">       ↓</span><br><span class="line">On-Die ECC 纠正</span><br><span class="line">       ↓</span><br><span class="line">向 CPU 输出正确数据</span><br></pre></td></tr></table></figure><p>但这里有一个问题：</p><blockquote><p>向 CPU 输出正确数据，不一定意味着阵列中原来的错误已经被清掉。</p></blockquote><p>如果错误数据长期留在阵列里，之后同一内部 ECC 保护块中又出现第二个错误，单比特纠错能力就可能不够。</p><p>ECS 因此会进一步执行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">读出错误数据</span><br><span class="line">      ↓</span><br><span class="line">纠正</span><br><span class="line">      ↓</span><br><span class="line">把正确数据重新写回 DRAM</span><br></pre></td></tr></table></figure><p>这叫 <strong>scrub</strong>。它的主要作用是防止多个潜伏错误逐渐累积。</p><h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><p>Allows the DRAM to:</p><ol><li>internally read</li><li>correct single-bit errors</li><li>write back corrected data bits to the DRAM array</li></ol><h2 id="error-counts"><a href="#error-counts" class="headerlink" title="error counts"></a>error counts</h2><p>虽然错误在 DRAM 内部被纠正了，但 DRAM仍可以把错误统计信息暴露出来。</p><p>如果没有 ECS 统计，系统可能看到：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">CPU 每次读取的数据都是正确的</span><br><span class="line">              ↓</span><br><span class="line">误以为 DRAM 完全健康</span><br></pre></td></tr></table></figure><p>实际上可能是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DRAM 内部已经纠正了大量单比特错误</span><br></pre></td></tr></table></figure><p>ECS 让管理软件能够知道：</p><ul><li>扫描期间纠正了多少错误</li><li>有多少行或内部数据块出现过错误</li><li>哪一行的错误最多</li><li>是否已经超过设定阈值</li></ul><p>因此，ECS 在一定程度上弥补了 On-Die ECC 带来的“错误不可见”问题。在完整扫描后，DDR5 可以报告已纠正错误数量，并报告错误数量最多的行。</p><h2 id="ECS-threshold-count"><a href="#ECS-threshold-count" class="headerlink" title="ECS threshold count"></a>ECS threshold count</h2><p>它是一个<strong>报告门槛</strong>。</p><p>例如，假设阈值设置为 256：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">错误计数 &lt; 256</span><br><span class="line">    ↓</span><br><span class="line">继续计数，通常不触发阈值告警</span><br><span class="line"></span><br><span class="line">错误计数 ≥ 256</span><br><span class="line">    ↓</span><br><span class="line">标记超过阈值</span><br><span class="line">    ↓</span><br><span class="line">允许固件或管理软件获取告警/日志</span><br></pre></td></tr></table></figure><p>设置阈值是为了避免一个偶发单比特错误就不断产生告警。</p><p>它通常用于回答：</p><blockquote><p>这颗 DRAM 中被纠正的小错误是否已经多到值得关注？</p></blockquote><p>控制接口还可以配置是统计“出现错误的行”，还是统计“出现错误的内部 ECC 数据块”。</p><p>需要注意：</p><blockquote><p>阈值主要控制统计和报告策略，不是说超过阈值之后 ECS 才开始纠错。</p></blockquote><p>ECS 扫描时发现可纠正错误，就会进行纠正；阈值更多决定何时将其视作值得报告的可靠性事件。</p><h2 id="When"><a href="#When" class="headerlink" title="When"></a>When</h2><p>主要有两种方式。</p><h3 id="自动模式"><a href="#自动模式" class="headerlink" title="自动模式"></a>自动模式</h3><p>DRAM 自己安排 ECS 操作：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">正常运行</span><br><span class="line">   ↓</span><br><span class="line">DRAM 利用合适时机逐步扫描阵列</span><br><span class="line">   ↓</span><br><span class="line">最终完成一次全阵列 ECS</span><br></pre></td></tr></table></figure><p>自动模式由 DRAM 调度 ECS，以在建议的 24 小时周期内完成一次数据阵列扫描。</p><h3 id="手动模式"><a href="#手动模式" class="headerlink" title="手动模式"></a>手动模式</h3><p>内存控制器或固件向 DRAM 发出命令，要求执行 ECS：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">内存控制器</span><br><span class="line">     ↓ 特定命令</span><br><span class="line">DDR5 DRAM</span><br><span class="line">     ↓</span><br><span class="line">执行一次或一系列 ECS 操作</span><br></pre></td></tr></table></figure><p>手动模式适合：</p><ul><li>启动时检测</li><li>维护窗口</li><li>管理员主动诊断</li><li>发现异常后提高扫描力度</li></ul><p>所以 <code>mode of operation</code> 本质上是在选择：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">由 DRAM 自动安排</span><br><span class="line">        或</span><br><span class="line">由系统显式触发</span><br></pre></td></tr></table></figure><h2 id="Reset-the-ECS-counter"><a href="#Reset-the-ECS-counter" class="headerlink" title="Reset the ECS counter"></a>Reset the ECS counter</h2><p>就是清空本轮 ECS 统计信息，例如：</p><ul><li>错误总数</li><li>超过阈值的状态</li><li>错误最多的行</li><li>该行的错误数量</li><li>相关地址信息</li></ul><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">过去一周：</span><br><span class="line">ECS count = 300</span><br><span class="line">        ↓</span><br><span class="line">管理员读取日志并完成维护</span><br><span class="line">        ↓</span><br><span class="line">Reset ECS counter</span><br><span class="line">        ↓</span><br><span class="line">ECS count = 0</span><br><span class="line">        ↓</span><br><span class="line">开始观察下一周期</span><br></pre></td></tr></table></figure><p>它不是关闭 ECS，也不是关闭 On-Die ECC。</p><p>清零之后，系统可以判断：</p><ul><li>错误是否继续增长</li><li>某次维修是否有效</li><li>错误是一次性事件还是持续性缺陷</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>ECS 是 <strong>DDR5 DRAM 内部</strong>的主动巡检机制：它周期性读取内部数据，用 On-Die ECC 纠正单比特错误，把正确数据写回，并记录错误数量及高风险行。</p><p>最关键的价值有两个：</p><ol><li>清除潜伏错误，防止错误累积成不可纠正错误</li><li>把原本被 On-Die ECC 隐藏的错误，以计数或日志形式暴露给系统</li></ol><hr><p>参考资料:</p><ol><li><a href="https://assets.micron.com/adobe/assets/urn%3Aaaid%3Aaem%3A5ea148c8-e3fe-489e-8489-99b1b9cdcd3c/renditions/original/as/ddr5-new-features-white-paper.pdf">Micron® DDR5 SDRAM: New Features</a></li><li><a href="https://lpc.events/event/18/contributions/1874/attachments/1416/3059/RAS%20Feature%20Control%20LPC%202024%20-%20Version%200.2.pdf">Unification of RAS feature control - Enhancing EDAC</a></li><li><a href="https://computeexpresslink.org/wp-content/uploads/2024/08/An-Overview-of-RAS-for-Compute-Express-Link-3.1-Whitepaper.pdf">An Overview of RAS for Compute Express Link® Covering from CXL® 2.0 to CXL® 3.1 </a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/08/01/Notes-about-Error-Check-Scrub-ECS/</id>
    <link href="http://liujunming.github.io/2026/08/01/Notes-about-Error-Check-Scrub-ECS/"/>
    <published>2026-08-01T10:45:59.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下Error Check Scrub (ECS)的相关notes。]]>
    </summary>
    <title>Notes about Error Check Scrub (ECS)</title>
    <updated>2026-08-01T11:53:56.646Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="RAS" scheme="http://liujunming.github.io/categories/RAS/"/>
    <category term="RAS" scheme="http://liujunming.github.io/tags/RAS/"/>
    <content>
      <![CDATA[<p>本文将mark下DDR5 Bounded Fault ECC技术的相关notes。<span id="more"></span></p><h2 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h2><ul><li><a href="/2025/01/05/DRAM-components/">DRAM components</a></li><li><a href="/2026/07/25/Notes-about-DDR5-On-Die-ECC%E6%8A%80%E6%9C%AF/">DDR5 On-Die ECC</a></li><li><a href="/2026/07/25/Notes-about-DRAM-Chipkill%E6%8A%80%E6%9C%AF/">DRAM Chipkill</a></li></ul><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>Bounded Fault ECC 的核心是DDR5 通过约束 DRAM 内部故障（fault）到外部数据输出（DQ pins）的映射关系，使某些故障的影响范围是确定且有限的（bounded），因此 ECC 不需要按照“整颗 DRAM 芯片完全损坏”的最坏情况设计，从而降低 ECC 开销。</p><p><img src="/images/2026/07/004.png"></p><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><p>先看传统 Chipkill。</p><p>一个 DRAM device 内部：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">       DRAM device</span><br><span class="line"></span><br><span class="line">    Memory Array</span><br><span class="line">         |</span><br><span class="line"> ----------------</span><br><span class="line"> |              |</span><br><span class="line">DQ0            DQ3</span><br></pre></td></tr></table></figure><p>内部可能发生很多故障：</p><ul><li>一整列坏</li><li>一些 row 坏</li><li>bank 坏</li><li>外围电路坏</li></ul><p>关键问题：</p><blockquote><p>系统看到的是 DQ pin 上的数据错误，但不知道内部 fault 会扩散到多少 DQ。</p></blockquote><p>例如一个 row fault：</p><ul><li>情况1</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">内部 fault</span><br><span class="line"></span><br><span class="line">      ↓</span><br><span class="line"></span><br><span class="line">DQ0 出错</span><br></pre></td></tr></table></figure><ul><li>情况2</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">内部 fault</span><br><span class="line"></span><br><span class="line">      ↓</span><br><span class="line"></span><br><span class="line">DQ0,DQ1 出错</span><br></pre></td></tr></table></figure><ul><li>情况3</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">内部 fault</span><br><span class="line"></span><br><span class="line">      ↓</span><br><span class="line"></span><br><span class="line">DQ0,DQ1,DQ2,DQ3 全错</span><br></pre></td></tr></table></figure><p>如果没有额外保证，系统只能按照最坏情况：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">整个 DRAM device 可能失效</span><br></pre></td></tr></table></figure><p>来设计 ECC。</p><p>这就是 Chipkill 的思想。</p><h2 id="核心思想"><a href="#核心思想" class="headerlink" title="核心思想"></a>核心思想</h2><p>DDR5 换了一种思路，不是：</p><blockquote><p>“我假设 DRAM 任意地方都可能坏。”</p></blockquote><p>而是：</p><blockquote><p>“我在 DRAM 内部设计阶段限制故障传播范围，让系统知道某些 fault 最大会影响多少数据。”</p></blockquote><p>这就是<strong>Fault Bounding(故障边界限制)</strong>。</p><p>举个简单例子，假设某个 DRAM 内部 row fault。</p><h3 id="传统-DRAM"><a href="#传统-DRAM" class="headerlink" title="传统 DRAM"></a>传统 DRAM</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Row fault</span><br><span class="line"></span><br><span class="line">可能影响：</span><br><span class="line"></span><br><span class="line">DQ0</span><br><span class="line">DQ1</span><br><span class="line">DQ2</span><br><span class="line">DQ3</span><br></pre></td></tr></table></figure><p>系统不知道范围，所以ECC保护<code>4个DQ</code>。</p><h3 id="DDR5-bounded-fault"><a href="#DDR5-bounded-fault" class="headerlink" title="DDR5 bounded fault"></a>DDR5 bounded fault</h3><p><img src="/images/2026/07/005.png"></p><p>规定这个 row fault最多影响：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">DQ0</span><br><span class="line">DQ1</span><br></pre></td></tr></table></figure><p>不会影响：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">DQ2</span><br><span class="line">DQ3</span><br></pre></td></tr></table></figure><p>那么系统知道最大故障范围 &#x3D; DQ0~DQ1，于是 ECC只需要覆盖这个bounded region，而不是整个 DRAM device。</p><h2 id="要点"><a href="#要点" class="headerlink" title="要点"></a>要点</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Bounded Fault =</span><br><span class="line">某些定义好的 fault mode</span><br><span class="line">具有确定的最大影响范围</span><br></pre></td></tr></table></figure><p>例如：</p><ul><li>某些 row fault</li><li>某些 column fault</li><li>某些内部阵列故障</li></ul><p>DDR5 标准规定这些 fault 到 DQ 的映射满足某些约束。</p><p>A Systematic Study of DDR4 DRAM Faults in the Field原文描述：</p><blockquote><p>DDR5 bounded fault 的目标，是保证 DRAM 内部组件（例如 row）如何映射到 DRAM data pins，使系统设计者能够设计覆盖特定 fault mode 的 ECC。</p></blockquote><p>所以重点是fault propagation boundary。</p><h2 id="降低-ECC-成本"><a href="#降低-ECC-成本" class="headerlink" title="降低 ECC 成本"></a>降低 ECC 成本</h2><p>Chipkill保护整个 DRAM device</p><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DQ0</span><br><span class="line">DQ1</span><br><span class="line">DQ2</span><br><span class="line">DQ3</span><br></pre></td></tr></table></figure><p>全部可能坏。</p><p>因此需要强ECC。</p><p>BF ECC保护有限范围</p><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">DQ0</span><br><span class="line">DQ1</span><br></pre></td></tr></table></figure><p>可能错误。</p><p>那么ECC只需要纠正这个范围即可。</p><p>所以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">保护范围减少</span><br><span class="line">        ↓</span><br><span class="line">ECC校验位减少</span><br><span class="line">        ↓</span><br><span class="line">减少ECC DRAM数量</span><br><span class="line">        ↓</span><br><span class="line">降低成本 / 提高容量效率</span><br></pre></td></tr></table></figure><h3 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h3><p>传统 Chipkill：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">10×4</span><br><span class="line"></span><br><span class="line">8颗 x4 DRAM:</span><br><span class="line">    数据</span><br><span class="line"></span><br><span class="line">2颗 x4 DRAM:</span><br><span class="line">    ECC</span><br></pre></td></tr></table></figure><p>即：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">8 data + 2 ECC</span><br></pre></td></tr></table></figure><p>如果使用 BF ECC：<br>因为故障范围已经被限制，可以：</p><ul><li>释放部分 ECC 位；</li><li>或减少 ECC 芯片。</li></ul><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">9×4</span><br><span class="line"></span><br><span class="line">8颗数据</span><br><span class="line">1颗ECC</span><br></pre></td></tr></table></figure><p>A Systematic Study of DDR4 DRAM Faults in the Field指出：bounded fault ECC 可以释放校验位，甚至减少一个 DRAM device。</p><h2 id="On-Die-ECC与BF-ECC-的关系"><a href="#On-Die-ECC与BF-ECC-的关系" class="headerlink" title="On-Die ECC与BF ECC 的关系"></a>On-Die ECC与BF ECC 的关系</h2><p>DDR5：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">      DRAM chip内部</span><br><span class="line"></span><br><span class="line">DRAM Cell Array</span><br><span class="line">      |</span><br><span class="line">  On-Die ECC</span><br><span class="line">      |</span><br><span class="line">  DQ pins</span><br><span class="line">      |</span><br><span class="line">      |</span><br><span class="line">Memory Controller</span><br><span class="line">      |</span><br><span class="line">  外部 ECC</span><br></pre></td></tr></table></figure><p>On-Die ECC解决DRAM芯片内部的小错误，例如：单 bit cell error。它发生在DRAM内部。</p><p>Bounded Fault ECC解决:经过DRAM输出之后，系统看到的较大fault。它发生在：Memory Controller侧。</p><p>两者关系：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">On-Die ECC</span><br><span class="line">    ↓</span><br><span class="line">过滤小错误</span><br><span class="line"></span><br><span class="line">Bounded Fault ECC</span><br><span class="line">    ↓</span><br><span class="line">降低外部ECC保护范围</span><br></pre></td></tr></table></figure><hr><p>DDR5 的 On-Die ECC 通常能够纠正单比特错误。但当一个故障产生多个错误比特时，它超出单错纠正能力，可能发生 <strong>miscorrection，误纠正</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">原本错误只在边界 A</span><br><span class="line">        ↓</span><br><span class="line">On-Die ECC 判断错了错误位置</span><br><span class="line">        ↓</span><br><span class="line">又翻转了边界 B 中的正常比特</span><br><span class="line">        ↓</span><br><span class="line">错误从一个边界扩散成两个边界</span><br></pre></td></tr></table></figure><p>这样会破坏 Bounded Fault 的保证：系统级 ECC 原本能纠正一个边界，但现在看到两个边界都错了，可能变成不可纠正错误。</p><p>所以要求 On-Die ECC 也必须“遵守边界”：</p><ul><li>可以在原故障边界内误纠正</li><li><strong>不能把错误扩散到另一个数据边界</strong></li></ul><h2 id="BF-ECC-的代价"><a href="#BF-ECC-的代价" class="headerlink" title="BF ECC 的代价"></a>BF ECC 的代价</h2><p><strong>可靠性下降</strong></p><p>如果 fault 超过 bounded 范围:例如设计认为最多影响 DQ0,DQ1，但是实际：DQ0,DQ1,DQ2,DQ3都坏，那么BF ECC无法保证恢复。</p><p>这就是unbounded fault，A Systematic Study of DDR4 DRAM Faults in the Field通过 DDR4 field data 建模发现，一些 fault,例如：</p><ul><li>two-column；</li><li>cluster-row；</li></ul><p>可能属于 unbounded fault。</p><p>因此BF ECC 相比 Chipkill 会增加 UE（不可纠正错误）。</p><h2 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">           DRAM内部故障</span><br><span class="line"></span><br><span class="line">                 |</span><br><span class="line">                 |</span><br><span class="line">       +----------------+</span><br><span class="line">       |                |</span><br><span class="line">       v                v</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">普通 DRAM               DDR5 BF设计</span><br><span class="line"></span><br><span class="line">不知道影响范围           限制fault传播范围</span><br><span class="line"></span><br><span class="line">       |                         |</span><br><span class="line">       |                         |</span><br><span class="line">       v                         v</span><br><span class="line"></span><br><span class="line">按最坏情况保护          按bounded范围保护</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">       |                         |</span><br><span class="line">       |                         |</span><br><span class="line"></span><br><span class="line">    Chipkill              Bounded Fault ECC</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">       |                         |</span><br><span class="line"></span><br><span class="line">ECC开销大                ECC开销小</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">DRAM 内部组件发生故障</span><br><span class="line">          ↓</span><br><span class="line">物理布局和数据映射限制影响范围</span><br><span class="line">          ↓</span><br><span class="line">错误只落在一个规定的 DQ 边界</span><br><span class="line">          ↓</span><br><span class="line">On-Die ECC 尝试纠正</span><br><span class="line">          ↓</span><br><span class="line">即使误纠正，也不能跨出该边界</span><br><span class="line">          ↓</span><br><span class="line">Rank-Level ECC 根据边界保证恢复数据</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th align="center"></th><th align="center">Chipkill</th><th align="center">Bounded Fault ECC</th></tr></thead><tbody><tr><td align="center">假设</td><td align="center">一整颗 DRAM 可以坏</td><td align="center">故障影响范围有限</td></tr><tr><td align="center">保护范围</td><td align="center">最大</td><td align="center">较小</td></tr><tr><td align="center">ECC 开销</td><td align="center">高</td><td align="center">低</td></tr><tr><td align="center">成本</td><td align="center">高</td><td align="center">低</td></tr><tr><td align="center">可靠性</td><td align="center">更强</td><td align="center">略弱</td></tr><tr><td align="center">依赖</td><td align="center">不需要 DRAM 保证</td><td align="center">需要 DDR5 保证 fault bounding</td></tr><tr><td align="center">可靠性</td><td align="center">更高</td><td align="center">略低</td></tr></tbody></table><p>DDR5 不再让系统假设“一颗 DRAM 芯片可能完全坏掉”，而是在 DRAM 内部设计阶段限制某些 fault 的传播范围，使系统提前知道错误最多影响哪些 DQ pins。这样 ECC 就可以针对有限 fault boundary 设计，而不是采用保护整个 DRAM device 的 Chipkill，从而减少 ECC 校验开销，提高容量效率。</p><p>简单记：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Chipkill:</span><br><span class="line">不知道坏多大 → 准备救整颗芯片</span><br><span class="line"></span><br><span class="line">Bounded Fault ECC:</span><br><span class="line">提前限制坏多大 → 只准备救这个范围</span><br></pre></td></tr></table></figure><p>这也是为什么它叫 <strong>Bounded Fault（有边界的故障）</strong>。</p><ol><li>Fault Bounding 不是消除故障，而是缩小故障对 Cache Line 的影响范围</li><li>On-Die ECC 的失败行为也必须受约束，不能把局部错误扩散到其他边界</li><li>它是一套 DRAM 结构、数据映射和系统 ECC 之间的跨层契约，而不是一种独立的新 ECC</li></ol><hr><p>参考资料:</p><ol><li>A Systematic Study of DDR4 DRAM Faults in the Field(HPCA’25)</li><li>Improving Memory Reliability by Bounding DRAM Faults(MEMSYS’20)</li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/07/26/Notes-about-DDR5-Bounded-Fault-ECC%E6%8A%80%E6%9C%AF/</id>
    <link href="http://liujunming.github.io/2026/07/26/Notes-about-DDR5-Bounded-Fault-ECC%E6%8A%80%E6%9C%AF/"/>
    <published>2026-07-26T04:44:21.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下DDR5 Bounded Fault ECC技术的相关notes。]]>
    </summary>
    <title>Notes about DDR5 Bounded Fault ECC技术</title>
    <updated>2026-08-01T09:30:25.301Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="RAS" scheme="http://liujunming.github.io/categories/RAS/"/>
    <category term="AMD" scheme="http://liujunming.github.io/tags/AMD/"/>
    <category term="RAS" scheme="http://liujunming.github.io/tags/RAS/"/>
    <content>
      <![CDATA[<p>本文将mark下DRAM Chipkill技术的相关notes。<span id="more"></span></p><h2 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h2><p><a href="/2025/01/05/DRAM-components/">DRAM components</a></p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>Chipkill 是一种高级内存 ECC 保护技术，它可以容忍一整颗 DRAM 芯片（chip）发生故障，而系统仍然能够正常运行。</p><p>简单理解：普通 ECC 只能保护几个 bit 出错，而 Chipkill 可以保护“一整颗 DRAM 芯片坏掉”。</p><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><p>传统 ECC可以解决：</p><ul><li>单 bit 错误</li><li>少量 bit 错误</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">DRAM</span><br><span class="line"> |</span><br><span class="line"> | 一个 bit 出错</span><br><span class="line"> v</span><br><span class="line"></span><br><span class="line">ECC 修复</span><br></pre></td></tr></table></figure><p>但是现代 DRAM 可能出现更严重的问题：</p><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">一个 DRAM 芯片损坏</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">大量 bit 同时错误</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">普通 ECC 无法恢复</span><br></pre></td></tr></table></figure><p>原因是随着：</p><ul><li>DRAM 容量越来越大</li><li>芯片数量越来越多</li><li>制程越来越小</li></ul><p><strong>单个 DRAM 芯片失效带来的影响越来越大</strong>。</p><p>因此需要：</p><blockquote><p>一种能够抵御“整个芯片坏掉”的保护机制。</p></blockquote><p>这就是 Chipkill。</p><h2 id="核心思想"><a href="#核心思想" class="headerlink" title="核心思想"></a>核心思想</h2><p>一句话：</p><blockquote><p>不要让一颗 DRAM 芯片负责一个完整的数据块，而是把数据分散到多个 DRAM 芯片，并增加额外校验信息，使任何一颗芯片坏掉后都能恢复数据。</p></blockquote><h3 id="普通-ECC"><a href="#普通-ECC" class="headerlink" title="普通 ECC"></a>普通 ECC</h3><p>假设一个数据：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Data:</span><br><span class="line">A B C D</span><br></pre></td></tr></table></figure><p>可能来自Chip0，如果 Chip0 坏：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">A B C D 全丢</span><br></pre></td></tr></table></figure><p>ECC 很难恢复。</p><h3 id="Chipkill"><a href="#Chipkill" class="headerlink" title="Chipkill"></a>Chipkill</h3><p>重新组织数据：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">          数据分散</span><br><span class="line"></span><br><span class="line">Chip0   A</span><br><span class="line">Chip1   B</span><br><span class="line">Chip2   C</span><br><span class="line">Chip3   D</span><br><span class="line"></span><br><span class="line">Chip4   校验信息</span><br></pre></td></tr></table></figure><p>如果Chip2 坏，系统：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">A</span><br><span class="line">B</span><br><span class="line">?</span><br><span class="line">D</span><br><span class="line">校验信息</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">恢复 C</span><br></pre></td></tr></table></figure><p>所以：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">一个 chip 故障</span><br><span class="line">        ↓</span><br><span class="line">数据仍然可恢复</span><br></pre></td></tr></table></figure><h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><h3 id="数据分散存储"><a href="#数据分散存储" class="headerlink" title="数据分散存储"></a>数据分散存储</h3><p>不要：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">一个 chip 保存连续数据</span><br></pre></td></tr></table></figure><p>而是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">多个 chip 共同组成一个 ECC 数据块</span><br></pre></td></tr></table></figure><p>这样一个 chip 故障，只影响部分数据。</p><h3 id="增加更强-ECC-信息"><a href="#增加更强-ECC-信息" class="headerlink" title="增加更强 ECC 信息"></a>增加更强 ECC 信息</h3><p>除了数据,还保存：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">数据 + 校验信息</span><br></pre></td></tr></table></figure><p>当某个 chip 消失，通过校验信息，重新计算缺失数据。</p><h3 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h3><p><img src="/images/2026/07/003.png"></p><h2 id="Chipkill-vs-普通-ECC"><a href="#Chipkill-vs-普通-ECC" class="headerlink" title="Chipkill vs 普通 ECC"></a>Chipkill vs 普通 ECC</h2><table><thead><tr><th align="center"></th><th align="center">普通 ECC</th><th align="center">Chipkill</th></tr></thead><tbody><tr><td align="center">保护范围</td><td align="center">bit 错误</td><td align="center">整个 DRAM chip 故障</td></tr><tr><td align="center">主要目标</td><td align="center">小错误</td><td align="center">大规模故障</td></tr><tr><td align="center">成本</td><td align="center">低</td><td align="center">更高</td></tr><tr><td align="center">可靠性</td><td align="center">一般</td><td align="center">更高</td></tr><tr><td align="center">应用</td><td align="center">普通服务器</td><td align="center">高可靠服务器&#x2F;HPC</td></tr></tbody></table><h2 id="为什么叫-Chipkill？"><a href="#为什么叫-Chipkill？" class="headerlink" title="为什么叫 Chipkill？"></a>为什么叫 Chipkill？</h2><p>名字含义：</p><blockquote><p>即使一个 chip “被杀死”（kill），系统仍然能够工作。</p></blockquote><p>也就是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Chip failure</span><br><span class="line"></span><br><span class="line">      ↓</span><br><span class="line"></span><br><span class="line">Chipkill ECC</span><br><span class="line"></span><br><span class="line">      ↓</span><br><span class="line"></span><br><span class="line">系统继续运行</span><br></pre></td></tr></table></figure><h2 id="在-DRAM-RAS-中的定位"><a href="#在-DRAM-RAS-中的定位" class="headerlink" title="在 DRAM RAS 中的定位"></a>在 DRAM RAS 中的定位</h2><p>现代内存保护通常是多层：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">DRAM Cell</span><br><span class="line">    |</span><br><span class="line">    |</span><br><span class="line">On-Die ECC</span><br><span class="line">    |</span><br><span class="line">    |</span><br><span class="line">Chipkill ECC</span><br><span class="line">    |</span><br><span class="line">    |</span><br><span class="line">系统软件 RAS</span><br></pre></td></tr></table></figure><p>其中：</p><ul><li>On-Die ECC: 解决 DRAM 内部小错误</li><li>Chipkill: 解决 DRAM 芯片级故障</li><li>OS&#x2F;RAS: 负责故障隔离、替换等</li></ul><h2 id="价值"><a href="#价值" class="headerlink" title="价值"></a>价值</h2><p>因为真实 DRAM fault 研究发现，错误并不只是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">单 bit error</span><br></pre></td></tr></table></figure><p>还包括：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Row fault</span><br><span class="line">Column fault</span><br><span class="line">Bank fault</span><br><span class="line">Chip fault</span><br></pre></td></tr></table></figure><p>例如A Study of DRAM Failures in the Field(SC’12) 发现大量 multi-bit fault，并证明：</p><blockquote><p>Chipkill 相比 SEC-DED(Single Error Correction Double Error Detection) ECC，可以显著降低不可纠正错误（UE）。论文中报告 Chipkill 可将节点因 DRAM 错误导致的失败率降低约 42 倍。</p></blockquote><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过数据分散 + 更强 ECC，让系统能够承受一整颗 DRAM 芯片失效，而不会丢失数据。它解决的是传统 ECC 无法处理的大规模 DRAM 故障问题，是服务器和 HPC 系统提高内存可靠性的关键技术。</p><h2 id="AMDC"><a href="#AMDC" class="headerlink" title="AMDC"></a>AMDC</h2><p>AMD’s Advanced Memory Device Correction (AMDC) which is designed to correct any number of faults occurring within a single DRAM device in a rank.</p><p>可以简单认为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Chipkill</span><br><span class="line">   =</span><br><span class="line">一种思想：</span><br><span class="line">保护一颗 DRAM device 故障</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">AMDC</span><br><span class="line">   =</span><br><span class="line">AMD 实现的一种类似 Chipkill 的机制</span><br></pre></td></tr></table></figure><p>区别主要是：</p><ul><li>Chipkill 是 IBM 提出的通用概念</li><li>AMDC 是 AMD 平台上的具体实现</li></ul><p>A Systematic Study of DDR4 DRAM Faults in the Field论文中对 Chipkill的描述：</p><blockquote><p>Chipkill ECC 可以纠正限制在单个 DRAM device 内的错误。</p></blockquote><p>DDR5 DRAM Faults in the Field论文中对AMDC的描述：</p><blockquote><p>纠正单颗 DRAM device 内任意数量的错误。</p></blockquote><h3 id="与OD-ECC的协作"><a href="#与OD-ECC的协作" class="headerlink" title="与OD-ECC的协作"></a>与OD-ECC的协作</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">DRAM 芯片内部：On-Die ECC</span><br><span class="line">        ↓</span><br><span class="line">主要纠正芯片内部的单 bit 错误</span><br><span class="line"></span><br><span class="line">内存控制器侧：Chipkill / AMDC</span><br><span class="line">        ↓</span><br><span class="line">主要纠正一整颗 DRAM device 范围内的错误</span><br></pre></td></tr></table></figure><p>The presence of OD-ECC in addition to AMDC can increase DRAM reliability by eliminating the risk that a single-bit fault in one DRAM device and a device fault in another DRAM device will be uncorrectable.</p><p>两者叠加后，On-Die ECC 先隐藏小规模单 bit 故障，Chipkill 再保护更严重的 device-level 故障。DDR5 DRAM Faults in the Field论文指出，这种组合能避免“一颗芯片已有单 bit 故障、另一颗芯片又发生 device fault”时直接变成不可纠正错误。</p><hr><p>参考资料:</p><ol><li><a href="https://my.eng.utah.edu/~cs7810/pres/14-7810-06.pdf">Lecture 6: Chipkill, PCM</a></li><li>A Study of DRAM Failures in the Field(SC’12)</li><li>DDR5 DRAM Faults in the Field(DSN-S’25)</li><li><a href="https://www.amd.com/content/dam/amd/en/documents/epyc-business-docs/white-papers/advanced-memory-device-correction.pdf">Advanced Memory Device Correction (AMDC) for Servers</a></li><li>A Systematic Study of DDR4 DRAM Faults in the Field(HPCA’25)</li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/07/25/Notes-about-DRAM-Chipkill%E6%8A%80%E6%9C%AF/</id>
    <link href="http://liujunming.github.io/2026/07/25/Notes-about-DRAM-Chipkill%E6%8A%80%E6%9C%AF/"/>
    <published>2026-07-25T06:42:37.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下DRAM Chipkill技术的相关notes。]]>
    </summary>
    <title>Notes about DRAM Chipkill技术</title>
    <updated>2026-07-25T12:34:15.985Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="RAS" scheme="http://liujunming.github.io/categories/RAS/"/>
    <category term="RAS" scheme="http://liujunming.github.io/tags/RAS/"/>
    <content>
      <![CDATA[<p>本文将mark下DDR5 On-Die ECC技术的相关notes。<span id="more"></span></p><h2 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h2><p><a href="/2025/01/05/DRAM-components/">DRAM components</a></p><h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>On-Die ECC 是放在 DRAM 芯片内部的一层错误保护机制，用来自动修复 DRAM cell 产生的小错误。</p><h3 id="Without-On-Die-ECC"><a href="#Without-On-Die-ECC" class="headerlink" title="Without On-Die ECC"></a>Without On-Die ECC</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">CPU</span><br><span class="line"> |</span><br><span class="line">Memory Controller</span><br><span class="line"> |</span><br><span class="line">DRAM</span><br><span class="line"> |</span><br><span class="line">Memory Cell</span><br></pre></td></tr></table></figure><p>错误发生：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Memory Cell 出错</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">Memory Controller 的 ECC 发现</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">修复</span><br></pre></td></tr></table></figure><h3 id="With-On-Die-ECC"><a href="#With-On-Die-ECC" class="headerlink" title="With On-Die ECC"></a>With On-Die ECC</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">CPU</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"></span><br><span class="line">Memory Controller</span><br><span class="line"></span><br><span class="line"> |</span><br><span class="line"></span><br><span class="line">DRAM Chip</span><br><span class="line"></span><br><span class="line">   |</span><br><span class="line">   |</span><br><span class="line"> On-Die ECC</span><br><span class="line"></span><br><span class="line">   |</span><br><span class="line">   |</span><br><span class="line"> DRAM Cell</span><br></pre></td></tr></table></figure><p>错误首先在 DRAM 内部处理：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">DRAM Cell 出错</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">On-Die ECC 修复</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">输出正确数据</span><br></pre></td></tr></table></figure><p>因此<strong>系统看到的错误变少了</strong>。</p><h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><h3 id="DRAM-工艺越来越复杂，cell-更容易出错"><a href="#DRAM-工艺越来越复杂，cell-更容易出错" class="headerlink" title="DRAM 工艺越来越复杂，cell 更容易出错"></a>DRAM 工艺越来越复杂，cell 更容易出错</h3><p>随着 DRAM 制程缩小：</p><ul><li>cell 更小</li><li>保存电荷更少</li><li>抗干扰能力下降</li></ul><p>打个比方</p><ul><li>以前</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">大水桶</span><br><span class="line"></span><br><span class="line">存水多</span><br></pre></td></tr></table></figure><ul><li>现在</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">小水杯</span><br><span class="line"></span><br><span class="line">容易洒水</span><br></pre></td></tr></table></figure><p>因此DRAM 内部错误增加。</p><h3 id="DRAM-芯片内部最清楚哪里出错"><a href="#DRAM-芯片内部最清楚哪里出错" class="headerlink" title="DRAM 芯片内部最清楚哪里出错"></a>DRAM 芯片内部最清楚哪里出错</h3><p>DRAM 芯片内部知道：</p><ul><li>哪个 cell 出问题</li><li>哪些 bit 相关</li></ul><p>因此在 DRAM 内部修复效率最高。</p><p>类似：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">汽车发动机坏</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">发动机内部维修</span><br><span class="line"></span><br><span class="line">比</span><br><span class="line"></span><br><span class="line">送到车外检测更快</span><br></pre></td></tr></table></figure><h3 id="减少系统级-ECC-压力"><a href="#减少系统级-ECC-压力" class="headerlink" title="减少系统级 ECC 压力"></a>减少系统级 ECC 压力</h3><p>如果没有 On-Die ECC，所有错误：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">DRAM</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">Memory Controller</span><br><span class="line"></span><br><span class="line">↓</span><br><span class="line"></span><br><span class="line">系统 ECC</span><br></pre></td></tr></table></figure><p>系统需要承担大量：</p><ul><li>小 bit error</li><li>随机错误</li></ul><hr><p>有 On-Die ECC：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">DRAM内部：</span><br><span class="line"></span><br><span class="line">处理大量小错误</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">系统ECC：</span><br><span class="line"></span><br><span class="line">处理更严重错误</span><br></pre></td></tr></table></figure><p>形成分层保护：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">    系统可靠性</span><br><span class="line"></span><br><span class="line">        ↑</span><br><span class="line"></span><br><span class="line">System ECC / Chipkill</span><br><span class="line"></span><br><span class="line">        ↑</span><br><span class="line"></span><br><span class="line">  On-Die ECC</span><br><span class="line"></span><br><span class="line">        ↑</span><br><span class="line"></span><br><span class="line">    DRAM Cell</span><br></pre></td></tr></table></figure><h2 id="On-Die-ECC-vs-系统-ECC"><a href="#On-Die-ECC-vs-系统-ECC" class="headerlink" title="On-Die ECC vs 系统 ECC"></a>On-Die ECC vs 系统 ECC</h2><table><thead><tr><th align="center"></th><th align="center">On-Die ECC</th><th align="center">系统 ECC</th></tr></thead><tbody><tr><td align="center">位置</td><td align="center">DRAM 芯片内部</td><td align="center">Memory Controller</td></tr><tr><td align="center">保护对象</td><td align="center">DRAM cell 内部错误</td><td align="center">整个内存系统错误</td></tr><tr><td align="center">主要作用</td><td align="center">修复小错误</td><td align="center">处理严重错误</td></tr><tr><td align="center">是否对系统可见</td><td align="center">通常不可见</td><td align="center">可见</td></tr><tr><td align="center">粒度</td><td align="center">芯片内部</td><td align="center">DIMM&#x2F;Rank级</td></tr></tbody></table><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>随着制程工艺不断缩小，每个 DRAM 单元（cell）中存储的电荷量减少，工艺波动（process variation）增加，同时晶体管也更容易受到老化（wear-out）的影响。为了缓解这些错误，现代 DRAM 集成了片上 ECC（On-Die ECC），在 DRAM 芯片内部对错误进行修复，并与外部系统 ECC（System ECC）协同工作。</p><p>每个 DRAM die（芯片颗粒）内部都包含隐藏的冗余存储单元（redundancy cells）以及一个紧凑的编码器&#x2F;解码器（encoder&#x2F;decoder）。该机制会在每个 bank group 内，对每次写入的数据进行编码，并在每次读取时进行解码（如图 1 中蓝色部分所示）。</p><p><img src="/images/2026/07/002.png"></p><p>通过在本地（DRAM 芯片内部）纠正错误，On-Die ECC可以有效地将原本存在轻微缺陷（marginal defects）的 DRAM die 转变为对外表现为无故障（externally fault-free）的组件，从而提高芯片制造良率，并保证组件级别的可靠性超过产品质保期限（warranty thresholds）。</p><hr><p>参考资料:</p><ol><li>XED: Exposing On-Die Error Detection Information for Strong Memory Reliability(ISCA’16)</li><li>DUO: Exposing On-chip Redundancy to Rank-Level ECC for High Reliability(HPCA’18)</li><li>Cerberus: Cross-Layer ECC Co-Design for Robust and Efficient Memory Protection(ISCA’26)</li><li>chatgpt</li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/07/25/Notes-about-DDR5-On-Die-ECC%E6%8A%80%E6%9C%AF/</id>
    <link href="http://liujunming.github.io/2026/07/25/Notes-about-DDR5-On-Die-ECC%E6%8A%80%E6%9C%AF/"/>
    <published>2026-07-25T01:48:24.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下DDR5 On-Die ECC技术的相关notes。]]>
    </summary>
    <title>Notes about DDR5 On-Die ECC技术</title>
    <updated>2026-07-25T06:34:43.299Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="计算机网络" scheme="http://liujunming.github.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <category term="虚拟化" scheme="http://liujunming.github.io/tags/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
    <category term="计算机网络" scheme="http://liujunming.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <content>
      <![CDATA[<p>本文将mark下macvtap的相关notes。<span id="more"></span></p><h2 id="tap-bridge"><a href="#tap-bridge" class="headerlink" title="tap + bridge"></a>tap + bridge</h2><p><img src="/images/2026/06/001.png"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">+-------------------+</span><br><span class="line">| QEMU VM           |</span><br><span class="line">|                   |</span><br><span class="line">|  virtio-net/e1000 |</span><br><span class="line">+---------+---------+</span><br><span class="line">          |</span><br><span class="line">          | TAP fd</span><br><span class="line">          v</span><br><span class="line">+-------------------+</span><br><span class="line">| tap0              |</span><br><span class="line">+---------+---------+</span><br><span class="line">          |</span><br><span class="line">          v</span><br><span class="line">+-------------------+</span><br><span class="line">| br0               |  Linux bridge</span><br><span class="line">|                   |</span><br><span class="line">| ports: tap0, eth0 |</span><br><span class="line">+---------+---------+</span><br><span class="line">          |</span><br><span class="line">          v</span><br><span class="line">+-------------------+</span><br><span class="line">| eth0 / ens33      |</span><br><span class="line">| physical NIC      |</span><br><span class="line">+---------+---------+</span><br><span class="line">          |</span><br><span class="line">          v</span><br><span class="line">+-------------------+</span><br><span class="line">| 外部二层网络       |</span><br><span class="line">| switch / LAN      |</span><br><span class="line">+-------------------+</span><br></pre></td></tr></table></figure><p>QEMU -&gt; tap0 -&gt; br0 -&gt; eth0 -&gt; LAN</p><h2 id="MACVLAN"><a href="#MACVLAN" class="headerlink" title="MACVLAN"></a>MACVLAN</h2><p><img src="/images/2026/06/002.png"></p><p>一般情况下，网卡只有一个MAC地址。然而，有些场景下需要给一个网卡设置多个MAC地址。Linux通过MACVLAN技术在一个物理网卡上创建多个MACVLAN虚拟设备，每个设备有着不同的MAC地址。当物理网卡收到数据包时，MACVLAN driver根据数据包MAC地址将数据包交由匹配的虚拟网卡处理。使用MACVLAN可以替代使用bridge来连接物理网卡和虚拟网络设备。</p><p>macvlan没有<code>/dev/tapX</code>。</p><h2 id="MACVTAP"><a href="#MACVTAP" class="headerlink" title="MACVTAP"></a>MACVTAP</h2><p><img src="/images/2026/06/003.png"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">+-------------------+</span><br><span class="line">| QEMU VM           |</span><br><span class="line">|                   |</span><br><span class="line">|  virtio-net/e1000 |</span><br><span class="line">+---------+---------+</span><br><span class="line">          |</span><br><span class="line">          | /dev/tapX fd</span><br><span class="line">          v</span><br><span class="line">+-------------------+</span><br><span class="line">| macvtap0          |</span><br><span class="line">|                   |</span><br><span class="line">| TAP 字符设备接口   |</span><br><span class="line">| + macvlan 二层逻辑 |</span><br><span class="line">+---------+---------+</span><br><span class="line">          |</span><br><span class="line">          | lowerdev</span><br><span class="line">          v</span><br><span class="line">+-------------------+</span><br><span class="line">| eth0 / ens33      |</span><br><span class="line">| physical NIC      |</span><br><span class="line">+---------+---------+</span><br><span class="line">          |</span><br><span class="line">          v</span><br><span class="line">+-------------------+</span><br><span class="line">| 外部二层网络       |</span><br><span class="line">| switch / LAN      |</span><br><span class="line">+-------------------+</span><br></pre></td></tr></table></figure><p>QEMU -&gt; macvtap0 -&gt; eth0 -&gt; LAN</p><p>虚拟化中一般使用TAP和bridge来组建虚拟网络，但这样组网结构会稍显复杂。Linux上的MACTAP设备可以简化这种结构。MACVTAP设备集成了MACVLAN和TAP设备二者的特性。它可以基于一个物理网卡创建多个MAC地址不同的虚拟网卡，同时虚拟网卡收到的包不再交给内核协议栈，而是通过TAP设备的文件描述符传递到用户态进程。</p><p>macvtap把两层能力合并了：<br>普通 TAP 提供给 QEMU 的 fd + macvlan 提供的二层接入能力 &#x3D; macvtap</p><p>所以它能替代 tap + bridge 的关键点是：</p><ul><li>QEMU 仍然拿到一个像 TAP 一样的 fd</li><li>VM 仍然拥有独立 MAC</li><li>数据包仍然能从物理网卡进入外部二层网络</li><li>但中间不再需要显式 br0</li></ul><hr><p>参考资料:</p><ol><li><a href="https://developers.redhat.com/articles/2026/04/03/introduction-to-linux-interfaces-for-virtual-networking#">Introduction to Linux interfaces for virtual networking</a></li><li><a href="https://just4coding.com/2016/12/04/virtualnetworkdevice/">Linux虚拟网络设备</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/06/20/Notes-about-macvtap/</id>
    <link href="http://liujunming.github.io/2026/06/20/Notes-about-macvtap/"/>
    <published>2026-06-20T14:49:08.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下macvtap的相关notes。]]>
    </summary>
    <title>Notes about macvtap</title>
    <updated>2026-06-20T15:19:32.451Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="文件系统" scheme="http://liujunming.github.io/categories/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/"/>
    <category term="文件系统" scheme="http://liujunming.github.io/tags/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/"/>
    <content>
      <![CDATA[<p>本文将mark下Fuse over io-uring的相关notes。<span id="more"></span></p><h2 id="Prerequisite"><a href="#Prerequisite" class="headerlink" title="Prerequisite"></a>Prerequisite</h2><ul><li><a href="/2022/11/12/Notes-about-io-uring/">io-uring</a></li><li><a href="/2024/12/21/Notes-about-FUSE-filesystem/">fuse</a></li></ul><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>fast’24 paper:<a href="https://app.yinxiang.com/fx/40d78b24-4552-4582-b2c8-9a1354af89c8">RFUSE: Modernizing Userspace Filesystem Framework through Scalable Kernel-Userspace Communication</a></p><p>FUSE性能瓶颈：</p><ul><li>FUSE在内核和用户空间以及内存复制开销之间进行了多次上下文切换</li><li>FUSE驱动程序在向用户空间FUSE守护进程调度文件系统请求时，使用单个队列阻碍了FUSE实现可伸缩性能</li></ul><p><img src="/images/2026/05/043.png"></p><h2 id="Motivation"><a href="#Motivation" class="headerlink" title="Motivation"></a>Motivation</h2><p><a href="https://lwn.net/Articles/976031/">fuse: fuse-over-io-uring</a>:</p><blockquote><p>Motivation for these patches is all to increase fuse performance. In fuse-over-io-uring requests avoid core switching (application on core X, processing of fuse server on random core Y) and use shared memory between kernel and userspace to transfer data.</p></blockquote><h2 id="Fuse-over-io-uring-vs-RFUSE"><a href="#Fuse-over-io-uring-vs-RFUSE" class="headerlink" title="Fuse over io-uring vs RFUSE"></a>Fuse over io-uring vs RFUSE</h2><p>Fuse over io-uring与论文RFUSE的<strong>大方向高度一致，但实现路线不同、细节有差异</strong>；可以把 FUSE-over-io_uring 看作 RFUSE 思想在 Linux 内核里的<strong>工程化落地（io_uring 版）</strong>。</p><h3 id="核心思想：高度一致"><a href="#核心思想：高度一致" class="headerlink" title="核心思想：高度一致"></a>核心思想：高度一致</h3><p>两者都针对传统 FUSE 的同一痛点：</p><ol><li><strong>单队列 + 锁竞争</strong>，多核下扩展性差</li><li><strong>频繁上下文切换 + 内存拷贝</strong>，延迟高、CPU 开销大</li></ol><p>共同核心思路：</p><ol><li>per-core 独立队列：每个核一个通道，无锁、并行处理</li><li>共享内存 &#x2F; 环形缓冲区：内核↔用户态通过 ring 传递请求 &#x2F; 响应，减少系统调用与拷贝</li><li>兼容现有 FUSE：用户态文件系统不用改代码即可提速</li></ol><h3 id="关键差异：实现方案不同"><a href="#关键差异：实现方案不同" class="headerlink" title="关键差异：实现方案不同"></a>关键差异：实现方案不同</h3><ol><li>RFUSE（FAST’24 论文）</li></ol><ul><li>自研 类 io_uring 的 ring，不依赖内核 io_uring 子系统</li><li>hybrid polling：短时间忙轮询 + 休眠，平衡延迟与 CPU</li><li>原型，未并入主线内核</li></ul><ol start="2"><li>FUSE-over-io_uring（Linux 6.14 主线）</li></ol><ul><li>直接复用内核 io_uring 子系统，通过 IORING_OP_URING_CMD 扩展 FUSE</li><li>per-core io_uring 队列，亲和性调度，减少核间迁移</li><li>用 io_uring 原生机制处理提交 &#x2F; 完成 &#x2F; 内存管理，不做自研 ring</li><li>已合入主线，生产可用</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>思想同源、实现分叉：</p><ul><li>RFUSE：学术验证，提出 “per-core ring + 共享内存 + 兼容 FUSE” 的高性能通信范式</li><li>FUSE-over-io_uring：工程实现，用标准io_uring把同一范式落地到 Linux 内核</li></ul><h2 id="Fuse-over-io-uring-per-core-io-uring"><a href="#Fuse-over-io-uring-per-core-io-uring" class="headerlink" title="Fuse over io-uring per-core io_uring"></a>Fuse over io-uring per-core io_uring</h2><p>内核文档:<br>在<a href="https://docs.kernel.org/next/filesystems/fuse-io-uring.html">FUSE-over-io-uring design documentation</a>可以看到:</p><blockquote><p>Note, every CPU core has its own fuse-io-uring queue.</p></blockquote><p><img src="/images/2026/05/044.png"></p><p>内核代码:<br><a href="https://elixir.bootlin.com/linux/v6.14/source/fs/fuse/dev_uring.c#L182-L194">https://elixir.bootlin.com/linux/v6.14/source/fs/fuse/dev_uring.c#L182-L194</a></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="keyword">struct</span> fuse_ring *<span class="title function_">fuse_uring_create</span><span class="params">(<span class="keyword">struct</span> fuse_conn *fc)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">fuse_ring</span> *<span class="title">ring</span>;</span></span><br><span class="line"><span class="type">size_t</span> nr_queues = num_possible_cpus();</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">fuse_ring</span> *<span class="title">res</span> =</span> <span class="literal">NULL</span>;</span><br><span class="line"><span class="type">size_t</span> max_payload_size;</span><br><span class="line"></span><br><span class="line">ring = kzalloc(<span class="keyword">sizeof</span>(*fc-&gt;ring), GFP_KERNEL_ACCOUNT);</span><br><span class="line"><span class="keyword">if</span> (!ring)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">ring-&gt;queues = kcalloc(nr_queues, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> fuse_ring_queue *),</span><br><span class="line">       GFP_KERNEL_ACCOUNT);</span><br></pre></td></tr></table></figure><p>每个CPU对应一个独立的 <code>struct fuse_ring_queue</code>。</p><p>请求分发:<br><a href="https://lwn.net/Articles/976031/">fuse: fuse-over-io-uring</a></p><blockquote><p>With fuse-over-io-uring requests are handled on the same core (sync requests)</p></blockquote><p>同步请求始终在发起的同一个 CPU 上处理，cache 友好：数据留在本地 core，减少 bouncing。</p><hr><p>参考资料:</p><ol><li><a href="https://docs.kernel.org/next/filesystems/fuse-io-uring.html">FUSE-over-io-uring design documentation</a></li><li><a href="https://lwn.net/Articles/976031/">fuse: fuse-over-io-uring</a></li><li><a href="https://www.phoronix.com/news/Linux-6.14-FUSE">FUSE Hooks Up With IO_uring For Greater Performance Potential In Linux 6.14</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/05/31/Notes-about-Fuse-over-io-uring/</id>
    <link href="http://liujunming.github.io/2026/05/31/Notes-about-Fuse-over-io-uring/"/>
    <published>2026-05-31T09:06:09.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下Fuse over io-uring的相关notes。]]>
    </summary>
    <title>Notes about Fuse over io-uring</title>
    <updated>2026-05-31T14:21:58.386Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="PCI&amp;PCIe" scheme="http://liujunming.github.io/categories/PCI-PCIe/"/>
    <category term="PCI&amp;PCIe" scheme="http://liujunming.github.io/tags/PCI-PCIe/"/>
    <content>
      <![CDATA[<p>本文将mark下PCIe Error Message机制的相关notes，内容主要转载自<a href="https://mp.weixin.qq.com/s/NZT6CtKFyCRkTZ36r51fvA">Error message控制</a>。<span id="more"></span></p><h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>error signaling: One agent notifying another agent of an error either by (1) sending an error Message, (2) sending a Completion with UR&#x2F;CA Status, or (3) poisoning a TLP.</p><p>PCIe Error Signaling Messages是一种带内(In-band)错误上报机制，通过专用的Message TLP(TLP Type is Msg)将错误信息从检测点传递给Root Complex，进而通知系统软件。</p><p>Error Signaling Messages are used to signal errors that occur on specific transactions and errors that are not necessarily associated with a particular transaction. These Messages are initiated by the agent that detected the error.</p><p>Message类型的TLP，使用隐式路由(Routed to Root Complex)，即无论从拓扑中哪个设备发出，都会沿上行方向逐跳传递，最终到达Root Complex。</p><h2 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h2><p><img src="/images/2026/05/029.png"></p><h2 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h2><p>The initiator of the Message is identified with the Requester ID of the Message header. The Root Complex translates these error Messages into platform level events.</p><p><img src="/images/2026/05/030.png"></p><p>如果Root Port支持AER(Advanced Error Report)，RC从message中提取Requester ID字段，并记录的Root port的Error Source Identification寄存器中，这个BDF号就是出错的设备。不过很多时候这个BDF是捕获不到的，或者出现多个错误，RC只能提取第一个错误的Requester ID。因此，需要driver遍历Root Port下的所有设备，检查哪些设备出现了错误。</p><p><img src="/images/2026/05/031.png"></p><p>如果Root Port支持AER，则收到error message(根据error code可以分为ERR_COR&#x2F;ERR_NONFATAL&#x2F;ERR_FATAL)或者Root port自己检查到对应错误时，会记录到Root Port的Root Error Status寄存器。</p><p><img src="/images/2026/05/032.png"></p><p>Error message从PCIe设备产生到路由到对应Root Por涉及一系列的控制寄存器和状态寄存器。</p><p><img src="/images/2026/05/033.png"></p><h2 id="PCIe-Error-Message产生MSI中断"><a href="#PCIe-Error-Message产生MSI中断" class="headerlink" title="PCIe Error Message产生MSI中断"></a>PCIe Error Message产生MSI中断</h2><p>Root Port下面的设备或Root Port本身发生PCIe错误想产生MSI中断话，需要关注哪些寄存器呢？</p><h3 id="整条链路上所有bridge的Bridge-ctrl-reg的SERR-bit"><a href="#整条链路上所有bridge的Bridge-ctrl-reg的SERR-bit" class="headerlink" title="整条链路上所有bridge的Bridge ctrl reg的SERR# bit"></a>整条链路上所有bridge的Bridge ctrl reg的SERR# bit</h3><p>如果该bit没有使能，则bridge不会往上游转发下游的设备上报的error message。如果是Root port本身产生的错误的则不需要关注该bit。</p><p><img src="/images/2026/05/034.png"></p><h3 id="整条链路上支持DPC的设备（DP和RP）是否使能了DPC"><a href="#整条链路上支持DPC的设备（DP和RP）是否使能了DPC" class="headerlink" title="整条链路上支持DPC的设备（DP和RP）是否使能了DPC"></a>整条链路上支持DPC的设备（DP和RP）是否使能了DPC</h3><p>如果使能了DPC trigger enable，下游设备发送的ERR_FATAL或ERR_NONFATAL的error message会被DPC拦截。</p><p><img src="/images/2026/05/035.png"></p><h3 id="整条链路上的所有PCIe设备的Device-ctrl-reg的对应bit是否使能"><a href="#整条链路上的所有PCIe设备的Device-ctrl-reg的对应bit是否使能" class="headerlink" title="整条链路上的所有PCIe设备的Device ctrl reg的对应bit是否使能"></a>整条链路上的所有PCIe设备的Device ctrl reg的对应bit是否使能</h3><p>Device ctrl reg的低4bit是一个比较大的控制开关，分布控制ERR_COR message、ERR_NONFATAL message、ERR_FATAL message和Unsupported Request的发送。</p><p><img src="/images/2026/05/036.png"></p><h3 id="Root-port的Root-error-command-reg对应bit是否为1"><a href="#Root-port的Root-error-command-reg对应bit是否为1" class="headerlink" title="Root port的Root error command reg对应bit是否为1"></a>Root port的Root error command reg对应bit是否为1</h3><p>如果Root error command reg对应bit为1，则Root port本身或者Root port收到下游设备上报错误时才会产生中断。</p><p><img src="/images/2026/05/037.png"></p><h3 id="Root-Port的Root-ctrl-reg对应bit是否清零"><a href="#Root-Port的Root-ctrl-reg对应bit是否清零" class="headerlink" title="Root Port的Root ctrl reg对应bit是否清零"></a>Root Port的Root ctrl reg对应bit是否清零</h3><p>如果Root ctrl reg对应bit没有清零，则会走左边的分支产生system error，对X86而言这是一条带外的pin。</p><p><img src="/images/2026/05/038.png"></p><h3 id="整个链路上的PCIe设备error-mask-reg是否置1"><a href="#整个链路上的PCIe设备error-mask-reg是否置1" class="headerlink" title="整个链路上的PCIe设备error mask reg是否置1"></a>整个链路上的PCIe设备error mask reg是否置1</h3><p>如果error mask reg为1，则产生对应错误时，只会更新PCIe CAP的error status寄存器，不会产生error message。</p><p><img src="/images/2026/05/039.png"></p><p><img src="/images/2026/05/040.png"></p><h3 id="Command寄存器的memory-space-enable-bit和bus-master-enable-bit是否为1，interrupt-disable是否为1"><a href="#Command寄存器的memory-space-enable-bit和bus-master-enable-bit是否为1，interrupt-disable是否为1" class="headerlink" title="Command寄存器的memory space enable bit和bus master enable bit是否为1，interrupt disable是否为1"></a>Command寄存器的memory space enable bit和bus master enable bit是否为1，interrupt disable是否为1</h3><p>MSI中断本质上是一个memory write请求，Command寄存器的memory space enable bit和bus master enable bit负责MSI中断的产生和转发。</p><p>Interrupt disable是禁止INTx中断。由于，INTx优先级比MSI中断要高，该bit需要禁止。</p><p><img src="/images/2026/05/041.png"></p><h3 id="Root-Port的MSI-enable是否为1"><a href="#Root-Port的MSI-enable是否为1" class="headerlink" title="Root Port的MSI enable是否为1"></a>Root Port的MSI enable是否为1</h3><p>Error message会路由到Root Port，如果想产生MSI中断，root port的MSI enable bit需要为1。</p><p><img src="/images/2026/05/042.png"></p><hr><p>参考资料:</p><ol><li><a href="https://mp.weixin.qq.com/s/NZT6CtKFyCRkTZ36r51fvA">Error message控制</a></li><li>PCI Express® Base Specification Revision 5.0 Version 1.0</li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/05/31/Notes-about-PCIe-Error-Message%E6%9C%BA%E5%88%B6/</id>
    <link href="http://liujunming.github.io/2026/05/31/Notes-about-PCIe-Error-Message%E6%9C%BA%E5%88%B6/"/>
    <published>2026-05-30T22:56:26.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下PCIe Error Message机制的相关notes，内容主要转载自<a href="https://mp.weixin.qq.com/s/NZT6CtKFyCRkTZ36r51fvA">Error message控制</a>。]]>
    </summary>
    <title>Notes about PCIe Error Message机制</title>
    <updated>2026-05-31T08:38:22.618Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="PCI&amp;PCIe" scheme="http://liujunming.github.io/categories/PCI-PCIe/"/>
    <category term="PCI&amp;PCIe" scheme="http://liujunming.github.io/tags/PCI-PCIe/"/>
    <content>
      <![CDATA[<p>本文将mark下PCIe中上行端口(upstream port)和多个下行端口(downstream port)的相关notes<span id="more"></span>，内容主要转载自<a href="https://mp.weixin.qq.com/s/gEPPooSG9ATud50Ch2Ldcw">Upstream 和 Dowstream</a>。</p><p><strong>上行(Upstream)和下行(Downstream)是一个方向的概念</strong>。</p><p>在一个PCIe系统中，方向的定义是以RC为准的:RC高高在上，面往RC方向称之为上行（红色粗箭头），反之称之为下行（蓝色粗箭头）。</p><p><img src="/images/2026/05/027.png"></p><p>由于PCIe是点对点连接的，每个连接的地方，我们称之为Port。对于Root Complex而言，它仅有一个下行端口。对于PCIe switch，它有一个上行端口（upstream port）和多个下行端口（downstream ports）。而PCIe设备（EP）仅有一个上行端口。</p><p>一般而言，上行端口是和一个下行端口连接在一起。<strong>注意，是一般而言，某些特殊的情况下，两个下行端口也是可以连接在一起的，我们称之为crosslink</strong>。</p><p>复习一下switch内部的结构图，可以加深我们对上下行的认识。</p><p><img src="/images/2026/05/028.png"></p><p>上行和下行是PCIe中非常重要的方向，初期很容易记混淆。有必要再默记三遍：<strong>以RC为中心，朝着它去的方向就是上行，远离它的方向就是下行</strong>。</p>]]>
    </content>
    <id>http://liujunming.github.io/2026/05/30/Notes-about-PCIe-upstream-port-and-downstream-port/</id>
    <link href="http://liujunming.github.io/2026/05/30/Notes-about-PCIe-upstream-port-and-downstream-port/"/>
    <published>2026-05-30T07:49:37.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下PCIe中上行端口(upstream port)和多个下行端口(downstream port)的相关notes]]>
    </summary>
    <title>Notes about PCIe upstream port and downstream port</title>
    <updated>2026-05-30T08:20:16.929Z</updated>
  </entry>
  <entry>
    <author>
      <name>liujunming</name>
    </author>
    <category term="AI Infra" scheme="http://liujunming.github.io/categories/AI-Infra/"/>
    <category term="AI Infra" scheme="http://liujunming.github.io/tags/AI-Infra/"/>
    <content>
      <![CDATA[<p>本文将mark下TP(Tensor Parallelism)、PP(Pipeline Parallelism)和DP(Data Parallelism)的相关notes，本文内容转载自<a href="https://mp.weixin.qq.com/s/XPHJTQVWSLLVBqiHbwdMkw">不是多卡，而是多种“切法”：一文讲透 TP、PP、DP</a>。<span id="more"></span></p><h2 id="标定前提"><a href="#标定前提" class="headerlink" title="标定前提"></a>标定前提</h2><p>本文统一用这个场景来讲：</p><ul><li>模型：70B</li><li>精度：FP16</li><li>模型参数体量：约 140GB</li><li>服务器：单机 8 张 GPU</li><li>每张 GPU 显存：80GB</li></ul><p>140GB模型参数体量意味着：如果你把模型参数按 FP16 存，总共大约要占<strong>140GB 显存</strong>。而单张卡只有 80GB。</p><p>所以第一件马上成立的事就是：<strong>单卡装不下</strong>。</p><p>这一步非常关键，因为它决定了一个基本事实：</p><blockquote><p>这不是“要不要多卡”的问题。这是“必须多卡”的问题。</p></blockquote><p>但“必须多卡”，并不自动等于你知道该怎么切。</p><p>因为你接下来马上会碰到第二个问题：既然 8 张卡总显存有 640GB，那是不是直接随便分一分就行？也不行。因为大模型不是一块静态硬盘文件。</p><p>它在跑的时候，除了参数本身，还有：</p><ul><li>激活值</li><li>中间结果</li><li>KV Cache（推理时）</li><li>梯度（训练时）</li><li>优化器状态（训练时更大）</li><li>通信缓存</li><li>框架额外开销</li></ul><p>所以真正的问题，不是“640GB 总显存是不是大于 140GB”。而是：<strong>你打算怎么把模型和计算过程，拆到这 8 张卡上</strong>。</p><p>这就引出了今天的主角：</p><ul><li>TP：Tensor Parallelism</li><li>PP：Pipeline Parallelism</li><li>DP：Data Parallelism</li></ul><p>它们都和“多卡”有关。但不是一回事。</p><p><img src="/images/2026/05/018.png"></p><h2 id="三种“切法”"><a href="#三种“切法”" class="headerlink" title="三种“切法”"></a>三种“切法”</h2><blockquote><p>TP 是横着切。<br>PP 是竖着切。<br>DP 不是切模型，而是复制模型、切数据。</p></blockquote><h3 id="TP-在切什么？"><a href="#TP-在切什么？" class="headerlink" title="TP 在切什么？"></a>TP 在切什么？</h3><p>它在切的是：<strong>同一层内部的张量计算</strong>。</p><p>也就是说，一层本来是一个大矩阵乘法。TP 会把这个大矩阵，拆到多张卡一起算。</p><h3 id="PP-在切什么？"><a href="#PP-在切什么？" class="headerlink" title="PP 在切什么？"></a>PP 在切什么？</h3><p>它在切的是：<strong>模型的层</strong>。</p><p>也就是说，模型原本是一层接一层串起来的。PP 会把前面几层放到一组卡，后面几层放到另一组卡。</p><h3 id="DP-在切什么？"><a href="#DP-在切什么？" class="headerlink" title="DP 在切什么？"></a>DP 在切什么？</h3><p>它不切模型结构。它切的是：<strong>输入数据 &#x2F; batch。</strong></p><p>也就是说，每一份模型副本都长得一样。但不同副本同时处理不同的数据，然后再同步梯度。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>TP 解决“单层太大”。<br>PP 解决“整模型太长、装不下”。<br>DP 解决“吞吐不够，想并行处理更多数据”。</p><p><img src="/images/2026/05/019.png"></p><h2 id="PP"><a href="#PP" class="headerlink" title="PP"></a>PP</h2><p>PP最先回答一个现实问题：<strong>140GB 模型，单卡 80GB 装不下，怎么办？</strong></p><p>最直观的办法是什么？把模型按层切开。</p><p>比如，一个大模型本质上可以粗略理解成很多层堆起来：</p><ul><li>Embedding</li><li>Transformer Block 1</li><li>Transformer Block 2</li><li>…</li><li>Transformer Block N</li><li>最后输出层</li></ul><p>这时候，PP 的思路就是：</p><blockquote><p>既然一整本书放不进一个抽屉，<br>那就把这本书按章节拆开，前半本放一个抽屉，后半本放另一个抽屉。</p></blockquote><p>在工程上，这就叫<strong>Pipeline Parallelism</strong>。</p><h3 id="PP-2是最自然的第一步"><a href="#PP-2是最自然的第一步" class="headerlink" title="PP&#x3D;2是最自然的第一步"></a>PP&#x3D;2是最自然的第一步</h3><p>70B 模型 140GB。<br>如果按 PP&#x3D;2 去切，意味着：</p><ul><li>模型前半段放在一组 GPU</li><li>模型后半段放在另一组 GPU</li></ul><p>从参数体量角度粗略看，就是：140GB&#x2F;2&#x3D;70GB</p><p>这就意味着：<strong>每一段模型参数大约 70GB</strong>，而 70GB 小于单卡 80GB。</p><p>于是，一个非常重要的事实出现了：<strong>从“参数能否装下”这个角度看，PP&#x3D;2 已经足够</strong>。</p><p><strong>PP首先是在解决“模型按层分段存放”</strong>，这是它最核心的意义之一。</p><h3 id="PP-2不代表只用2张卡"><a href="#PP-2不代表只用2张卡" class="headerlink" title="PP&#x3D;2不代表只用2张卡"></a>PP&#x3D;2不代表只用2张卡</h3><p>很多人一看到 PP&#x3D;2，就会下意识理解成：<strong>那是不是只用 2 张卡？</strong></p><p>不一定。PP&#x3D;2 的意思，是<strong>模型被分成2段</strong>。至于每一段用几张卡，那要看你这一段内部是否还继续做TP，或者做别的切法。</p><p>也就是说：</p><ul><li>PP 决定的是“分几段”</li><li>不是“总共几张卡”</li></ul><p><strong>这一点必须记住</strong>。</p><p>比如后面我们很可能会用：</p><ul><li>PP&#x3D;2</li><li>每段再用 TP&#x3D;2</li><li>那总共就是 2 × 2 &#x3D; 4 张卡</li></ul><p>甚至还可能在更多机器上叠加 DP。</p><p>所以 PP 和卡数之间，不是简单一一对应关系，它更像“先把模型分成几大段”</p><h3 id="PP的代价"><a href="#PP的代价" class="headerlink" title="PP的代价"></a>PP的代价</h3><p>PP 解决了装不下的问题，但它不是免费的。</p><p>因为一旦按层分段，数据在前向传播时就必须这样走：</p><ul><li>输入先进入第 1 段模型</li><li>第 1 段算完，把中间激活传给第 2 段</li><li>第 2 段再继续算</li></ul><p>反向传播也是一样，要一段一段回传。</p><p>所以 PP 带来的代价是：<strong>跨 stage 的激活传输</strong>，以及更重要的：<strong>流水线气泡（pipeline bubble）</strong>。</p><p>因为模型是串行分段的，如果流水线调度不好，就会出现：</p><ul><li>前一段忙</li><li>后一段在等</li><li>某些 GPU 一部分时间没有活干</li></ul><p>这就是为什么PP虽然很适合解决“装不下”，但它并不是性能上永远最优的切法。</p><p>它优先解决的是：<strong>模型太长、显存不够的问题</strong>。</p><p><img src="/images/2026/05/020.png"></p><h2 id="TP"><a href="#TP" class="headerlink" title="TP"></a>TP</h2><p>如果说 PP 像是“把一本书按章节撕成上下两半”，那 TP 更像是：<strong>把同一页内容，左右拆开，让两个人同时看</strong>。</p><p>大模型里的核心计算，本质上大量来自矩阵乘法。<br>比如一个线性层，本来可能是这样：<code>Y = XW</code><br>这里的 W 可能非常大。<br>如果这个矩阵很大，大到一张卡算它已经很吃力，或者你想进一步并行加速，那就可以用 TP。</p><p>TP 的思路是：</p><blockquote><p>不是把“前面几层”和“后面几层”分开。<br>而是让“同一层”本身，拆成几块，放到多张卡一起算。</p></blockquote><h3 id="在层内切矩阵"><a href="#在层内切矩阵" class="headerlink" title="在层内切矩阵"></a>在层内切矩阵</h3><p>比如一个很大的权重矩阵 W，你可以按列切成两半，或者按行切成两半。<br>这样：</p><ul><li>GPU0 算一部分</li><li>GPU1 算另一部分</li><li>最后再把结果拼起来，或者做归约</li></ul><p>这就是 TP 的核心。</p><p>所以 TP 不是“层和层之间分工”。而是：<strong>同一层内部，多张卡协同完成</strong>。</p><h3 id="为什么要做-TP？"><a href="#为什么要做-TP？" class="headerlink" title="为什么要做 TP？"></a>为什么要做 TP？</h3><p>主要有两个原因：<br><strong>原因一：单层太大</strong><br>有些单层参数和中间计算规模本来就很大。即使整模型通过 PP 已经分段了，某一段内部的某些层仍然很重。</p><p><strong>原因二：想提升单层并行度</strong><br>即便显存够，你也可能想让同一层在多张卡上并行算，加快速度。所以 TP 解决的不是“整本书放不下”。而是：<strong>同一页太宽，一个人看不过来</strong>。</p><h3 id="代价"><a href="#代价" class="headerlink" title="代价"></a>代价</h3><p>TP 最核心的代价是：<strong>层内通信</strong>。</p><p>为什么？因为同一层被拆到了多张卡上。那每次前向和反向过程中，多张卡之间都要交换部分结果。</p><p>比如常见的：</p><ul><li>AllReduce</li><li>AllGather</li><li>ReduceScatter</li></ul><p>你一旦做了 TP，这些 collective 通信几乎就绕不过去。也就是说，TP 带来的好处是：</p><ul><li>同一层可以多卡一起算</li><li>单卡显存压力更小</li><li>单层算力更容易并行起来</li></ul><p>但它的代价就是：<strong>通信更频繁</strong>。</p><p>而这件事一旦放到多机环境，就会和 NCCL、AllReduce、Incast、ECN 这些网络问题直接撞上。<br>这也是为什么很多人后来会发现：</p><blockquote><p>TP 不是“白赚性能”。<br>它经常是在“更多并行”和“更多通信”之间做权衡。</p></blockquote><p><img src="/images/2026/05/021.png"></p><h2 id="DP"><a href="#DP" class="headerlink" title="DP"></a>DP</h2><p>如果 PP 解决“装不下”，TP 解决“同一层太大或想并行更快”，那 DP 解决的是什么？答案是：<strong>吞吐</strong>。</p><p>也就是：</p><blockquote><p>我希望同一时间处理更多样本，让更多 GPU 一起吃不同数据。</p></blockquote><p>这就是 Data Parallelism。</p><h3 id="DP的本质：每份模型副本都长一样"><a href="#DP的本质：每份模型副本都长一样" class="headerlink" title="DP的本质：每份模型副本都长一样"></a>DP的本质：每份模型副本都长一样</h3><p>这是理解 DP 的第一关键点。</p><p>DP 不会把模型切开，它会做的，是把<strong>同一份模型复制多份</strong>。</p><p>比如你有两个 DP 副本，那就是：</p><ul><li>副本 A：一整份模型</li><li>副本 B：一整份模型</li></ul><p>这两份模型参数初始相同，但它们分别处理不同的数据 batch。</p><h3 id="为什么最后还要同步？"><a href="#为什么最后还要同步？" class="headerlink" title="为什么最后还要同步？"></a>为什么最后还要同步？</h3><p>因为虽然两份模型处理的是不同数据，但你希望训练完之后，它们还是同一个模型。<br>所以每一轮反向传播结束后，不同副本的梯度要做同步。<br>这一步最经典的就是：<strong>AllReduce</strong>。</p><p>也正因为如此，DP 一旦规模上去，网络压力会迅速增大。因为它不是偶尔同步，而是每一步都在同步。</p><h3 id="在我们的这个场景里，单机8卡适合直接做DP吗？"><a href="#在我们的这个场景里，单机8卡适合直接做DP吗？" class="headerlink" title="在我们的这个场景里，单机8卡适合直接做DP吗？"></a>在我们的这个场景里，单机8卡适合直接做DP吗？</h3><p>如果你只从“模型装不装得下”看，答案是：<strong>不能先直接谈 DP</strong>。</p><p>为什么？因为 DP 的前提是：<strong>每个副本本身得先成立</strong>。</p><p>也就是说，你得先能把“一份模型副本”放到某些卡上，然后才谈复制成多份。</p><p>而我们这里，70B 模型 140GB，单卡 80GB，单卡本身放不下。</p><p>所以如果没有 PP 或 TP 先把单副本做成立，你根本没法谈 DP，这一步非常关键。</p><p>很多人第一次接触时，会本能问：</p><blockquote><p>8 张卡，DP&#x3D;8 行不行？</p></blockquote><p>不行。因为 DP&#x3D;8 的含义是：你要有 8 份完整模型副本。</p><p>而完整模型 140GB，单卡 80GB 放不下，所以你不能把一份完整模型直接塞到一张卡上，再复制 8 份。</p><p>这也是为什么我们说：</p><blockquote><p>DP 不是第一步。<br>它通常建立在 TP&#x2F;PP 已经先让“单副本可运行”之上。</p></blockquote><p><img src="/images/2026/05/022.png"></p><h2 id="具体例子"><a href="#具体例子" class="headerlink" title="具体例子"></a>具体例子</h2><p>直接回到这个问题：</p><blockquote><p>70B 模型，FP16，140GB。<br>单机 8 张 GPU，每张 80GB。<br>TP、PP、DP 到底该怎么理解？</p></blockquote><p>最关键的是先建立一个顺序。</p><p>第一步：<strong>先解决“单副本能不能成立”</strong><br>这个问题最先决定的是：<strong>不能先想 DP</strong>。<br>因为 DP 是复制副本。但一份副本本身都放不下，复制无从谈起，所以要先考虑 TP 和 PP。</p><p>第二步：最直观的第一刀，通常是 PP&#x3D;2<br>因为 140GB ÷ 2 &#x3D; 70GB。</p><p>从参数体量上看，每段 70GB，可以落进 80GB 卡，所以从“装下参数”这件事出发，PP&#x3D;2 是一个非常自然的第一步。</p><p>但这里你要立刻意识到：这只是“参数大致装下”，并不等于实际运行一定完美，因为还有激活、缓存、框架开销等。</p><p>第三步：再考虑每一段内部要不要 TP<br>如果某个 stage 内部的单层计算仍然很重，或者你希望更高的并行度，那就可以在每个 stage 内部再做 TP。</p><p>比如一个很常见的思路：</p><ul><li>PP&#x3D;2</li><li>每个 stage 再 TP&#x3D;2</li></ul><p>那总共就用到：<code>2 × 2 = 4 张 GPU</code>，这时，每个模型副本占 4 张卡。</p><p>这一步很重要，因为它让你第一次看到：<strong>PP 和 TP 是可以叠加的</strong>。不是二选一，而是先按层分段，再在段内继续拆张量。</p><p>第四步：剩余 GPU 才有可能拿来做 DP<br>如果你总共有 8 张卡，而一份模型副本用了 4 张卡（PP&#x3D;2 × TP&#x3D;2），那你剩下的还能干什么？</p><p>答案是：可以再复制一份副本。</p><p>于是就得到：</p><ul><li>PP&#x3D;2</li><li>TP&#x3D;2</li><li>DP&#x3D;2</li></ul><p>因为：<code>PP × TP × DP = 2 × 2 × 2 = 8</code><br>刚好用满 8 张卡。</p><p>这就是为什么很多人第一次听到这种组合时，会突然“通了”，它不是在背一个缩写组合，而是在做一件非常具体的资源分解：</p><ol><li>先按 PP&#x3D;2 解决模型分段装载</li><li>再按 TP&#x3D;2 解决段内并行与层内切分</li><li>最后按 DP&#x3D;2 利用剩余资源复制两份副本，提升吞吐</li></ol><p>这就是一个完整的思考链。<br><img src="/images/2026/05/023.png"></p><h2 id="DP的计算"><a href="#DP的计算" class="headerlink" title="DP的计算"></a>DP的计算</h2><p>比如你可能会听到这样的问题：</p><blockquote><p>70B 模型，8 张卡，PP&#x3D;2，TP&#x3D;2，那 DP 到底是不是 2？</p></blockquote><p>答案是：<strong>如果你总共只有 8 张卡，而且一份副本占 4 张卡，那 DP 就是 2</strong>。</p><p>因为 DP 的定义不是“我想设几就设几”，而是由总卡数和单副本占卡数一起决定的。</p><p>公式其实非常简单：<code>DP = 总GPU数 / (TP × PP)</code></p><p>在这个例子里：<code>DP = 8 / (2 × 2) = 2</code></p><p>所以很多人真正没想明白的，不是数学，而是逻辑顺序：</p><ul><li>TP × PP<br>先定义了“一份模型副本需要多少卡”</li><li>DP<br>再定义“在总卡数里，可以复制多少份副本”</li></ul><p>也就是说：DP 不是凭空来的，DP 是在 TP&#x2F;PP 先把单副本做成立之后，剩余资源所能容纳的副本数。</p><p><img src="/images/2026/05/024.png"></p><h2 id="代价-1"><a href="#代价-1" class="headerlink" title="代价"></a>代价</h2><p>真正理解这三个东西，不能只看它们“怎么切”,还要看：<strong>它们分别引入了什么代价</strong>。</p><p>因为工程上最难的，从来不是名词,而是代价。</p><h3 id="PP-的代价：段间依赖-流水线气泡"><a href="#PP-的代价：段间依赖-流水线气泡" class="headerlink" title="PP 的代价：段间依赖 + 流水线气泡"></a>PP 的代价：段间依赖 + 流水线气泡</h3><p>PP 把模型按层切成几段,它最大的优点是：</p><ul><li>最直观地解决“装不下”</li><li>每段模型相对独立</li></ul><p>但它的问题是：</p><ul><li>前后段天然串行依赖</li><li>要传激活</li><li>流水线不饱满时容易空等</li></ul><p>所以 PP 更像是在说：<strong>我接受一定的串行和调度复杂度，换模型能装下</strong>。</p><h3 id="TP-的代价：层内高频通信"><a href="#TP-的代价：层内高频通信" class="headerlink" title="TP 的代价：层内高频通信"></a>TP 的代价：层内高频通信</h3><p>TP 的优点是：</p><ul><li>同一层可以多卡一起算</li><li>单层规模可拆</li><li>层内并行度提高</li></ul><p>但它的问题是：</p><ul><li>每层都要频繁 collective 通信</li><li>通信会非常密</li><li>一旦跨机，网络压力会快速放大</li></ul><p>所以 TP 更像是在说：<strong>我接受更多层内通信，换单层并行和显存拆分</strong>。</p><h3 id="DP-的代价：每一步都要梯度同步"><a href="#DP-的代价：每一步都要梯度同步" class="headerlink" title="DP 的代价：每一步都要梯度同步"></a>DP 的代价：每一步都要梯度同步</h3><p>DP 的优点是：</p><ul><li>思路最直观</li><li>最容易提高吞吐</li><li>每个副本内部逻辑一致</li></ul><p>但它的问题是：</p><ul><li>训练时每一步都要 AllReduce 梯度</li><li>副本越多，同步压力越大</li><li>网络会直接成为性能关键因素</li></ul><p>所以 DP 更像是在说：<strong>我接受更大的梯度同步压力，换更高吞吐</strong>。</p><h3 id="网络通信"><a href="#网络通信" class="headerlink" title="网络通信"></a>网络通信</h3><p>因为一旦你不再是单卡，而是 TP&#x2F;PP&#x2F;DP 混合，通信就不再只是“有一点”。</p><p>而是会分成不同形态：</p><ul><li>TP：层内高频通信</li><li>PP：段间激活传输</li><li>DP：副本间梯度同步</li></ul><p>这些通信最终都会落到：</p><ul><li>NCCL</li><li>AllReduce</li><li>AllGather</li><li>ReduceScatter</li><li>P2P</li><li>Incast 风险</li><li>ECN &#x2F; PFC &#x2F; queue 管理</li></ul><p>也就是说，TP&#x2F;PP&#x2F;DP 不只是“模型切法”。<br>它们本质上也在定义：<strong>你的网络压力会长成什么样</strong>。</p><p><img src="/images/2026/05/025.png"></p><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>到这里，我们把全文压缩成三句话。</p><p><strong>PP 在回答：模型太长、整份装不下，怎么办？</strong><br>答案是：按层分段。</p><p><strong>TP 在回答：同一层太大，或者想让同一层多卡并行，怎么办？</strong><br>答案是：把同一层内部的张量拆开。</p><p><strong>DP 在回答：单副本已经能跑了，但我想同时处理更多数据，怎么办？</strong><br>答案是：复制模型副本，切数据。</p><p><img src="/images/2026/05/026.png"></p><hr><p>参考资料:</p><ol><li><a href="https://mp.weixin.qq.com/s/XPHJTQVWSLLVBqiHbwdMkw">不是多卡，而是多种“切法”：一文讲透 TP、PP、DP</a></li></ol>]]>
    </content>
    <id>http://liujunming.github.io/2026/05/24/Notes-about-TP-PP-DP/</id>
    <link href="http://liujunming.github.io/2026/05/24/Notes-about-TP-PP-DP/"/>
    <published>2026-05-24T11:55:46.000Z</published>
    <summary>
      <![CDATA[<p>本文将mark下TP(Tensor Parallelism)、PP(Pipeline Parallelism)和DP(Data Parallelism)的相关notes，本文内容转载自<a href="https://mp.weixin.qq.com/s/XPHJTQVWSLLVBqiHbwdMkw">不是多卡，而是多种“切法”：一文讲透 TP、PP、DP</a>。]]>
    </summary>
    <title>
      <![CDATA[Notes about TP && PP && DP]]>
    </title>
    <updated>2026-05-24T14:14:22.869Z</updated>
  </entry>
</feed>
