<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>萌约 MoeYork</title>
  <icon>https://york.moe/icon.png</icon>
  <subtitle>一个普通的博客站点</subtitle>
  <link href="https://york.moe/atom.xml" rel="self"/>
  
  <link href="https://york.moe/"/>
  <updated>2026-05-26T11:39:58.867Z</updated>
  <id>https://york.moe/</id>
  
  <author>
    <name>MaPl</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Javacard 不完全指北</title>
    <link href="https://york.moe/2026/05/25/javacard-intro/"/>
    <id>https://york.moe/2026/05/25/javacard-intro/</id>
    <published>2026-05-25T13:54:00.000Z</published>
    <updated>2026-05-26T11:39:58.867Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Javacard-不完全指北"><a href="#Javacard-不完全指北" class="headerlink" title="Javacard 不完全指北"></a>Javacard 不完全指北</h2><blockquote><p>本文旨在在尽量不出错的情况下简单介绍 Javacard 相关资讯</p></blockquote><p>笔者第一次了解到 Javacard 的存在是在 tuna 的一次<a href="https://tuna.moe/event/2021/canokey/">金枪鱼之夜</a>分享中</p><h3 id="智能卡相关"><a href="#智能卡相关" class="headerlink" title="智能卡相关"></a>智能卡相关</h3><p>以下信息不仅适用于 Javacard，通常也适用于其他智能卡</p><blockquote><p>智能卡芯片也有其他封装形式和电气连接方式 (如常见的 ST33 等芯片)</p></blockquote><h4 id="机械尺寸"><a href="#机械尺寸" class="headerlink" title="机械尺寸"></a>机械尺寸</h4><p>正常 Javacard 的尺寸与银行卡相同（毕竟是同一个标准），该尺寸为 ISO&#x2F;IEC 7810 中的 ID‑1 规格；卡片上的芯片触点排列和定义同样由该标准确定</p><p>在通常情况下，Javacard 的芯片仅位于芯片触点下方，NFC 通信天线嵌在卡片塑料层中，可以透过高亮度的光源观察</p><p>也有尺寸为全尺寸 SIM (1FF) 的 Javacard 售卖</p><h4 id="通信协议"><a href="#通信协议" class="headerlink" title="通信协议"></a>通信协议</h4><h5 id="物理层"><a href="#物理层" class="headerlink" title="物理层"></a>物理层</h5><p>接触式通信通过与卡片上的芯片触点接触来建立连接，通信协议在 ISO&#x2F;IEC 7816-3 中规定，为异步半双工同步传输协议</p><p>非接触式通信通过与卡片上的线圈电磁感应耦合来获得能量和建立连接，通信协议在 ISO&#x2F;IEC 14443 中规定，工作在 13.56 MHz ISM 频段，标准中规定了两种通信机制 <code>Type A</code> 和 <code>Type B</code>，具有不同的调制、编码和防冲突方式</p><p>磁条不参与与芯片的通信，根据矫顽力不同有高抗和低抗等分类，按磁条的磁轨数有双轨、三轨等，不同磁轨的编码方式在ISO&#x2F;IEC 7811中定义。</p><p>通常同时具有接触式和非接触式接口的卡片被称为 “双界面”，额外印刷的磁条也可能被作为一个“界面”，与其他两种的组合被称为 “双界面”或“三界面”</p><h5 id="传输层"><a href="#传输层" class="headerlink" title="传输层"></a>传输层</h5><p>接触式通信采用两种传输协议：</p><ul><li>T&#x3D;0 (面向字节的半双工传输协议)</li><li>T&#x3D;1 (面向块的半双工异步传输协议)</li></ul><p>非接触式采用一种传输协议：</p><ul><li>T&#x3D;CL (非接触式传输协议 Transmission &#x3D; ContactLess)</li></ul><p>传输层负责在卡片上电、初始化完成后打包上层的应用通信数据并交由物理层传输</p><h5 id="应用层"><a href="#应用层" class="headerlink" title="应用层"></a>应用层</h5><p>应用层采用统一格式的 APDU (Application Protocol Data Unit) 指令进行通信，该格式在 ISO&#x2F;IEC 7816-4 中描述</p><h6 id="C-APDU"><a href="#C-APDU" class="headerlink" title="C-APDU"></a>C-APDU</h6><p>终端向智能卡发送的格式为 COMMAND APDU (C-APDU)</p><figure class="highlight txt"><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">|                      COMMAND APDU (C-APDU)                      |</span><br><span class="line">|                         终端 ----&gt; 智能卡                       |</span><br><span class="line">===================================================================</span><br><span class="line">|   CLA   |   INS   |   P1   |   P2   ||  Lc  |   Data   |   Le   |</span><br><span class="line">| (1 Byte)| (1 Byte)| (1 Byte)|(1 Byte)|| (Var)|  (Var)   | (Var)  |</span><br><span class="line">--------------------------------------  ---------------------------</span><br><span class="line">| &lt;---------- Header (必选) ---------&gt;|| &lt;----- Body (可选) -----&gt;|</span><br><span class="line">===================================================================</span><br></pre></td></tr></table></figure><p>Header 指令头 必选</p><ul><li>CLA (Class): 指令类别。用来区分应用类型或安全级别 (如 00 表示标准的 ISO 7816 指令，80 常常用于各家卡商的自定义指令)</li><li>INS (Instruction): 指令码。告诉卡片要做什么 (如 A4 表示 SELECT 选择应用，B0 表示 READ BINARY 读取数据)。</li><li>P1 &#x2F; P2 (Parameter 1 &amp; 2): 指令参数。提供指令所需的具体参数，通常用作文件偏移量、安全标志或配置选项</li></ul><p>Body 可选</p><ul><li>Lc (Length of Command Data): 指示后面 Data 字段的字节长度。</li><li>Data: 终端实际发送给卡片的载荷数据</li><li>Le (Length of Expected Data): 终端期望卡片返回的数据最大长度 (如果填 00，通常表示期望返回最大可用长度，最高 256 字节；扩展 APDU 可达 65536 字节)</li></ul><h6 id="R-APDU"><a href="#R-APDU" class="headerlink" title="R-APDU"></a>R-APDU</h6><p>卡片处理完指令后，会返回一个响应</p><figure class="highlight txt"><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">|         RESPONSE APDU (R-APDU)        |</span><br><span class="line">|             智能卡 ----&gt; 终端         |</span><br><span class="line">=========================================</span><br><span class="line">|        Data        ||   SW1  |   SW2  |</span><br><span class="line">|       (Var)        ||(1 Byte)|(1 Byte)|</span><br><span class="line">---------------------  ------------------</span><br><span class="line">| &lt;--- Body (可选) ---&gt;||&lt;-Trailer(必选)-&gt;|</span><br><span class="line">=========================================</span><br></pre></td></tr></table></figure><ul><li>Data (可选): 卡片返回的实际业务数据。它的长度由 C-APDU 中的 Le 以及卡片内部逻辑共同决定</li><li>SW1 &#x2F; SW2 (Status Word 1 &amp; 2，必选): 状态字。用于指示指令执行的结果</li></ul><p>通过状态字可以知道命令的执行情况</p><table><thead><tr><th align="left">SW1</th><th align="left">SW2</th><th align="left">状态说明 (Description)</th></tr></thead><tbody><tr><td align="left"><code>90</code></td><td align="left"><code>00</code></td><td align="left">成功 (Success)</td></tr><tr><td align="left"><code>61</code></td><td align="left"><code>XX</code></td><td align="left">有额外数据待读取 (XX bytes remaining)</td></tr><tr><td align="left"><code>62</code></td><td align="left"><code>83</code></td><td align="left">文件已失效 (File invalidated)</td></tr><tr><td align="left"><code>63</code></td><td align="left"><code>00</code></td><td align="left">认证失败 (Authentication failed)</td></tr><tr><td align="left"><code>63</code></td><td align="left"><code>CX</code></td><td align="left">认证失败，剩余重试次数为 X</td></tr><tr><td align="left"><code>65</code></td><td align="left"><code>81</code></td><td align="left">内存故障 (Memory failure)</td></tr><tr><td align="left"><code>67</code></td><td align="left"><code>00</code></td><td align="left">Lc 长度错误 (Wrong length)</td></tr><tr><td align="left"><code>68</code></td><td align="left"><code>81</code></td><td align="left">不支持的逻辑通道</td></tr><tr><td align="left"><code>68</code></td><td align="left"><code>82</code></td><td align="left">不支持的安全报文格式</td></tr><tr><td align="left"><code>69</code></td><td align="left"><code>82</code></td><td align="left">安全状态不满足 &#x2F; 未授权 (Security condition not satisfied)</td></tr><tr><td align="left"><code>69</code></td><td align="left"><code>83</code></td><td align="left">认证方法被锁定 (Method blocked)</td></tr><tr><td align="left"><code>69</code></td><td align="left"><code>85</code></td><td align="left">使用条件不满足 (Conditions not satisfied)</td></tr><tr><td align="left"><code>6A</code></td><td align="left"><code>80</code></td><td align="left">数据域 (Data) 参数不正确</td></tr><tr><td align="left"><code>6A</code></td><td align="left"><code>81</code></td><td align="left">不支持该功能 (Function not supported)</td></tr><tr><td align="left"><code>6A</code></td><td align="left"><code>82</code></td><td align="left">未找到文件或应用 (File&#x2F;Applet not found)</td></tr><tr><td align="left"><code>6A</code></td><td align="left"><code>83</code></td><td align="left">未找到记录 (Record not found)</td></tr><tr><td align="left"><code>6A</code></td><td align="left"><code>86</code></td><td align="left">P1&#x2F;P2 参数不正确</td></tr><tr><td align="left"><code>6C</code></td><td align="left"><code>XX</code></td><td align="left">Le 长度错误，实际应为 XX 字节</td></tr><tr><td align="left"><code>6D</code></td><td align="left"><code>00</code></td><td align="left">不支持的 INS &#x2F; 无效指令码</td></tr><tr><td align="left"><code>6E</code></td><td align="left"><code>00</code></td><td align="left">不支持的 CLA &#x2F; 无效指令类</td></tr><tr><td align="left"><code>6F</code></td><td align="left"><code>00</code></td><td align="left">未知内部错误 (Unknown error)</td></tr></tbody></table><h3 id="读卡器相关"><a href="#读卡器相关" class="headerlink" title="读卡器相关"></a>读卡器相关</h3><p>通常免驱动的智能卡读卡器为 USB CCID 设备，PC&#x2F;SC 为事实 API 标准</p><p>接触式和未接触式需要不同的读卡器类型，也有同时支持两种接口的读卡器售卖。</p><p>安卓手机自带的 NFC 功能允许应用通过非接触式接口向智能卡发送和接收 APDU，可以安装 NXP TagInfo &#x2F; NFC Tools 等读取卡片信息。也可以通过 HCE 功能模拟智能卡接收和响应外部读卡器发送的 APDU</p><blockquote><p>Canokey 和 Yubikey 声明自己为 USB CCID 设备，以允许操作系统读取其中的 “智能卡” 实现相关功能</p></blockquote><h3 id="GlobalPlatform-相关"><a href="#GlobalPlatform-相关" class="headerlink" title="GlobalPlatform 相关"></a>GlobalPlatform 相关</h3><blockquote><p>如果你需要了解如何管理 Javacard：进行如安装或删除 applet 等操作，或者对 Javacard 的生命周期、ISD 等感兴趣请直接阅读 <a href="https://globalplatform.org/wp-content/uploads/2018/05/GPC_CardSpecification_v2.3.1_PublicRelease_CC.pdf">CardSpecification_v2.3.1</a>，这份文档内详细说明了卡片生命周期以及应用 (applet)、模块和安全域 (发卡方安全域 ISD、补充安全域 SSD) 和安全通道 (SCP02、SCP03) 、密钥等概念和对应的 APDU</p></blockquote><p>GlobalPlatform (GP) 是一个跨行业的国际标准化组织，GlobalPlatform 制定的标准定义了如何在安全环境中安全地加载、安装、更新和删除应用程序，以及如何进行密钥管理和身份认证。不同生产厂家的 Javacard 卡通常均遵守<strong>特定版本</strong>的 GlobalPlatform 规范</p><blockquote><p>如果你不了解你正在对卡进行的操作，<strong>至少</strong>以下操作是危险的，误操作或多次失败尝试可能会导致卡片变砖、锁定或不可用</p><ul><li>更改卡片的生命周期状态 (单向不可逆+影响可用管理功能)</li><li>使用软件提供的默认密钥或猜测的密钥进行安全通道认证 (可能导致锁定)</li><li>替换、添加、删除密钥或添加新的安全通道 (错误的配置或组合可能导致失去管理权)</li><li>在不可靠的连接状态下写入卡片 (断连可能导致卡片变砖)</li></ul></blockquote><p>以下软件提供 GlobalPlatform 规范下的卡片管理功能</p><ul><li><a href="https://github.com/martinpaljak/GlobalPlatformPro">GlobalPlatformPro</a> 非常经典简洁的 CLI 工具</li><li><a href="https://javacardos.com/javacardforum/viewtopic.php?f=3&t=38?ws=github&prj=OpenEMV">PyApduTool</a> 随 JCIDE 安装，虽然很旧但是图形界面很好用</li></ul><h3 id="Applet-相关"><a href="#Applet-相关" class="headerlink" title="Applet 相关"></a>Applet 相关</h3><p>你可以自己编写或使用开源的 Applet 项目，编译得到 <code>.cap</code> 文件并将其加载到 Javacard 上</p><blockquote><p>不同的Applet通常具有不同的AID，以在选择应用时加以区分</p></blockquote><blockquote><p>这部分信息可能过时，你可以参考以下链接：</p><ul><li><a href="https://github.com/crocs-muni/javacard-curated-list">javacard-curated-list</a> 一份非常详细的 applet 列表</li><li><a href="https://thinkalone.win/Play-with-JavaCard.html">一位安了很多 Applet 的博主的文章</a></li></ul></blockquote><blockquote><p>卡片载入应用后，通常需要特定的应用来配套使用，你可以参考 <a href="https://docs.canokeys.org/zh-hans/userguide/">CanoKey 帮助文档</a> 和 网络上使用 Yubikey、Canokey 的相关博客以及下列项目中的说明文档来了解相关应用的配置和使用方法</p></blockquote><p>PIV: <a href="https://github.com/makinako/OpenFIPS201">makinako&#x2F;OpenFIPS201</a></p><ul><li>2026年4月仍在更新</li><li>正在进行FIPS认证</li></ul><p>GPG: <a href="https://github.com/github-af/SmartPGP">github-af&#x2F;SmartPGP</a></p><ul><li>2025年9月仍在更新</li><li>OpenPGP 3.4</li><li>使用 requestObjectDeletion</li></ul><p>FIDO2: <a href="https://github.com/BryanJacobs/FIDO2Applet">BryanJacobs&#x2F;FIDO2Applet</a></p><ul><li>2026年4月仍在更新</li><li>完整覆盖 FIDO2 CTAP2.1 标准</li><li>需要 Javacard 3.0.5+</li><li>需要较多空间</li></ul><p>GIDS: <a href="https://github.com/vletoux/GidsApplet">vletoux&#x2F;GidsApplet</a></p><ul><li>2023年停更</li><li>需要 Java Card 2.2.1+</li><li><a href="https://www.mysmartlogon.com/download#GIDS">初始化工具</a></li></ul><p>BIP39: <a href="https://github.com/keycard-tech/status-keycard">keycard-tech&#x2F;status-keycard</a></p><ul><li>不会用</li></ul><p>NDEF: <a href="https://github.com/OpenJavaCard/openjavacard-ndef">OpenJavaCard&#x2F;openjavacard-ndef</a></p><ul><li>还没用</li></ul><p>MRTD (机读旅行证件): <a href="https://github.com/E3V3A/JMRTD/tree/master/passportapplet">https://github.com/E3V3A/JMRTD/tree/master/passportapplet</a></p><ul><li><a href="https://www.icao.int/sites/default/files/publications/DocSeries/9303_p10_cons_zh.pdf">标准文档p10</a></li><li><a href="https://www.icao.int/sites/default/files/publications/DocSeries/9303_p11_cons_zh.pdf">标准文档p11</a></li></ul><p>除此之外还有HOTP&#x2F;TOTP、EMV 和 SIM 等可通过 Applet 实现的功能</p><h3 id="如何购买-Javacard"><a href="#如何购买-Javacard" class="headerlink" title="如何购买 Javacard"></a>如何购买 Javacard</h3><blockquote><p>不构成购买建议</p></blockquote><p>你可以在淘宝上搜索特定的 Javacard 产品型号来购买。对于 NXP 生产的 Javacard，其初始化文档为<strong>保密资料</strong>，通常需要卖家帮助完成卡片初始化操作 (卡片通信参数: ATR、历史字符等；卡片通信配置: APDU缓冲区大小、逻辑通道数、VHBR、SCP通道配置；卡片功能配置: RSA最大长度等) 或直接购买未初始化的卡片、使用<strong>卖家提供的出厂密钥</strong>自行初始化 (需要 NXP 私有 APDU 命令)</p><p>目前推荐 NXP 的 J3R 系列，比较新，而且可以买到：</p><ul><li>NXP J3R110 &#x2F; J3R180</li><li>NXP J3R150 &#x2F; J3R200</li></ul><p>其可用容量约为J3R后面的三位数字(单位: KB)，购买时需要注意 EMV &#x2F; SecID 版本的区别 (是否支持 ECC)，以及接口是否为你需要的 (接触式 &#x2F; 双界面 &#x2F; 纯非接)，还有最重要的卡片密钥</p><blockquote><p>如果你对卡片购买、初始化命令感兴趣，欢迎邮件交流讨论</p></blockquote>]]></content>
    
    
    <summary type="html">关于 Javacard，笔者所了解的资讯汇总</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>WSL 2 分离式 home 挂载</title>
    <link href="https://york.moe/2025/11/13/wsl2-split-home/"/>
    <id>https://york.moe/2025/11/13/wsl2-split-home/</id>
    <published>2025-11-13T15:33:31.000Z</published>
    <updated>2026-05-26T11:39:58.867Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近清理WSL2发行版的磁盘，发现大部分空间都被<code>/home</code>占用，所以决定用独立的vhdx磁盘作为<code>/home</code>挂载到发行版里</p></blockquote><h1 id="关于-WSL-2-架构"><a href="#关于-WSL-2-架构" class="headerlink" title="关于 WSL 2 架构"></a>关于 WSL 2 架构</h1><blockquote><p>WSL 2 架构可以参考<a href="https://wsl.dev/">wsl.dev</a>，微软于25年5月19<a href="https://github.com/microsoft/WSL">开源</a>了 WSL 2 大部分组件。</p></blockquote><p>WSL 2 内核运行在一个使用[HCS API](<a href="https://learn.microsoft.com/zh-cn/virtualization/api/hcs/overview">Host Compute System Overview | Microsoft Learn</a>)创建的隐藏虚拟机里，各发行版实际上是通过namespace隔离的容器。在内核启动后，WSL 进入其预制的<a href="https://github.com/microsoft/WSLG/blob/main/CONTRIBUTING.md#building-the-wslg-system-distro">WSLg System Distro</a>中，加载各已安装发行版的磁盘，并将其挂载到各发行版的mount namespace中（这里写死了是ext4，所以应该是没法去改根目录文件系统的）。</p><p>PS：所有发行版共用同一个network namespace</p><p>可以通过下面的命令进入system distro：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl --system</span><br></pre></td></tr></table></figure><p>还可以以root权限进入：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl --debug-shell</span><br></pre></td></tr></table></figure><h1 id="关于-VHD-VHDX"><a href="#关于-VHD-VHDX" class="headerlink" title="关于 VHD&#x2F;VHDX"></a>关于 VHD&#x2F;VHDX</h1><blockquote><p>还得是 VHDX</p></blockquote><p>VHD&#x2F;VHDX有三个很有用的特性：</p><ul><li>动态VHD: 支持空间调整</li><li>稀疏VHD: 支持自动回收空间</li><li>父子VHD: 类似overlayfs</li></ul><p>常用的管理命令：</p><figure class="highlight powershell"><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 class="comment"># 设置动态VHD大小</span></span><br><span class="line"><span class="built_in">Resize-VHD</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 压缩动态VHDX空间</span></span><br><span class="line"><span class="built_in">Optimize-VHD</span></span><br></pre></td></tr></table></figure><h1 id="关于挂载"><a href="#关于挂载" class="headerlink" title="关于挂载"></a>关于挂载</h1><ul><li>WSL2 所有发行版共享同一个内核，通过<code>wsl --mount --vhd --bare &lt;vhd path&gt;</code>挂载的磁盘同时对所有发行版可见</li><li>Linux <a href="https://man7.org/linux/man-pages/man2/mount.2.html#HISTORY">从 2.4 开始</a>就支持把一个文件系统挂在多个挂载点上，或者在一个挂载点上挂多个文件系统</li><li><code>mount --bind</code>可以挂载一个目录到另一个目录（与挂载点不同，可以是非空目录，且同样可以被挂载多次）</li></ul><h1 id="关于稀疏-VHD-SparseVHD"><a href="#关于稀疏-VHD-SparseVHD" class="headerlink" title="关于稀疏 VHD (SparseVHD)"></a>关于稀疏 VHD (SparseVHD)</h1><p>稀疏VHD实际上是NTFS的<a href="https://learn.microsoft.com/zh-cn/windows/win32/fileio/sparse-files">稀疏文件</a>，它需要通过<a href="https://github.com/microsoft/WSL/blob/d9c69a50abdd158ad0257c96d0451dcd8adc8962/src/windows/service/exe/LxssUserSession.cpp#L1749">特殊的ioctl</a>设置，通过特殊API写入全0数据时可以回收磁盘空间。但是<a href="https://github.com/microsoft/WSL/issues/10991">被WSL加了警告</a></p><h1 id="操作过程"><a href="#操作过程" class="headerlink" title="操作过程"></a>操作过程</h1><h2 id="分离-home到-VHDX"><a href="#分离-home到-VHDX" class="headerlink" title="分离/home到 VHDX"></a>分离<code>/home</code>到 VHDX</h2><p>这里笔者选择了 btrfs，创建了一个空白<code>vhdx</code>，作为<code>/dev/sdxx</code>临时挂载在<code>/mnt/home</code>：</p><figure class="highlight bash"><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 class="comment"># 创建子卷结构</span></span><br><span class="line"><span class="built_in">sudo</span> mount /dev/sdxx /mnt/home</span><br><span class="line"><span class="built_in">sudo</span> btrfs subvolume create /mnt/home/@home</span><br><span class="line"><span class="built_in">sudo</span> btrfs subvolume create /mnt/home/@.snapshots</span><br><span class="line"><span class="built_in">sudo</span> umount /mnt/home</span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制已有的/home</span></span><br><span class="line"><span class="built_in">sudo</span> mount -t btrfs -o subvol=/@home /dev/sdxx /mnt/home</span><br><span class="line"><span class="built_in">sudo</span> rsync -aXS /home/ /mnt/home</span><br><span class="line"><span class="built_in">sudo</span> umount /mnt/home</span><br><span class="line"></span><br><span class="line"><span class="comment"># 挂载到/home</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">mv</span> /home /home_old</span><br><span class="line"><span class="built_in">sudo</span> mount --<span class="built_in">mkdir</span> -t btrfs -o subvol=/@home /dev/sdxx /home</span><br></pre></td></tr></table></figure><h2 id="自动挂载配置"><a href="#自动挂载配置" class="headerlink" title="自动挂载配置"></a>自动挂载配置</h2><p>在上面配置完后其实就已经可用了，但是WSL再次冷启动时之前挂载的VHDX并不会自动挂载，挂载上了磁盘也需要手动挂载<code>/home</code>，所以还需要进行自动挂载的配置。这里利用了WSL 2内置的systemd进行自动挂载：</p><p>在发行版<code>/etc/wsl.conf</code>中进行以下配置：</p><figure class="highlight ini"><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"><span class="section">[automount]</span></span><br><span class="line"><span class="attr">mountFsTab</span>=<span class="literal">false</span> <span class="comment"># 因为在加载发行版时还没有挂载vhdx，所以需要推迟到systemd引导过程中</span></span><br></pre></td></tr></table></figure><p>创建<code>/usr/lib/systemd/system/wsl-automount.service</code>：</p><blockquote><p>其中VHDX路径为<code>&quot;C:\\Users\\UserName\\AppData\\Local\\wsl-home\\home.vhdx&quot;</code></p></blockquote><figure class="highlight ini"><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 class="section">[Unit]</span></span><br><span class="line"><span class="attr">Description</span>=WSL2 Auto Mount</span><br><span class="line"><span class="attr">After</span>=systemd-binfmt.service systemd-udevd.service</span><br><span class="line"><span class="attr">Requires</span>=systemd-binfmt.service</span><br><span class="line"><span class="attr">Before</span>=home.mount</span><br><span class="line"><span class="attr">DefaultDependencies</span>=<span class="literal">no</span></span><br><span class="line"></span><br><span class="line"><span class="section">[Service]</span></span><br><span class="line"><span class="attr">Type</span>=<span class="literal">on</span>eshot</span><br><span class="line"><span class="attr">ExecStart</span>=/bin/sh -c <span class="string">&#x27;/mnt/c/Windows/System32/wsl.exe --mount --vhd --bare &quot;C:\\Users\\UserName\\AppData\\Local\\wsl-home\\home.vhdx&quot; || true&#x27;</span></span><br><span class="line"><span class="attr">StandardOutput</span>=null</span><br><span class="line"><span class="attr">StandardError</span>=null</span><br><span class="line"><span class="attr">RemainAfterExit</span>=<span class="literal">yes</span></span><br><span class="line"><span class="attr">TimeoutStopSec</span>=<span class="number">5</span></span><br><span class="line"></span><br><span class="line"><span class="section">[Install]</span></span><br><span class="line"><span class="attr">WantedBy</span>=basic.target</span><br></pre></td></tr></table></figure><p>创建<code>/usr/lib/systemd/system/home.mount</code></p><blockquote><p>使用<code>lsblk -f</code>查看磁盘UUID</p></blockquote><figure class="highlight ini"><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 class="section">[Unit]</span></span><br><span class="line"><span class="attr">Description</span>=Mount VHD Filesystem</span><br><span class="line"><span class="attr">After</span>=wsl-automount.service</span><br><span class="line"><span class="attr">Requires</span>=wsl-automount.service</span><br><span class="line"><span class="attr">Before</span>=shutdown.target</span><br><span class="line"><span class="attr">Conflicts</span>=shutdown.target</span><br><span class="line"><span class="attr">DefaultDependencies</span>=<span class="literal">no</span></span><br><span class="line"></span><br><span class="line"><span class="section">[Mount]</span></span><br><span class="line"><span class="attr">What</span>=UUID=a936001a-<span class="number">3044</span>-<span class="number">4285</span>-<span class="number">8</span>a4a-<span class="number">8</span>be821931f08</span><br><span class="line"><span class="attr">Where</span>=/home</span><br><span class="line"><span class="attr">Type</span>=btrfs</span><br><span class="line"><span class="attr">Options</span>=compress=zstd,noatime,subvol=/@home</span><br><span class="line"></span><br><span class="line"><span class="section">[Install]</span></span><br><span class="line"><span class="attr">WantedBy</span>=basic.target</span><br></pre></td></tr></table></figure><p>创建<code>&#39;/usr/lib/systemd/system/\x2esnapshots.mount&#39;</code>：</p><blockquote><p>快照目录，记得创建<code>/.snapshots</code></p></blockquote><figure class="highlight ini"><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 class="section">[Unit]</span></span><br><span class="line"><span class="attr">Description</span>=Mount VHD Filesystem @.snapshots</span><br><span class="line"><span class="attr">After</span>=wsl-automount.service</span><br><span class="line"><span class="attr">Requires</span>=wsl-automount.service</span><br><span class="line"><span class="attr">Before</span>=shutdown.target</span><br><span class="line"><span class="attr">Conflicts</span>=shutdown.target</span><br><span class="line"><span class="attr">DefaultDependencies</span>=<span class="literal">no</span></span><br><span class="line"></span><br><span class="line"><span class="section">[Mount]</span></span><br><span class="line"><span class="attr">What</span>=UUID=a936001a-<span class="number">3044</span>-<span class="number">4285</span>-<span class="number">8</span>a4a-<span class="number">8</span>be821931f08</span><br><span class="line"><span class="attr">Where</span>=/.snapshots</span><br><span class="line"><span class="attr">Type</span>=btrfs</span><br><span class="line"><span class="attr">Options</span>=compress=zstd,noatime,subvol=/@.snapshots</span><br><span class="line"></span><br><span class="line"><span class="section">[Install]</span></span><br><span class="line"><span class="attr">WantedBy</span>=basic.target</span><br></pre></td></tr></table></figure><p>可以<code>wsl --shutdown</code>后等待8s，重启wsl检查效果</p>]]></content>
    
    
    <summary type="html">最近清理WSL2发行版的磁盘，发现大部分空间都被`/home`占用，所以决定用独立的vhdx磁盘作为`/home`挂载到发行版里</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>OpenWRT 配置教程</title>
    <link href="https://york.moe/2025/07/14/openwrt-conf-tutorial/"/>
    <id>https://york.moe/2025/07/14/openwrt-conf-tutorial/</id>
    <published>2025-07-14T18:57:00.000Z</published>
    <updated>2026-05-26T11:39:58.867Z</updated>
    
    <content type="html"><![CDATA[<h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>笔者最近为一台设备刷写了最新的 OpenWRT 24.10.1（虽然没过几天 24.10.2 版本已出 Orz）。为了让这台路由器符合笔者的需求，笔者研究了负载均衡、无线 mesh 组网和 IPv6 等配置。然而现有的简中互联网并没有详细的配置教程或说明文档，很多配置只知其然不知其所以然，甚至以讹传讹，让人迷惑甚至误解。笔者在现有资料的基础上对部分国内网络环境下的常见需求进行了探索和分析，给出了设置项位置和相关设置说明，力求简明准确。虽然笔者写到最后仍是一知半解，但还是记录下来，希望能给读者和未来的自己节省一些时间。</p><h2 id="配置细节"><a href="#配置细节" class="headerlink" title="配置细节"></a>配置细节</h2><h3 id="自定义构建"><a href="#自定义构建" class="headerlink" title="自定义构建"></a>自定义构建</h3><blockquote><p>通过<a href="https://firmware-selector.openwrt.org/?version=24.10.1">在线构建</a>可以保证安装的软件包是最新的，避免在 overlayfs 上重复安装浪费存储空间</p></blockquote><ul><li>启用 dnsmasq-full</li><li>启用 kmod-tcp-bbr</li><li>启用 wpad-mbedtls 为 802.11s 做准备，这个就是完整版的 hostapd &#x2F; wpad，按加密套件不同分为openssl &#x2F; wolfssl &#x2F; bedtls 三个版本</li><li>安装 luci-i18n-upnp-zh-cn 添加 upnp 管理</li><li>安装 luci-i18n-wol-zh-cn 添加 WOL 支持</li><li>安装 block-mount 为 luci 添加<code>系统 / 挂载点</code>选项</li></ul><h3 id="IPv6-网络配置"><a href="#IPv6-网络配置" class="headerlink" title="IPv6 网络配置"></a>IPv6 网络配置</h3><ul><li>允许扩展前缀分配: <code>网络 / 接口 / 接口选项卡</code> wan6 端口 <code>编辑 / 常规设置</code> 打开 <code>扩展前缀</code> （端口协议应为<code>DHCPv6 客户端</code>）</li><li>IPv6 中继相关配置 <code>网络 / 接口 / 接口选项卡</code> 选择端口 <code>编辑 / DHCP 服务器</code></li><li>IPv6 前缀相关配置: <code>网络 / 接口 / 接口选项卡</code> 选择端口 <code>编辑 / 高级设置</code></li><li>开启MSS钳制: <code>网络 / 防火墙 / 常规设置选项卡 / 区域</code> 选择防火墙区域（WAN区域） <code>编辑 / 常规设置</code> 打开 <code>MSS 钳制</code></li></ul><h4 id="IPv6-地址分配"><a href="#IPv6-地址分配" class="headerlink" title="IPv6 地址分配"></a>IPv6 地址分配</h4><p>IPv6地址由网络前缀+接口标识组成，有以下几种类型：</p><table><thead><tr><th>类型</th><th>名称</th><th>前缀标识</th></tr></thead><tbody> <tr><td rowspan=4>单播地址</td><td>全球单播地址 GUA</td><td>2000::/3</td></tr> <tr><td>唯一本地地址 ULA</td><td>fc00::/7</td></tr> <tr><td>站点本地地址 SLA</td><td>fec0::/10</td></tr> <tr><td>链路本地地址 LLA</td><td>fe80::/10</td></tr> <tr><td rowspan=2 >特殊地址</td><td>环回地址</td><td>::1/128</td></tr> <tr><td>未指定地址</td><td>::/128</td></tr> <tr><td colspan=2>组播地址</td><td>ff00::/8</td></tr></tbody></table><p>IPv6 地址分配有两种方式：</p><ul><li>有状态：由 DHCPv6 服务器分配</li><li>无状态：由 SLAAC 过程设备自行分配</li></ul><p>两种方式可同时存在，注意安卓设备<strong>只支持 SLAAC</strong>，SLAAC 过程使用 NDP（邻居发现协议）的 RS &#x2F; RA 消息；NDP 还有类似 ARP 的功能，使用 NS &#x2F; NA 消息</p><p>实际分配时，路由器通过 RA（路由器通告）消息应答设备的 RS （路由器请求）消息，下发配置和 SLAAC 前缀。RA 消息是定期发送的，但主机发送 RS 消息后，可立即得到响应：</p><ul><li>A标志（Autonomous Address Configuration，每个前缀独立配置）<ul><li><code>A=1</code>：客户端可在该前缀范围内进行 SLAAC，获取 IPv6 地址和 DNS 等参数</li><li><code>A=0</code>：客户端不应当在该前缀范围内进行 SLAAC</li></ul></li><li>M标志（Managed Address Configuration）：<ul><li><code>M=1</code>：客户端可使用 DHCPv6 获取 IPv6 地址和 DNS 等参数</li><li><code>M=0</code>：判断O标志</li></ul></li><li>O标志（Other Configuration，仅当M&#x3D;0时有效）：<ul><li><code>O=1</code>：当有A&#x3D;1（全部 A&#x3D;0 没有 IP 地址）时，客户端可通过 DHCPv6 获取 DNS 等参数</li><li><code>O=0</code>：不通过 DHCPv6 获取 DNS 等参数</li></ul></li></ul><p>要注意单个 IPv6 地址与子网的区别，获取到一个 IPv6 地址≠获取了一条路由：</p><p>假设 2001:1:2:3:a:b:c:d&#x2F;64 是一个 IPv6 地址，IPv6 地址本身不包含前缀信息，但在网络配置中，前缀长度（如 &#x2F;64）类似子网掩码，用于决定该地址所属的子网范围。网络接口使用前缀（2001:1:2:3）判断一个 IPv6 地址是否与自己同属一个子网（即在本地链路上）。网络接口在没有<strong>明确路由</strong>的情况下，只能在<strong>本地链路</strong>上通信。在内核根据路由表判断某个报文应从接口 B 发出时，首先要通过 NDP 在<strong>本地链路</strong>上确定目的 IPv6 地址对应的 MAC 地址，然后在链路层向该 MAC 地址转发 IPv6 报文</p><p><strong>路由转发</strong>操作在同一个路由器的不同接口之间进行，由 Linux 内核遵循路由表进行转发。例如路由器有两个接口A、B，IPv6 地址分别为 2001:1:a::1&#x2F;48 和 2001:1:b::1&#x2F;48，其网络前缀不同，<strong>分属两个不同的子网</strong>，且假设具有一条路由（将2001:1:b:6789::&#x2F;64转发到2001:1:b:6789::1）。</p><ul><li>当接口 A 收到目的 IPv6 地址为 2001:1:b:abcd::1 的 IPv6 报文时，Linux 内核判断应由接口 B 发出，在接口 B 的本地链路上执行 NDP 查找 2001:1:b:abcd::1 对应的 MAC 地址，成功后在其本地链路上向 2001:1:b:abcd::1 发送 IPv6 报文。</li><li>而当接口A 收到目的 IPv6 地址为 <strong>2001:1:b:6789::1234</strong> 的 IPv6 报文时，Linux 内核根据路由表向 2001:1:b:6789::1 转发该报文，2001:1:b:6789::1 位于接口 B，因此在接口 B 的本地链路上执行 NDP 查找 <strong>2001:1:b:6789::1 对应的 MAC 地址</strong>，成功后在其本地链路上向 2001:1:b:6789::1 发送 IPv6 报文</li></ul><p>在该例子下的一个 IPv6 地址分配过程示例（IPv6 地址中的&#x2F;64代表其前缀长度为 &#x2F;64）：</p><ul><li>新设备 C 生成链路本地地址</li><li>新设备 C 连接到路由器接口 B，发送 RS 消息，获取 RA 通告为 M&#x3D;1，O&#x3D;1，一条可用前缀（2001:1:b:0，A&#x3D;1，前缀长度64），一个可用 DNS 2001:1:b::1，源 MAC 地址 aa:bb:cc:dd:ee:ff，MTU&#x3D;1500</li><li>设备通过 SLAAC 为自己分配 IPv6 地址 2001:1:b:6666:8888::abcd&#x2F;64 和 2001:1:b:6666:8888::cdef&#x2F;64，网关 IPv6 地址为 fe80:xxxx（链路本地地址）</li><li>设备通过 DHCPv6 （IA_NA）获得地址 2001:1:b:6666::abcd&#x2F;128</li><li>设备通过 DHCPv6-PD（IA_PD）申请到子网前缀 2001:1:b:6789::&#x2F;64，路由器<strong>添加新的路由条目</strong>（将2001:1:b:6789::&#x2F;64 转发到 2001:1:b:6666:::abcd）</li></ul><h4 id="DHCPv6-PD"><a href="#DHCPv6-PD" class="headerlink" title="DHCPv6-PD"></a>DHCPv6-PD</h4><p>DHCPv6 前缀代理（PD，Prefix Delegation）允许下一级路由器通过 DHCPv6 协议获得可自行分配的子网前缀，以下内容参考<a href="https://zhuanlan.zhihu.com/p/362151770">IPV6 DHCPv6-PD 前缀子网简单拆解</a></p><ul><li>所有全球单播地址（公网 IPv6 地址）中<strong>网络前缀</strong>最长只能是64bit，不能再划分，因此路由器 wan 口至少会占一个 <code>/64</code> 子网</li><li>SLAAC只能使用64位网络前缀</li></ul><p>SLAAC、有状态 DHCPv6 、无状态 DHCPv6 和 DHCPv6-PD 可同时存在，但是只有 DHCPv6-PD 可以申请到<strong>新的子网路由</strong>。在获取到 PD 前缀后，路由器可以将其下发给 LAN 口使用，这样局域网中的设备也可以有全球单播地址。</p><p>DHCPv6-PD 示例：</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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">上级 LAN 口</span><br><span class="line"></span><br><span class="line">- fdba:c244:5433::1/60</span><br><span class="line"></span><br><span class="line">本级 WAN 口</span><br><span class="line"></span><br><span class="line">- fdba:c244:5433:4::/62（DHCPv6-PD）</span><br><span class="line">- fdba:c244:5433:0:::d26/128（DHCPv6）</span><br><span class="line">- fdba:c244:5433:0:2276:93ff:fe57:647d/64（SLAAC）</span><br><span class="line"></span><br><span class="line">本级 LAN 口</span><br><span class="line"></span><br><span class="line">- fdba:c244:5433:4::1/62</span><br><span class="line"></span><br><span class="line">下级 WAN 口</span><br><span class="line"></span><br><span class="line">- fdba:c244:5433:6::/63（DHCPv6-PD）</span><br><span class="line">- fdba:c244:5433:4::3cf/128（DHCPv6）</span><br><span class="line">- fdba:c244:5433:4:cc4f:c2ff:fe67:7fa8/64（SLAAC）</span><br><span class="line"></span><br><span class="line">子网划分情况</span><br><span class="line"></span><br><span class="line">fdba:c244:5433::1/60（上级 LAN 口子网）</span><br><span class="line">  fdba:c244:5433:0::/62</span><br><span class="line">    fdba:c244:5433:0::/64（本级 WAN 口子网）</span><br><span class="line">    fdba:c244:5433:1::/64</span><br><span class="line">    fdba:c244:5433:2::/64</span><br><span class="line">    fdba:c244:5433:3::/64</span><br><span class="line">  fdba:c244:5433:4::/62（本级获取的 PD 前缀 &amp;&amp; 本级 LAN 口子网）</span><br><span class="line">   fdba:c244:5433:4::/63</span><br><span class="line">    fdba:c244:5433:4::/64（下级 WAN 口子网）</span><br><span class="line">    fdba:c244:5433:5::/64</span><br><span class="line">   fdba:c244:5433:6::/63（下级获取的 PD 前缀 &amp;&amp; 下级 LAN 口子网）</span><br><span class="line">  fdba:c244:5433:8::/62</span><br><span class="line">  fdba:c244:5433:c::/62</span><br></pre></td></tr></table></figure><h4 id="RFC-7278"><a href="#RFC-7278" class="headerlink" title="RFC 7278"></a>RFC 7278</h4><p>但很糟糕的是，在笔者的校园网环境下 SLAAC 和 DHCPv6-PD 获得的 IPv6 地址<strong>前缀相同</strong>（均为&#x2F;64）</p><p>在进行前缀委派时，上游路由器（例如通过PD拿到2001:1:a::1&#x2F;48）可以通过剔除与下游路由器通信的&#x2F;64子网，把剩下的部分分派给各下游路由器，但是在地址前缀长度到64位时，将没有可供下游分配的前缀。如果强行为路由器的LAN使用该前缀，将有两条相同的路由分别指向LAN和WAN。打开扩展前缀选项将会把WAN口转为&#x2F;128路由，并将剩余的&#x2F;64地址池交给下游分配。</p><blockquote><p>来自<a href="https://www.rfc-editor.org/rfc/rfc6603.html#section-3">RFC 6603</a>：DHCPv6 前缀委派（DHCPv6-PD）{RFC 3633} 存在<a href="https://www.rfc-editor.org/rfc/rfc3633.html#section-12.1">第 12.1 节</a>所述的明确限制：委派给请求路由器（本级）的前缀不能被委派路由器（上一级）使用。该限制意味着委派路由器将拥有两条（不可聚合的）指向客户的路由：一条用于请求路由器与委派路由器间的链路，另一条用于请求路由器背后的客户站点。</p></blockquote><blockquote><p>来自<a href="https://www.rfc-editor.org/rfc/rfc3633.html#section-12.1">RFC 3633</a>：the requesting router MUST NOT assign any delegated prefixes or subnets from the delegated prefix(es) to the link through which it received the DHCP message  from the delegating router 请求路由器不得将任何从委派前缀（或其子网）中分配的前缀，用于分配给它通过同一链路（即从委派路由器接收 DHCP 消息的那条链路）的接口</p></blockquote><blockquote><p>来自<a href="https://www.rfc-editor.org/rfc/rfc6603.html#section-4.1">RFC 6603</a>：The OPTION_PD_EXCLUDE option allows prefix delegation where a requesting router is delegated a prefix (e.g., &#x2F;56) and the delegating router uses one prefix (e.g., &#x2F;64) on the link through which it exchanges DHCPv6 messages with the requesting router with a prefix out of the same delegated prefix set. OPTION_PD_EXCLUDE 选项允许在前缀委派中进行如下情况：请求路由器被委派一个前缀（例如 &#x2F;56），而委派路由器在与其交换 DHCPv6 消息的链路上（即 WAN 链路）使用同一委派前缀集合中的某个前缀（例如 &#x2F;64）</p></blockquote><blockquote><p>来自<a href="https://www.rfc-editor.org/rfc/rfc6603.html#section-4.1">RFC 6603</a>：The delegating router includes the prefix in the OPTION_PD_EXCLUDE option that is excluded from the delegated prefix set.  The requesting router MUST NOT assign the excluded prefix to any of its downstream interfaces. 委托路由器在 OPTION_PD_EXCLUDE 选项中包含需要排除的前缀，该前缀将从所分配的前缀集合中被剔除。请求路由器不得将所排除的前缀分配给任何下游接口</p></blockquote><p>当然这个问题直接打开扩展前缀选项就可以解决（RFC 7278）</p><h4 id="实现局域网获取全球单播地址"><a href="#实现局域网获取全球单播地址" class="headerlink" title="实现局域网获取全球单播地址"></a>实现局域网获取全球单播地址</h4><p>对于有 PD 的情况，路由器会自动将可用的 PD 前缀分配给 lan 使用</p><p>对于没有 PD 的情况，要实现局域网获取全球单播地址，就需要将局域网设备暴露到上层网络中</p><ul><li>启用 RA 中继，为局域网内设备转发上层网络 RA 消息</li><li>启用 DHCPv6 中继，为局域网内设备转发上层网络 DHCPv6 消息</li><li>启用 NDP 中继，向上层网络转发局域网设备 IPv6地址 与 mac 对应关系</li></ul><p>这种方法在上层网络看来，会有多个 IPv6 地址（<code>/64</code> 前缀）对应到同一个 MAC 地址（路由器 wan 口），因此在某些网络下不可行。在一个 MAC 地址只能对应一个 IPv6 地址（<code>/64</code> 前缀）的情况下，只能通过 NAT6 为局域网分配地址</p><p>更新：之前提到限制MAC的情况，这里看到另一种上游错误配置导致丢包的情况 <a href="https://blog.icpz.dev/articles/notes/odhcpd-relay-mode-discuss/#%E6%A1%88%E4%BE%8B%E8%AF%B4%E6%98%8E">https://blog.icpz.dev/articles/notes/odhcpd-relay-mode-discuss/#%E6%A1%88%E4%BE%8B%E8%AF%B4%E6%98%8E</a></p><p>更新：还有一种情况适合 NAT66，那就是运营商的 PD 前缀有效期很短，失效后会导致子网设备 IPv6 地址失效</p><p>更新：NAT66 在 24.10.2 上的配置已经十分简单：<br><a href="https://openwrt.org/docs/guide-user/network/ipv6/ipv6_extras">https://openwrt.org/docs/guide-user/network/ipv6/ipv6_extras</a><br><a href="https://openwrt.org/docs/guide-user/network/ipv6/ipv6.nat6">https://openwrt.org/docs/guide-user/network/ipv6/ipv6.nat6</a></p><ul><li>DNS 不过滤 IPv6 AAAA 记录</li><li>在上游防火墙区域启用 IPv6 伪装功能+MSS钳制</li><li>在上游接口禁用 IPv6 源地址过滤 uci set network.wan6.sourcefilter&#x3D;”0”</li><li>注意性能问题 <a href="https://github.com/openwrt/openwrt/issues/15623">https://github.com/openwrt/openwrt/issues/15623</a> 该问题在 24.10.0-rc1 中修复</li><li>ULA 地址优先级比 IPv4 低，要默认使用 IPv6需要更改 ULA 地址 <a href="https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml">https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml</a></li></ul><h3 id="桥接配置"><a href="#桥接配置" class="headerlink" title="桥接配置"></a>桥接配置</h3><blockquote><p>操作要考虑清楚，否则可能连不上路由器</p></blockquote><ul><li>修改路由器局域网 IP 网段（桥接不需要）: <code>网络 / 接口 / 接口选项卡</code> lan 端口 <code>编辑 / 常规设置</code> IPv4 地址 改为需要的路由器自身IP，配合下面的子网掩码生效</li><li>在局域网上禁用 DHCP 服务器: <code>网络 / 接口 / 接口选项卡</code> lan 端口 <code>编辑 / DHCP 服务器 / 常规设置</code> 打开 <code>忽略此端口</code></li><li>将路由器自身 IP 改为 DHCP 获取: <code>网络 / 接口 / 接口选项卡</code> lan 端口 <code>编辑 / 常规设置</code> 协议 选择 <code>DHCP 客户端</code></li><li>不应当简单关闭 wan 口的 IP 动态伪装（即 NAT 功能），会造成往返非对称路由: <code>网络 / 防火墙 / 常规设置选项卡 / 区域</code> wan区域 <code>编辑 / 常规设置</code> 打开 <code>IP 动态伪装</code></li></ul><h3 id="802-11s-配置"><a href="#802-11s-配置" class="headerlink" title="802.11s 配置"></a>802.11s 配置</h3><blockquote><p>这是关于真正的 mesh 的配置，定义参考<a href="https://openwrt.org/zh/docs/guide-user/network/wifi/mesh/80211s">官网教程</a></p></blockquote><ul><li>WiFi可用频段受国家代码限制（不符合系统日志里会报错），可配置为CN: <code>网络 / 无线</code> 选择网络 <code>编辑 / 设备配置 / 常规设置</code> 国家代码 选择 <code>CN</code></li><li>不同 mesh 节点 同页面位置 模式 信道 通道宽度 应一致</li><li>启用 802.11s 模式: <code>网络 / 无线</code> 选择网络 <code>编辑 / 接口配置 / 常规设置</code> 选择 <code>802.11s</code></li><li>加入 lan 网络: 同页面位置 网络 选择 <code>lan</code>（会自动被桥接，非桥接网络如 wan，原设备可能会被替换掉）</li></ul><p>802.11s 为二层网络，如果配置好以后没有关掉其他节点 lan 网络的 DHCP 服务器，新加入其他节点的设备 DHCP 请求会被抢答</p><p>除此之外还可以通过 WDS 来实现无线桥接，一方设置为接入点 AP（WDS），另一方设置为客户端（WDS）即可实现二层桥接</p><p>当然 ad-hoc 也是一种组网方式，但是笔者不懂</p><h3 id="DSA-架构下的-VLAN-配置"><a href="#DSA-架构下的-VLAN-配置" class="headerlink" title="DSA 架构下的 VLAN 配置"></a>DSA 架构下的 VLAN 配置</h3><p>位置: <code>网络 / 接口 / 设备选项卡</code> 选择交换机设备（br-lan）<code>配置 / 网桥 VLAN 过滤</code></p><p>DSA 架构可以让 Linux 在 CPU 侧区分流量来自具体哪个交换机端口，因此在 DSA 架构下 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><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"># refer: https://docs.kernel.org/networking/dsa/dsa.html</span><br><span class="line"></span><br><span class="line">        +----------------------------------+</span><br><span class="line">        |        +------------------------+|</span><br><span class="line">        |        |         br-lan         ||</span><br><span class="line">        |        +--|^-------|^-------|^--+|</span><br><span class="line">        |+-----+ +--v|--+ +--v|--+ +--v|--+|</span><br><span class="line">        || wan | | lan1 | | lan2 | | lan3 ||</span><br><span class="line">        |+-----+-+------+-+------+-+------+|</span><br><span class="line">        |             DSA驱动程序           | 软件</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">        |             | eth0 |             |</span><br><span class="line">        |             +------+             |</span><br><span class="line">        |         网络接口驱动程序           | 软件</span><br><span class="line">--------+----------------------------------+------------</span><br><span class="line">        |          CPU侧网络接口            | 硬件</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">        |             | eth0 |             |</span><br><span class="line">        |             +------+             |</span><br><span class="line">        |               交换机              |</span><br><span class="line">        |+-----+ +------+ +------+ +------+|</span><br><span class="line">        || wan | | lan1 &lt;=&gt; lan2 &lt;=&gt; lan3 || # br-lan 控制桥接 lan1 lan2 lan3</span><br><span class="line">        ++-----+-+------+-+------+-+------++</span><br></pre></td></tr></table></figure><p>在 DSA 架构下配置 VLAN 时，首先要理清以下几点：</p><ul><li>下述 物理端口 &#x2F; 交换机上的xxx端口 指物理交换机上的真实网络接口（在上图中硬件部分）</li><li>下述 虚拟接口 &#x2F; Linux 下的xxx接口 指在 Linux 下可见（或 Luci 中展示）的 交换机端口（在上图中软件部分）</li></ul><h4 id="对于虚拟接口"><a href="#对于虚拟接口" class="headerlink" title="对于虚拟接口"></a>对于虚拟接口</h4><ul><li>Linux 下的 eth0 是真实的 CPU 侧与交换机通信的接口，该接口由 DSA 驱动收发带有特定交换机标识（用于区分来源，不是 VLAN tag）的二层数据，不应该直接访问。</li><li>所有 <strong>Linux 下</strong>可见的交换机接口（wan、lan1..3）都<strong>不是</strong>交换机上的真实端口，而是对交换机（按来源端口区分后）<strong>发往 CPU 数据</strong>抽象出的虚拟接口，区分工作由 DSA 驱动通过解析特定交换机标识完成。</li><li>Linux 下没有交换机上 eth0 物理端口对应的虚拟接口</li></ul><h4 id="对于转发策略"><a href="#对于转发策略" class="headerlink" title="对于转发策略"></a>对于转发策略</h4><ul><li>所有 <strong>Linux 下</strong>可见的虚拟接口（br-lan，wan，lan1..3）都隐式的包含主机端（就是流量可被转发至 CPU 侧）。简单理解的话，br-lan + wan就相当于上文提到的交换机上 eth0 物理端口对应的虚拟接口，毕竟概念上这些虚拟接口就是对应的物理接口转发到 CPU 侧的抽象（可以理解为相当于eth0-from-wan，eth0-from-br-lan）</li><li>所有<strong>没有被桥接</strong>的 <strong>Linux 下</strong>可见的虚拟接口对应的物理端口均只转发至 Linux 下的接口（即对应的虚拟接口）。如果同一台交换机上的物理端口对应的虚拟接口没有通过 Linux 下的桥接设备连接，即使它们在同一台交换机上，也不会相互转发</li><li>Linux 下的桥接设备<strong>唯一的控制</strong>哪些<strong>Linux 下的接口对应的物理端口</strong>在二层互通。如果桥接的物理端口在同一台交换机上，会自动开启硬件offload，流量<strong>不经过</strong> Linux 中转。而没有在同一个物理交换机上的端口被桥接将通过 Linux 中转进行交换</li></ul><p>理清上述几条以后，就可以解释实际的接口配置：</p><ul><li>笔者的路由器工作在 DSA 架构下，在 br-lan 中没有 eth0：没有 eth0 是因为 Linux 下的 eth0 是带有特定交换机标识（用于区分来源，不是 VLAN tag）的原始网络接口，在套了 DSA 驱动之后变成了  Linux 下的 wan、lan1..3 接口，只需要关注这些虚拟接口即可。</li><li>笔者的路由器默认没有启用 VLAN：在 Linux 下 br-lan 仅桥接了 lan1..4，没有将它们与 wan 桥接，两者已经隔离开了</li></ul><p>在了解 DSA 架构下的 Linux 下的虚拟接口的特性后，就可以关注 VLAN 相关的特性：</p><ul><li>所有 Linux 下可见的接口都丢弃带有 VLAN tag 的报文，要处理从某端口发来的带有 VLAN tag 的报文，要套一层 802.1q 设备，这个新设备将收到的带有<strong>指定</strong> VLAN tag 的报文转为普通报文，并将发出报文加上指定 VLAN tag</li><li>交换机内部所有报文都带 VLAN tag</li></ul><p>然后就可以了解 OpenWRT 的 VLAN 配置项的含义：</p><blockquote><p>开启 VLAN 过滤时，硬件必须被编程为拒绝 VLAN ID 未在已编程允许的 VLAN ID 映射&#x2F;规则范围内的 802.1Q 帧。若交换机端口未配置 PVID（默认 VLAN），则必须同时拒绝未标记帧。关闭过滤时，交换机必须接受任何 VLAN ID 的 802.1Q 帧且允许未标记帧。</p></blockquote><ul><li>开启 VLAN 过滤：在桥接设备上启用 VLAN 功能，让交换机按配置的 VLAN 设置进行工作</li><li>已标记: 带对应的 VLAN ID 的流量可原样流入流出该端口。流入流量没有 VLAN 的将打上该端口对应的 PVID</li><li>未标记: 带对应的 VLAN ID 的流量可流入流出该端口。流出该端口的流量（注意是对应 VLAN ID）将被剥离 VLAN ID。流入流量没有 VLAN 的将打上该端口对应的 PVID</li><li>主 VLAN：即该VLAN ID 是 该端口的 PVID，一个端口最多设一个 PVID。</li><li>禁用: 该 VLAN 下禁止流入流出</li><li><a href="https://openwrt.org/docs/guide-user/network/dsa/converting-to-dsa#local">local</a>：是否为该 VLAN ID 创建对应的 802.1q 设备。没有设备的话不会被转发到 CPU 侧</li></ul><p>还有一点需要注意的边际情况，你可以<a href="https://openwrt.org/docs/guide-user/network/dsa/converting-to-dsa#egress_untagged_egress_tagged_and_pvid_examples">点击连接查看详细的例子</a>：</p><ul><li>一个端口有两个或以上 未标记 时<a href="https://openwrt.org/docs/guide-user/network/dsa/converting-to-dsa#untagged_tagged_and_pvid">才需要设置 PVID</a>，没有设置的话默认为 VID 较小的</li><li>一个端口没有 未标记 时，流入的无 VLAN 流量将被丢弃。除非该端口设置了 PVID</li><li>不同 VLAN 如果设置了 local，路由器将<strong>根据防火墙设置</strong>在三层上不同防火墙区域间转发</li></ul><p>SG105 Pro MTU VLAN &#x2F; 端口 VLAN &#x2F; 802.11q VLAN 是三种不同的 VLAN 模式</p><p>还有链路聚合相关 kmod-bonding、proto-bonding，笔者不太了解</p><h3 id="MultiWAN-配置"><a href="#MultiWAN-配置" class="headerlink" title="MultiWAN 配置"></a>MultiWAN 配置</h3><p>安装 luci-i18n-mwan3-zh-cn iptables-nft ip6tables-nft</p><p>后面两个用于兼容高版本 OpenWRT 使用的nftable 注意默认配置会使网络不可用</p><h3 id="无线配置"><a href="#无线配置" class="headerlink" title="无线配置"></a>无线配置</h3><p>高版本 OpenWRT 下可以为同一个 SSID 设置多个密码，并导向不同 VLAN</p><p>参考 <a href="https://forum.openwrt.org/t/individual-per-passphrase-per-mac-wifi-vlans-using-wpa-psk-file-no-radius-required/161696">https://forum.openwrt.org/t/individual-per-passphrase-per-mac-wifi-vlans-using-wpa-psk-file-no-radius-required/161696</a> 有非常详细的讨论和介绍</p>]]></content>
    
    
    <summary type="html">记录了笔者 OpenWRT 24.10.1 的配置过程和研究笔记</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>为 Hexo Next 主题集成 Cusdis 评论组件</title>
    <link href="https://york.moe/2025/06/26/hexo-next-cusdis/"/>
    <id>https://york.moe/2025/06/26/hexo-next-cusdis/</id>
    <published>2025-06-26T18:57:00.000Z</published>
    <updated>2026-05-26T11:39:58.867Z</updated>
    
    <content type="html"><![CDATA[<p>因为本站为纯静态站点，为了让博客能评论，找了一个可以托管的评论服务 <a href="https://cusdis.com/">Cusdis</a></p><p>Next 主题默认不提供 Cusdis 的集成，所以简单搓了一个</p><h2 id="配置修改"><a href="#配置修改" class="headerlink" title="配置修改"></a>配置修改</h2><h3 id="新建评论模板"><a href="#新建评论模板" class="headerlink" title="新建评论模板"></a>新建评论模板</h3><ul><li>添加了中文语言（没写自适应倒是）</li><li>参考 <a href="https://github.com/djyde/cusdis/issues/283">issue</a> 修正了容器高度</li></ul><p>在 <code>source/_data/post-body-end.swig</code> 中：</p><figure class="highlight html"><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">&#123;% if theme.cusdis.enable %&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">hr</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h3</span>&gt;</span>评论<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;cusdis_thread&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">data-host</span>=<span class="string">&quot;&#123;&#123; theme.cusdis.host &#125;&#125;&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">data-app-id</span>=<span class="string">&quot;&#123;&#123; theme.cusdis.app_id &#125;&#125;&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">data-page-id</span>=<span class="string">&quot;&#123;&#123; page.path &#125;&#125;&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">data-page-url</span>=<span class="string">&quot;&#123;&#123; page.permalink &#125;&#125;&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">data-page-title</span>=<span class="string">&quot;&#123;&#123; page.title &#125;&#125;&quot;</span></span></span><br><span class="line"><span class="tag">      <span class="attr">style</span>=<span class="string">&quot;display: flex; height: 24em;&quot;</span></span></span><br><span class="line"><span class="tag">    &gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">async</span> <span class="attr">defer</span> <span class="attr">src</span>=<span class="string">&quot;https://cusdis.com/js/widget/lang/zh-cn.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">async</span> <span class="attr">defer</span> <span class="attr">src</span>=<span class="string">&quot;https://cusdis.com/js/cusdis.es.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">&#123;% endif %&#125;</span><br></pre></td></tr></table></figure><h3 id="配置-Next-主题"><a href="#配置-Next-主题" class="headerlink" title="配置 Next 主题"></a>配置 Next 主题</h3><p>cusdis 这部分对应评论模板中的内容</p><p>在 <code>_config.next.yml</code> 中：</p><figure class="highlight yaml"><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"><span class="attr">custom_file_path:</span></span><br><span class="line">  <span class="attr">postBodyEnd:</span> <span class="string">source/_data/post-body-end.swig</span></span><br><span class="line"><span class="attr">cusdis:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">host:</span> <span class="comment"># 你的 cusdis 实例地址</span></span><br><span class="line">  <span class="attr">app_id:</span> <span class="comment"># 你的 app_id</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">记录了笔者为 Hexo Next 主题集成 Cusdis 评论组件的过程</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>某光猫提权过程</title>
    <link href="https://york.moe/2024/07/06/modem_get_shell/"/>
    <id>https://york.moe/2024/07/06/modem_get_shell/</id>
    <published>2024-07-06T08:00:00.000Z</published>
    <updated>2026-05-26T11:39:58.867Z</updated>
    
    <content type="html"><![CDATA[<p>今天研究家里的光猫（KD-YUN-811G），发现默认开了 telnet，用户名 <code>telnetadmin</code>, 密码 <code>telnetadmin</code>。结果登陆上去发现是个假 shell，里面什么命令都没有，也没有 <code>sh</code> 命令。</p><p>折腾一顿发现 http 界面可以开 ftp 服务器，登陆进 ftp 可以访问到光猫文件系统。</p><p>下载下来 <code>/etc/telnetd</code>, 拖进 ida 反编译找到登陆逻辑：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// start -&gt; _ftext:</span></span><br><span class="line">    <span class="comment">/* read arguments */</span></span><br><span class="line">    <span class="comment">/* cmsMsg_initWithFlags */</span></span><br><span class="line">    <span class="comment">/* cmsMdm_initWithAcc */</span></span><br><span class="line">    <span class="comment">/* initLoggingFromConfig */</span></span><br><span class="line">    <span class="comment">/* make_new_session */</span> &#123;</span><br><span class="line">        cmsCli_printWelcomeBanner();</span><br><span class="line">        <span class="keyword">if</span> (!cmsCli_Telnetauthenticate(NetworkAccessMode_igd, <span class="number">600</span>)) &#123;</span><br><span class="line">            cmsCli_run(msgHandle, <span class="number">600</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这些函数都在 <code>libcms_cli.so</code> 中，这里重点关注 <code>cmsCli_run</code>: 参考<a href="https://github.com/ad7843/hi/blob/master/cli.c#L277">网上的源码</a>可知处理输入的逻辑在 <code>processInput</code> 中:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// processInput</span></span><br><span class="line">    <span class="keyword">if</span> (input[<span class="number">0</span>]) &#123;</span><br><span class="line">        <span class="keyword">if</span> ( cli_processCliCmd(input) != <span class="number">1</span> &amp;&amp; cli_processHiddenCmd(input) != <span class="number">1</span> )</span><br><span class="line">            log_log(<span class="number">3</span>, <span class="string">&quot;processInput&quot;</span>, <span class="number">393</span>, <span class="string">&quot;unrecognized command %s&quot;</span>, v3);</span><br><span class="line">        <span class="comment">/* XXXXXX */</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>再看看 <code>cli_processHiddenCmd</code>:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// cli_processHiddenCmd</span></span><br><span class="line">    <span class="keyword">if</span> ( strncasecmp(input, <span class="string">&quot;muma@door&quot;</span>, <span class="built_in">strlen</span>(input)) ) &#123;</span><br><span class="line">      prctl_runCommandInShellBlocking(input);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>输入 <code>muma@door</code>，发现其实是 <code>busybox</code>，这样就可以正常使用命令了:<br><img src="https://s2.loli.net/2024/07/06/bkwLeUxztumJ4Rc.png" alt="结果截图"></p>]]></content>
    
    
    <summary type="html">研究了下家里的光猫KD-YUN-811G，找到了打开shell的方法</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>ADOC 实现分析</title>
    <link href="https://york.moe/2024/01/16/ADOC_impl_analyze/"/>
    <id>https://york.moe/2024/01/16/ADOC_impl_analyze/</id>
    <published>2024-01-16T08:23:00.000Z</published>
    <updated>2026-05-26T11:39:58.867Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文基于仓库 <a href="https://github.com/supermt/FEAT_7.11">FEAT_7.11</a></p><p>base(FEAT_7.11): <a href="https://github.com/supermt/FEAT_7.11/tree/5fbcc8c54d4a8704405b568d53faf23cd0722eeb">5fbcc8c54d4a8704405b568d53faf23cd0722eeb</a></p><p>base(rocksdb): <a href="https://github.com/facebook/rocksdb/tree/5fbcc8c54d4a8704405b568d53faf23cd0722eeb">5fbcc8c54d4a8704405b568d53faf23cd0722eeb</a></p></blockquote><h2 id="tools-db-bench-tool-cc"><a href="#tools-db-bench-tool-cc" class="headerlink" title="tools&#x2F;db_bench_tool.cc"></a><a href="https://github.com/supermt/FEAT_7.11/blob/main/tools/db_bench_tool.cc#L774">tools&#x2F;db_bench_tool.cc</a></h2><figure class="highlight cpp"><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 class="comment">// 相关选项</span></span><br><span class="line"><span class="comment">// @db_bench_tool.cc:783</span></span><br><span class="line"><span class="built_in">DEFINE_bool</span>(DOTA_enabled, <span class="literal">false</span>, <span class="string">&quot;Whether trigger the DOTA framework&quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_bool</span>(FEA_enable, <span class="literal">false</span>, <span class="string">&quot;Trigger FEAT tuner&#x27;s FEA component&quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_bool</span>(TEA_enable, <span class="literal">false</span>, <span class="string">&quot;Trigger FEAT tuner&#x27;s TEA component&quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_int32</span>(SILK_bandwidth_limitation, <span class="number">200</span>, <span class="string">&quot;MBPS of disk limitation&quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_bool</span>(SILK_triggered, <span class="literal">false</span>, <span class="string">&quot;Whether the SILK tuner is triggered&quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_double</span>(idle_rate, <span class="number">1.25</span>,</span><br><span class="line">              <span class="string">&quot;TEA will decide this as the idle rate of the threads&quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_double</span>(FEA_gap_threshold, <span class="number">1.5</span>,</span><br><span class="line">              <span class="string">&quot;The negative feedback loop&#x27;s threshold&quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_double</span>(TEA_slow_flush, <span class="number">0.5</span>, <span class="string">&quot;The negative feedback loop&#x27;s threshold&quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_double</span>(DOTA_tuning_gap, <span class="number">1.0</span>, <span class="string">&quot;Tuning gap of the DOTA agent, in secs &quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_int64</span>(random_fill_average, <span class="number">150</span>,</span><br><span class="line">             <span class="string">&quot;average inputs rate of background write operations&quot;</span>);</span><br><span class="line"><span class="built_in">DEFINE_bool</span>(detailed_running_stats, <span class="literal">false</span>,</span><br><span class="line">            <span class="string">&quot;Whether record more detailed information in report agent&quot;</span>);</span><br></pre></td></tr></table></figure><figure class="highlight cpp"><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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 设置相关 flag 后, 初始化局部变量 reporter_agent 为自定义的实现</span></span><br><span class="line"><span class="comment">// rocksdb::Benchmark::RunBenchmark</span></span><br><span class="line"><span class="comment">//     @db_bench_tool.cc:3854</span></span><br><span class="line">std::unique_ptr&lt;ReporterAgent&gt; reporter_agent;</span><br><span class="line"><span class="keyword">if</span> (FLAGS_report_interval_seconds &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> ( FLAGS_DOTA_enabled || FLAGS_TEA_enable ||</span><br><span class="line">        FLAGS_FEA_enable) &#123;</span><br><span class="line">    <span class="comment">// need to use another Report Agent</span></span><br><span class="line">    <span class="keyword">if</span> (FLAGS_DOTA_tuning_gap == <span class="number">0</span>) &#123;</span><br><span class="line">        reporter_agent.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">ReporterAgentWithTuning</span>(</span><br><span class="line">            <span class="built_in">reinterpret_cast</span>&lt;DBImpl*&gt;(db_.db), FLAGS_env, FLAGS_report_file,</span><br><span class="line">            FLAGS_report_interval_seconds, FLAGS_report_interval_seconds));</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        reporter_agent.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">ReporterAgentWithTuning</span>(</span><br><span class="line">            <span class="built_in">reinterpret_cast</span>&lt;DBImpl*&gt;(db_.db), FLAGS_env, FLAGS_report_file,</span><br><span class="line">            FLAGS_report_interval_seconds, FLAGS_DOTA_tuning_gap));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">auto</span> tuner_agent =</span><br><span class="line">        <span class="built_in">reinterpret_cast</span>&lt;ReporterAgentWithTuning*&gt;(reporter_agent.<span class="built_in">get</span>());</span><br><span class="line">    tuner_agent-&gt;<span class="built_in">UseFEATTuner</span>(FLAGS_TEA_enable, FLAGS_FEA_enable);</span><br><span class="line">    tuner_agent-&gt;<span class="built_in">GetTuner</span>()-&gt;<span class="built_in">set_idle_ratio</span>(FLAGS_idle_rate);</span><br><span class="line">    tuner_agent-&gt;<span class="built_in">GetTuner</span>()-&gt;<span class="built_in">set_gap_threshold</span>(FLAGS_FEA_gap_threshold);</span><br><span class="line">    tuner_agent-&gt;<span class="built_in">GetTuner</span>()-&gt;<span class="built_in">set_slow_flush_threshold</span>(FLAGS_TEA_slow_flush);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (FLAGS_detailed_running_stats) &#123;</span><br><span class="line">    reporter_agent.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">ReporterWithMoreDetails</span>(</span><br><span class="line">        <span class="built_in">reinterpret_cast</span>&lt;DBImpl*&gt;(db_.db), FLAGS_env, FLAGS_report_file,</span><br><span class="line">        FLAGS_report_interval_seconds));</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (FLAGS_SILK_triggered) &#123;</span><br><span class="line">    reporter_agent.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">ReporterAgentWithSILK</span>(</span><br><span class="line">        <span class="built_in">reinterpret_cast</span>&lt;DBImpl*&gt;(db_.db), FLAGS_env, FLAGS_report_file,</span><br><span class="line">        FLAGS_report_interval_seconds, FLAGS_value_size,</span><br><span class="line">        FLAGS_SILK_bandwidth_limitation));</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123; <span class="comment">/* 注: 这里是原始的 ReporterAgent */</span></span><br><span class="line">    reporter_agent.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">ReporterAgent</span>(FLAGS_env, FLAGS_report_file,</span><br><span class="line">                                            FLAGS_report_interval_seconds));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="utilities-DOTA-report-agent-cc"><a href="#utilities-DOTA-report-agent-cc" class="headerlink" title="utilities&#x2F;DOTA&#x2F;report_agent.cc"></a><a href="https://github.com/supermt/FEAT_7.11/blob/main/utilities/DOTA/report_agent.cc">utilities&#x2F;DOTA&#x2F;report_agent.cc</a></h2><h3 id="启动路径"><a href="#启动路径" class="headerlink" title="启动路径"></a>启动路径</h3><blockquote><p>作者将 <code>ReporterAgent</code> 的定义 从 <code>tools/db_bench_tool.cc</code> 移动到了 <code>include/rocksdb/utilities/report_agent.h</code> 中</p></blockquote><p>该实现在 <code>ReporterAgent</code> 构造函数中, 启动了一个新的线程, 主要逻辑为周期性的执行 <code>DetectAndTuning</code> 函数</p><figure class="highlight cpp"><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 class="comment">// rocksdb::ReporterAgent::ReporterAgent</span></span><br><span class="line"><span class="comment">//     @report_agent.h:67</span></span><br><span class="line">reporting_thread_ = port::<span class="built_in">Thread</span>([&amp;]() &#123; <span class="built_in">SleepAndReport</span>(); &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// @report_agent.h:92</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">SleepAndReport</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="comment">// sleep report_interval_secs_</span></span><br><span class="line">        ...</span><br><span class="line">        <span class="built_in">DetectAndTuning</span>(secs_elapsed);</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 <code>ReporterAgent::DetectAndTuning</code> 为虚函数, 该类本身 将其实现为空函数; 该函数在 <code>ReporterAgent</code> 的三个子类中被 override, 主要逻辑在 <code>ReporterAgentWithTuning::DetectAndTuning</code> 中</p><figure class="highlight cpp"><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 class="comment">// @report_agent.cc:40</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">ReporterAgentWithTuning::DetectAndTuning</span><span class="params">(<span class="type">int</span> secs_elapsed)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (secs_elapsed % tuning_gap_secs_ == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="built_in">DetectChangesPoints</span>(secs_elapsed);</span><br><span class="line">    <span class="comment">//    this-&gt;running_db_-&gt;immutable_db_options().job_stats-&gt;clear();</span></span><br><span class="line">    last_metrics_collect_secs = secs_elapsed;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (tuning_points.<span class="built_in">empty</span>() ||</span><br><span class="line">      tuning_points.<span class="built_in">front</span>().change_timing &lt; secs_elapsed) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">PopChangePoints</span>(secs_elapsed);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// @report_agent.cc:182</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">ReporterWithMoreDetails::DetectAndTuning</span><span class="params">(<span class="type">int</span> secs_elapsed)</span> </span>&#123;</span><br><span class="line">  <span class="comment">//  report_file_-&gt;Append(&quot;,&quot;);</span></span><br><span class="line">  <span class="comment">//  ReportLine(secs_elapsed, total_ops_done_);</span></span><br><span class="line">  secs_elapsed++;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 没有 ReporterAgentWithSILK::DetectAndTuning</span></span><br></pre></td></tr></table></figure><blockquote><p>作者在仓库 <a href="https://github.com/supermt/FEAT_7.11/blob/main/README.md">README</a> 中提到:</p><p>The SILK implementation in this repo is a placeholder, since we failed to reproduce the read performance as mentioned in its paper.</p></blockquote><h3 id="调节过程"><a href="#调节过程" class="headerlink" title="调节过程"></a>调节过程</h3><p>在 <code>DetectChangesPoints</code> 和 <code>PopChangePoints</code> 中, 该实现 通过 <code>tuner-&gt;DetectTuningOperations</code> 函数动态获得新的 rocksdb 的配置选项 <code>change_points</code>; 然后通过 <code>ApplyChangePointsInstantly</code> 更新数据库和列族设置选项</p><figure class="highlight cpp"><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 class="comment">// @report_agent.cc:31</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">ReporterAgentWithTuning::DetectChangesPoints</span><span class="params">(<span class="type">int</span> sec_elapsed)</span> </span>&#123;</span><br><span class="line">  std::vector&lt;ChangePoint&gt; change_points;</span><br><span class="line">  <span class="keyword">if</span> (applying_changes) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  tuner-&gt;<span class="built_in">DetectTuningOperations</span>(sec_elapsed, &amp;change_points);</span><br><span class="line">  <span class="built_in">ApplyChangePointsInstantly</span>(&amp;change_points);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// @report_agent.cc:169</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">ReporterAgentWithTuning::PopChangePoints</span><span class="params">(<span class="type">int</span> secs_elapsed)</span> </span>&#123;</span><br><span class="line">  std::vector&lt;ChangePoint&gt; valid_point;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">auto</span> it = tuning_points.<span class="built_in">begin</span>(); it != tuning_points.<span class="built_in">end</span>(); it++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (it-&gt;change_timing &lt;= secs_elapsed) &#123;</span><br><span class="line">      <span class="keyword">if</span> (running_db_ != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">        valid_point.<span class="built_in">push_back</span>(*it);</span><br><span class="line">      &#125;</span><br><span class="line">      tuning_points.<span class="built_in">erase</span>(it--);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="built_in">ApplyChangePointsInstantly</span>(&amp;valid_point);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="utilities-DOTA-DOTA-tuner-cc"><a href="#utilities-DOTA-DOTA-tuner-cc" class="headerlink" title="utilities&#x2F;DOTA&#x2F;DOTA_tuner.cc"></a><a href="https://github.com/supermt/FEAT_7.11/blob/main/utilities/DOTA/DOTA_tuner.cc">utilities&#x2F;DOTA&#x2F;DOTA_tuner.cc</a></h2><p>本文件实现了两个 Tuner: <code>FEAT_Tuner</code> 和 <code>DOTA_Tuner</code>, 需要注意 <code>FEAT_Tuner</code> 是 <code>DOTA_Tuner</code> 的子类, override 了 <code>DetectTuningOperations</code></p><h3 id="新的设置列表生成-std-vector"><a href="#新的设置列表生成-std-vector" class="headerlink" title="新的设置列表生成 (std::vector&lt;ChangePoint&gt;)"></a>新的设置列表生成 (<code>std::vector&lt;ChangePoint&gt;</code>)</h3><p>新的设置参数计算操作主要在 <code>DetectTuningOperations</code> 中, <code>DOTA_Tuner</code> 在打分和获取统计信息后调用 <code>AdjustmentTuning</code> 获得新的设置列表</p><figure class="highlight cpp"><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><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// @DOTA_tuner.cc:12</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">DOTA_Tuner::DetectTuningOperations</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="type">int</span> secs_elapsed, std::vector&lt;ChangePoint&gt; *change_list_ptr)</span> </span>&#123;</span><br><span class="line">  current_sec = secs_elapsed;</span><br><span class="line">  <span class="comment">//  UpdateSystemStats();</span></span><br><span class="line">  SystemScores current_score = <span class="built_in">ScoreTheSystem</span>();</span><br><span class="line">  <span class="built_in">UpdateMaxScore</span>(current_score);</span><br><span class="line">  scores.<span class="built_in">push_back</span>(current_score);</span><br><span class="line">  gradients.<span class="built_in">push_back</span>(current_score - scores.<span class="built_in">front</span>());</span><br><span class="line"></span><br><span class="line">  <span class="keyword">auto</span> thread_stat = <span class="built_in">LocateThreadStates</span>(current_score);</span><br><span class="line">  <span class="keyword">auto</span> batch_stat = <span class="built_in">LocateBatchStates</span>(current_score);</span><br><span class="line"></span><br><span class="line">  <span class="built_in">AdjustmentTuning</span>(change_list_ptr, current_score, thread_stat, batch_stat);</span><br><span class="line">  <span class="comment">// decide the operation based on the best behavior and last behavior</span></span><br><span class="line">  <span class="comment">// update the histories</span></span><br><span class="line">  last_thread_states = thread_stat;</span><br><span class="line">  last_batch_stat = batch_stat;</span><br><span class="line">  tuning_rounds++;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// @DOTA_tuner.cc:195</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">DOTA_Tuner::AdjustmentTuning</span><span class="params">(std::vector&lt;ChangePoint&gt; *change_list,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  SystemScores &amp;score,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  ThreadStallLevels thread_levels,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  BatchSizeStallLevels batch_levels)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// tune for thread number</span></span><br><span class="line">  <span class="comment">/* 注: 这里得到状态 */</span></span><br><span class="line">  <span class="keyword">auto</span> tuning_op = <span class="built_in">VoteForOP</span>(score, thread_levels, batch_levels);</span><br><span class="line">  <span class="comment">// tune for memtable</span></span><br><span class="line">  <span class="comment">/* 注: 这里根据状态(tuning_op)更新数据库设置列表(change_list) */</span></span><br><span class="line">  <span class="built_in">FillUpChangeList</span>(change_list, tuning_op);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>FEAT_Tuner</code> 计算状态 (TuningOP) 的方式与 <code>DOTA_Tuner</code> 不同:</p><figure class="highlight cpp"><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"><span class="function"><span class="type">void</span> <span class="title">FEAT_Tuner::DetectTuningOperations</span><span class="params">(<span class="type">int</span> <span class="comment">/*secs_elapsed*/</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        std::vector&lt;ChangePoint&gt; *change_list)</span> </span>&#123;</span><br><span class="line">  <span class="comment">//   first, we tune only when the flushing speed is slower than before</span></span><br><span class="line">  <span class="keyword">auto</span> current_score = <span class="keyword">this</span>-&gt;<span class="built_in">ScoreTheSystem</span>();</span><br><span class="line">  <span class="keyword">if</span> (current_score.flush_speed_avg == <span class="number">0</span>) <span class="keyword">return</span> ;</span><br><span class="line">  scores.<span class="built_in">push_back</span>(current_score);</span><br><span class="line">  <span class="keyword">if</span> (scores.<span class="built_in">size</span>() == <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">this</span>-&gt;<span class="built_in">UpdateMaxScore</span>(current_score);</span><br><span class="line">  <span class="keyword">if</span> (scores.<span class="built_in">size</span>() &gt;= (<span class="type">size_t</span>)<span class="keyword">this</span>-&gt;score_array_len) &#123;</span><br><span class="line">    <span class="comment">// remove the first record</span></span><br><span class="line">    scores.<span class="built_in">pop_front</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="built_in">CalculateAvgScore</span>();</span><br><span class="line"></span><br><span class="line">  current_score_ = current_score;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (current_score_.flush_speed_avg &gt;<span class="number">0</span> )&#123;</span><br><span class="line">   TuningOP result&#123;kKeep, kKeep&#125;;</span><br><span class="line">   <span class="keyword">if</span> (TEA_enable) &#123;</span><br><span class="line">      result = <span class="built_in">TuneByTEA</span>();</span><br><span class="line">   &#125;</span><br><span class="line">    <span class="keyword">if</span> (FEA_enable) &#123;</span><br><span class="line">      TuningOP fea_result = <span class="built_in">TuneByFEA</span>();</span><br><span class="line">      result.BatchOp = fea_result.BatchOp;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">FillUpChangeList</span>(change_list, result);</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="设置参数细节"><a href="#设置参数细节" class="headerlink" title="设置参数细节"></a>设置参数细节</h3><p>FillUpChangeList 会根据计算好的状态 更新参数, 然后通过 <code>SetBatchSize</code> <code>SetThreadNum</code> 追加到 <code>std::vector&lt;ChangePoint&gt; *change_list</code> 中</p><figure class="highlight cpp"><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><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// @DOTA_tuner.cc:251</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">DOTA_Tuner::SetBatchSize</span><span class="params">(std::vector&lt;ChangePoint&gt; *change_list,</span></span></span><br><span class="line"><span class="params"><span class="function">                                     <span class="type">uint64_t</span> target_value)</span> </span>&#123;</span><br><span class="line">  ChangePoint memtable_size_cp;</span><br><span class="line">  ChangePoint L1_total_size;</span><br><span class="line">  ChangePoint sst_size_cp;</span><br><span class="line">  <span class="comment">//  ChangePoint write_buffer_number;</span></span><br><span class="line"></span><br><span class="line">  sst_size_cp.opt = sst_size;</span><br><span class="line">  L1_total_size.opt = total_l1_size;</span><br><span class="line">  <span class="comment">// adjust the memtable size</span></span><br><span class="line">  memtable_size_cp.db_width = <span class="literal">false</span>;</span><br><span class="line">  memtable_size_cp.opt = memtable_size;</span><br><span class="line"></span><br><span class="line">  target_value = std::<span class="built_in">max</span>(target_value, min_memtable_size);</span><br><span class="line">  target_value = std::<span class="built_in">min</span>(target_value, max_memtable_size);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// SST sizes should be controlled to be the same as memtable size</span></span><br><span class="line">  memtable_size_cp.value = std::<span class="built_in">to_string</span>(target_value);</span><br><span class="line">  sst_size_cp.value = std::<span class="built_in">to_string</span>(target_value);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// calculate the total size of L1</span></span><br><span class="line">  <span class="type">uint64_t</span> l1_size = current_opt.level0_file_num_compaction_trigger *</span><br><span class="line">                     current_opt.min_write_buffer_number_to_merge *</span><br><span class="line">                     target_value;</span><br><span class="line"></span><br><span class="line">  L1_total_size.value = std::<span class="built_in">to_string</span>(l1_size);</span><br><span class="line">  sst_size_cp.db_width = <span class="literal">false</span>;</span><br><span class="line">  L1_total_size.db_width = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//  change_list-&gt;push_back(write_buffer_number);</span></span><br><span class="line">  change_list-&gt;<span class="built_in">push_back</span>(memtable_size_cp);</span><br><span class="line">  change_list-&gt;<span class="built_in">push_back</span>(L1_total_size);</span><br><span class="line">  change_list-&gt;<span class="built_in">push_back</span>(sst_size_cp);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// @DOTA_tuner.cc:240</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">DOTA_Tuner::SetThreadNum</span><span class="params">(std::vector&lt;ChangePoint&gt; *change_list,</span></span></span><br><span class="line"><span class="params"><span class="function">                                     <span class="type">int</span> target_value)</span> </span>&#123;</span><br><span class="line">  ChangePoint thread_num_cp;</span><br><span class="line">  thread_num_cp.opt = max_bg_jobs;</span><br><span class="line">  thread_num_cp.db_width = <span class="literal">true</span>;</span><br><span class="line">  target_value = std::<span class="built_in">max</span>(target_value, min_thread);</span><br><span class="line">  target_value = std::<span class="built_in">min</span>(target_value, max_thread);</span><br><span class="line">  thread_num_cp.value = std::<span class="built_in">to_string</span>(target_value);</span><br><span class="line">  change_list-&gt;<span class="built_in">push_back</span>(thread_num_cp);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">阅读 ADOC 实现时的笔记, 列举了一些主要的函数</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>RocksDB 内部结构浅析</title>
    <link href="https://york.moe/2024/01/10/rocksdb-innards/"/>
    <id>https://york.moe/2024/01/10/rocksdb-innards/</id>
    <published>2024-01-10T14:39:31.000Z</published>
    <updated>2026-05-26T11:39:58.867Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文基于 RocksDB v6.8.1 版本</p></blockquote><h2 id="键值的内部编码"><a href="#键值的内部编码" class="headerlink" title="键值的内部编码"></a>键值的内部编码</h2><p>RocksDB 内部将用户提供的键值对 称为 <code>user_key</code>, 并在内部将其编码为 <code>internal_key</code>. 编码过程发生在 <code>MemTable::Add</code> 中, <code>internal_key</code> 的编码过程大致如下:</p><figure class="highlight cpp"><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 class="type">uint32_t</span> key_size = <span class="built_in">static_cast</span>&lt;<span class="type">uint32_t</span>&gt;(key.<span class="built_in">size</span>());</span><br><span class="line"><span class="type">uint32_t</span> val_size = <span class="built_in">static_cast</span>&lt;<span class="type">uint32_t</span>&gt;(value.<span class="built_in">size</span>());</span><br><span class="line"><span class="type">uint32_t</span> internal_key_size = key_size + <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">uint64_t</span> packed = <span class="built_in">PackSequenceAndType</span>(SequenceNumber s, ValueType type);</span><br><span class="line"></span><br><span class="line"><span class="comment">// internal_key 结构示意</span></span><br><span class="line"><span class="built_in">Varint32</span>(internal_key_size) + key + <span class="built_in">Fixed64</span>(packed) + <span class="built_in">Varint32</span>(val_size) + value;</span><br></pre></td></tr></table></figure><p>其将序列号(<code>SequenceNumber s</code>)和键值类型(<code>ValueType type</code>) 编码为一个8字节整数, 并添加到 <code>internal_key</code> 内部.</p><p>因此 <code>internal_key</code> 的整体排布如下:</p><figure class="highlight text"><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">|user_key|timestamp|seqno|type|</span><br><span class="line">|&lt;-------internal_key--------&gt;|</span><br></pre></td></tr></table></figure><p><code>Memtable</code> 提供的比较器 <code>Memtable::KeyComparator comparator_</code> 在进行比较时, 只使用 <code>InternalKeyComparator comparator</code> 比较 <code>key + Fixed64(packed)</code> 这部分, 其中 <code>key</code> 部分 <code>InternalKeyComparator</code> 使用 用户指定的 <code>Comparator user_comparator_</code> 进行比较.</p><p>值得注意的是 RocksDB 支持 <a href="https://github.com/facebook/rocksdb/wiki/User-defined-Timestamp">User-defined-Timestamp</a> 功能, 这个功能允许用户为键值对插入一个可指定长度的时间戳, 并可指定一个 <code>Timestamp-aware</code> 的 <code>Comparator</code>. 时间戳部分在进入 <code>MemTable::Add</code> 时是<strong>已经编码在 <code>key</code> 中的尾部</strong>的, 其比较过程直接由 <code>Comparator::Compare</code> 处理.</p><h2 id="键值的内部类型"><a href="#键值的内部类型" class="headerlink" title="键值的内部类型"></a>键值的内部类型</h2><p>键值类型 <code>ValueType</code> 定义在 <code>db/dbformat.h</code> 中, 其主要类型如下:</p><figure class="highlight cpp"><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"><span class="keyword">enum</span> <span class="title class_">ValueType</span> : <span class="type">unsigned</span> <span class="type">char</span> &#123;</span><br><span class="line">    kTypeDeletion = <span class="number">0x0</span>,</span><br><span class="line">    kTypeValue = <span class="number">0x1</span>,</span><br><span class="line">    kTypeMerge = <span class="number">0x2</span>,</span><br><span class="line">    ...</span><br><span class="line">    kTypeSingleDeletion = <span class="number">0x7</span>,</span><br><span class="line">    ...</span><br><span class="line">    kTypeRangeDeletion = <span class="number">0xF</span>,  <span class="comment">// meta block</span></span><br><span class="line">    ...</span><br><span class="line">    kTypeBlobIndex = <span class="number">0x11</span>,     <span class="comment">// Blob DB only</span></span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>其中<code>kTypeDeletion</code>, <code>kTypeValue</code>, <code>kTypeMerge</code>, <code>kTypeBlobIndex</code> 及 <code>kTypeSingleDeletion</code> 为 “值类型”</p><figure class="highlight cpp"><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 class="comment">// Checks whether a type is an inline value type</span></span><br><span class="line"><span class="comment">// (i.e. a type used in memtable skiplist and sst file datablock).</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">bool</span> <span class="title">IsValueType</span><span class="params">(ValueType t)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> t &lt;= kTypeMerge || t == kTypeSingleDeletion || t == kTypeBlobIndex;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>具体实现中, RocksDB 将 ValueType 的数据与 <code>kTypeRangeDeletion</code> 的数据分开存储, 这些类型被合称为 “扩展值类型”</p><figure class="highlight cpp"><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 class="comment">// Checks whether a type is from user operation</span></span><br><span class="line"><span class="comment">// kTypeRangeDeletion is in meta block so this API is separated from above</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">bool</span> <span class="title">IsExtendedValueType</span><span class="params">(ValueType t)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">IsValueType</span>(t) || t == kTypeRangeDeletion;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>kTypeMerge</code>, <code>kTypeSingleDeletion</code> 及 <code>kTypeRangeDeletion</code> 类型分别对应 RocksDB 提供的 <a href="https://github.com/facebook/rocksdb/wiki/Merge-Operator">Merge</a> 和 <a href="https://github.com/facebook/rocksdb/wiki/Single-Delete">Single-Delete</a> 以及 <a href="https://github.com/facebook/rocksdb/wiki/DeleteRange">DeleteRange</a> 操作, 此处不再赘述.</p><h2 id="键值的存储过程"><a href="#键值的存储过程" class="headerlink" title="键值的存储过程"></a>键值的存储过程</h2><h3 id="WAL"><a href="#WAL" class="headerlink" title="WAL"></a>WAL</h3><p>这部分还没仔细看, 鸽了.</p><h3 id="MemTable"><a href="#MemTable" class="headerlink" title="MemTable"></a>MemTable</h3><p>键值对通过 <code>MemTable::Add</code> 添加到 <code>Memtable</code> 中, 在编码为 <code>internal_key</code> 后, <code>Memtable</code> 根据值的类型对其进行存储.</p><p><code>Memtable</code> 默认的存储结构为跳表(<code>SkipListRep : MemTableRep</code>), RocksDB 支持的所有存储结构均继承了 <code>MemTableRep</code> (memtable representation). 完整的 <code>MemTableRep</code> 需实现内存分配(<code>Allocate</code>), 插入(<code>Insert</code>), 读取(<code>Get</code>) 以及 迭代器(<code>GetIterator</code>) 等操作, <code>Memtable</code> 通过这些操作来实现最终的键值插入和查找功能.</p><p>在 <code>MemTable::Add</code> 中, 值类型(<code>IsValueType() == true</code>) 放置在 <code>MemTableRep table_</code> 中, <code>kTypeRangeDeletion</code> 类型放置在 <code>MemTableRep range_del_table_</code> 中. 值得注意的一点是 <code>range_del_table_</code> 总是 <code>SkipListRep</code>.</p><p>在满足条件后, <code>DBImpl::SwitchMemtable</code> 将被调用, 将 <code>Memtable</code> 变为 Immutable memtable. 具体过程就是通过 <code>MemTableList::Add</code> 将 <code>Memtable</code> 放入 <code>MemTableList cfd-&gt;imm()</code>, 在 <code>MemTableList::Add</code> 中还会调用 <code>Memtable::MarkImmutable</code> 通知 Memtable.</p><h3 id="Flush-Compaction"><a href="#Flush-Compaction" class="headerlink" title="Flush &amp; Compaction"></a>Flush &amp; Compaction</h3><p>Flush 过程主要在 <code>FlushJob::WriteLevel0Table</code> 中, 笔者认为其与 Compaction 过程的最大不同只是其构建 SST 的数据来源不同.</p><p>在 Flush 过程中, 所有待 Flush 的 <code>Memtable</code> 的迭代器(<code>(Memtable m)-&gt;NewIterator</code>) 将通过 <code>NewMergingIterator</code> 被组合为一个 <code>ScopedArenaIterator</code>; 并和所有的 <code>(Memtable m)-&gt;NewRangeTombstoneIterator</code> 一起传递给 <code>BuildTable</code>, 进行最终的 SST 创建过程.</p><p>Compaction 过程主要在 <code>CompactionJob::ProcessKeyValueCompaction</code> 中.</p><h3 id="SST-构建"><a href="#SST-构建" class="headerlink" title="SST 构建"></a>SST 构建</h3><p><a href="https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format">BlockBasedTable</a> 是 RocksDB 默认的 SST 实现. 它由 <code>Block</code> 和 <code>Footer</code> 组成, 其文件布局如下:</p><figure class="highlight text"><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></pre></td><td class="code"><pre><span class="line">&lt;beginning_of_file&gt;</span><br><span class="line">[data block 1]</span><br><span class="line">[data block 2]</span><br><span class="line">...</span><br><span class="line">[data block N]</span><br><span class="line">[meta block 1: filter block]</span><br><span class="line">[meta block 2: index block]</span><br><span class="line">[meta block 3: compression dictionary block]</span><br><span class="line">[meta block 4: range deletion block]</span><br><span class="line">[meta block 5: stats block]</span><br><span class="line">...</span><br><span class="line">[meta block K: future extended block]</span><br><span class="line">[metaindex block]</span><br><span class="line">[Footer] (fixed size; starts at file_size - sizeof(Footer))</span><br><span class="line">&lt;end_of_file&gt;</span><br></pre></td></tr></table></figure><p>其中 <code>[Footer]</code> 中存放了 metaindex block 和 index block 的 <code>BlockHandle</code>; <code>[metaindex block]</code> 则按 (块名称, <code>BlockHandle</code>) 存储了 元信息块 (meta block) 的偏移, 例如 range deletion block 的 key 为 <code>std::string kRangeDelBlock = &quot;rocksdb.range_del&quot;;</code></p><p><code>[Footer]</code> 内容:</p><figure class="highlight text"><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">legacy footer format:</span><br><span class="line">    metaindex handle</span><br><span class="line">    index handle</span><br><span class="line">    &lt;padding&gt; </span><br><span class="line">        // make the total size 2 * BlockHandle::kMaxEncodedLength</span><br><span class="line">    table_magic_number (8 bytes)</span><br><span class="line">new footer format:</span><br><span class="line">    checksum type (char, 1 byte)</span><br><span class="line">    metaindex handle</span><br><span class="line">    index handle</span><br><span class="line">    &lt;padding&gt;</span><br><span class="line">        // make the total size 2 * BlockHandle::kMaxEncodedLength + 1</span><br><span class="line">    footer version (4 bytes)</span><br><span class="line">    table_magic_number (8 bytes)</span><br></pre></td></tr></table></figure><p><code>BlockHandles</code> 用于描述块的偏移量和大小</p><figure class="highlight text"><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">offset:         varint64</span><br><span class="line">size:           varint64</span><br></pre></td></tr></table></figure><p>值得注意的是每个 Block (除 <code>[Footer]</code> 外的所以类型的均相同), 都设计为用于存储键值数据, 其大概结构如下:</p><figure class="highlight text"><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">&lt;完整KV 1&gt;</span><br><span class="line">&lt;去除相同前缀KV 2&gt;</span><br><span class="line">&lt;去除相同前缀KV 3&gt;</span><br><span class="line">...</span><br><span class="line">&lt;完整KV k&gt;</span><br><span class="line">&lt;去除相同前缀KV k+1&gt;</span><br><span class="line">...</span><br><span class="line">&lt;重启点 1&gt;</span><br><span class="line">&lt;重启点 2&gt;</span><br><span class="line">...</span><br><span class="line">&lt;IndexTypeAndNumRestarts&gt; // block footer</span><br></pre></td></tr></table></figure><p>其中 <code>&lt;IndexTypeAndNumRestarts&gt;</code> 通过 <code>PackIndexTypeAndNumRestarts</code> 编码, 记录了索引类型及重启点个数.</p><p>Block 通过 <code>BlockBuilder</code> 进行创建, 通过 <code>BlockBuilder::Add</code> 方法可写入一个键值对; 在写入完成后通过 <code>BlockBuilder::Finish</code> 写入重启点信息和 block footer 完成单个块的构建过程.</p><p>在存储和读取时, SST 会以 Block 为单位进行读取和写入. 创建 SST 时, 在使用 <code>BlockBasedTableBuilder::Add</code> 按顺加入全部键值对后, 调用 <code>BlockBasedTableBuilder::Finish</code> 将 按文件布局<strong>顺序写入</strong>各元信息块和<code>[Footer]</code>, 完成整个 SST 的构建过程.</p><p>写入顺序:</p><figure class="highlight text"><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">1. [meta block: filter]</span><br><span class="line">2. [meta block: index]</span><br><span class="line">     index_blocks.meta_blocks ( &lt;-- metaindex_block )</span><br><span class="line">     index_blocks.index_block_contents ( &lt;-- Footer )</span><br><span class="line">3. [meta block: compression dictionary]</span><br><span class="line">4. [meta block: range deletion tombstone]</span><br><span class="line">5. [meta block: properties]</span><br><span class="line">6. [metaindex block]</span><br><span class="line">7. [Footer]</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">简单介绍了RocksDB内部键值的编码和存储过程</summary>
    
    
    
    
  </entry>
  
</feed>
