<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>嵌入式开发 on 夜云泊</title>
    <link>https://lifeislife.cn/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/</link>
    <description>feedId:57980998056508425+userId:73222296380546048 Recent content in 嵌入式开发 on 夜云泊</description>
    <generator>Hugo -- 0.163.1</generator>
    <language>zh</language>
    <lastBuildDate>Sun, 08 Feb 2026 10:34:10 +0000</lastBuildDate>
    <atom:link href="https://lifeislife.cn/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>硬件仿真平台 (Palladium / ZeBu / HAPS) 和软件仿真平台 (QEMU) 的区别</title>
      <link>https://lifeislife.cn/posts/%E7%A1%AC%E4%BB%B6%E4%BB%BF%E7%9C%9F%E5%B9%B3%E5%8F%B0palladium/zebu/haps%E5%92%8C%E8%BD%AF%E4%BB%B6%E4%BB%BF%E7%9C%9F%E5%B9%B3%E5%8F%B0qemu%E7%9A%84%E5%8C%BA%E5%88%AB/</link>
      <pubDate>Sun, 08 Feb 2026 10:34:10 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/%E7%A1%AC%E4%BB%B6%E4%BB%BF%E7%9C%9F%E5%B9%B3%E5%8F%B0palladium/zebu/haps%E5%92%8C%E8%BD%AF%E4%BB%B6%E4%BB%BF%E7%9C%9F%E5%B9%B3%E5%8F%B0qemu%E7%9A%84%E5%8C%BA%E5%88%AB/</guid>
      <description>&lt;h3 id=&#34;核心区别翻译vs模拟&#34;&gt;核心区别——“翻译”vs“模拟”&lt;/h3&gt;
&lt;p&gt;最本质的区别在于它们运行的&lt;strong&gt;对象&lt;/strong&gt;不同：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;QEMU（软件仿真）：运行的是“行为模型”。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理：&lt;/strong&gt; QEMU 是一个纯软件程序，运行在通用的服务器（x86 架构）上。它不关心芯片内部具体的电路是怎么连的，它只关心&lt;strong&gt;功能&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;比喻：&lt;/strong&gt; 就像你在电脑上玩“超级马里奥”模拟器。电脑并不含有任天堂的游戏机电路，它只是用软件模拟了“按 A 键跳跃”这个&lt;strong&gt;行为&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键词：&lt;/strong&gt; &lt;strong&gt;功能级（Functional）&lt;/strong&gt;。它知道“CPU 写了这个寄存器，灯就会亮”，但它不在乎电流是怎么流过去的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Palladium / ZeBu / HAPS（硬件仿真/原型）：运行的是“RTL 电路”。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这些机器内部塞满了大量的专用芯片（FPGA 或 定制处理器）。我们将芯片设计的&lt;strong&gt;源代码（RTL）&lt;/strong&gt; 综合成电路网表，真刀真枪地烧录或者映射到这些机器里运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;比喻：&lt;/strong&gt; 这就像是用乐高积木（FPGA/专用芯片）按照设计图纸，1:1 搭建了一个巨大的、虽然跑得慢但结构完全真实的“游戏机”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键词：&lt;/strong&gt; &lt;strong&gt;周期级（Cycle-accurate）&lt;/strong&gt;。它精确地模拟了每一个时钟周期内，信号在电线上的翻转。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;三大维度深度对比&#34;&gt;三大维度深度对比&lt;/h3&gt;
&lt;p&gt;为了方便理解，我将对比分为三个维度：&lt;strong&gt;速度&lt;/strong&gt;、&lt;strong&gt;真实度（精度）&lt;/strong&gt;、&lt;strong&gt;可观测性&lt;/strong&gt;。&lt;/p&gt;
&lt;h4 id=&#34;速度-speed&#34;&gt;速度 (Speed)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QEMU：&lt;/strong&gt; &lt;strong&gt;极快&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;因为它跳过了复杂的电路细节，直接用主机 CPU 指令模拟目标指令，速度可以达到数百 MIPS（每秒百万条指令）。&lt;/li&gt;
&lt;li&gt;用途：适合你们软件团队开发上层应用、操作系统（Linux/Android）启动、UI 交互。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HAPS (FPGA Prototyping)：&lt;/strong&gt; &lt;strong&gt;较快&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;它通常能跑到 5MHz - 100MHz。虽然比真实芯片（几 GHz）慢，但比下面的 Emulation 快。&lt;/li&gt;
&lt;li&gt;用途：适合驱动开发、长时间的压力测试、视频编解码测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Palladium / ZeBu (Emulation)：&lt;/strong&gt; &lt;strong&gt;较慢&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;通常在 500kHz - 2MHz 左右。启动一个 Android 可能需要几小时（虽然现在有混合模式加速，但纯硬件部分依然慢）。&lt;/li&gt;
&lt;li&gt;用途：芯片逻辑除错、极早期的驱动验证。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;真实度精度-accuracy--这是最关键的区别&#34;&gt;真实度/精度 (Accuracy) —— 这是最关键的区别&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QEMU：&lt;/strong&gt; &lt;strong&gt;时序是假的&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;在 QEMU 里，你写一个指令 &lt;code&gt;Delay(1ms)&lt;/code&gt;，它可能并不真的精确对应硬件的多少个时钟周期。它往往假设总线交互是瞬间完成的。&lt;/li&gt;
&lt;li&gt;风险：它测不出&lt;strong&gt;竞争冒险（Race Condition）&lt;strong&gt;或者&lt;/strong&gt;带宽瓶颈&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件平台 (Palladium/ZeBu/HAPS)：&lt;/strong&gt; &lt;strong&gt;时序是真的&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;它严格遵守电路设计的时序。如果你的代码需要等待硬件模块响应，而那个模块需要耗费 100 个时钟周期才能把数据准备好，硬件平台就会真的让你等 100 个周期。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;可观测性-visibility--也就是-debug-的难度&#34;&gt;可观测性 (Visibility) —— 也就是 Debug 的难度&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QEMU：&lt;/strong&gt; 你只能看到 CPU 寄存器、内存和你在代码里打印的 Log。你看不到芯片内部的一根“电线”是高电平还是低电平。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Palladium / ZeBu：&lt;/strong&gt; &lt;strong&gt;上帝视角&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;我可以随时“暂停”时间，查看芯片内部几十亿个晶体管中任意一个的状态，甚至可以回放（Waveform dumping）。这对于查硬件 Bug 是救命的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HAPS：&lt;/strong&gt; 比较难查。因为它为了追求速度，牺牲了可观测性。通常需要预埋一些探针才能看波形。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;实战拆解一个简单的-dma-搬运在不同平台的表现&#34;&gt;实战拆解：一个“简单”的 DMA 搬运，在不同平台的表现&lt;/h2&gt;
&lt;p&gt;光讲理论太枯燥，我们用一个经典的 &lt;strong&gt;“CPU 指挥 DMA 搬数据”&lt;/strong&gt; 的例子，来看看 QEMU 是怎么“欺骗”你的，而硬件仿真平台又是如何工作的。&lt;/p&gt;
&lt;p&gt;DMA 搬运一搬可以分为下面几步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CPU 往内存写一段数据。&lt;/li&gt;
&lt;li&gt;CPU 配置 DMA，把这段数据搬到另一个地方。&lt;/li&gt;
&lt;li&gt;DMA 搬完后，发一个中断告诉 CPU：“我干完了”。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;在-qemu-里的美好世界&#34;&gt;在 QEMU 里的美好世界&lt;/h3&gt;
&lt;p&gt;在 QEMU 的代码里，DMA 设备通常是一个 C++ 类。当你写入“开始”寄存器时，QEMU 内部可能就直接调用了一个 &lt;code&gt;memcpy()&lt;/code&gt; 函数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;总线拥堵？&lt;/strong&gt; 不存在的，QEMU 里内存读写瞬间完成。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存同步？&lt;/strong&gt; 没关系的，QEMU 往往默认 CPU 和 DMA 看到的是同一块内存，不需要你操心 Cache 一致性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果：&lt;/strong&gt; 代码逻辑通顺，中断准时到达，数据完美无缺。你觉得你的驱动无懈可击。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;在硬件仿真平台真实电路里的残酷真相&#34;&gt;在硬件仿真平台（真实电路）里的残酷真相&lt;/h3&gt;
&lt;p&gt;当你把同一段代码放到 Palladium 上（这里跑的是真实的芯片电路设计图），情况完全变了。&lt;/p&gt;
&lt;h4 id=&#34;总线上的堵车事故axi-背压&#34;&gt;总线上的“堵车”事故（AXI 背压）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QEMU:&lt;/strong&gt; 一路畅通。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件真相：&lt;/strong&gt; DMA 发出写请求，但此时内存控制器正忙着处理 GPU 的请求，于是回了一个 &lt;code&gt;Wait&lt;/code&gt; 信号。如果你的 DMA 硬件设计在处理这个 &lt;code&gt;Wait&lt;/code&gt; 信号时状态机写得有问题，它可能就卡死在这里，傻等着永远不会来的“通行证”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后果：&lt;/strong&gt; &lt;strong&gt;死锁&lt;/strong&gt;。软件一直在等中断，但中断永远不会来。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;缓存里的谎言-cache-coherency&#34;&gt;缓存里的“谎言” (Cache Coherency)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QEMU:&lt;/strong&gt; CPU 写完数据，DMA 立刻就能读到最新的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件真相：&lt;/strong&gt; CPU 写的数据还在 CPU 的 L1 Cache 里，还没来得及写到 DDR 内存条上。此时 DMA 去读内存，读到的是旧数据（全是 0）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后果：&lt;/strong&gt; &lt;strong&gt;数据校验错误&lt;/strong&gt;。这是最坑的，因为系统没挂，但数据是错的，且极难复现。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;中断的生死时速-race-condition&#34;&gt;中断的“生死时速” (Race Condition)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QEMU:&lt;/strong&gt; 逻辑上的先后顺序，先发中断，再处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件真相：&lt;/strong&gt; DMA 拉高中断线需要 1 个时钟周期，信号经过总线传到 CPU 需要 10 个周期。就在这几纳秒的时间差里，如果你的驱动程序刚好去操作了清除中断的寄存器，或者总线出现了乱序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后果：&lt;/strong&gt; &lt;strong&gt;丢中断&lt;/strong&gt;。DMA 觉得我发了，CPU 觉得我没收到，两人面面相觑。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;既然-qemu-这么多坑为什么还要用&#34;&gt;既然 QEMU 这么多坑，为什么还要用？&lt;/h2&gt;
&lt;p&gt;硬件仿真平台（Palladium/ZeBu）虽然真实，但也有致命缺点：&lt;strong&gt;贵、慢、难伺候。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它启动一次 Linux 可能要几个小时（QEMU 只要几秒）。&lt;/li&gt;
&lt;li&gt;想加个断点调试？流程极其繁琐。&lt;/li&gt;
&lt;li&gt;机器非常昂贵，通常全公司也没几台，得排队用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;最佳实践软硬结合的三步走&#34;&gt;最佳实践：软硬结合的“三步走”&lt;/h2&gt;
&lt;p&gt;作为一名聪明的开发者，你应该清楚每种工具的定位：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;早期开发 (QEMU):&lt;/strong&gt; 这里的目的是&lt;strong&gt;跑通软件逻辑&lt;/strong&gt;。验证任务调度、UI 交互、驱动的基本状态机。只要 QEMU 跑通了，说明你的代码逻辑大体是对的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中期验证 (硬件仿真/FPGA):&lt;/strong&gt; 代码逻辑没问题了，现在要验证&lt;strong&gt;时序和硬件交互&lt;/strong&gt;。把代码放到 Palladium 或 FPGA 上跑。这时候你会遇到上面说的死锁、数据错、超时。别慌，这正是硬件仿真平台的价值所在——帮你在流片前把这些深层 Bug 挖出来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最终回流:&lt;/strong&gt; 当你在硬件平台上修复了 Bug（比如加了内存屏障指令），记得把这些改动同步回 QEMU 仓库，虽然 QEMU 不需要它也能跑，但保持代码一致性很重要。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;硬件三巨头&#34;&gt;硬件三巨头&lt;/h3&gt;
&lt;p&gt;同样作为硬件仿真平台，Palladium、ZeBu、HAPS 之间也有一些区别：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Palladium (Cadence 公司) / ZeBu (Synopsys 公司)：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;学名：&lt;/strong&gt; &lt;strong&gt;硬件仿真器 (Emulator)&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 极贵（一台机器几千万甚至上亿人民币）。编译代码很快，Debug 能力最强。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;你的用法：&lt;/strong&gt; 芯片设计早期（RTL 刚写好），如果你的固件跑挂了，你需要找硬件设计人员看波形，这时候用这个。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HAPS (Synopsys 公司)：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;学名：&lt;/strong&gt; &lt;strong&gt;FPGA 原型验证系统 (FPGA Prototyping)&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 也就是很多块高性能 FPGA 连在一起。跑得比 Emulator 快很多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;你的用法：&lt;/strong&gt; 芯片设计中后期。主要用来给你做驱动开发、跑真实的操作系统。因为它跑得够快，你能感觉到系统的流畅度。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;附录术语表&#34;&gt;附录：术语表&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RTL (Register Transfer Level)：&lt;/strong&gt; 寄存器传输级代码。这是硬件工程师写的代码（通常用 Verilog 或 VHDL 语言）。它是芯片设计的“源代码”。QEMU 不跑这个，硬件仿真平台跑这个。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流片 (Tape-out)：&lt;/strong&gt; 指芯片设计完成，把设计数据发送给晶圆厂（如台积电）进行制造的过程。流片极其昂贵，一旦失败损失巨大，所以必须在流片前用仿真平台测准。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FPGA (Field-Programmable Gate Array)：&lt;/strong&gt; 现场可编程门阵列。一种“万能芯片”。你可以通过编程改变它内部的电路连接。我们用它来模拟还没造出来的 ASIC 芯片。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时钟周期 (Clock Cycle)：&lt;/strong&gt; 芯片心脏跳动一次的时间。2GHz 的 CPU，一个周期就是 0.5 纳秒。硬件仿真就是精确模拟每一次跳动发生了什么。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网表 (Netlist)：&lt;/strong&gt; 类似于软件编译后的“汇编语言”或二进制。RTL 代码经过“综合（Synthesis）”工具处理后，就变成了由门电路（与门、非门、触发器）组成的网表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时钟域 (Clock Domain)：&lt;/strong&gt; 芯片里不同模块跑的速度不一样（比如 CPU 跑 2G，USB 模块跑 100M）。信号从 2G 传到 100M 的区域，需要特殊的同步处理，这往往是 Bug 的高发区。&lt;/li&gt;
&lt;/ul&gt;
</description>
      <content:encoded><![CDATA[<h3 id="核心区别翻译vs模拟">核心区别——“翻译”vs“模拟”</h3>
<p>最本质的区别在于它们运行的<strong>对象</strong>不同：</p>
<ol>
<li>
<p><strong>QEMU（软件仿真）：运行的是“行为模型”。</strong></p>
<ul>
<li><strong>原理：</strong> QEMU 是一个纯软件程序，运行在通用的服务器（x86 架构）上。它不关心芯片内部具体的电路是怎么连的，它只关心<strong>功能</strong>。</li>
<li><strong>比喻：</strong> 就像你在电脑上玩“超级马里奥”模拟器。电脑并不含有任天堂的游戏机电路，它只是用软件模拟了“按 A 键跳跃”这个<strong>行为</strong>。</li>
<li><strong>关键词：</strong> <strong>功能级（Functional）</strong>。它知道“CPU 写了这个寄存器，灯就会亮”，但它不在乎电流是怎么流过去的。</li>
</ul>
</li>
<li>
<p><strong>Palladium / ZeBu / HAPS（硬件仿真/原型）：运行的是“RTL 电路”。</strong></p>
<ul>
<li><strong>原理：</strong> 这些机器内部塞满了大量的专用芯片（FPGA 或 定制处理器）。我们将芯片设计的<strong>源代码（RTL）</strong> 综合成电路网表，真刀真枪地烧录或者映射到这些机器里运行。</li>
<li><strong>比喻：</strong> 这就像是用乐高积木（FPGA/专用芯片）按照设计图纸，1:1 搭建了一个巨大的、虽然跑得慢但结构完全真实的“游戏机”。</li>
<li><strong>关键词：</strong> <strong>周期级（Cycle-accurate）</strong>。它精确地模拟了每一个时钟周期内，信号在电线上的翻转。</li>
</ul>
</li>
</ol>
<h3 id="三大维度深度对比">三大维度深度对比</h3>
<p>为了方便理解，我将对比分为三个维度：<strong>速度</strong>、<strong>真实度（精度）</strong>、<strong>可观测性</strong>。</p>
<h4 id="速度-speed">速度 (Speed)</h4>
<ul>
<li><strong>QEMU：</strong> <strong>极快</strong>。
<ul>
<li>因为它跳过了复杂的电路细节，直接用主机 CPU 指令模拟目标指令，速度可以达到数百 MIPS（每秒百万条指令）。</li>
<li>用途：适合你们软件团队开发上层应用、操作系统（Linux/Android）启动、UI 交互。</li>
</ul>
</li>
<li><strong>HAPS (FPGA Prototyping)：</strong> <strong>较快</strong>。
<ul>
<li>它通常能跑到 5MHz - 100MHz。虽然比真实芯片（几 GHz）慢，但比下面的 Emulation 快。</li>
<li>用途：适合驱动开发、长时间的压力测试、视频编解码测试。</li>
</ul>
</li>
<li><strong>Palladium / ZeBu (Emulation)：</strong> <strong>较慢</strong>。
<ul>
<li>通常在 500kHz - 2MHz 左右。启动一个 Android 可能需要几小时（虽然现在有混合模式加速，但纯硬件部分依然慢）。</li>
<li>用途：芯片逻辑除错、极早期的驱动验证。</li>
</ul>
</li>
</ul>
<h4 id="真实度精度-accuracy--这是最关键的区别">真实度/精度 (Accuracy) —— 这是最关键的区别</h4>
<ul>
<li><strong>QEMU：</strong> <strong>时序是假的</strong>。
<ul>
<li>在 QEMU 里，你写一个指令 <code>Delay(1ms)</code>，它可能并不真的精确对应硬件的多少个时钟周期。它往往假设总线交互是瞬间完成的。</li>
<li>风险：它测不出<strong>竞争冒险（Race Condition）<strong>或者</strong>带宽瓶颈</strong>。</li>
</ul>
</li>
<li><strong>硬件平台 (Palladium/ZeBu/HAPS)：</strong> <strong>时序是真的</strong>。
<ul>
<li>它严格遵守电路设计的时序。如果你的代码需要等待硬件模块响应，而那个模块需要耗费 100 个时钟周期才能把数据准备好，硬件平台就会真的让你等 100 个周期。</li>
</ul>
</li>
</ul>
<h4 id="可观测性-visibility--也就是-debug-的难度">可观测性 (Visibility) —— 也就是 Debug 的难度</h4>
<ul>
<li><strong>QEMU：</strong> 你只能看到 CPU 寄存器、内存和你在代码里打印的 Log。你看不到芯片内部的一根“电线”是高电平还是低电平。</li>
<li><strong>Palladium / ZeBu：</strong> <strong>上帝视角</strong>。
<ul>
<li>我可以随时“暂停”时间，查看芯片内部几十亿个晶体管中任意一个的状态，甚至可以回放（Waveform dumping）。这对于查硬件 Bug 是救命的。</li>
</ul>
</li>
<li><strong>HAPS：</strong> 比较难查。因为它为了追求速度，牺牲了可观测性。通常需要预埋一些探针才能看波形。</li>
</ul>
<h2 id="实战拆解一个简单的-dma-搬运在不同平台的表现">实战拆解：一个“简单”的 DMA 搬运，在不同平台的表现</h2>
<p>光讲理论太枯燥，我们用一个经典的 <strong>“CPU 指挥 DMA 搬数据”</strong> 的例子，来看看 QEMU 是怎么“欺骗”你的，而硬件仿真平台又是如何工作的。</p>
<p>DMA 搬运一搬可以分为下面几步：</p>
<ol>
<li>CPU 往内存写一段数据。</li>
<li>CPU 配置 DMA，把这段数据搬到另一个地方。</li>
<li>DMA 搬完后，发一个中断告诉 CPU：“我干完了”。</li>
</ol>
<h3 id="在-qemu-里的美好世界">在 QEMU 里的美好世界</h3>
<p>在 QEMU 的代码里，DMA 设备通常是一个 C++ 类。当你写入“开始”寄存器时，QEMU 内部可能就直接调用了一个 <code>memcpy()</code> 函数。</p>
<ul>
<li><strong>总线拥堵？</strong> 不存在的，QEMU 里内存读写瞬间完成。</li>
<li><strong>缓存同步？</strong> 没关系的，QEMU 往往默认 CPU 和 DMA 看到的是同一块内存，不需要你操心 Cache 一致性。</li>
<li><strong>结果：</strong> 代码逻辑通顺，中断准时到达，数据完美无缺。你觉得你的驱动无懈可击。</li>
</ul>
<h3 id="在硬件仿真平台真实电路里的残酷真相">在硬件仿真平台（真实电路）里的残酷真相</h3>
<p>当你把同一段代码放到 Palladium 上（这里跑的是真实的芯片电路设计图），情况完全变了。</p>
<h4 id="总线上的堵车事故axi-背压">总线上的“堵车”事故（AXI 背压）</h4>
<ul>
<li><strong>QEMU:</strong> 一路畅通。</li>
<li><strong>硬件真相：</strong> DMA 发出写请求，但此时内存控制器正忙着处理 GPU 的请求，于是回了一个 <code>Wait</code> 信号。如果你的 DMA 硬件设计在处理这个 <code>Wait</code> 信号时状态机写得有问题，它可能就卡死在这里，傻等着永远不会来的“通行证”。</li>
<li><strong>后果：</strong> <strong>死锁</strong>。软件一直在等中断，但中断永远不会来。</li>
</ul>
<h4 id="缓存里的谎言-cache-coherency">缓存里的“谎言” (Cache Coherency)</h4>
<ul>
<li><strong>QEMU:</strong> CPU 写完数据，DMA 立刻就能读到最新的。</li>
<li><strong>硬件真相：</strong> CPU 写的数据还在 CPU 的 L1 Cache 里，还没来得及写到 DDR 内存条上。此时 DMA 去读内存，读到的是旧数据（全是 0）。</li>
<li><strong>后果：</strong> <strong>数据校验错误</strong>。这是最坑的，因为系统没挂，但数据是错的，且极难复现。</li>
</ul>
<h4 id="中断的生死时速-race-condition">中断的“生死时速” (Race Condition)</h4>
<ul>
<li><strong>QEMU:</strong> 逻辑上的先后顺序，先发中断，再处理。</li>
<li><strong>硬件真相：</strong> DMA 拉高中断线需要 1 个时钟周期，信号经过总线传到 CPU 需要 10 个周期。就在这几纳秒的时间差里，如果你的驱动程序刚好去操作了清除中断的寄存器，或者总线出现了乱序。</li>
<li><strong>后果：</strong> <strong>丢中断</strong>。DMA 觉得我发了，CPU 觉得我没收到，两人面面相觑。</li>
</ul>
<h2 id="既然-qemu-这么多坑为什么还要用">既然 QEMU 这么多坑，为什么还要用？</h2>
<p>硬件仿真平台（Palladium/ZeBu）虽然真实，但也有致命缺点：<strong>贵、慢、难伺候。</strong></p>
<ul>
<li>它启动一次 Linux 可能要几个小时（QEMU 只要几秒）。</li>
<li>想加个断点调试？流程极其繁琐。</li>
<li>机器非常昂贵，通常全公司也没几台，得排队用。</li>
</ul>
<h2 id="最佳实践软硬结合的三步走">最佳实践：软硬结合的“三步走”</h2>
<p>作为一名聪明的开发者，你应该清楚每种工具的定位：</p>
<ol>
<li><strong>早期开发 (QEMU):</strong> 这里的目的是<strong>跑通软件逻辑</strong>。验证任务调度、UI 交互、驱动的基本状态机。只要 QEMU 跑通了，说明你的代码逻辑大体是对的。</li>
<li><strong>中期验证 (硬件仿真/FPGA):</strong> 代码逻辑没问题了，现在要验证<strong>时序和硬件交互</strong>。把代码放到 Palladium 或 FPGA 上跑。这时候你会遇到上面说的死锁、数据错、超时。别慌，这正是硬件仿真平台的价值所在——帮你在流片前把这些深层 Bug 挖出来。</li>
<li><strong>最终回流:</strong> 当你在硬件平台上修复了 Bug（比如加了内存屏障指令），记得把这些改动同步回 QEMU 仓库，虽然 QEMU 不需要它也能跑，但保持代码一致性很重要。</li>
</ol>
<h3 id="硬件三巨头">硬件三巨头</h3>
<p>同样作为硬件仿真平台，Palladium、ZeBu、HAPS 之间也有一些区别：</p>
<ol>
<li><strong>Palladium (Cadence 公司) / ZeBu (Synopsys 公司)：</strong>
<ul>
<li><strong>学名：</strong> <strong>硬件仿真器 (Emulator)</strong>。</li>
<li><strong>特点：</strong> 极贵（一台机器几千万甚至上亿人民币）。编译代码很快，Debug 能力最强。</li>
<li><strong>你的用法：</strong> 芯片设计早期（RTL 刚写好），如果你的固件跑挂了，你需要找硬件设计人员看波形，这时候用这个。</li>
</ul>
</li>
<li><strong>HAPS (Synopsys 公司)：</strong>
<ul>
<li><strong>学名：</strong> <strong>FPGA 原型验证系统 (FPGA Prototyping)</strong>。</li>
<li><strong>特点：</strong> 也就是很多块高性能 FPGA 连在一起。跑得比 Emulator 快很多。</li>
<li><strong>你的用法：</strong> 芯片设计中后期。主要用来给你做驱动开发、跑真实的操作系统。因为它跑得够快，你能感觉到系统的流畅度。</li>
</ul>
</li>
</ol>
<h3 id="附录术语表">附录：术语表</h3>
<ul>
<li><strong>RTL (Register Transfer Level)：</strong> 寄存器传输级代码。这是硬件工程师写的代码（通常用 Verilog 或 VHDL 语言）。它是芯片设计的“源代码”。QEMU 不跑这个，硬件仿真平台跑这个。</li>
<li><strong>流片 (Tape-out)：</strong> 指芯片设计完成，把设计数据发送给晶圆厂（如台积电）进行制造的过程。流片极其昂贵，一旦失败损失巨大，所以必须在流片前用仿真平台测准。</li>
<li><strong>FPGA (Field-Programmable Gate Array)：</strong> 现场可编程门阵列。一种“万能芯片”。你可以通过编程改变它内部的电路连接。我们用它来模拟还没造出来的 ASIC 芯片。</li>
<li><strong>时钟周期 (Clock Cycle)：</strong> 芯片心脏跳动一次的时间。2GHz 的 CPU，一个周期就是 0.5 纳秒。硬件仿真就是精确模拟每一次跳动发生了什么。</li>
<li><strong>网表 (Netlist)：</strong> 类似于软件编译后的“汇编语言”或二进制。RTL 代码经过“综合（Synthesis）”工具处理后，就变成了由门电路（与门、非门、触发器）组成的网表。</li>
<li><strong>时钟域 (Clock Domain)：</strong> 芯片里不同模块跑的速度不一样（比如 CPU 跑 2G，USB 模块跑 100M）。信号从 2G 传到 100M 的区域，需要特殊的同步处理，这往往是 Bug 的高发区。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>PCIe Part 2 - 关于内存的一切 MMIO DMA TLPs !</title>
      <link>https://lifeislife.cn/posts/%E8%AF%91%E6%96%87-pcie-part2/</link>
      <pubDate>Sat, 31 Aug 2024 17:00:13 +0800</pubDate>
      <guid>https://lifeislife.cn/posts/%E8%AF%91%E6%96%87-pcie-part2/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;本文翻译自：&lt;a href=&#34;https://ctf.re/kernel/pcie/tutorial/dma/mmio/tlp/2024/03/26/pcie-part-2/&#34;&gt;PCIe 第 2 部分 - 关于内存：MMIO、DMA、TLP 等！– 灵魂的逆向工程 &amp;mdash; PCIe Part 2 - All About Memory: MMIO, DMA, TLPs, and more! – Reversing Engineering for the Soul&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在这个系列文章的第一部分中，我们讨论了 ECAM 以及软件和硬件数据包网络中的配置空间访问。在讨论中，引入了 TLP（Transaction Layer Packets）的概念，这是所有 PCIe 数据在层次结构中移动的通用数据包结构。我们还讨论了这些数据包如何像以太网一样传输，即路由设备使用一个地址（在这种情况下是 BDF）来发送配置空间数据包穿过网络。&lt;/p&gt;
&lt;p&gt;配置空间读取和写入只是可以直接使用设备执行 I/O 的几种方式之一。通过“configuration”这个名称，我们可以知道很明显它的意图不是为了执行大量数据传输。主要缺点是它的速度，因为配置空间数据包最多只能包含双向读取或写入的 64 位数据（通常只有 32 位）。对于如此少量的可用数据，数据包和其他链路标头的开销非常大，因此浪费了带宽。&lt;/p&gt;
&lt;p&gt;正如第 1 部分所讨论的，理解内存和地址将继续是理解 PCIe 的关键。在这篇文章中，我们将更深入地研究更快的设备 I/O 事务形式，并开始了解软件设备驱动程序如何实际与 PCIe 设备连接以完成有用的工作。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：您无需成为计算机体系结构或 TCP/IP 网络方面的专家即可从这篇文章中获得一些信息。但是，了解 TCP/IP 和虚拟内存的基础知识对于掌握本文的一些核心概念是必要的。这篇文章也以 第 1 部分 中的信息为基础。如果您需要查看这些内容，请立即查看！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;pcie-中的数据传输方法简介&#34;&gt;PCIe 中的数据传输方法简介&lt;/h2&gt;
&lt;p&gt;配置空间是一种在枚举时间内通过其 BDF 与设备通信的一种简单而有效的方式。这是一种简单的传输模式是有原因的 - 它必须是配置和可用的所有其他数据传输方法的基础。枚举设备后，配置空间已设置设备与主机一起执行实际工作所需的所有信息。配置空间仍用于允许主机监控和响应设备及其链接状态的变化，但它不会用于执行设备的实际高速传输或功能。&lt;/p&gt;
&lt;p&gt;配置空间是在枚举时间通过设备的 BDF 进行通信的一种简单有效的方式。它是一种简单的传输模式，是所有数据传输方法的基础。一旦设备被枚举，配置空间就已经设置了设备执行实际工作所需的所有信息，与主机机器一起。配置空间仍用于允许主机计算机监视和响应设备及其链接的变化，但不会用于执行设备的实际高速传输或功能。&lt;/p&gt;
&lt;p&gt;我们现在需要的是数据传输方法，让我们真正开始利用 PCIe 设计的高速传输吞吐量。吞吐量是对给定时间段内传输的字节数的度量。这意味着为了最大限度地提高吞吐量，我们必须最小化每个数据包的开销，以传输每个数据包的最大字节数。如果我们每个数据包只发送几个 DWORD（每个 4 字节），就像在配置空间的情况下一样，PCIe 高速传输能力就浪费了。&lt;/p&gt;
&lt;p&gt;废话不多说，先介绍一下 PCIe 中高速 I/O 的两种主要形式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内存映射输入/输出（简称 MMIO）- 与主机 CPU 读取和写入内存到 ECAM 以执行配置空间访问类似，MMIO 可以用来映射设备的地址空间，以执行内存传输。主机机器在其物理地址空间中配置“内存窗口”，使 CPU 拥有一个内存地址窗口，这些内存地址神奇地转换为直接读取和写入设备。内存窗口在 RC 中解码，将 CPU 的读取和写入转换为传输到设备的数据 TLPs。硬件优化使得这种方法可以实现比配置空间访问快得多的吞吐量。然而，其速度仍然远远落后于 DMA 的批量传输速度。&lt;/li&gt;
&lt;li&gt;直接内存访问（简称 DMA）- DMA 是迄今为止最常见的数据传输形式，因为它具有原始传输速度和低延迟。每当驱动程序需要在主机和设备之间沿任一方向进行任何重要大小的传输时，它肯定会是 DMA。但与 MMIO 不同的是，DMA 是由设备本身启动的，而不是由主机 CPU 启动的。主机 CPU 将通过 MMIO 告诉设备 DMA 应该去哪里，设备本身负责开始和完成 DMA 传输。这允许设备在没有 CPU 参与的情况下执行 DMA 事务，与设备必须等待主机 CPU 告诉它每次传输做什么相比，这节省了大量的 CPU 周期。由于 DMA 的普遍性和重要性，从硬件实现和软件层面了解 DMA 非常有价值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/ba1527f1a6204106c586315818355039.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/ba1527f1a6204106c586315818355039.png&#34; alt=&#34;MMIO 方法概述&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/a3c6fbf5ac22aed25da2b62c20ecb3f3.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/a3c6fbf5ac22aed25da2b62c20ecb3f3.png&#34; alt=&#34;从器件到 RAM 执行 DMA 的高级概述。当传输到 RAM 完成时，设备会中断 CPU。&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&#34;mmio-简介&#34;&gt;MMIO 简介&lt;/h2&gt;
&lt;h3 id=&#34;什么是-bar&#34;&gt;什么是 BAR？&lt;/h3&gt;
&lt;p&gt;由于配置空间限制为 4096 字节，因此之后没有太多可用空间用于特定于设备的功能。如果设备想要映射 1GB 的 MMIO 空间来访问其内部 RAM，该怎么办？没有办法将其放入 4096 字节的配置空间。因此，它将需要请求一个被称为 BAR（基地址寄存器）的东西。这是通过配置空间公开的一个寄存器，允许主机机器配置其内存的一个区域，直接映射到设备上。然后主机机器上的软件通过对 BAR 的物理地址的内存读/写指令来访问 BAR，就像我们在 ECAM 的第一部分中看到的 MMIO 一样。对设备内存映射进行读取或写入的操作将直接转换为发送到层次结构上的设备的数据包。当设备需要响应时，它将通过层次结构向主机机器发送一个新的数据包。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/333e258e5613b3c17bb19e1b5bcbf87b.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/333e258e5613b3c17bb19e1b5bcbf87b.png&#34; alt=&#34;在主机上运行的设备驱动程序访问 BAR 映射，这些映射转换为通过 PCIe 发送到设备的数据包。&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;当 CPU 指令读取器件 MMIO 区域的内存时，会生成一个内存读取请求事务层数据包（MemRd TLP），该数据包从主机的 RC 向下传输到器件。这个 TLP 包的目的是通知设备希望读取设备，然后设备需要尽快响应请求地址上的内容。&lt;/p&gt;
&lt;p&gt;在 PCIe 中发送和接收的所有数据传输数据包都将采用 TLP 形式。回想一下第 1 部分，这些数据包是设备之间的所有通信都在 PCIe 中发生的中心抽象。这些数据包在出现数据传输错误（类似于网络中的 TCP）的情况下是可靠的，并且可以根据需要重试/重新发送。这确保了数据传输免受 PCIe 可以达到的极高速度下发生的恶劣电气干扰。我们很快就会仔细研究 TLP 的结构，但现在只需将这些视为您在 TCP 中看到的常规网络数据包。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/1167d45f8d32476dcb144eee4e65634e.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/1167d45f8d32476dcb144eee4e65634e.png&#34; alt=&#34;当器件响应时，CPU 会使用器件的结果更新 register 的内容。&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;当设备收到请求者数据包时，设备会使用 MemRd TLP 响应内存请求。此 TLP 包含从设备内存空间读取的结果，给定原始请求者数据包中的地址和大小。设备将它正在响应的特定请求数据包和发送方标记到响应数据包中，交换层次结构知道如何将响应数据包返回给请求者。然后，请求者将使用数据包中的数据来更新发起请求的 CPU 寄存器。&lt;/p&gt;
&lt;p&gt;同时，当 TLP 正在传输时，CPU 必须等待内存请求完成，并且它不能被中断或执行许多有用的工作。正如你可能看到的，如果需要执行大量这样的请求，CPU 将需要花费大量时间等待设备响应每个请求。虽然在硬件级别进行了优化，使此过程更加简化，但使用 CPU 周期等待数据传输完成仍然不是最佳选择。希望您能看到我们需要第二种类型的传输，即 DMA，来解决 BAR 访问的这些缺点。&lt;/p&gt;
&lt;p&gt;这里的另一个重点是，设备内存并不严格需要用于设备的 RAM。虽然通常会看到具有板载 RAM 的设备通过 BAR 公开其内部 RAM 的映射，但这不是必需的。例如，访问设备的 BAR 可能会访问设备的内部寄存器，也可能导致设备执行某些操作。例如，写入 BAR 是设备开始执行 DMA 的主要方式。一个核心要点是，设备 BAR 非常灵活，可用于控制设备或执行与设备之间的数据传输。&lt;/p&gt;
&lt;h3 id=&#34;如何枚举-bar&#34;&gt;如何枚举 BAR？&lt;/h3&gt;
&lt;p&gt;设备使用其配置空间从软件请求内存区域。在枚举时，由主机确定该区域将放置在物理内存中的位置。每个器件在其配置空间（称为“寄存器”，因此称为基址寄存器）中都有 6 个 32 位值，当枚举器件时，软件将读取和写入这些值。这些寄存器描述了器件希望分配的每个 MMIO 区域的长度和对齐要求，每个可能的 BAR 一个，总共 6 个不同的区域。如果设备希望能够将其 BAR 映射到 4GB 空间（64 位 BAR）以上，它可以将两个 32 位寄存器组合在一起，形成一个 64 位 BAR，最多只留下三个 64 位 BAR。&lt;/p&gt;
&lt;p&gt;PCIe&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/037c6e9bb852b45b9e12c1a046ceec59.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/037c6e9bb852b45b9e12c1a046ceec59.png&#34; alt=&#34;Type 0 配置空间结构，显示 6 个 BAR。&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;术语注释：尽管首字母缩略词 BAR 表示基址寄存器，但你会看到上面的文本也将 MMIO 的内存窗口称为 BAR。不幸的是，这意味着配置空间中的寄存器名称也与给 device 的 MMIO 区域相同（两者都称为 BAR）。你可能需要根据上下文，以确定它们是指内存窗口，还是配置空间本身的实际寄存器。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;BARs 是配置空间中另一个示例，它不是常量寄存器。在第一部分中，我们看了一些常量寄存器，比如 VendorID 和 DeviceID。但是 BARs 不是常量寄存器，它们应该由软件写入和读取。实际上，由软件写入寄存器的值是特殊的，因为将某些类型的值写入寄存器将导致读取时功能不同。如果你没有牢记设备内存并非总是 RAM，读取回来的值可能与写入的不同，那么现在正是时候这么做了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;设备内存可以是 RAM，但它并不总是 RAM，也不需要像 RAM 那样工作！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;什么是-dma引言和理论&#34;&gt;什么是 DMA？引言和理论&lt;/h2&gt;
&lt;p&gt;到目前为止，我们已经看到了两种形式的 I/O，配置空间访问和通过 BAR 的 MMIO 访问。我们将讨论的最后一种也是最后一种访问形式是直接内存访问（DMA）。DMA 是迄今为止最快的 PCIe 批量传输方法，因为它的传输开销最小。也就是说，通过链路传输最大字节数所需的资源最少。这使得 DMA 对于真正利用 PCIe 提供的高速链路至关重要。&lt;/p&gt;
&lt;p&gt;但是，强大的力量会带来巨大的混乱。对于软件开发人员来说，DMA 是一个非常陌生的概念，因为我们在软件中没有类似的东西可以比较。对于 MMIO，我们可以将内存访问概念化为从设备内存中读取和写入的指令。但 DMA 与此非常不同。这是因为 DMA 是异步的，它不利用 CPU 来执行传输。相反，顾名思义，读取和写入的内存直接来自系统 RAM。一旦 DMA 开始，唯一涉及的各方是系统主内存的内存控制器和设备本身。因此，CPU 不会花费周期等待单个内存访问。相反，它只是启动转移，并让平台在后台自行完成 DMA。然后，平台将在传输完成时通知 CPU，通常是通过中断。&lt;/p&gt;
&lt;p&gt;让我们想一想，为什么异步执行 DMA 如此重要。考虑 CPU 从计算机上的 NVMe SSD 解密大量文件的情况。一旦主机上的 NVMe 驱动程序启动 DMA，设备就会不断以最快的速度将文件数据从 SSD 的内部存储传输到 CPU 可以访问的系统 RAM 中的位置。然后，CPU 可以使用其 100% 的处理能力来执行解密数学运算，以便在从系统内存中读取数据时解密文件块。CPU 不会花时间等待对设备进行单个内存读取，而是简单地连接数据，并允许设备尽可能快地传输，而 CPU 会尽可能快地处理它。在此期间，任何额外的数据都会在系统 RAM 中缓冲，直到 CPU 可以访问它。这样，任何过程的任何部分都不会等待其他事情发生。所有这些都以尽可能快的速度同时发生。&lt;/p&gt;
&lt;p&gt;由于 DMA 的复杂性和涉及的部件数量，我将尝试以最直接的方式解释 DMA，并用大量图表来显示该过程。更令人困惑的是，每个设备都有不同的 DMA 接口。没有用于执行 DMA 的通用软件接口，只有器件的设计人员知道如何告诉该器件执行 DMA。值得庆幸的是，某些设备类别使用普遍认可的接口，例如大多数 SSD 使用的 NVMe 接口或 USB 3.0 的 XHCI 接口。如果没有标准接口，则只有硬件设计人员知道设备如何执行 DMA，因此生产设备的公司或个人需要是编写设备驱动程序的人，而不是依赖与操作系统捆绑的通用驱动程序与设备通信。&lt;/p&gt;
&lt;h2 id=&#34;一个简单的-dma-传输---step-by-step&#34;&gt;一个简单的 DMA 传输 - Step by Step&lt;/h2&gt;
&lt;p&gt;我们 DMA 旅程的第一步是查看传输的初始设置。这涉及几个步骤，为即将到来的 DMA 传输准备系统内存、内核和设备。在这种情况下，我们将设置 DMA，以便读取系统 RAM 中存在的 DMA 缓冲区中的内存内容，并将其放入 Target Memory 的器件板载 RAM 中。此时我们已经选择将此内存从 DMA Buffer 读取到器件上地址为 &lt;code&gt;0x8000&lt;/code&gt; 中。目标是尽快将此内存从系统内存传输到设备，以便它可以开始处理它。假设在这种情况下，内存量几 M 字节，MMIO 会太慢，但为简单起见，我们将仅显示 32 字节的内存。这种传输将是最简单的 DMA 传输类型：将内存块的已知大小和地址从系统 RAM 复制到设备 RAM。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/e485c2c5093a3098acb5d4fc616de33c.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/e485c2c5093a3098acb5d4fc616de33c.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h3 id=&#34;第-1-步---从操作系统分配-dma-内存&#34;&gt;第 1 步 - 从操作系统分配 DMA 内存&lt;/h3&gt;
&lt;p&gt;此过程的第一步是从 OS 分配 DMA 内存。这意味着设备驱动程序必须进行 OS API 调用，以请求 OS 为设备分配一个内存区域以将数据写入。这一点很重要，因为操作系统可能需要执行特殊的内存管理操作才能使数据对设备可用，例如删除保护或重新组织现有分配以促进请求。&lt;/p&gt;
&lt;p&gt;传统上，DMA（直接内存访问）存储器必须是连续的物理存储器，这意味着设备从某个地址和长度的起始处开始，并线性地从缓冲区的起始位置读取/写入数据直到结束。因此，操作系统必须负责组织其物理内存，以创建足够大的连续范围，以满足驱动程序请求的 DMA 缓冲区。有时，对于长时间运行或物理内存有限的系统来说，这可能非常困难。因此，这一领域的增强功能允许更现代的设备使用 Scatter-Gather 和 IOMMU Remapping 等功能传输到非连续的存储器区域。稍后，我们将看一些这些功能。但现在，我们只专注于更简单的连续内存情况。&lt;/p&gt;
&lt;p&gt;一旦请求的分配成功，API 将返回内存地址，并指向系统 RAM 中的缓冲区。这将是设备通过 DMA 访问内存的地址。DMA 意图为 API 返回的地址将被赋予一个特殊的名称; 设备逻辑地址或逻辑地址。对于我们的示例，逻辑地址等同于物理地址。设备看到的是操作系统看到的物理内存的完全相同视图，没有额外的转换。然而，在更高级的传输形式中，情况可能并非总是如此。因此最好意识到，设备给出的地址可能并非总是与其在 RAM 中实际的物理地址相同。&lt;/p&gt;
&lt;p&gt;分配缓冲区后，由于目的是将数据从此缓冲区移动到设备，因此设备驱动程序将提前使用写入设备所需的信息填充缓冲区。在此示例中，由重复 01 02 03 04 模式组成的数据正在传输到设备的 RAM。&lt;/p&gt;
&lt;h3 id=&#34;第-2-步---将-dma-地址写入设备并开始传输&#34;&gt;第 2 步 - 将 DMA 地址写入设备并开始传输&lt;/h3&gt;
&lt;p&gt;传输的下一步是准备设备执行事务所需的信息。这通常是了解器件的特定 DMA 接口最重要的地方。每个设备都以自己的方式进行编程，了解驱动程序应该如何对设备进行编程的唯一方法是参考其通用标准（如 NVMe 规范）或简单地与硬件设计人员合作。&lt;/p&gt;
&lt;p&gt;在这个例子中，我将为一个只有执行传输所需的最基本功能的设备构建一个简化的 DMA 接口。在下面的图表中，我们可以看到这个设备通过向 BAR0 MMIO 区域写入数值来进行编程。这意味着为了为这个设备编程 DMA，驱动程序必须将内存写入由 BAR0 指定的 MMIO 区域。每个寄存器在 BAR0 区域内的位置是由驱动程序编写者提前知道的，并且被集成到设备驱动程序的代码中。&lt;/p&gt;
&lt;p&gt;对于此示例、我在 BAR0 中创建了四个器件寄存器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Destination Address（目标地址） - 设备内部 RAM 中用于写入从系统 RAM 读取的数据的地址。这是我们将对已经确定的目标地址 0x8000 进行编程的地方。&lt;/li&gt;
&lt;li&gt;Source Address（源地址） - 设备将从中读取数据的系统 RAM 的逻辑地址。这将对我们希望设备读取的 DMA Buffer 的逻辑地址进行编程。&lt;/li&gt;
&lt;li&gt;Transfer Size（传输大小） - 我们要传输的大小（以字节为单位）。&lt;/li&gt;
&lt;li&gt;Initiate Transfer（启动传输标志位）- 一旦次寄存器写入 1，器件将开始在上面给出的地址之间进行传输。通过这种方式，驱动程序可以判断设备已完成填充缓冲区并准备好开始传输。这通常被称为门铃（Door Bell）寄存器。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8c2b333dbb59e8bb8d2a92ae5766299e.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8c2b333dbb59e8bb8d2a92ae5766299e.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在上图中，驱动程序需要使用器件的 BAR0 的映射内存将必要的值写入寄存器（它如何映射此内存取决于 OS）。此图中的值如下所示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Target Memory - 我们要从器件复制的内存将为 &lt;code&gt;0x00008000&lt;/code&gt;，它映射到器件板载 RAM 中的内存区域。这将是我们的目标地址。&lt;/li&gt;
&lt;li&gt;DMA 缓冲区 - 操作系统在 &lt;code&gt;0x001FF000&lt;/code&gt; 分配内存块，因此这将是我们的源地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有了这些信息，驱动程序现在可以将值编程到设备中，如下所示：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/d481c375d9d43db63faf509106fd6dcd.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/d481c375d9d43db63faf509106fd6dcd.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;现在，驱动程序已经配置了执行传输所需的所有寄存器。最后一步是向启动传输寄存器写入一个值，该寄存器充当开始传输的 Door bell 寄存器。一旦写入此值，设备将驱动 DMA 传输，并独立于驱动程序或 CPU 的参与执行它。驱动程序已完成启动传输的工作，此时 CPU 可在等待设备通知系统 DMA 完成的同时，自由进行其他工作。&lt;/p&gt;
&lt;h3 id=&#34;第-3-步---设备执行-dma-事务&#34;&gt;第 3 步 - 设备执行 DMA 事务&lt;/h3&gt;
&lt;p&gt;现在，驱动程序已写入 Door bell 寄存器，设备现在将接管处理实际传输。在设备本身上，存在一个名为 DMA 引擎的模块，负责处理和维护事务的所有方面。当器件被编程时，对 BAR0 的寄存器写入正在对 DMA 引擎进行编程，其中包含开始在 PCIe 链路上发送必要的 TLP 以执行内存事务所需的信息。&lt;/p&gt;
&lt;p&gt;如上一节所述，PCIe 链路上的所有内存操作都是通过 Memory Write/Read TLP 完成的。在这里，我们将深入研究在交易发生时设备的 DMA 引擎发送和接收的 TLP。请记住，更容易将 TLP 视为在单个可靠连接上发送和接收数据的网络数据包。&lt;/p&gt;
&lt;p&gt;如在前文部分讨论过的，PCIe 链接上的所有内存操作都是通过内存写入/读取 TLPs 来完成的。在这里，我们将深入探讨设备的 DMA 引擎在传输过程中发送和接收的 TLPs。要牢记的是，将 TLPs 视为在一个单一、可靠的连接上发送和接收数据的网络数据包更易于理解。&lt;/p&gt;
&lt;h3 id=&#34;插曲快速了解-tlp&#34;&gt;插曲：快速了解 TLP&lt;/h3&gt;
&lt;p&gt;在查看链路上的 TLP 之前，让我们仔细了解一下数据包结构本身的概览。&lt;/p&gt;
&lt;p&gt;以下是内存读取请求和响应的两个 TLP。如前所述，用于内存操作的 TLP 利用请求和响应系统。执行读取的设备将生成特定地址和长度（以 4 字节 DWORD 为单位）的读取请求 TLP，然后坐下来等待完成数据包到达包含响应数据的链路。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8354d9b795fe77dfbf296da3509b32b7.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8354d9b795fe77dfbf296da3509b32b7.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;我们可以看到，有与生成请求的设备、Requester 以及唯一的 Tag 值相关的元数据。此 Tag 值用于将请求与其完成匹配。当设备生成请求时，它会使用唯一值标记 TLP 以跟踪待处理的请求。该值由请求的发送者选择，并且由发送者来跟踪其分配的 Tags。&lt;/p&gt;
&lt;p&gt;随着完成的数据通过链路到达，完成的 Tag 让设备能够将接入的数据正确地移至特定传输所需的位置。该系统允许单一设备有多个独特的未完成传输任务，尽管它们接收的数据包相互交错，但仍能保持作为独立传输任务的有序性。&lt;/p&gt;
&lt;p&gt;数据包内部还包含了必要的信息，使得 PCIe 切换层次结构能够确定请求和完成需要去向的位置。例如，内存地址被用来确定正在请求访问的设备是哪一个。在枚举期间，层次结构中的每一个设备都被编程以拥有各自独特的地址范围。切换结构根据数据包中的内存地址，确定数据包需要去向哪里以访问那个地址。&lt;/p&gt;
&lt;p&gt;设备收到并处理请求后，响应数据将以 Completion TLP 的形式发送回去。完成或“响应”数据包可以而且通常会被分段为许多较小的 TLP，这些 TLP 发送整体响应的一部分。这是因为在枚举期间，已确定设备和总线可以处理最大有效载荷大小（MPS）。MPS 可根据平台和设备功能进行配置，从 128 开始，最高可达 4096 的 2 次方大小。通常，此值约为 256 字节，这意味着需要将大型读取请求拆分为许多较小的 TLP。这些数据包中的每一个都有一个字段，该字段指示完成响应的原始请求的偏移量，有效负载中是返回的数据块。&lt;/p&gt;
&lt;p&gt;有一个常见的误解，即内存 TLP 使用 BDF 来寻址数据包需要去往的位置。其实请求仅使用内存地址来指示数据包的目的地，并且设备和目标之间的桥接设备负责将该数据包发送到其正确的位置。。然而，完成数据包确实利用请求者的 BDF 将数据返回给发起请求的设备。&lt;/p&gt;
&lt;p&gt;以下是一个展示内存读取和响应过程的图表，图中展示出请求会使用一个地址来发起请求，而完成的操作会使用请求中 Request 字段的 BDF 来发送相应应答：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7b3a9443c759cbf8fe091903bb602eaf.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7b3a9443c759cbf8fe091903bb602eaf.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/39cc0bf0067bf486aaa2062470c8e45e.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/39cc0bf0067bf486aaa2062470c8e45e.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h3 id=&#34;现在回到实际的传输&#34;&gt;现在回到实际的传输&lt;/h3&gt;
&lt;p&gt;让我们看看 DMA 引擎为了执行我们的请求而发送和接收的所有内容。由于我们请求了 32 字节的数据，因此只有一个单一的 Memory Read Request 和一个带有响应的单一 Memory Read Completion 数据包。为了便于您理解，请停止向前阅读，并考虑一下在此事务中哪个设备将发送和接收哪个 TLP。如果您需要再次查看第 2 步的图表，请向上滚动。&lt;/p&gt;
&lt;p&gt;现在，让我们深入研究一下传输的实际数据包。虽然我将继续绘制这个模拟示例，但我认为对于这个练习，当执行真实传输时，实际看到其中一些 TLP 是什么样子可能会很有趣。&lt;/p&gt;
&lt;p&gt;在实验中，我使用真实设备配置了如上文所示的同类通用参数，并启动了 DMA。这个设备会发送真实的 TLPs，将系统 RAM 中的内存读取到设备里。因此，你将有机会罕见地查看在执行这种 DMA 时发送的实际 TLPs 的例子，除非有分析器，否则这些在传输过程中几乎无法看到。&lt;/p&gt;
&lt;p&gt;要查看此实验，请点击此链接至配套文章：&lt;a href=&#34;https://ctf.re/pcie/experiment/linux/keysight/protocol-analyzer/2024/03/26/pcie-experiment-1/&#34;&gt;Experiment - Packet Dumping PCIe DMA TLPs with a Protocol Analyzer and Pcileech – Reversing Engineering for the Soul&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;以下是器件生成的内存读取请求以及请求如何遍历层次结构的框图。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/d5ceb838e8b89196864f787a56d02197.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/d5ceb838e8b89196864f787a56d02197.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;勘误表：0x32 应该是 32&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;此图中概述的步骤如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DMA 引擎创建 TLP - DMA 引擎识别出它必须从 0x001FF000 读取 32 字节。它生成一个包含此请求的 TLP，并通过其本地 PCIe 链路将其发送出去。&lt;/li&gt;
&lt;li&gt;TLP 遍历层次结构 - PCIe 的交换层次结构通过桥接设备移动此请求，直到它到达其目的地，即 RC。回想一下，RC 负责处理所有用于访问系统 RAM 的传入数据包。&lt;/li&gt;
&lt;li&gt;通知 DRAM 控制器 - RC 在内部与 DRAM 控制器通信，该控制器负责实际访问系统 DRAM 的内存。&lt;/li&gt;
&lt;li&gt;从 DRAM 读取内存 - 从地址 0x001FF000 的 DRAM 请求给定长度的 32 字节，并将其返回到值为 01 02 03 04&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;尽量不要被这些信息淹没，因为我确实知道仅针对单个内存请求 TLP 就有很多事情要做。所有这些在高层次上归结为仅从 RAM 中的地址 0x001FF000 读取 32 字节的内存。平台如何通过与 DRAM 控制器通信来实际读取系统 DRAM，仅供您参考。设备本身不知道 Root Complex 实际上是如何读取此内存的，它只是使用 TLP 启动传输。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：此处未显示更复杂的 RAM 缓存过程。在 x86-64 上，来自设备的所有内存访问都是缓存一致的，这意味着平台会自动将 CPU 缓存与设备正在访问的值同步。在其他平台（如 ARM 平台）上，由于其缓存架构，这是一个更复杂的过程。现在，我们只假设缓存一致性正在自动为我们处理，我们对此没有任何特别的担忧。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当 RC 收到此 TLP 时，它会在内部标记 Requester 和 Tag 的读取内容。当它等待 DRAM 响应该值时，此请求的信息将在 RC 中等待。要概念化这一点，可以将其视为网络套接字中的“open connection”。RC 知道它需要响应什么，因此会等到响应数据可用后，再通过套接字将数据发送回去。&lt;/p&gt;
&lt;p&gt;最后，将 Completion 从 Root Complex 发送回设备。请注意，Destination 与 Requester 相同：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/e4d729f807a08b1dab3dddb0f10c88b2.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/e4d729f807a08b1dab3dddb0f10c88b2.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;以下是响应数据包概述的步骤，如上所示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从 DRAM 读取内存 - DRAM 控制器从系统 DRAM 中 0x001FF000 的 DMA 缓冲区地址读取 32 字节。&lt;/li&gt;
&lt;li&gt;DRAM 控制器响应根复合体 - DRAM 控制器在内部响应从 DRAM 向 RC 请求的内存&lt;/li&gt;
&lt;li&gt;RC 生成完成 - RC 跟踪传输并为从 DRAM 读取的值创建完成 TLP。在此 TLP 中，元数据值是根据 RC 对待处理传输的了解来设置的，例如发送的字节数、传输的标记以及从原始请求的 Requester 字段复制的目标 BDF。&lt;/li&gt;
&lt;li&gt;DMA 引擎接收 TLP - DMA 引擎通过 PCIe 链路接收 TLP，并查看标签是否与原始请求的相同标签匹配。它还会在内部跟踪此值，并知道有效负载中的内存应写入 Target Memory，该内存在设备内部 RAM 中处于 0x8000。&lt;/li&gt;
&lt;li&gt;Target Memory is Written（目标内存已写入） - 设备内存中的值将更新为从数据包的 Payload 中复制的值。&lt;/li&gt;
&lt;li&gt;系统中断 - 虽然这是可选的，但大多数 DMA 引擎将配置为在 DMA 完成时中断主机 CPU。这会在设备成功完成 DMA 时向设备驱动程序发出通知。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同样，仅处理这个 complete 数据包就涉及很多步骤。但是，同样，您可以将整个过程简单地视为“从设备的请求中收到 32 字节的响应”。这些步骤的其余部分只是为了向您展示此响应处理的完整端到端是什么样子。&lt;/p&gt;
&lt;p&gt;从这里，设备驱动程序会收到 DMA 已完成的通知，设备驱动程序的代码负责清理 DMA 缓冲区或将其存储起来以供下次使用。&lt;/p&gt;
&lt;p&gt;在我们的艰苦努力后，我们终于完成了一次单一的 DMA 传输事务！想到这就是我能提供的最“简单”的传输方式，真是让人惊讶。有了 IOMMU 重映射和 Scatter-Gather 能力的加入，这些事务甚至可能变得更复杂。但就现在而言，你应该对 DMA 的全部内容以及它如何在真实设备中运作有了深入的理解。&lt;/p&gt;
&lt;h2 id=&#34;尾声---关于复杂性的小说明&#34;&gt;尾声 - 关于复杂性的小说明&lt;/h2&gt;
&lt;p&gt;如果您读完这篇文章并觉得自己没有完全掌握所有抛给您的概念，或者对复杂性感到不知所措，您不必担心。这些帖子如此复杂的原因是它不仅涵盖广泛的主题，而且还涵盖广泛的专业。通常，整个系统的每个部分在行业中都有不同的团队，他们只关注他们在这个复杂机器中的“齿轮”。通常，硬件开发人员专注于设备，驱动程序开发人员专注于驱动程序代码，而操作系统开发人员专注于资源管理。这些团队之间很少有太多重叠，除非在他们的边界交接，以便另一支团队可以连接到它。&lt;/p&gt;
&lt;p&gt;这些帖子有点独特，因为它们试图将系统作为一个整体进行记录，以便于概念理解，而不是实现。这意味着，在通常划定团队边界的地方，这些帖子根本不关心。我鼓励觉得这个话题有趣的读者在自己的时间里继续深入研究。也许您可以了解一些 FPGA 并开始制作自己的设备，或者您可以购买一个设备并开始尝试对其进行逆向工程，并通过您自己的定制软件与它进行通信。&lt;/p&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;
&lt;p&gt;希望您喜欢这篇深入探讨 PCIe 内存传输的文章！虽然我在这篇文章中涵盖了大量信息，但兔子洞总是更深。值得庆幸的是，通过学习配置空间访问、MMIO（BAR）和 DMA，您现在已经涵盖了 PCIe 中可用的各种形式的数据通信！对于连接到 PCIe 总线的每个设备，主机系统和设备之间的通信将通过这三种方法中的一种进行。设备的链接、资源和驱动程序软件的所有设置和配置最终都是为了促进这三种形式的通信。&lt;/p&gt;
&lt;p&gt;这篇文章花了这么长时间才发布的一个重要原因是，为了理解这一切，我必须向读者展示大量信息。很难决定什么值得写，什么太深以至于理解变得模糊。这个决定瘫痪使博客写作过程花费的时间比我预期的要长得多。再加上全职工作，很难找到时间来撰写这些帖子。&lt;/p&gt;
&lt;p&gt;在即将发布的帖子中，我期待着讨论以下一些或所有的话题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;层次结构的 PCIe 交换/桥接和枚举&lt;/li&gt;
&lt;li&gt;更高级的 DMA 主题，例如 DMA 重新映射&lt;/li&gt;
&lt;li&gt;电源管理;设备如何“睡眠”和“唤醒”&lt;/li&gt;
&lt;li&gt;平台/OS 的中断及其分配和处理&lt;/li&gt;
&lt;li&gt;设备的简单驱动程序开发示例&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;与往常一样，如果你有任何问题或想评论或讨论这个系列的某个方面，你最好在我的 discord 的 #hardware 频道中通过“@gbps”找到我，逆向工程 &lt;a href=&#34;https://discord.com/invite/rtfm&#34;&gt;discord&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;请期待未来的帖子！&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<blockquote>
<p>本文翻译自：<a href="https://ctf.re/kernel/pcie/tutorial/dma/mmio/tlp/2024/03/26/pcie-part-2/">PCIe 第 2 部分 - 关于内存：MMIO、DMA、TLP 等！– 灵魂的逆向工程 &mdash; PCIe Part 2 - All About Memory: MMIO, DMA, TLPs, and more! – Reversing Engineering for the Soul</a></p>
</blockquote>
<p>在这个系列文章的第一部分中，我们讨论了 ECAM 以及软件和硬件数据包网络中的配置空间访问。在讨论中，引入了 TLP（Transaction Layer Packets）的概念，这是所有 PCIe 数据在层次结构中移动的通用数据包结构。我们还讨论了这些数据包如何像以太网一样传输，即路由设备使用一个地址（在这种情况下是 BDF）来发送配置空间数据包穿过网络。</p>
<p>配置空间读取和写入只是可以直接使用设备执行 I/O 的几种方式之一。通过“configuration”这个名称，我们可以知道很明显它的意图不是为了执行大量数据传输。主要缺点是它的速度，因为配置空间数据包最多只能包含双向读取或写入的 64 位数据（通常只有 32 位）。对于如此少量的可用数据，数据包和其他链路标头的开销非常大，因此浪费了带宽。</p>
<p>正如第 1 部分所讨论的，理解内存和地址将继续是理解 PCIe 的关键。在这篇文章中，我们将更深入地研究更快的设备 I/O 事务形式，并开始了解软件设备驱动程序如何实际与 PCIe 设备连接以完成有用的工作。</p>
<blockquote>
<p>注意：您无需成为计算机体系结构或 TCP/IP 网络方面的专家即可从这篇文章中获得一些信息。但是，了解 TCP/IP 和虚拟内存的基础知识对于掌握本文的一些核心概念是必要的。这篇文章也以 第 1 部分 中的信息为基础。如果您需要查看这些内容，请立即查看！</p>
</blockquote>
<h2 id="pcie-中的数据传输方法简介">PCIe 中的数据传输方法简介</h2>
<p>配置空间是一种在枚举时间内通过其 BDF 与设备通信的一种简单而有效的方式。这是一种简单的传输模式是有原因的 - 它必须是配置和可用的所有其他数据传输方法的基础。枚举设备后，配置空间已设置设备与主机一起执行实际工作所需的所有信息。配置空间仍用于允许主机监控和响应设备及其链接状态的变化，但它不会用于执行设备的实际高速传输或功能。</p>
<p>配置空间是在枚举时间通过设备的 BDF 进行通信的一种简单有效的方式。它是一种简单的传输模式，是所有数据传输方法的基础。一旦设备被枚举，配置空间就已经设置了设备执行实际工作所需的所有信息，与主机机器一起。配置空间仍用于允许主机计算机监视和响应设备及其链接的变化，但不会用于执行设备的实际高速传输或功能。</p>
<p>我们现在需要的是数据传输方法，让我们真正开始利用 PCIe 设计的高速传输吞吐量。吞吐量是对给定时间段内传输的字节数的度量。这意味着为了最大限度地提高吞吐量，我们必须最小化每个数据包的开销，以传输每个数据包的最大字节数。如果我们每个数据包只发送几个 DWORD（每个 4 字节），就像在配置空间的情况下一样，PCIe 高速传输能力就浪费了。</p>
<p>废话不多说，先介绍一下 PCIe 中高速 I/O 的两种主要形式：</p>
<ul>
<li>内存映射输入/输出（简称 MMIO）- 与主机 CPU 读取和写入内存到 ECAM 以执行配置空间访问类似，MMIO 可以用来映射设备的地址空间，以执行内存传输。主机机器在其物理地址空间中配置“内存窗口”，使 CPU 拥有一个内存地址窗口，这些内存地址神奇地转换为直接读取和写入设备。内存窗口在 RC 中解码，将 CPU 的读取和写入转换为传输到设备的数据 TLPs。硬件优化使得这种方法可以实现比配置空间访问快得多的吞吐量。然而，其速度仍然远远落后于 DMA 的批量传输速度。</li>
<li>直接内存访问（简称 DMA）- DMA 是迄今为止最常见的数据传输形式，因为它具有原始传输速度和低延迟。每当驱动程序需要在主机和设备之间沿任一方向进行任何重要大小的传输时，它肯定会是 DMA。但与 MMIO 不同的是，DMA 是由设备本身启动的，而不是由主机 CPU 启动的。主机 CPU 将通过 MMIO 告诉设备 DMA 应该去哪里，设备本身负责开始和完成 DMA 传输。这允许设备在没有 CPU 参与的情况下执行 DMA 事务，与设备必须等待主机 CPU 告诉它每次传输做什么相比，这节省了大量的 CPU 周期。由于 DMA 的普遍性和重要性，从硬件实现和软件层面了解 DMA 非常有价值。</li>
</ul>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/ba1527f1a6204106c586315818355039.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/ba1527f1a6204106c586315818355039.png" alt="MMIO 方法概述"  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/a3c6fbf5ac22aed25da2b62c20ecb3f3.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/a3c6fbf5ac22aed25da2b62c20ecb3f3.png" alt="从器件到 RAM 执行 DMA 的高级概述。当传输到 RAM 完成时，设备会中断 CPU。"  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h2 id="mmio-简介">MMIO 简介</h2>
<h3 id="什么是-bar">什么是 BAR？</h3>
<p>由于配置空间限制为 4096 字节，因此之后没有太多可用空间用于特定于设备的功能。如果设备想要映射 1GB 的 MMIO 空间来访问其内部 RAM，该怎么办？没有办法将其放入 4096 字节的配置空间。因此，它将需要请求一个被称为 BAR（基地址寄存器）的东西。这是通过配置空间公开的一个寄存器，允许主机机器配置其内存的一个区域，直接映射到设备上。然后主机机器上的软件通过对 BAR 的物理地址的内存读/写指令来访问 BAR，就像我们在 ECAM 的第一部分中看到的 MMIO 一样。对设备内存映射进行读取或写入的操作将直接转换为发送到层次结构上的设备的数据包。当设备需要响应时，它将通过层次结构向主机机器发送一个新的数据包。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/333e258e5613b3c17bb19e1b5bcbf87b.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/333e258e5613b3c17bb19e1b5bcbf87b.png" alt="在主机上运行的设备驱动程序访问 BAR 映射，这些映射转换为通过 PCIe 发送到设备的数据包。"  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>当 CPU 指令读取器件 MMIO 区域的内存时，会生成一个内存读取请求事务层数据包（MemRd TLP），该数据包从主机的 RC 向下传输到器件。这个 TLP 包的目的是通知设备希望读取设备，然后设备需要尽快响应请求地址上的内容。</p>
<p>在 PCIe 中发送和接收的所有数据传输数据包都将采用 TLP 形式。回想一下第 1 部分，这些数据包是设备之间的所有通信都在 PCIe 中发生的中心抽象。这些数据包在出现数据传输错误（类似于网络中的 TCP）的情况下是可靠的，并且可以根据需要重试/重新发送。这确保了数据传输免受 PCIe 可以达到的极高速度下发生的恶劣电气干扰。我们很快就会仔细研究 TLP 的结构，但现在只需将这些视为您在 TCP 中看到的常规网络数据包。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/1167d45f8d32476dcb144eee4e65634e.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/1167d45f8d32476dcb144eee4e65634e.png" alt="当器件响应时，CPU 会使用器件的结果更新 register 的内容。"  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>当设备收到请求者数据包时，设备会使用 MemRd TLP 响应内存请求。此 TLP 包含从设备内存空间读取的结果，给定原始请求者数据包中的地址和大小。设备将它正在响应的特定请求数据包和发送方标记到响应数据包中，交换层次结构知道如何将响应数据包返回给请求者。然后，请求者将使用数据包中的数据来更新发起请求的 CPU 寄存器。</p>
<p>同时，当 TLP 正在传输时，CPU 必须等待内存请求完成，并且它不能被中断或执行许多有用的工作。正如你可能看到的，如果需要执行大量这样的请求，CPU 将需要花费大量时间等待设备响应每个请求。虽然在硬件级别进行了优化，使此过程更加简化，但使用 CPU 周期等待数据传输完成仍然不是最佳选择。希望您能看到我们需要第二种类型的传输，即 DMA，来解决 BAR 访问的这些缺点。</p>
<p>这里的另一个重点是，设备内存并不严格需要用于设备的 RAM。虽然通常会看到具有板载 RAM 的设备通过 BAR 公开其内部 RAM 的映射，但这不是必需的。例如，访问设备的 BAR 可能会访问设备的内部寄存器，也可能导致设备执行某些操作。例如，写入 BAR 是设备开始执行 DMA 的主要方式。一个核心要点是，设备 BAR 非常灵活，可用于控制设备或执行与设备之间的数据传输。</p>
<h3 id="如何枚举-bar">如何枚举 BAR？</h3>
<p>设备使用其配置空间从软件请求内存区域。在枚举时，由主机确定该区域将放置在物理内存中的位置。每个器件在其配置空间（称为“寄存器”，因此称为基址寄存器）中都有 6 个 32 位值，当枚举器件时，软件将读取和写入这些值。这些寄存器描述了器件希望分配的每个 MMIO 区域的长度和对齐要求，每个可能的 BAR 一个，总共 6 个不同的区域。如果设备希望能够将其 BAR 映射到 4GB 空间（64 位 BAR）以上，它可以将两个 32 位寄存器组合在一起，形成一个 64 位 BAR，最多只留下三个 64 位 BAR。</p>
<p>PCIe</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/037c6e9bb852b45b9e12c1a046ceec59.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/037c6e9bb852b45b9e12c1a046ceec59.png" alt="Type 0 配置空间结构，显示 6 个 BAR。"  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<blockquote>
<p>术语注释：尽管首字母缩略词 BAR 表示基址寄存器，但你会看到上面的文本也将 MMIO 的内存窗口称为 BAR。不幸的是，这意味着配置空间中的寄存器名称也与给 device 的 MMIO 区域相同（两者都称为 BAR）。你可能需要根据上下文，以确定它们是指内存窗口，还是配置空间本身的实际寄存器。</p>
</blockquote>
<p>BARs 是配置空间中另一个示例，它不是常量寄存器。在第一部分中，我们看了一些常量寄存器，比如 VendorID 和 DeviceID。但是 BARs 不是常量寄存器，它们应该由软件写入和读取。实际上，由软件写入寄存器的值是特殊的，因为将某些类型的值写入寄存器将导致读取时功能不同。如果你没有牢记设备内存并非总是 RAM，读取回来的值可能与写入的不同，那么现在正是时候这么做了。</p>
<p><strong>设备内存可以是 RAM，但它并不总是 RAM，也不需要像 RAM 那样工作！</strong></p>
<h2 id="什么是-dma引言和理论">什么是 DMA？引言和理论</h2>
<p>到目前为止，我们已经看到了两种形式的 I/O，配置空间访问和通过 BAR 的 MMIO 访问。我们将讨论的最后一种也是最后一种访问形式是直接内存访问（DMA）。DMA 是迄今为止最快的 PCIe 批量传输方法，因为它的传输开销最小。也就是说，通过链路传输最大字节数所需的资源最少。这使得 DMA 对于真正利用 PCIe 提供的高速链路至关重要。</p>
<p>但是，强大的力量会带来巨大的混乱。对于软件开发人员来说，DMA 是一个非常陌生的概念，因为我们在软件中没有类似的东西可以比较。对于 MMIO，我们可以将内存访问概念化为从设备内存中读取和写入的指令。但 DMA 与此非常不同。这是因为 DMA 是异步的，它不利用 CPU 来执行传输。相反，顾名思义，读取和写入的内存直接来自系统 RAM。一旦 DMA 开始，唯一涉及的各方是系统主内存的内存控制器和设备本身。因此，CPU 不会花费周期等待单个内存访问。相反，它只是启动转移，并让平台在后台自行完成 DMA。然后，平台将在传输完成时通知 CPU，通常是通过中断。</p>
<p>让我们想一想，为什么异步执行 DMA 如此重要。考虑 CPU 从计算机上的 NVMe SSD 解密大量文件的情况。一旦主机上的 NVMe 驱动程序启动 DMA，设备就会不断以最快的速度将文件数据从 SSD 的内部存储传输到 CPU 可以访问的系统 RAM 中的位置。然后，CPU 可以使用其 100% 的处理能力来执行解密数学运算，以便在从系统内存中读取数据时解密文件块。CPU 不会花时间等待对设备进行单个内存读取，而是简单地连接数据，并允许设备尽可能快地传输，而 CPU 会尽可能快地处理它。在此期间，任何额外的数据都会在系统 RAM 中缓冲，直到 CPU 可以访问它。这样，任何过程的任何部分都不会等待其他事情发生。所有这些都以尽可能快的速度同时发生。</p>
<p>由于 DMA 的复杂性和涉及的部件数量，我将尝试以最直接的方式解释 DMA，并用大量图表来显示该过程。更令人困惑的是，每个设备都有不同的 DMA 接口。没有用于执行 DMA 的通用软件接口，只有器件的设计人员知道如何告诉该器件执行 DMA。值得庆幸的是，某些设备类别使用普遍认可的接口，例如大多数 SSD 使用的 NVMe 接口或 USB 3.0 的 XHCI 接口。如果没有标准接口，则只有硬件设计人员知道设备如何执行 DMA，因此生产设备的公司或个人需要是编写设备驱动程序的人，而不是依赖与操作系统捆绑的通用驱动程序与设备通信。</p>
<h2 id="一个简单的-dma-传输---step-by-step">一个简单的 DMA 传输 - Step by Step</h2>
<p>我们 DMA 旅程的第一步是查看传输的初始设置。这涉及几个步骤，为即将到来的 DMA 传输准备系统内存、内核和设备。在这种情况下，我们将设置 DMA，以便读取系统 RAM 中存在的 DMA 缓冲区中的内存内容，并将其放入 Target Memory 的器件板载 RAM 中。此时我们已经选择将此内存从 DMA Buffer 读取到器件上地址为 <code>0x8000</code> 中。目标是尽快将此内存从系统内存传输到设备，以便它可以开始处理它。假设在这种情况下，内存量几 M 字节，MMIO 会太慢，但为简单起见，我们将仅显示 32 字节的内存。这种传输将是最简单的 DMA 传输类型：将内存块的已知大小和地址从系统 RAM 复制到设备 RAM。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/e485c2c5093a3098acb5d4fc616de33c.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/e485c2c5093a3098acb5d4fc616de33c.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h3 id="第-1-步---从操作系统分配-dma-内存">第 1 步 - 从操作系统分配 DMA 内存</h3>
<p>此过程的第一步是从 OS 分配 DMA 内存。这意味着设备驱动程序必须进行 OS API 调用，以请求 OS 为设备分配一个内存区域以将数据写入。这一点很重要，因为操作系统可能需要执行特殊的内存管理操作才能使数据对设备可用，例如删除保护或重新组织现有分配以促进请求。</p>
<p>传统上，DMA（直接内存访问）存储器必须是连续的物理存储器，这意味着设备从某个地址和长度的起始处开始，并线性地从缓冲区的起始位置读取/写入数据直到结束。因此，操作系统必须负责组织其物理内存，以创建足够大的连续范围，以满足驱动程序请求的 DMA 缓冲区。有时，对于长时间运行或物理内存有限的系统来说，这可能非常困难。因此，这一领域的增强功能允许更现代的设备使用 Scatter-Gather 和 IOMMU Remapping 等功能传输到非连续的存储器区域。稍后，我们将看一些这些功能。但现在，我们只专注于更简单的连续内存情况。</p>
<p>一旦请求的分配成功，API 将返回内存地址，并指向系统 RAM 中的缓冲区。这将是设备通过 DMA 访问内存的地址。DMA 意图为 API 返回的地址将被赋予一个特殊的名称; 设备逻辑地址或逻辑地址。对于我们的示例，逻辑地址等同于物理地址。设备看到的是操作系统看到的物理内存的完全相同视图，没有额外的转换。然而，在更高级的传输形式中，情况可能并非总是如此。因此最好意识到，设备给出的地址可能并非总是与其在 RAM 中实际的物理地址相同。</p>
<p>分配缓冲区后，由于目的是将数据从此缓冲区移动到设备，因此设备驱动程序将提前使用写入设备所需的信息填充缓冲区。在此示例中，由重复 01 02 03 04 模式组成的数据正在传输到设备的 RAM。</p>
<h3 id="第-2-步---将-dma-地址写入设备并开始传输">第 2 步 - 将 DMA 地址写入设备并开始传输</h3>
<p>传输的下一步是准备设备执行事务所需的信息。这通常是了解器件的特定 DMA 接口最重要的地方。每个设备都以自己的方式进行编程，了解驱动程序应该如何对设备进行编程的唯一方法是参考其通用标准（如 NVMe 规范）或简单地与硬件设计人员合作。</p>
<p>在这个例子中，我将为一个只有执行传输所需的最基本功能的设备构建一个简化的 DMA 接口。在下面的图表中，我们可以看到这个设备通过向 BAR0 MMIO 区域写入数值来进行编程。这意味着为了为这个设备编程 DMA，驱动程序必须将内存写入由 BAR0 指定的 MMIO 区域。每个寄存器在 BAR0 区域内的位置是由驱动程序编写者提前知道的，并且被集成到设备驱动程序的代码中。</p>
<p>对于此示例、我在 BAR0 中创建了四个器件寄存器：</p>
<ul>
<li>Destination Address（目标地址） - 设备内部 RAM 中用于写入从系统 RAM 读取的数据的地址。这是我们将对已经确定的目标地址 0x8000 进行编程的地方。</li>
<li>Source Address（源地址） - 设备将从中读取数据的系统 RAM 的逻辑地址。这将对我们希望设备读取的 DMA Buffer 的逻辑地址进行编程。</li>
<li>Transfer Size（传输大小） - 我们要传输的大小（以字节为单位）。</li>
<li>Initiate Transfer（启动传输标志位）- 一旦次寄存器写入 1，器件将开始在上面给出的地址之间进行传输。通过这种方式，驱动程序可以判断设备已完成填充缓冲区并准备好开始传输。这通常被称为门铃（Door Bell）寄存器。</li>
</ul>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8c2b333dbb59e8bb8d2a92ae5766299e.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8c2b333dbb59e8bb8d2a92ae5766299e.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>在上图中，驱动程序需要使用器件的 BAR0 的映射内存将必要的值写入寄存器（它如何映射此内存取决于 OS）。此图中的值如下所示：</p>
<ul>
<li>Target Memory - 我们要从器件复制的内存将为 <code>0x00008000</code>，它映射到器件板载 RAM 中的内存区域。这将是我们的目标地址。</li>
<li>DMA 缓冲区 - 操作系统在 <code>0x001FF000</code> 分配内存块，因此这将是我们的源地址。</li>
</ul>
<p>有了这些信息，驱动程序现在可以将值编程到设备中，如下所示：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/d481c375d9d43db63faf509106fd6dcd.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/d481c375d9d43db63faf509106fd6dcd.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>现在，驱动程序已经配置了执行传输所需的所有寄存器。最后一步是向启动传输寄存器写入一个值，该寄存器充当开始传输的 Door bell 寄存器。一旦写入此值，设备将驱动 DMA 传输，并独立于驱动程序或 CPU 的参与执行它。驱动程序已完成启动传输的工作，此时 CPU 可在等待设备通知系统 DMA 完成的同时，自由进行其他工作。</p>
<h3 id="第-3-步---设备执行-dma-事务">第 3 步 - 设备执行 DMA 事务</h3>
<p>现在，驱动程序已写入 Door bell 寄存器，设备现在将接管处理实际传输。在设备本身上，存在一个名为 DMA 引擎的模块，负责处理和维护事务的所有方面。当器件被编程时，对 BAR0 的寄存器写入正在对 DMA 引擎进行编程，其中包含开始在 PCIe 链路上发送必要的 TLP 以执行内存事务所需的信息。</p>
<p>如上一节所述，PCIe 链路上的所有内存操作都是通过 Memory Write/Read TLP 完成的。在这里，我们将深入研究在交易发生时设备的 DMA 引擎发送和接收的 TLP。请记住，更容易将 TLP 视为在单个可靠连接上发送和接收数据的网络数据包。</p>
<p>如在前文部分讨论过的，PCIe 链接上的所有内存操作都是通过内存写入/读取 TLPs 来完成的。在这里，我们将深入探讨设备的 DMA 引擎在传输过程中发送和接收的 TLPs。要牢记的是，将 TLPs 视为在一个单一、可靠的连接上发送和接收数据的网络数据包更易于理解。</p>
<h3 id="插曲快速了解-tlp">插曲：快速了解 TLP</h3>
<p>在查看链路上的 TLP 之前，让我们仔细了解一下数据包结构本身的概览。</p>
<p>以下是内存读取请求和响应的两个 TLP。如前所述，用于内存操作的 TLP 利用请求和响应系统。执行读取的设备将生成特定地址和长度（以 4 字节 DWORD 为单位）的读取请求 TLP，然后坐下来等待完成数据包到达包含响应数据的链路。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8354d9b795fe77dfbf296da3509b32b7.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8354d9b795fe77dfbf296da3509b32b7.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>我们可以看到，有与生成请求的设备、Requester 以及唯一的 Tag 值相关的元数据。此 Tag 值用于将请求与其完成匹配。当设备生成请求时，它会使用唯一值标记 TLP 以跟踪待处理的请求。该值由请求的发送者选择，并且由发送者来跟踪其分配的 Tags。</p>
<p>随着完成的数据通过链路到达，完成的 Tag 让设备能够将接入的数据正确地移至特定传输所需的位置。该系统允许单一设备有多个独特的未完成传输任务，尽管它们接收的数据包相互交错，但仍能保持作为独立传输任务的有序性。</p>
<p>数据包内部还包含了必要的信息，使得 PCIe 切换层次结构能够确定请求和完成需要去向的位置。例如，内存地址被用来确定正在请求访问的设备是哪一个。在枚举期间，层次结构中的每一个设备都被编程以拥有各自独特的地址范围。切换结构根据数据包中的内存地址，确定数据包需要去向哪里以访问那个地址。</p>
<p>设备收到并处理请求后，响应数据将以 Completion TLP 的形式发送回去。完成或“响应”数据包可以而且通常会被分段为许多较小的 TLP，这些 TLP 发送整体响应的一部分。这是因为在枚举期间，已确定设备和总线可以处理最大有效载荷大小（MPS）。MPS 可根据平台和设备功能进行配置，从 128 开始，最高可达 4096 的 2 次方大小。通常，此值约为 256 字节，这意味着需要将大型读取请求拆分为许多较小的 TLP。这些数据包中的每一个都有一个字段，该字段指示完成响应的原始请求的偏移量，有效负载中是返回的数据块。</p>
<p>有一个常见的误解，即内存 TLP 使用 BDF 来寻址数据包需要去往的位置。其实请求仅使用内存地址来指示数据包的目的地，并且设备和目标之间的桥接设备负责将该数据包发送到其正确的位置。。然而，完成数据包确实利用请求者的 BDF 将数据返回给发起请求的设备。</p>
<p>以下是一个展示内存读取和响应过程的图表，图中展示出请求会使用一个地址来发起请求，而完成的操作会使用请求中 Request 字段的 BDF 来发送相应应答：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7b3a9443c759cbf8fe091903bb602eaf.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7b3a9443c759cbf8fe091903bb602eaf.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/39cc0bf0067bf486aaa2062470c8e45e.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/39cc0bf0067bf486aaa2062470c8e45e.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h3 id="现在回到实际的传输">现在回到实际的传输</h3>
<p>让我们看看 DMA 引擎为了执行我们的请求而发送和接收的所有内容。由于我们请求了 32 字节的数据，因此只有一个单一的 Memory Read Request 和一个带有响应的单一 Memory Read Completion 数据包。为了便于您理解，请停止向前阅读，并考虑一下在此事务中哪个设备将发送和接收哪个 TLP。如果您需要再次查看第 2 步的图表，请向上滚动。</p>
<p>现在，让我们深入研究一下传输的实际数据包。虽然我将继续绘制这个模拟示例，但我认为对于这个练习，当执行真实传输时，实际看到其中一些 TLP 是什么样子可能会很有趣。</p>
<p>在实验中，我使用真实设备配置了如上文所示的同类通用参数，并启动了 DMA。这个设备会发送真实的 TLPs，将系统 RAM 中的内存读取到设备里。因此，你将有机会罕见地查看在执行这种 DMA 时发送的实际 TLPs 的例子，除非有分析器，否则这些在传输过程中几乎无法看到。</p>
<p>要查看此实验，请点击此链接至配套文章：<a href="https://ctf.re/pcie/experiment/linux/keysight/protocol-analyzer/2024/03/26/pcie-experiment-1/">Experiment - Packet Dumping PCIe DMA TLPs with a Protocol Analyzer and Pcileech – Reversing Engineering for the Soul</a></p>
<p>以下是器件生成的内存读取请求以及请求如何遍历层次结构的框图。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/d5ceb838e8b89196864f787a56d02197.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/d5ceb838e8b89196864f787a56d02197.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<blockquote>
<p>勘误表：0x32 应该是 32</p>
</blockquote>
<p>此图中概述的步骤如下：</p>
<ul>
<li>DMA 引擎创建 TLP - DMA 引擎识别出它必须从 0x001FF000 读取 32 字节。它生成一个包含此请求的 TLP，并通过其本地 PCIe 链路将其发送出去。</li>
<li>TLP 遍历层次结构 - PCIe 的交换层次结构通过桥接设备移动此请求，直到它到达其目的地，即 RC。回想一下，RC 负责处理所有用于访问系统 RAM 的传入数据包。</li>
<li>通知 DRAM 控制器 - RC 在内部与 DRAM 控制器通信，该控制器负责实际访问系统 DRAM 的内存。</li>
<li>从 DRAM 读取内存 - 从地址 0x001FF000 的 DRAM 请求给定长度的 32 字节，并将其返回到值为 01 02 03 04&hellip;</li>
</ul>
<p>尽量不要被这些信息淹没，因为我确实知道仅针对单个内存请求 TLP 就有很多事情要做。所有这些在高层次上归结为仅从 RAM 中的地址 0x001FF000 读取 32 字节的内存。平台如何通过与 DRAM 控制器通信来实际读取系统 DRAM，仅供您参考。设备本身不知道 Root Complex 实际上是如何读取此内存的，它只是使用 TLP 启动传输。</p>
<blockquote>
<p>注意：此处未显示更复杂的 RAM 缓存过程。在 x86-64 上，来自设备的所有内存访问都是缓存一致的，这意味着平台会自动将 CPU 缓存与设备正在访问的值同步。在其他平台（如 ARM 平台）上，由于其缓存架构，这是一个更复杂的过程。现在，我们只假设缓存一致性正在自动为我们处理，我们对此没有任何特别的担忧。</p>
</blockquote>
<p>当 RC 收到此 TLP 时，它会在内部标记 Requester 和 Tag 的读取内容。当它等待 DRAM 响应该值时，此请求的信息将在 RC 中等待。要概念化这一点，可以将其视为网络套接字中的“open connection”。RC 知道它需要响应什么，因此会等到响应数据可用后，再通过套接字将数据发送回去。</p>
<p>最后，将 Completion 从 Root Complex 发送回设备。请注意，Destination 与 Requester 相同：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/e4d729f807a08b1dab3dddb0f10c88b2.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/e4d729f807a08b1dab3dddb0f10c88b2.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>以下是响应数据包概述的步骤，如上所示：</p>
<ul>
<li>从 DRAM 读取内存 - DRAM 控制器从系统 DRAM 中 0x001FF000 的 DMA 缓冲区地址读取 32 字节。</li>
<li>DRAM 控制器响应根复合体 - DRAM 控制器在内部响应从 DRAM 向 RC 请求的内存</li>
<li>RC 生成完成 - RC 跟踪传输并为从 DRAM 读取的值创建完成 TLP。在此 TLP 中，元数据值是根据 RC 对待处理传输的了解来设置的，例如发送的字节数、传输的标记以及从原始请求的 Requester 字段复制的目标 BDF。</li>
<li>DMA 引擎接收 TLP - DMA 引擎通过 PCIe 链路接收 TLP，并查看标签是否与原始请求的相同标签匹配。它还会在内部跟踪此值，并知道有效负载中的内存应写入 Target Memory，该内存在设备内部 RAM 中处于 0x8000。</li>
<li>Target Memory is Written（目标内存已写入） - 设备内存中的值将更新为从数据包的 Payload 中复制的值。</li>
<li>系统中断 - 虽然这是可选的，但大多数 DMA 引擎将配置为在 DMA 完成时中断主机 CPU。这会在设备成功完成 DMA 时向设备驱动程序发出通知。</li>
</ul>
<p>同样，仅处理这个 complete 数据包就涉及很多步骤。但是，同样，您可以将整个过程简单地视为“从设备的请求中收到 32 字节的响应”。这些步骤的其余部分只是为了向您展示此响应处理的完整端到端是什么样子。</p>
<p>从这里，设备驱动程序会收到 DMA 已完成的通知，设备驱动程序的代码负责清理 DMA 缓冲区或将其存储起来以供下次使用。</p>
<p>在我们的艰苦努力后，我们终于完成了一次单一的 DMA 传输事务！想到这就是我能提供的最“简单”的传输方式，真是让人惊讶。有了 IOMMU 重映射和 Scatter-Gather 能力的加入，这些事务甚至可能变得更复杂。但就现在而言，你应该对 DMA 的全部内容以及它如何在真实设备中运作有了深入的理解。</p>
<h2 id="尾声---关于复杂性的小说明">尾声 - 关于复杂性的小说明</h2>
<p>如果您读完这篇文章并觉得自己没有完全掌握所有抛给您的概念，或者对复杂性感到不知所措，您不必担心。这些帖子如此复杂的原因是它不仅涵盖广泛的主题，而且还涵盖广泛的专业。通常，整个系统的每个部分在行业中都有不同的团队，他们只关注他们在这个复杂机器中的“齿轮”。通常，硬件开发人员专注于设备，驱动程序开发人员专注于驱动程序代码，而操作系统开发人员专注于资源管理。这些团队之间很少有太多重叠，除非在他们的边界交接，以便另一支团队可以连接到它。</p>
<p>这些帖子有点独特，因为它们试图将系统作为一个整体进行记录，以便于概念理解，而不是实现。这意味着，在通常划定团队边界的地方，这些帖子根本不关心。我鼓励觉得这个话题有趣的读者在自己的时间里继续深入研究。也许您可以了解一些 FPGA 并开始制作自己的设备，或者您可以购买一个设备并开始尝试对其进行逆向工程，并通过您自己的定制软件与它进行通信。</p>
<h2 id="总结">总结</h2>
<p>希望您喜欢这篇深入探讨 PCIe 内存传输的文章！虽然我在这篇文章中涵盖了大量信息，但兔子洞总是更深。值得庆幸的是，通过学习配置空间访问、MMIO（BAR）和 DMA，您现在已经涵盖了 PCIe 中可用的各种形式的数据通信！对于连接到 PCIe 总线的每个设备，主机系统和设备之间的通信将通过这三种方法中的一种进行。设备的链接、资源和驱动程序软件的所有设置和配置最终都是为了促进这三种形式的通信。</p>
<p>这篇文章花了这么长时间才发布的一个重要原因是，为了理解这一切，我必须向读者展示大量信息。很难决定什么值得写，什么太深以至于理解变得模糊。这个决定瘫痪使博客写作过程花费的时间比我预期的要长得多。再加上全职工作，很难找到时间来撰写这些帖子。</p>
<p>在即将发布的帖子中，我期待着讨论以下一些或所有的话题：</p>
<ul>
<li>层次结构的 PCIe 交换/桥接和枚举</li>
<li>更高级的 DMA 主题，例如 DMA 重新映射</li>
<li>电源管理;设备如何“睡眠”和“唤醒”</li>
<li>平台/OS 的中断及其分配和处理</li>
<li>设备的简单驱动程序开发示例</li>
</ul>
<p>与往常一样，如果你有任何问题或想评论或讨论这个系列的某个方面，你最好在我的 discord 的 #hardware 频道中通过“@gbps”找到我，逆向工程 <a href="https://discord.com/invite/rtfm">discord</a></p>
<p>请期待未来的帖子！</p>
]]></content:encoded>
    </item>
    <item>
      <title>PCIe Part 1 - 面向 Windows 初学者的 PCIe 实用教程</title>
      <link>https://lifeislife.cn/posts/%E8%AF%91%E6%96%87-pcie-part1/</link>
      <pubDate>Sat, 31 Aug 2024 10:00:13 +0800</pubDate>
      <guid>https://lifeislife.cn/posts/%E8%AF%91%E6%96%87-pcie-part1/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;本文翻译自：&lt;a href=&#34;https://ctf.re/windows/kernel/pcie/tutorial/2023/02/14/pcie-part-1/&#34;&gt;面向 Windows 初学者的 PCIe 实用教程（第 1 部分）– 灵魂的逆向工程 &amp;mdash; A Practical Tutorial on PCIe for Total Beginners on Windows (Part 1) – Reversing Engineering for the Soul&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hello！我最近一直在与一些朋友和同事交谈，他们有兴趣了解更多关于 PCIe 的信息，但对复杂性或缺乏适合初学者的简单资源感到害怕。我最近经常使用 PCIe，觉得可能值得以博客文章的形式分享我的一些经验。&lt;/p&gt;
&lt;p&gt;本文主要供具有计算机系统背景并且喜欢亲身实践的人员使用。它也适用于 PCIe 的初学者，或者是对通用概念有所理解但却无法将它们联系在一起的人。&lt;/p&gt;
&lt;p&gt;第一件事是第一件事：不要被吓倒。有很多首字母缩略词和令人困惑的概念，当你&amp;quot;明白&amp;quot;时，它们就会变得简单。当时要迈出一步，不要害怕提出问题！（如果你想问我问题，可以考虑在 &lt;a href=&#34;https://discord.gg/rtfm&#34;&gt;Reverse Engineering Discord&lt;/a&gt; 的 #hardware 频道@Gbps ping 我）&lt;/p&gt;
&lt;p&gt;我&lt;strong&gt;打算&lt;/strong&gt;在这个系列中做几件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从软件方面将 PCIe 分解为我认为最重要的内容，以学习和为现代 PC/服务器系统构建一个良好的基线思想模型。&lt;/li&gt;
&lt;li&gt;展示使用各种工具（通常是 WinDbg）在 Windows 上调查 PCIe 层次结构和设备的实际示例。&lt;/li&gt;
&lt;li&gt;为避免造成混淆，我会有意识地简化或略过一些具体的细节。在这里，可能会有一些术语的使用不准确，甚至信息本身也可能在技术上有所出入。但是，这样做的目的是为了学习整个系统，而不是规范的具体细节。PCIe 是复杂的，当我们处于初学阶段时，陷入过多的细节和特殊情况是没有意义的。&lt;/li&gt;
&lt;li&gt;我们希望通过将这项技术与你已经熟悉的概念相联系，来揭开其神秘面纱。PCIe 并未重新发明轮子，通过理解与它类似的技术，你可能已经比你自己意识到的了解得更多。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我&lt;strong&gt;不打算&lt;/strong&gt;用这个系列做以下事情：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;详细了解传统 PCI 或 PCI-X。一般来说，这项技术除了历史价值之外并不重要。&lt;/li&gt;
&lt;li&gt;演示如何为 PCIe 设备编写设备驱动程序。这是非常特定于操作系统的，并且比这里要讨论的要高得多。&lt;/li&gt;
&lt;li&gt;详细介绍 PCIe 的链路层。该规范的一半以上都花在了这个主题上，并包含了一些世界上最前沿的高速数据传输技术。我不处理这边的事情，但是将来我可能会谈论使用 FPGA 构建 PCIe 设备（我以前做过）。&lt;/li&gt;
&lt;li&gt;帮助你使用 PCIe 在视频游戏中作弊。是的，它存在。不，我不会帮忙。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这并不是对技术或协议的全面研究。要获得真正详尽的了解，你应该参考永远难以捉摸的 PCI-SIG PCI Express 基本规范。这是实现所有 PCIe 代码所依据的规范。目前，在撰写本文时，我们使用的是该规范的 6.0 版，但 3.0 及更高版本的版本都与现代 PCIe 完全相关。如何获得这种昂贵的规格对读者来说是一项练习。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：我有时会在“PCI”和“PCIe”之间来回切换，将技术描述为一种习惯的力量。除非另有说明，否则本系列中的所有内容都是关于 PCIe 的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;什么是-pcie我为什么要关注&#34;&gt;什么是 PCIe，我为什么要关注？&lt;/h2&gt;
&lt;p&gt;PCIe 代表 Peripheral Component Interconnect Express，外围设备组件互联传输。它于 2003 年首次推出，是从早期 PC 时代越来越流行的旧 PCI 和 PCI-X 规范演变而来的（为 Express 添加了“e”以区分它）。&lt;/p&gt;
&lt;p&gt;大多数使用计算机的人都认为它是主板上插入显卡或适配器卡的 PCIe 插槽，但 PCIe 不仅仅是这几个扩展 Port。PCIe 是现代 CPU 与连接到系统的几乎所有设备通信的基础。&lt;/p&gt;
&lt;p&gt;自推出以来，PCIe 的受欢迎程度飙升，成为短距离高速数据传输的近乎通用的标准。几乎所有的 M.2 SSD 都使用 NVMe over PCIe 作为其传输协议。Thunderbolt 3 能够使用外部线将 PCIe 设备直接动态热插拔到系统（支持扩展坞和 eGPU 等技术）。在此基础上，USB4 正在扩展 Thunderbolt 3，以使这种 PCIe 路由技术能够达到开放的 USB 规范。CXL 等新型传输协议，用于数据中心服务器，以 PCIe 为基础规范并在其上扩展他们的特别功能。&lt;/p&gt;
&lt;p&gt;即使与之通信的设备本身不使用 PCIe 作为其物理层协议，系统仍必须使用 PCI 的软件接口进行通信。这是因为系统使用适配器（通常称为主机控制器），这些适配器是 PCI 设备，有助于将来自 CPU 的 PCI 请求转换为主机控制器支持的任何协议或总线。例如，此测试计算机上的所有 USB 3.1 都使用 USB XHCI 协议，该协议是一种通信协议，通过与 USB 主机控制器通信的 PCI 驱动程序将 PCIe 桥接到 USB。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/4ab558e7fb773c276583ff528ede9df0.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/4ab558e7fb773c276583ff528ede9df0.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;毋庸置疑，PCI 如今无处不在，并且已被计算机世界的各个部分完全采用。因此，我们必须对这项技术有很好的理解，以更好地理解现代计算。&lt;/p&gt;
&lt;h2 id=&#34;研究-pcie-层次结构---一种分组交换网络&#34;&gt;研究 PCIe 层次结构 - 一种分组交换网络&lt;/h2&gt;
&lt;p&gt;从传统的 PCI 转变到 PCIe 最重要的变化是从真正的总线拓扑结构转变为点对点链接。你可以将这看作是以太网集线器向今天的以太网交换机的演变。每个链接都是一个单独的点对点链接，其路由方式就像在一个分组交换的以太网网络上的以太网线一样。这意味着 PCIe 实际上并不是一个“总线协议”，尽管在各种文献和技术规范中让人困惑的频繁使用“总线”这个词。人们必须仔细理解，这个词“总线”并不意味着多个 PCIe 设备在同一个物理链接上进行通信。数据包（也被称为 TLPs）经过每个单独的链接，层次结构中的交换设备使用数据包内的路由信息将数据包传送到正确的 Port。&lt;/p&gt;
&lt;p&gt;在我们进入 PCIe 的技术细节之前，首先我们需要谈谈整个系统的布局。我们研究 PCIe 层次结构的第一种方法是通过 Windows 设备管理器。大多数熟悉 Windows 的人以前都用过它，但没有多少人知道 View &amp;gt; Devices by Connection 中发现的非常方便的功能。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/5e0afe87ec5a2f1049d03ceca8038673.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/5e0afe87ec5a2f1049d03ceca8038673.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;通过选择此视图，我们可以从根 PNP（Plug-N-Play）节点看到系统的完整拓扑。PNP 根节点是 Windows 上所有设备树的根，无论它们使用什么总线或协议。每个设备（无论是虚拟设备还是物理设备）都被枚举并放置在此 PNP 树上。我们可以利用 Device Manager 的这个视图来查看这个树的布局。&lt;/p&gt;
&lt;p&gt;特别是，我们希望在系统上找到 PCI 设备的布局。这样，我们就可以开始构建 PCI 树在这台机器上的外观的可视化模型。为此，我们需要找到 PCI 树的根：RC。RC（缩写为 RC）是系统上所有 PCIe 的所有者。它物理上位于 CPU 芯片上，负责充当所有 PCIe 设备接收和发送数据包的主机。它可以被认为是软件（在你的机器上执行的指令）和硬件（PCIe 和 RAM 的外部世界）之间的桥梁。&lt;/p&gt;
&lt;p&gt;在这个系统中，它位于这里的 PNP 层次结构中：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/485cfac384549d6d081e49b7c94e55f8.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/485cfac384549d6d081e49b7c94e55f8.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：你现在可能会问：“如果 PCI 主导了一切，为什么 PCI 根复合物不在树的顶部？答案是由于 PCIe 总线不是启动期间固件提供的系统初始布局。相反，ACPI（高级配置和电源接口）是描述 PCIe 到操作系统存在的东西。虽然你永远不会在 PC 中看到它，但可以描述一个没有 PCI 总线的系统，所有内容都完全由 ACPI 提供。我们稍后会详细讨论 ACPI，但现在不要太担心这个，只要知道 ACPI 是固件告诉我们RC在哪里的方式，然后帮助操作系统枚举树中的 PCI 设备。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以现在我们知道 RC 是 PCIe 树的顶部，现在让我们看一下它下面的所有内容：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8168e2ee37f0a29a65ce5e35bcae0361.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8168e2ee37f0a29a65ce5e35bcae0361.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;不出所料，此 PCI 总线上有许多设备。在这里，我们可以看到负责音频、集成显卡、USB、串行和 SATA 的各种控制器。此外，我们还看到其中一些设备称为 PCI Express Root Port。Root Port 是RC上的一个 Port，另一个 PCIe 端点（又名物理“设备”）或交换机（又名“路由器”）可以连接到该 Port。出于 PCI 规范的考虑，你将听到 Endpoint 称为 Type 0 设备，而 Switch（或网桥）称为 Type 1 设备，因为一个被配置为用于通信的设备，另一个被配置为用于路由数据包的设备。RC 将具有与其物理支持的一样多的 Root Port。也就是说，可以连接到 CPU 芯片的次数越多。CPU 上的一些 Root Port 可能直接路由到物理 PCIe 插槽，而其他 Root Port 可能路由到其他类型的插槽，如 NVMe 插槽。它也可能被路由到另一个 PCIe 交换设备，该设备可以将数据包路由到多个 Port，从而一次路由到多个端点。&lt;/p&gt;
&lt;p&gt;我会继续提出这个比较，但我觉得这很重要——如果你已经了解以太网交换机，你就已经了解 PCIe 交换机。你可以想象这些 Root Port 就像台式计算机上的以太网 Port。你可以将这些直接连接到其他设备（例如摄像头），也可以将它们连接到像家用路由器/调制解调器这样的交换机，这将交换数据包以公开更多连接，以便与更多设备和机器通信。在这种情况下，以太网线是将一个 PCIe Port 连接到另一个 PCIe Port 的铜线，从而使其成为“点对点”。&lt;/p&gt;
&lt;p&gt;考虑到这一点，让我们开始绘制这个层次结构（部分）图表，以便我们直观地看到它的全部布局：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/f9830973b55ef87fb9bf88615fb6c3cd.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/f9830973b55ef87fb9bf88615fb6c3cd.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在 PCI 中，系统上的所有“总线”都用 0 到 255（含）之间的数字标识。此外，所有设备都使用“设备 ID”和“功能 ID”进行标识。这通常被描述为 Bus/Device/Function，或简称为 BDF。在更正确的规范术语中，这称为 RID（请求者 ID）。为了减少混淆，我将它称为 BDF。BDF 很重要，因为它专门告诉我们设备在 PCIe 层次结构中的位置，以便我们可以与之通信。&lt;/p&gt;
&lt;p&gt;因为这些都位于层级结构的顶层，所以我们将为这个“bus”提供一个数字标识符，即“Bus 0”或 Root Bus。我们可以通过右键单击顶级设备并选择 Properties 并查看 Location 来验证所有这些设备是否都是 Bus 0 设备：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/369d7cf106107b3aa9ee08b6617e6af1.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/369d7cf106107b3aa9ee08b6617e6af1.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;对于此集成图形设备，它的 BDF 为 0:2.0。它位于总线 0（根总线）上，设备 ID 为 2，功能 ID 为 0。在这种情况下，“设备”表示物理设备，例如显卡。“功能”是物理设备向系统公开的独特功能。无论出于何种意图和目的，都可以将其视为一个单独的实体。公开多个功能的设备被恰当地称为多功能设备（MFD）。这意味着它向系统公开两个或多个 PCI 连接，而实际上只有一个设备。我们很快就会看到一个真正的 MFD 示例。&lt;/p&gt;
&lt;p&gt;敏锐的读者会注意到，我们已经打破了我之前提到的“规则”：与这个独特的总线 0 相连的设备有很多。这是 PCIe 中“点对点”规则的第一个例外，只有在因为总线 0 物理上位于 CPU 的硅片上的情况下才允许这样做。也就是说，这些设备之间没有电气路径，这是一个想象中的连接。所有这些设备都存在于 CPU 封装内，并使用极高速电气互连进行路由。这些处理器互连使用的是特定于 CPU 供应商的内部协议，尽管这些协议并未公开文档，但我们仍然以 PCIe 的“语言”与它进行通信。这些端点（标记为绿色），由于其特殊性质，将被赋予一个特殊的名称：根复合集成端点（RC Integrated Endpoints，简称 RCIE），因为它们直接集成在 RC 上。&lt;/p&gt;
&lt;p&gt;这并不奇怪，你会期望集成 UHD 图形等设备将物理位于 CPU 上（因为它是 CPU 规格的一部分）。但是，我们可以通过观察其他 RCIE 来了解系统的一些更有趣的拓扑结构，例如这里也存在 RAM 控制器（与内存的 DRAM DIMM 通信的硅）和 USB 控制器（与外部 USB 设备通信的硅）。这就是为什么某些 CPU 仅支持某些类型的 RAM 和 USB 规范的原因——因为通信的设备在物理上位于 CPU 上，并且仅支持它们在物理上创建时要支持的规范。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更新：这种说法是不正确的。一些 IO 控制器仍然可以在称为 PCH（Intel）或也称为芯片组（AMD）的分立芯片上找到，该芯片位于 CPU 附近，并且具有高速链路，使其看起来像是集成到 CPU 芯片中。上面这句话错误地说你可以在物理 CPU 上找到 USB 控制器，而它更有可能在“芯片组”上。但是，为了提高速度，与 RAM 通信的内存控制器位于 CPU 芯片上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;此图是层次结构第一级的最小化版本，但现在让我们通过在设备管理器中展开其余的 Root Ports 来构建层次结构的其余部分。&lt;/p&gt;
&lt;p&gt;这是填充的图表的样子：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8d9ee9bf28e10363f2b2f42b55e2e404.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8d9ee9bf28e10363f2b2f42b55e2e404.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：我已经标记了 UHD Graphics 设备和总线 0 的 BDF。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这些 Root Port 物理上位于 CPU 上，但连接到它的设备并不在其中。这台机器的外部 PCIe 插槽上连接了 3 个设备：一块 NVIDIA Quadro P400 图形卡和两个 NVMe 驱动器。通过进入设备管理器中每个设备的属性，我们可以获取并更新它们在视觉上的 BDF(总线、设备、功能) 信息。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/3ad55c7a1e1a1482a63679a009d6c414.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/3ad55c7a1e1a1482a63679a009d6c414.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在每个 Root Port 下，我们可以看到一个设备已物理连接。但是，我们还可以看到，我们在每个 Bus 下都公开了一个新的 Bus。Root Port 充当了桥，它将我们从总线 0 桥接到新的总线，因此必须为新总线分配一个新的数字 ID，并且该 Port 下的所有设备/功能都将继承该新总线编号。这与 OS/固件在引导期间的总线枚举期间使用的逻辑相同：所有网桥和交换机都公开一条新总线，必须为其分配新的总线 ID 号。&lt;/p&gt;
&lt;p&gt;在这种情况下，我们还可以看到一个多功能设备的好例子。Quadro P400 显卡充当具有两种功能的 MFD。第一个函数是 0（BDF 01:00.0），是显卡设备本身。第二个功能是 1（BDF 01:00.1），它是音频控制器，允许从 HDMI 等 Port 播放音频。这两个功能是不同的——它们用于完全不同的目的，并且具有与之关联的单独驱动程序和配置，但它们仍然由相同的物理设备（即设备 0）实现，并且位于同一总线（即总线 1）上。这与 PCIe 的点对点规则是一致的，一个链路上只能连接一个物理设备，因此总线上只能存在一个物理设备（除了例外，总线 0）。&lt;/p&gt;
&lt;h2 id=&#34;从-windbg-探索-pcie-层次结构和设备&#34;&gt;从 WinDbg 探索 PCIe 层次结构和设备&lt;/h2&gt;
&lt;p&gt;到目前为止，我们已经通过使用 Device Manager 的“View by Connection”功能看到了标准的 PCI 总线层次结构。还有另一种更详细的方法来调查 PCIe 层次结构：使用 WinDbg 提供的可靠内核调试扩展。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：我们假设你了解如何在一台机器上设置内核调试器来继续下面的操作。你也可以用 LiveKD 来完成大部分练习。如果你并不了解如何设置，可以参考微软提供的指南：&lt;a href=&#34;https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-up-a-network-debugging-connection-automatically&#34;&gt;设置 KDNET&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我已经连接到了一台与上述使用的机器不同的新测试机。我们将通过调试器的输出，来演练如何绘制这台机器的层次结构图。我们也将学习如何通过其配置内存来查找设备的信息。&lt;/p&gt;
&lt;p&gt;放入调试器后，我们将使用！pcitree 命令开始。这将转储系统上列举的 PCI 设备的文本树形图。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8: kd&amp;gt; !pcitree
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Bus 0x0 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89b9f75920&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f00 devext 0xffffdc89b0759270 devstack 0xffffdc89b0759120 &lt;span class=&#34;m&#34;&gt;0600&lt;/span&gt; Bridge/HOST to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f02 devext 0xffffdc89ba0c74c0 devstack 0xffffdc89ba0c7370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x1 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba0aa190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    No devices have been enumerated on this bus.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;2,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f04 devext 0xffffdc89ba0c94c0 devstack 0xffffdc89ba0c9370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x2 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba0a8190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 10de13bb devext 0xffffdc89ba04f270 devstack 0xffffdc89ba04f120 &lt;span class=&#34;m&#34;&gt;0300&lt;/span&gt; Display Controller/VGA
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 10de0fbc devext 0xffffdc89ba051270 devstack 0xffffdc89ba051120 &lt;span class=&#34;m&#34;&gt;0403&lt;/span&gt; Multimedia Device/Unknown Sub Class
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;3,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f08 devext 0xffffdc89ba0cb4c0 devstack 0xffffdc89ba0cb370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x3 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba08f190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    No devices have been enumerated on this bus.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;5,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f28 devext 0xffffdc89ba0cd4c0 devstack 0xffffdc89ba0cd370 &lt;span class=&#34;m&#34;&gt;0880&lt;/span&gt; Base System Device/&lt;span class=&#34;s1&#34;&gt;&amp;#39;Other&amp;#39;&lt;/span&gt; base system device
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;5,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f29 devext 0xffffdc89ba0cf4c0 devstack 0xffffdc89ba0cf370 &lt;span class=&#34;m&#34;&gt;0880&lt;/span&gt; Base System Device/&lt;span class=&#34;s1&#34;&gt;&amp;#39;Other&amp;#39;&lt;/span&gt; base system device
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;5,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;2&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f2a devext 0xffffdc89ba0d14c0 devstack 0xffffdc89ba0d1370 &lt;span class=&#34;m&#34;&gt;0880&lt;/span&gt; Base System Device/&lt;span class=&#34;s1&#34;&gt;&amp;#39;Other&amp;#39;&lt;/span&gt; base system device
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;5,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;4&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f2c devext 0xffffdc89ba0d34c0 devstack 0xffffdc89ba0d3370 &lt;span class=&#34;m&#34;&gt;0800&lt;/span&gt; Base System Device/Interrupt Controller
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;11, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d7c devext 0xffffdc89ba0d84c0 devstack 0xffffdc89ba0d8370 ff00 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;Explicitly&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Undefined/Unknown Sub Class
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;11, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;4&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d62 devext 0xffffdc89ba0da4c0 devstack 0xffffdc89ba0da370 &lt;span class=&#34;m&#34;&gt;0106&lt;/span&gt; Mass Storage Controller/Unknown Sub Class
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;14, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d31 devext 0xffffdc89ba0dc4c0 devstack 0xffffdc89ba0dc370 0c03 Serial Bus Controller/USB
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;16, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d3a devext 0xffffdc89ba0de4c0 devstack 0xffffdc89ba0de370 &lt;span class=&#34;m&#34;&gt;0780&lt;/span&gt; Simple Serial Communications Controller/&lt;span class=&#34;s1&#34;&gt;&amp;#39;Other&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;16, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;3&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d3d devext 0xffffdc89ba0e04c0 devstack 0xffffdc89ba0e0370 &lt;span class=&#34;m&#34;&gt;0700&lt;/span&gt; Simple Serial Communications Controller/Serial Port
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;19, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 808615a0 devext 0xffffdc89ba0e24c0 devstack 0xffffdc89ba0e2370 &lt;span class=&#34;m&#34;&gt;0200&lt;/span&gt; Network Controller/Ethernet
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1a, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d2d devext 0xffffdc89ba0e44c0 devstack 0xffffdc89ba0e4370 0c03 Serial Bus Controller/USB
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1b, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d20 devext 0xffffdc89ba0254c0 devstack 0xffffdc89ba025370 &lt;span class=&#34;m&#34;&gt;0403&lt;/span&gt; Multimedia Device/Unknown Sub Class
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1c, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d10 devext 0xffffdc89ba0274c0 devstack 0xffffdc89ba027370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x4 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba0a9190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    No devices have been enumerated on this bus.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1c, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d12 devext 0xffffdc89ba02c4c0 devstack 0xffffdc89ba02c370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x5 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89b9fe6190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    No devices have been enumerated on this bus.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1c, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;3&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d16 devext 0xffffdc89ba02e4c0 devstack 0xffffdc89ba02e370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x6 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba0a7190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;12838893&lt;/span&gt; devext 0xffffdc89ba062270 devstack 0xffffdc89ba062120 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Bus 0x7 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba064250&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      No devices have been enumerated on this bus.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1c, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;4&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d18 devext 0xffffdc89ba0304c0 devstack 0xffffdc89ba030370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x8 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba0b2190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    No devices have been enumerated on this bus.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1d, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d26 devext 0xffffdc89ba0364c0 devstack 0xffffdc89ba036370 0c03 Serial Bus Controller/USB
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1f, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d44 devext 0xffffdc89ba0384c0 devstack 0xffffdc89ba038370 &lt;span class=&#34;m&#34;&gt;0601&lt;/span&gt; Bridge/PCI to ISA
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1f, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;2&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d02 devext 0xffffdc89ba03a4c0 devstack 0xffffdc89ba03a370 &lt;span class=&#34;m&#34;&gt;0106&lt;/span&gt; Mass Storage Controller/Unknown Sub Class
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1f, &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;3&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80868d22 devext 0xffffdc89ba03c4c0 devstack 0xffffdc89ba03c370 0c05 Serial Bus Controller/Unknown Sub Class
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;注意：如果你遇到“无法获取 PciFdoExtensionListHead 地址”的错误，确保你的符号设置正确，并执行.reload pci.sys 操作来重新加载 PCI 的符号。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当显示此输出时，由于空格的格式设置方式，可能很难直观地看到“tree”。解释此输出的方法是查看 Bus 0x 文本的缩进。任何比 Bus 0x 行进一步缩进一组空格的东西都是该总线上的设备。我们可以看到，在器件的正下方还有其他 Bus 0x 线路。这意味着 Bus 0x 线上方的器件正在向我们公开一条新总线，并且总线编号在那里给出。&lt;/p&gt;
&lt;p&gt;让我们看一下此输出的特定部分：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Bus 0x0 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89b9f75920&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f00 devext 0xffffdc89b0759270 devstack 0xffffdc89b0759120 &lt;span class=&#34;m&#34;&gt;0600&lt;/span&gt; Bridge/HOST to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f02 devext 0xffffdc89ba0c74c0 devstack 0xffffdc89ba0c7370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x1 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba0aa190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    No devices have been enumerated on this bus.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;2,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f04 devext 0xffffdc89ba0c94c0 devstack 0xffffdc89ba0c9370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x2 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba0a8190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 10de13bb devext 0xffffdc89ba04f270 devstack 0xffffdc89ba04f120 &lt;span class=&#34;m&#34;&gt;0300&lt;/span&gt; Display Controller/VGA
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;1&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 10de0fbc devext 0xffffdc89ba051270 devstack 0xffffdc89ba051120 &lt;span class=&#34;m&#34;&gt;0403&lt;/span&gt; Multimedia Device/Unknown Sub Class
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;3,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 80866f08 devext 0xffffdc89ba0cb4c0 devstack 0xffffdc89ba0cb370 &lt;span class=&#34;m&#34;&gt;0604&lt;/span&gt; Bridge/PCI to PCI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Bus 0x3 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;FDO Ext ffffdc89ba08f190&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    No devices have been enumerated on this bus.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在此输出中，我们可以看到每个设备显示的 BDF。我们还可以看到总线 0 上存在的一组 Root Port，这些 Port 下面没有列举任何设备，这意味着插槽尚未连接到任何设备。&lt;/p&gt;
&lt;p&gt;在这里看到树结构应该更容易，但无论如何，让我们把它画出来：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7a507a4048a8a03806ced4cd6714bbf7.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7a507a4048a8a03806ced4cd6714bbf7.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：这只是一个巧合，即公交号恰好与桥梁/PCI 的设备编号匹配到 PCI 端口。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如你现在所知，标记为 Bridge/PCI to PCI 的设备实际上是 Root Port，而总线 2 上的设备实际上是一个多功能设备。与设备管理器不同，我们看不到！pcitree 中的设备真实名称。相反，我们只得到了一个通用的 PCI 名称，用于设备“类型”将自己宣传为什么。这是因为设备管理器 从驱动程序读取设备名称，而不是直接从 PCI 读取设备名称。&lt;/p&gt;
&lt;p&gt;要了解更多关于这个显示控制器设备的信息，我们可以使用命令 &lt;code&gt;！devext [pointer]&lt;/code&gt;，其中 &lt;code&gt;[pointer]&lt;/code&gt; 是布局中单词 &lt;code&gt;devext&lt;/code&gt; 后面的值。在本例中，它是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,  &lt;span class=&#34;nv&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 10de13bb devext 0xffffdc89ba04f270 devstack 0xffffdc89ba04f120 &lt;span class=&#34;m&#34;&gt;0300&lt;/span&gt; Display Controller/VGA
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;!devext 0xffffdc89ba04f270
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从这里，我们将从 Windows 中的 PCI 总线驱动程序获得此 PCI 设备的摘要的打印输出，&lt;code&gt;pci.sys&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8: kd&amp;gt; !devext 0xffffdc89ba04f270
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PDO Extension, Bus 0x2, Device 0, Function 0.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  DevObj 0xffffdc89ba04f120  Parent FDO DevExt 0xffffdc89ba0a8190
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Device &lt;span class=&#34;nv&#34;&gt;State&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; PciStarted
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Vendor ID 10de &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;NVIDIA CORPORATION&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;  Device ID 13BB
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Subsystem Vendor ID 103c &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;HEWLETT-PACKARD COMPANY&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;  Subsystem ID &lt;span class=&#34;m&#34;&gt;1098&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Header Type 0, Class Base/Sub 03/00  &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;Display Controller/VGA&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Programming Interface: 00, Revision: a2, IntPin: 01, RawLine &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Possible Decodes &lt;span class=&#34;o&#34;&gt;((&lt;/span&gt;cmd &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt; 7&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; 7&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;: BMI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Capabilities: &lt;span class=&#34;nv&#34;&gt;Ptr&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;60, power msi express 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Express capabilities: &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;BIOS controlled&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Logical Device Power State: D0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Device Wake Level:          Unspecified
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  WaitWakeIrp:                &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Requirements:     Alignment Length    Minimum          Maximum
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    BAR0    Mem:    &lt;span class=&#34;m&#34;&gt;01000000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;01000000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;0000000000000000&lt;/span&gt; 00000000ffffffff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    BAR1    Mem:    &lt;span class=&#34;m&#34;&gt;10000000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;10000000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;0000000000000000&lt;/span&gt; ffffffffffffffff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    BAR3    Mem:    &lt;span class=&#34;m&#34;&gt;02000000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;02000000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;0000000000000000&lt;/span&gt; ffffffffffffffff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    BAR5     Io:    &lt;span class=&#34;m&#34;&gt;00000080&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;00000080&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;0000000000000000&lt;/span&gt; 00000000ffffffff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      ROM BAR:      &lt;span class=&#34;m&#34;&gt;00080000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;00080000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;0000000000000000&lt;/span&gt; 00000000ffffffff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    VF BAR0 Mem:    &lt;span class=&#34;m&#34;&gt;00080000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;00080000&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;0000000000000000&lt;/span&gt; 00000000ffffffff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Resources:        Start            Length
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    BAR0    Mem:    00000000f2000000 &lt;span class=&#34;m&#34;&gt;01000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    BAR1    Mem:    00000000e0000000 &lt;span class=&#34;m&#34;&gt;10000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    BAR3    Mem:    00000000f0000000 &lt;span class=&#34;m&#34;&gt;02000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    BAR5     Io:    &lt;span class=&#34;m&#34;&gt;0000000000001000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Interrupt Requirement:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Line Based - Min &lt;span class=&#34;nv&#34;&gt;Vector&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; 0x0, Max &lt;span class=&#34;nv&#34;&gt;Vector&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; 0xffffffff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Message Based: Type - Msi, 0x1 messages requested
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Interrupt Resource:    Type - MSI, 0x1 Messages Granted
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里有很多内核知道的关于这个设备的信息。此信息是通过 配置空间（缩写为“config space”）检索的，配置空间 是系统上的内存部分，允许内核以标准化的方式枚举、查询信息和设置 PCI 设备。软件从设备读取内存以查询供应商 ID 等信息，设备（如果已打开电源）使用该信息进行响应。在下一节中，我将更多地讨论这实际上是如何发生的，但要知道这里查询的信息是从配置空间生成的。&lt;/p&gt;
&lt;p&gt;因此，让我们分解一些重要的东西：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DevObj：指向 &lt;code&gt;nt！_DEVICE_OBJECT&lt;/code&gt; 结构的指针，该结构表示内核中的物理设备。&lt;/li&gt;
&lt;li&gt;Vendor ID：注册给特定设备制造商的 16 位 ID 号。此值是标准化的，PCI-SIG 必须为新供应商分配一个唯一 ID，以便它们不会重叠。在本例中，我们看到这是 NVIDIA 显卡。&lt;/li&gt;
&lt;li&gt;Device  ID：执行 PCIe 的特定芯片的 16 位 ID 号。类似的想法是，公司必须为其芯片请求一个唯一的 ID，这样它就不会与任何其他芯片冲突。&lt;/li&gt;
&lt;li&gt;Subsystem Vendor ID：芯片所在电路板的供应商 ID。在这种情况下，“HP”是显卡的生产商，而“NVIDIA”设计了图形芯片。&lt;/li&gt;
&lt;li&gt;Subsystem Device ID：芯片所在电路板的设备 ID。&lt;/li&gt;
&lt;li&gt;Logical Device Power State：此设备的电源状态。PCI 中有两种主要的电源状态，D0 = 设备已通电，D3 = 设备处于低功耗状态或完全关闭。&lt;/li&gt;
&lt;li&gt;Requirements：设备要求 OS 为其分配的内存要求。稍后会详细介绍。&lt;/li&gt;
&lt;li&gt;Resources：操作系统分配给此设备的内存资源。此设备已打开电源并启动，因此已为其分配了资源。&lt;/li&gt;
&lt;li&gt;Interrupt Requirement/Resource：与上述相同，但是对于中断则不同。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要实际获取有关此设备的完整信息，我们可以使用 &lt;a href=&#34;https://pcilookup.com/&#34;&gt;PCI Lookup&lt;/a&gt; 中的出色工具来查询有关在 PCI-SIG 中注册的 PCI 设备的公共信息。让我们将有关设备和 Vendor ID 的信息放入框中：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/a70cefe8ad2cbb145ce145c1b16e562d.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/a70cefe8ad2cbb145ce145c1b16e562d.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;当我们搜索时，我们得到这个：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/098ff685f1e2ca1d27f87df7378a836a.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/098ff685f1e2ca1d27f87df7378a836a.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;这告诉我们该设备是 NVIDIA 创建的 Quadro K620 显卡。子系统 ID 告诉我们，这个特定的卡 PCB 是由 HP 生产的，该公司已获得 NVIDIA 的许可。&lt;/p&gt;
&lt;p&gt;我们在 &lt;code&gt;！devext&lt;/code&gt; 中看到的很好地概述了 &lt;code&gt;pci.sys&lt;/code&gt; 在摘要中特别关心向我们展示的内容，但它只触及了配置空间中所有信息的皮毛。要将所有信息转储到配置空间中，我们可以使用扩展名 &lt;code&gt;！pci 100 B D F&lt;/code&gt;，其中 BDF 是我们相关设备的 BDF。100 是一组标志，指定我们要转储有关设备的所有信息。显示的信息将按照它在设备的 config space 中存在的顺序进行布局。每个部分的前缀是一个偏移量，例如 &lt;code&gt;02&lt;/code&gt; 表示 Device ID。这指定了从中读取此值的 config 空间的偏移量。这些偏移量在 PCI 规范中进行了详细说明，并且不会出于向后兼容性目的在 PCI 版本之间更改。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8: kd&amp;gt; !pci &lt;span class=&#34;m&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PCI Configuration Space &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;Segment:0000 Bus:02 Device:00 Function:00&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Common Header:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    00: VendorID       10de Nvidia Corporation
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    02: DeviceID       13bb
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    04: Command        &lt;span class=&#34;m&#34;&gt;0507&lt;/span&gt; IOSpaceEn MemSpaceEn BusInitiate SERREn InterruptDis 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    06: Status         &lt;span class=&#34;m&#34;&gt;0010&lt;/span&gt; CapList 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    08: RevisionID     a2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    09: ProgIF         &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; VGA
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0a: SubClass       &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; VGA Compatible Controller
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0b: BaseClass      &lt;span class=&#34;m&#34;&gt;03&lt;/span&gt; Display Controller
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0c: CacheLineSize  &lt;span class=&#34;m&#34;&gt;0000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0d: LatencyTimer   &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0e: HeaderType     &lt;span class=&#34;m&#34;&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0f: BIST           &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    10: BAR0           f2000000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    14: BAR1           e000000c
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    18: BAR2           &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    1c: BAR3           f000000c
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    20: BAR4           &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    24: BAR5           &lt;span class=&#34;m&#34;&gt;00001001&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    28: CBCISPtr       &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    2c: SubSysVenID    103c
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    2e: SubSysID       &lt;span class=&#34;m&#34;&gt;1098&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    30: ROMBAR         &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    34: CapPtr         &lt;span class=&#34;m&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    3c: IntLine        &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    3d: IntPin         &lt;span class=&#34;m&#34;&gt;01&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    3e: MinGnt         &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    3f: MaxLat         &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Device Private:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    40: 1098103c &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    50: &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000001&lt;/span&gt; 0023d6ce &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    60: &lt;span class=&#34;m&#34;&gt;00036801&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000008&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00817805&lt;/span&gt; fee001f8
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    70: &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00120010&lt;/span&gt; 012c8de1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    80: &lt;span class=&#34;m&#34;&gt;00003930&lt;/span&gt; 00453d02 &lt;span class=&#34;m&#34;&gt;11010140&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    90: &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00040013&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    a0: &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000006&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000002&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    b0: &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;01140009&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    c0: &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    d0: &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    e0: &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    f0: &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Capabilities:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    60: CapID          &lt;span class=&#34;m&#34;&gt;01&lt;/span&gt; PwrMgmt Capability
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    61: NextPtr        &lt;span class=&#34;m&#34;&gt;68&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    62: PwrMgmtCap     &lt;span class=&#34;m&#34;&gt;0003&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;Version&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    64: PwrMgmtCtrl    &lt;span class=&#34;m&#34;&gt;0008&lt;/span&gt; DataScale:0 DataSel:0 D0 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    68: CapID          &lt;span class=&#34;m&#34;&gt;05&lt;/span&gt; MSI Capability
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    69: NextPtr        &lt;span class=&#34;m&#34;&gt;78&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    6a: MsgCtrl        64BitCapable MSIEnable MultipleMsgEnable:0 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;0x1&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; MultipleMsgCapable:0 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;0x1&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    6c: MsgAddrLow     fee001f8
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    70: MsgAddrHi      &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    74: MsgData        &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    78: CapID          &lt;span class=&#34;m&#34;&gt;10&lt;/span&gt; PCI Express Capability
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    79: NextPtr        &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    7a: Express Caps   &lt;span class=&#34;m&#34;&gt;0012&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ver. 2&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Type:LegacyEP
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    7c: Device Caps    012c8de1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    80: Device Control &lt;span class=&#34;m&#34;&gt;3930&lt;/span&gt; bcre/flr MRR:1K NS ap pf ET MP:256 RO ur fe nf ce
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    82: Device Status  &lt;span class=&#34;m&#34;&gt;0000&lt;/span&gt; tp ap ur fe nf ce
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    84: Link Caps      00453d02
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    88: Link Control   &lt;span class=&#34;m&#34;&gt;0140&lt;/span&gt; es CC rl ld RCB:64 ASPM:None 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    8a: Link Status    &lt;span class=&#34;m&#34;&gt;1101&lt;/span&gt; SCC lt lte NLW:x16 LS:2.5 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    9c: DeviceCaps2    &lt;span class=&#34;m&#34;&gt;00040013&lt;/span&gt; CTR:3 CTDIS arifwd aor aoc32 aoc64 cas128 noro ltr TPH:0 OBFF:1 extfmt eetlp EETLPMax:0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    a0: DeviceControl2 &lt;span class=&#34;m&#34;&gt;0000&lt;/span&gt; CTVal:0 ctdis arifwd aor aoeb idoreq idocom ltr OBFF:0 eetlp
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Enhanced Capabilities:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    100: CapID         &lt;span class=&#34;m&#34;&gt;0002&lt;/span&gt; Virtual Channel Capability
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         Version       &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         NextPtr       &lt;span class=&#34;m&#34;&gt;258&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0104: Port VC Capability &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;        &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0108: Port VC Capability &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;        &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    010c: Port VC Control             &lt;span class=&#34;m&#34;&gt;0000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    010e: Port VC Status              &lt;span class=&#34;m&#34;&gt;0000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0110: VC Resource&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; Cap          &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    0114: VC Resource&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; Control      800000ff
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    011a: VC Resource&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;0&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; Status       &lt;span class=&#34;m&#34;&gt;0000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    258: CapID         001e L1 PM SS Capability
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         Version       &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         NextPtr       &lt;span class=&#34;m&#34;&gt;128&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    25c: Capabilities  0028ff1f  PTPOV:5 PTPOS:0 PCMRT:255 L1PMS ASPML11 ASPML12 PCIPML11 PCIPML12
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    260: Control1      &lt;span class=&#34;m&#34;&gt;00000000&lt;/span&gt;  LTRL12TS:0 LTRL12TV:0 CMRT:0 aspml11 aspml12 pcipml11 pcipml12
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    264: Control2      &lt;span class=&#34;m&#34;&gt;00000028&lt;/span&gt;  TPOV:5 TPOS:0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    128: CapID         &lt;span class=&#34;m&#34;&gt;0004&lt;/span&gt; Power Budgeting Capability
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         Version       &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         NextPtr       &lt;span class=&#34;m&#34;&gt;600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    600: CapID         000b Vendor Specific Capability
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         Version       &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         NextPtr       &lt;span class=&#34;m&#34;&gt;000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         Vendor Specific ID &lt;span class=&#34;m&#34;&gt;0001&lt;/span&gt; - Ver. &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;  Length: &lt;span class=&#34;m&#34;&gt;024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个视图的好处是，我们可以看到有关配置空间的 Capabilities 部分的详细信息。Capabilities 是 config 空间中的一组结构，它准确描述了 device 能够实现的功能。Capabilities 包括链接速度和设备支持的中断类型等信息。PCI 规范中添加的任何新功能都将通过这些结构进行公布，这些结构在配置空间中形成了一个功能链表，可以迭代以发现设备的所有功能。并非所有这些功能都与操作系统相关，有些功能仅与本文未涵盖的硬件方面相关。现在，我不会详细介绍该设备的功能。&lt;/p&gt;
&lt;h2 id=&#34;pcie一切都与内存相关&#34;&gt;PCIe：一切都与内存相关&lt;/h2&gt;
&lt;p&gt;现在我们已经研究了几个设备和 PCI 总线的层次结构，让我们谈谈与软件和 PCI 设备的通信实际上是如何运作的。当我第一次学习 PCI 时，我很难理解当软件与 PCI 设备连接时到底发生了什么。因为整个事务对作为软件开发人员的你来说是抽象出来的，所以很难仅通过从调试工具中探入 PCI 内存来构建所发生的事情的心智模型。希望这篇文章能提供比我刚开始时所能得到的更好的概述。&lt;/p&gt;
&lt;p&gt;首先，我要做一个大胆的声明：&lt;strong&gt;所有现代 PCIe 通信都是通过内存读写完成的&lt;/strong&gt;。如果你了解 PCIe 中的内存如何工作，你就会了解 PCIe 软件通信的工作原理。（是的，在某些平台上还有其他传统的通信方式，但我们不会讨论这些方式，因为它们已被弃用）。&lt;/p&gt;
&lt;p&gt;现在，让我们谈谈现代平台上不同类型的内存。在启动的早期，操作系统的 CPU 将使用虚拟内存。也就是说，CPU 看到的内存地址是映射到物理内存世界的内存视图。&lt;/p&gt;
&lt;p&gt;就我们的目的而言，系统上有两种类型的物理内存：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RAM - 读取或写入时从计算机上的 DRAM DIMM 存储和检索的地址。这就是大多数人在想到“内存”时所想到的。&lt;/li&gt;
&lt;li&gt;Device Memory（设备内存） - 在读取或写入时与系统上的设备“对话”的地址。这里的关键词是“对话”。它不会在设备上存储内存，也不会检索设备上的内存（尽管设备可能同时能够同时检索两者）。你可能正在与之通信的地址甚至可能根本不是内存，而是一个更抽象的“device register” ，用于配置设备的内部工作。这种访问会发生什么取决于设备。你所做的只是与设备通信。你通常会看到这称为 MMIO，它全称是 Memory-Mapped I/O。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：每当设备不响应设备内存区域中访问的地址时，PCI 的设备内存将始终读取“全 1”或“所有 FF”。这是了解设备何时实际响应的便捷方法。如果你看到所有 FF，则表示你正在读取无效的设备地址。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;初学者认为所有物理内存都是 RAM，这是错误的。当软件与 PCI 区域中的 PCI 设备通信时，它不会从 RAM 读取和写入数据。相反，该设备从 RC 接收一个数据包（TLP，传输层数据包），当 PCI 区域内的地址被读/写时，你的 CPU 会立即自动生成该数据包。你无需在软件中创建这些数据包，所有这些数据包都是在访问此内存后立即完全在后台生成的。在软件中，你甚至无法查看或捕获这些数据包，而需要一个特殊的硬件测试设备来拦截和查看正在发送的数据包。稍后会详细介绍。&lt;/p&gt;
&lt;p&gt;如果有帮助，请将物理内存视为设备的映射。RAM 是为你映射到物理内存中的设备。PCI 还会自动为你映射区域。尽管它们截然不同且行为也非常不同，但它们在软件中看起来是相同的。&lt;/p&gt;
&lt;p&gt;在下图中，我们可以看到典型系统如何将虚拟内存映射到物理内存。请注意，有两个 RAM 区域和两个 PCI 内存区域。这是因为某些较旧的 PCI 设备只能寻址 32 位内存。因此，如果你的 RAM 不适合 4GB 以下的地址窗口，则一些 RAM 会上移到 4GB 以上。由于你的处理器支持 64 位地址，因此这不是问题。此外，在 4GB 行上方为支持 64 位地址的 PCI 设备创建第二个窗口。由于 4GB 区域可能非常有限，因此设备最好在 4GB 以上移动尽可能多的内存，以免弄乱下面的空间。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/2f1b3cf2195ea201e631c0d29929398b.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/2f1b3cf2195ea201e631c0d29929398b.png&#34; alt=&#34;&#34;  title=&#34;如何将虚拟地址范围映射到物理地址的非常简化的视图。这忽略了物理内存中的大量 “特殊” 区域，但展示了 RAM 和设备内存是如何不同的。&#34; style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;首先，让我们来谈谈我们已经见过存储器：&lt;strong&gt;配置空间&lt;/strong&gt;（Configuration Space）。&lt;/p&gt;
&lt;p&gt;配置空间位于一个名为 ECAM（Extended Configuration Access Management，扩展配置访问管理）的内存部分。因为它是一种设备内存，所以要从内核（使用虚拟内存）访问这段内存，内核必须请求内存管理器将这部分物理内存映射到一个虚拟地址上。然后，软件指令可以使用映射的虚拟地址来从物理地址读取和写入。在 Windows 上，定位和映射这段内存的工作部分由&lt;code&gt;pci.sys&lt;/code&gt;处理，部分由&lt;code&gt;acpi.sys&lt;/code&gt;处理，还有部分由内核（具体来说是 HAL）处理。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：通常，在 Windows 中映射设备内存的方式是通过 &lt;a href=&#34;https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmmapiospaceex&#34;&gt;MmMapIoSpaceEx&lt;/a&gt;，这是驱动程序可用于映射物理设备内存的 API。但是，为了进行配置空间访问，软件必须使用 &lt;a href=&#34;https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-halgetbusdatabyoffset&#34;&gt;HalGetBusDataByOffset&lt;/a&gt; 和 &lt;a href=&#34;https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-halsetbusdatabyoffset&#34;&gt;HalSetBusDataByOffset&lt;/a&gt; 来确保 &lt;code&gt;pci.sys&lt;/code&gt; 的内部状态与你正在执行的配置空间读/写保持同步。如果你尝试自己映射和更改配置空间，则可能会使 &lt;code&gt;pci.sys&lt;/code&gt; 状态不同步并导致蓝屏死机。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：ECAM/PCI 区域在物理内存中的位置取决于平台。引导时的固件将分配系统物理内存的所有特殊区域。然后，固件会在引导期间向操作系统公布这些区域的位置。在 x86-64 系统上，ECAM 区域将使用称为 MCFG 的表（结构）通过 ACPI 从固件进行通信。现在知道使用什么特定协议来检索此信息不是很重要吗，只需了解操作系统从固件中检索这些区域的地址，固件决定了将它们放在哪里。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因此，为了进行配置空间访问，内核必须将配置空间（ECAM）映射到虚拟内存。这是这样的事情会是什么样子：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7bb04dca3ffe48821f8bf9f6f48bc71b.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7bb04dca3ffe48821f8bf9f6f48bc71b.png&#34; alt=&#34;&#34;  title=&#34;ECAM 到虚拟内存的映射。可怕的是没有规模。&#34; style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在此之后，内核现在可以使用虚拟映射与设备的配置空间进行通信。但是这个配置空间是什么样的呢？嗯，它只是我们上面讨论的一堆配置空间结构块。设备可能具有的每个可能的 BDF 都在 ECAM 中提供了空间来对其进行配置。它的布局方式是，设备的 BDF 会告诉你其配置空间在 ECAM 中的确切位置。也就是说，给定一个 BDF，我们可以计算要添加到 ECAM 区域基数的偏移量，以便与设备通信，因为每个功能的所有 ECAM 区域的大小都相同。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/54b366fd88125e12c7b1cffd70f8f0bd.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/54b366fd88125e12c7b1cffd70f8f0bd.png&#34; alt=&#34;&#34;  title=&#34;如果设备不存在，系统将读回所有 FF（二进制中的所有 1）。这将表明设备当前在系统上未处于活动状态&#34; style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;从这张图中，我们可以开始看到 PCIe 的枚举实际上是如何发生的。当我们读回有效的配置空间数据时，我们知道该 BDF 上存在设备。如果我们改为读回 FF，我们知道设备不在该插槽或功能中。当然，我们不会为了枚举所有设备而暴力破解每个地址，因为由于 MMIO 的开销，代价比较大。但是，这种蛮力的高级版本是我们如何快速枚举所有已通电并在配置空间上响应我们的设备。&lt;/p&gt;
&lt;h2 id=&#34;把它们放在一起---软件配置空间访问&#34;&gt;把它们放在一起 - 软件配置空间访问&lt;/h2&gt;
&lt;p&gt;现在我们了解了如何访问配置空间，我们可以将两端（层次结构和 MMIO）放在一起，以查看从内核模式读取配置空间的指令的完整路径。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/ed43867c9ad22be7633a41a8cb397d12.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/ed43867c9ad22be7633a41a8cb397d12.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;让我们逐步完成此处采用的整个路径（从左到右）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在内核模式下运行的某些代码从 ECAM 虚拟映射中读取偏移量。&lt;/li&gt;
&lt;li&gt;虚拟映射由 CPU 的页表转换为 ECAM 中的物理地址。&lt;/li&gt;
&lt;li&gt;读取物理地址，导致内部 CPU 互连中发生操作，以通知RC访问。&lt;/li&gt;
&lt;li&gt;RC将请求的数据包化版本生成为 TLP，该 TLP 显示“读取设备 02:00.0 的偏移量 0x0 处的值”，并通过层次结构发送该请求。&lt;/li&gt;
&lt;li&gt;TLP 由总线 2 上的此显示控制器接收，并看到它是一个配置空间 TLP。现在，它知道使用包含偏移量 0x0 处的值内容的配置空间响应 TLP 进行响应。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现在让我们看看响应：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7636af73b1e906b7cbf1cc44fe620dfc.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7636af73b1e906b7cbf1cc44fe620dfc.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;响应路径没那么有趣了。设备以含有偏移 0 处的值（我们知道这是供应商 ID）的特殊 TLP 进行响应。这个数据包找到回到请求者（即RC），然后互连通知 CPU 更新 rax 的值为 0x10DE，这是 NVIDIA 显卡的供应商 ID。然后，CPU 开始执行下一条指令。&lt;/p&gt;
&lt;p&gt;如你所想那样，通过这种方式进行访问可能比通过全部的 TLP 生成的 RAM 慢很多。这确实是事实，并且这也是存在比这种 MMIO 方法更多的方式去与设备通信的主要原因之一。在接下来的文章中，我将详细介绍另一种方法，即 DMA，以及它对于确保软件能够尽可能快地在 CPU 和设备之间传输内存的至关重要性。&lt;/p&gt;
&lt;h2 id=&#34;练习通过-windbg-手动访问-ecam&#34;&gt;练习：通过 WinDbg 手动访问 ECAM&lt;/h2&gt;
&lt;p&gt;我们看了一下 config space access 理论上是如何发生的，但让我们自己用 debugger 做同样的事情。为此，我们希望：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;找到 ECAM 在系统上的位置。&lt;/li&gt;
&lt;li&gt;计算到 ECAM 的偏移量以读取设备的供应商 ID。为此，我选择了 NVIDIA 显卡上的&lt;code&gt;Multimedia Device @ 02:00.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在该地址执行物理内存读取以检索值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第一步是找到 ECAM。鉴于 ECAM 的位置来自 ACPI，特别是 ACPI 中的 MCFG 表，这部分有点棘手。这是 firmware 用来告诉操作系统 ECAM 在系统的物理内存映射中的位置的表。关于 ACPI 以及如何将其与 PCI 结合使用，有很多内容要讨论，但现在，我将快速跳到相关部分以实现我们的目标。&lt;/p&gt;
&lt;p&gt;在我们的调试器中，我们可以通过使用&lt;code&gt;!acpicache&lt;/code&gt;来转储所有 ACPI 表的缓存副本。要转储 MCFG，请点击链接 MCFG 来转储其内容，或手动键入&lt;code&gt;!acpitable MCFG&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8: kd&amp;gt; !acpicache
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Dumping cached ACPI tables...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  XSDT @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;fffff7b6c0004018&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x0000bc TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  MCFG @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;fffff7b6c0005018&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x00003c TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  FACP @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;fffff7b6c0007018&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x4 Len: 0x0000f4 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  APIC @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;fffff7b6c0008018&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x2 Len: 0x000afc TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  DMAR @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;fffff7b6c000a018&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x0000c0 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  HPET @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;fffff7b6c015a018&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x000038 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  TCPA @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b07209f8&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x2 Len: 0x000064 TableID: EDK2    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  SSDT @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b0720a88&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x2 Len: 0x0003b3 TableID: Tpm2Tabl
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  TPM2 @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b0720e68&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x3 Len: 0x000034 TableID: EDK2    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  SSDT @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b07fc018&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x0013a1 TableID: Plat_Wmi
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  UEFI @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b07fd3e8&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x000042 TableID: 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  BDAT @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b07fd458&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x000030 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  MSDM @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b07fd4b8&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x3 Len: 0x000055 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  SLIC @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b07fd538&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x000176 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  WSMT @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b07fd6d8&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x000028 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  WDDT @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b0721a68&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x000040 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  SSDT @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b2580018&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x2 Len: 0x086372 TableID: SSDT  PM
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  NITR @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b26063b8&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x2 Len: 0x000071 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ASF! @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b2606548&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x20 Len: 0x000074 TableID:  HCG
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  BGRT @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b26065e8&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x1 Len: 0x000038 TableID: TIANO   
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  DSDT @&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;ffffdc89b0e94018&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Rev: 0x2 Len: 0x021c89 TableID: SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8: kd&amp;gt; !acpitable MCFG
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;HEADER - fffff7b6c0005018
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Signature:               MCFG
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Length:                  0x0000003c
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Revision:                0x01
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Checksum:                0x3c
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  OEMID:                   HPQOEM
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  OEMTableID:              SLIC-WKS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  OEMRevision:             0x00000001
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  CreatorID:               INTL
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  CreatorRev:              0x20091013
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BODY - fffff7b6c000503c
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fffff7b6&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;c000503c  &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; 00-00 &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; d0 &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;  ................
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fffff7b6&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;c000504c  &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; ff &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;                          ........
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;要了解如何阅读此表，遗憾的是，我们需要查看 ACPI 规范。与其让你这样做，不如省去你的痛苦，把相关部分拉到这里：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/2e26ba8d45b7e876b78500fa7392440d.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/2e26ba8d45b7e876b78500fa7392440d.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;由于 &lt;code&gt;！acpitable&lt;/code&gt; 命令已经解析并显示此表中 &lt;code&gt;Creator Revision&lt;/code&gt; 之前的所有内容，因此 &lt;code&gt;BODY&lt;/code&gt; 的前 8 个字节将是偏移量 36 处的 8 个字节的 &lt;code&gt;Reserved&lt;/code&gt; 内存。因此，我们跳过这 8 个字节并找到以下结构：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/936801aafdfbd7bc6e13e82310ce925f.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/936801aafdfbd7bc6e13e82310ce925f.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;此字节的前 8 个字节是 &lt;code&gt;Reserved&lt;/code&gt; 后面的区域的 &lt;code&gt;ECAM&lt;/code&gt; 区域的地址。这意味着 &lt;code&gt;ECAM&lt;/code&gt; 基址的偏移量为偏移量 8。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BODY - fffff7b6c000503c
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fffff7b6&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;c000503c  &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; 00-00 &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; d0 &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;  ................
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fffff7b6&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;c000504c  &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; ff &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;                          ........
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于这个系统，ECAM 位于地址：&lt;code&gt;0xD0000000&lt;/code&gt;。（请别忘了以小端序来读取这个地址）&lt;/p&gt;
&lt;p&gt;为了验证我们得到了正确的地址，让我们读取&lt;code&gt;00:00.0&lt;/code&gt;的供应商 ID，这也是 ECAM 的前两个字节。我们将使用&lt;code&gt;!dw&lt;/code&gt;命令来完成这个操作，该命令代表的&lt;code&gt;dump physical word&lt;/code&gt;（感叹号代表物理）。这个命令要求你指定一个缓存类型，在我们的情况下，总是使用&lt;code&gt;[uc]&lt;/code&gt;或者说未缓存。它还提供了一个长度，这是由 L1 指定要读取的 word 的数量。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：请务必始终将目标设备内存的大小与我们从软件中读取的大小相匹配。这意味着，如果我们要读取的值是 16 位值（如供应商 ID），则必须执行 16 位读取。执行 32 位读取可能会更改设备响应的结果。对于配置空间，我们可以读取供应商 ID 的更大大小，但并非在所有情况下都是如此。最好养成将读取大小与目标大小匹配的习惯，以避免任何意外结果。请记住：设备内存不是 RAM。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;综上所述，我们读取 &lt;code&gt;00：00.0&lt;/code&gt; 的 VendorID，如下所示：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8: kd&amp;gt; !dw &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;uc&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; D0000000 L1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#d0000000 8086&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们读取的结果值为 &lt;code&gt;0x8086&lt;/code&gt;，它恰好是 &lt;code&gt;Intel&lt;/code&gt; 的供应商 ID。为了验证这是正确的，让我们使用 &lt;code&gt;！pci&lt;/code&gt; 转储相同的内容。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8: kd&amp;gt; !pci &lt;span class=&#34;m&#34;&gt;100&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PCI Configuration Space &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;Segment:0000 Bus:00 Device:00 Function:00&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Common Header:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    00: VendorID       &lt;span class=&#34;m&#34;&gt;8086&lt;/span&gt; Intel Corporation
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;从特定函数读取-vendorid&#34;&gt;从特定函数读取 VendorID&lt;/h3&gt;
&lt;p&gt;现在要计算我们希望与之通信的另一个函数（&lt;code&gt;02：00.1&lt;/code&gt; 的 NVIDIA 卡）的 ECAM 地址，我们需要通过使用目标函数的 BDF 和一些位数学计算到 ECAM 的偏移量来手动执行“数组访问”。&lt;/p&gt;
&lt;p&gt;计算方法存在于 PCIe 规范中，该规范为总线、器件和函数分配了一定数量的 ECAM 位来计算偏移量：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;27&lt;/span&gt; - &lt;span class=&#34;m&#34;&gt;20&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;19&lt;/span&gt; - &lt;span class=&#34;m&#34;&gt;15&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;14&lt;/span&gt; - &lt;span class=&#34;m&#34;&gt;12&lt;/span&gt;     &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;11&lt;/span&gt; - &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;       &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; Bus Nr  &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; Dev Nr  &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; Function Nr &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; Register      &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;通过填写 BDF 并根据每个元素的位位置对结果进行移位和 OR 运算，我们可以计算出要添加到 ECAM 的偏移量。&lt;/p&gt;
&lt;p&gt;我将使用 python，但你可以使用任何你想要的计算器：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt;&amp;gt;&amp;gt; hex&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;0xD0000000 + &lt;span class=&#34;o&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt; 20) | (0 &amp;lt;&amp;lt; 15) | (1 &amp;lt;&amp;lt; 12)))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;&amp;#39;0xd020&lt;/span&gt;1000&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这意味着 &lt;code&gt;02：00.1&lt;/code&gt; 的 ECAM 区域位于 &lt;code&gt;0xD0201000&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;现在，要从函数中读取 VendorID 的值：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8: kd&amp;gt; !dw &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;uc&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; D0201000 L1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#d0201000 10de&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;结果是 &lt;code&gt;0x10de&lt;/code&gt;，我们从上面知道它是 NVIDIA Corporation！这意味着我们成功地从 ECAM 中读取了此函数的第一个值。&lt;/p&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;
&lt;p&gt;这篇帖子最终比我预期的要长得多！我不会继续这篇文章，而是将其拆分并随着时间的推移充实该系列。关于 PCIe，我想介绍的主题太多了，但空闲时间却很少，但在下一篇文章中，我将更详细地介绍设备 BAR（一种特定于设备的 MMIO 形式）和 DMA（直接内存访问）。本系列将继续使用与以前相同的租户，更侧重于理解而不是具体细节。&lt;/p&gt;
&lt;p&gt;希望你喜欢这个对 PCIe 世界的小小了解！期待更多精彩。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://lifeislife.cn/posts/%E8%AF%91%E6%96%87-pcie-part2/&#34;&gt;单击此处查看第 2 部分！&lt;/a&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<blockquote>
<p>本文翻译自：<a href="https://ctf.re/windows/kernel/pcie/tutorial/2023/02/14/pcie-part-1/">面向 Windows 初学者的 PCIe 实用教程（第 1 部分）– 灵魂的逆向工程 &mdash; A Practical Tutorial on PCIe for Total Beginners on Windows (Part 1) – Reversing Engineering for the Soul</a></p>
</blockquote>
<p>Hello！我最近一直在与一些朋友和同事交谈，他们有兴趣了解更多关于 PCIe 的信息，但对复杂性或缺乏适合初学者的简单资源感到害怕。我最近经常使用 PCIe，觉得可能值得以博客文章的形式分享我的一些经验。</p>
<p>本文主要供具有计算机系统背景并且喜欢亲身实践的人员使用。它也适用于 PCIe 的初学者，或者是对通用概念有所理解但却无法将它们联系在一起的人。</p>
<p>第一件事是第一件事：不要被吓倒。有很多首字母缩略词和令人困惑的概念，当你&quot;明白&quot;时，它们就会变得简单。当时要迈出一步，不要害怕提出问题！（如果你想问我问题，可以考虑在 <a href="https://discord.gg/rtfm">Reverse Engineering Discord</a> 的 #hardware 频道@Gbps ping 我）</p>
<p>我<strong>打算</strong>在这个系列中做几件事：</p>
<ul>
<li>从软件方面将 PCIe 分解为我认为最重要的内容，以学习和为现代 PC/服务器系统构建一个良好的基线思想模型。</li>
<li>展示使用各种工具（通常是 WinDbg）在 Windows 上调查 PCIe 层次结构和设备的实际示例。</li>
<li>为避免造成混淆，我会有意识地简化或略过一些具体的细节。在这里，可能会有一些术语的使用不准确，甚至信息本身也可能在技术上有所出入。但是，这样做的目的是为了学习整个系统，而不是规范的具体细节。PCIe 是复杂的，当我们处于初学阶段时，陷入过多的细节和特殊情况是没有意义的。</li>
<li>我们希望通过将这项技术与你已经熟悉的概念相联系，来揭开其神秘面纱。PCIe 并未重新发明轮子，通过理解与它类似的技术，你可能已经比你自己意识到的了解得更多。</li>
</ul>
<p>我<strong>不打算</strong>用这个系列做以下事情：</p>
<ul>
<li>详细了解传统 PCI 或 PCI-X。一般来说，这项技术除了历史价值之外并不重要。</li>
<li>演示如何为 PCIe 设备编写设备驱动程序。这是非常特定于操作系统的，并且比这里要讨论的要高得多。</li>
<li>详细介绍 PCIe 的链路层。该规范的一半以上都花在了这个主题上，并包含了一些世界上最前沿的高速数据传输技术。我不处理这边的事情，但是将来我可能会谈论使用 FPGA 构建 PCIe 设备（我以前做过）。</li>
<li>帮助你使用 PCIe 在视频游戏中作弊。是的，它存在。不，我不会帮忙。</li>
</ul>
<p>这并不是对技术或协议的全面研究。要获得真正详尽的了解，你应该参考永远难以捉摸的 PCI-SIG PCI Express 基本规范。这是实现所有 PCIe 代码所依据的规范。目前，在撰写本文时，我们使用的是该规范的 6.0 版，但 3.0 及更高版本的版本都与现代 PCIe 完全相关。如何获得这种昂贵的规格对读者来说是一项练习。</p>
<blockquote>
<p>注意：我有时会在“PCI”和“PCIe”之间来回切换，将技术描述为一种习惯的力量。除非另有说明，否则本系列中的所有内容都是关于 PCIe 的。</p>
</blockquote>
<h2 id="什么是-pcie我为什么要关注">什么是 PCIe，我为什么要关注？</h2>
<p>PCIe 代表 Peripheral Component Interconnect Express，外围设备组件互联传输。它于 2003 年首次推出，是从早期 PC 时代越来越流行的旧 PCI 和 PCI-X 规范演变而来的（为 Express 添加了“e”以区分它）。</p>
<p>大多数使用计算机的人都认为它是主板上插入显卡或适配器卡的 PCIe 插槽，但 PCIe 不仅仅是这几个扩展 Port。PCIe 是现代 CPU 与连接到系统的几乎所有设备通信的基础。</p>
<p>自推出以来，PCIe 的受欢迎程度飙升，成为短距离高速数据传输的近乎通用的标准。几乎所有的 M.2 SSD 都使用 NVMe over PCIe 作为其传输协议。Thunderbolt 3 能够使用外部线将 PCIe 设备直接动态热插拔到系统（支持扩展坞和 eGPU 等技术）。在此基础上，USB4 正在扩展 Thunderbolt 3，以使这种 PCIe 路由技术能够达到开放的 USB 规范。CXL 等新型传输协议，用于数据中心服务器，以 PCIe 为基础规范并在其上扩展他们的特别功能。</p>
<p>即使与之通信的设备本身不使用 PCIe 作为其物理层协议，系统仍必须使用 PCI 的软件接口进行通信。这是因为系统使用适配器（通常称为主机控制器），这些适配器是 PCI 设备，有助于将来自 CPU 的 PCI 请求转换为主机控制器支持的任何协议或总线。例如，此测试计算机上的所有 USB 3.1 都使用 USB XHCI 协议，该协议是一种通信协议，通过与 USB 主机控制器通信的 PCI 驱动程序将 PCIe 桥接到 USB。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/4ab558e7fb773c276583ff528ede9df0.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/4ab558e7fb773c276583ff528ede9df0.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>毋庸置疑，PCI 如今无处不在，并且已被计算机世界的各个部分完全采用。因此，我们必须对这项技术有很好的理解，以更好地理解现代计算。</p>
<h2 id="研究-pcie-层次结构---一种分组交换网络">研究 PCIe 层次结构 - 一种分组交换网络</h2>
<p>从传统的 PCI 转变到 PCIe 最重要的变化是从真正的总线拓扑结构转变为点对点链接。你可以将这看作是以太网集线器向今天的以太网交换机的演变。每个链接都是一个单独的点对点链接，其路由方式就像在一个分组交换的以太网网络上的以太网线一样。这意味着 PCIe 实际上并不是一个“总线协议”，尽管在各种文献和技术规范中让人困惑的频繁使用“总线”这个词。人们必须仔细理解，这个词“总线”并不意味着多个 PCIe 设备在同一个物理链接上进行通信。数据包（也被称为 TLPs）经过每个单独的链接，层次结构中的交换设备使用数据包内的路由信息将数据包传送到正确的 Port。</p>
<p>在我们进入 PCIe 的技术细节之前，首先我们需要谈谈整个系统的布局。我们研究 PCIe 层次结构的第一种方法是通过 Windows 设备管理器。大多数熟悉 Windows 的人以前都用过它，但没有多少人知道 View &gt; Devices by Connection 中发现的非常方便的功能。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/5e0afe87ec5a2f1049d03ceca8038673.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/5e0afe87ec5a2f1049d03ceca8038673.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>通过选择此视图，我们可以从根 PNP（Plug-N-Play）节点看到系统的完整拓扑。PNP 根节点是 Windows 上所有设备树的根，无论它们使用什么总线或协议。每个设备（无论是虚拟设备还是物理设备）都被枚举并放置在此 PNP 树上。我们可以利用 Device Manager 的这个视图来查看这个树的布局。</p>
<p>特别是，我们希望在系统上找到 PCI 设备的布局。这样，我们就可以开始构建 PCI 树在这台机器上的外观的可视化模型。为此，我们需要找到 PCI 树的根：RC。RC（缩写为 RC）是系统上所有 PCIe 的所有者。它物理上位于 CPU 芯片上，负责充当所有 PCIe 设备接收和发送数据包的主机。它可以被认为是软件（在你的机器上执行的指令）和硬件（PCIe 和 RAM 的外部世界）之间的桥梁。</p>
<p>在这个系统中，它位于这里的 PNP 层次结构中：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/485cfac384549d6d081e49b7c94e55f8.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/485cfac384549d6d081e49b7c94e55f8.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<blockquote>
<p>注意：你现在可能会问：“如果 PCI 主导了一切，为什么 PCI 根复合物不在树的顶部？答案是由于 PCIe 总线不是启动期间固件提供的系统初始布局。相反，ACPI（高级配置和电源接口）是描述 PCIe 到操作系统存在的东西。虽然你永远不会在 PC 中看到它，但可以描述一个没有 PCI 总线的系统，所有内容都完全由 ACPI 提供。我们稍后会详细讨论 ACPI，但现在不要太担心这个，只要知道 ACPI 是固件告诉我们RC在哪里的方式，然后帮助操作系统枚举树中的 PCI 设备。</p>
</blockquote>
<p>所以现在我们知道 RC 是 PCIe 树的顶部，现在让我们看一下它下面的所有内容：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8168e2ee37f0a29a65ce5e35bcae0361.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8168e2ee37f0a29a65ce5e35bcae0361.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>不出所料，此 PCI 总线上有许多设备。在这里，我们可以看到负责音频、集成显卡、USB、串行和 SATA 的各种控制器。此外，我们还看到其中一些设备称为 PCI Express Root Port。Root Port 是RC上的一个 Port，另一个 PCIe 端点（又名物理“设备”）或交换机（又名“路由器”）可以连接到该 Port。出于 PCI 规范的考虑，你将听到 Endpoint 称为 Type 0 设备，而 Switch（或网桥）称为 Type 1 设备，因为一个被配置为用于通信的设备，另一个被配置为用于路由数据包的设备。RC 将具有与其物理支持的一样多的 Root Port。也就是说，可以连接到 CPU 芯片的次数越多。CPU 上的一些 Root Port 可能直接路由到物理 PCIe 插槽，而其他 Root Port 可能路由到其他类型的插槽，如 NVMe 插槽。它也可能被路由到另一个 PCIe 交换设备，该设备可以将数据包路由到多个 Port，从而一次路由到多个端点。</p>
<p>我会继续提出这个比较，但我觉得这很重要——如果你已经了解以太网交换机，你就已经了解 PCIe 交换机。你可以想象这些 Root Port 就像台式计算机上的以太网 Port。你可以将这些直接连接到其他设备（例如摄像头），也可以将它们连接到像家用路由器/调制解调器这样的交换机，这将交换数据包以公开更多连接，以便与更多设备和机器通信。在这种情况下，以太网线是将一个 PCIe Port 连接到另一个 PCIe Port 的铜线，从而使其成为“点对点”。</p>
<p>考虑到这一点，让我们开始绘制这个层次结构（部分）图表，以便我们直观地看到它的全部布局：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/f9830973b55ef87fb9bf88615fb6c3cd.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/f9830973b55ef87fb9bf88615fb6c3cd.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>在 PCI 中，系统上的所有“总线”都用 0 到 255（含）之间的数字标识。此外，所有设备都使用“设备 ID”和“功能 ID”进行标识。这通常被描述为 Bus/Device/Function，或简称为 BDF。在更正确的规范术语中，这称为 RID（请求者 ID）。为了减少混淆，我将它称为 BDF。BDF 很重要，因为它专门告诉我们设备在 PCIe 层次结构中的位置，以便我们可以与之通信。</p>
<p>因为这些都位于层级结构的顶层，所以我们将为这个“bus”提供一个数字标识符，即“Bus 0”或 Root Bus。我们可以通过右键单击顶级设备并选择 Properties 并查看 Location 来验证所有这些设备是否都是 Bus 0 设备：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/369d7cf106107b3aa9ee08b6617e6af1.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/369d7cf106107b3aa9ee08b6617e6af1.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>对于此集成图形设备，它的 BDF 为 0:2.0。它位于总线 0（根总线）上，设备 ID 为 2，功能 ID 为 0。在这种情况下，“设备”表示物理设备，例如显卡。“功能”是物理设备向系统公开的独特功能。无论出于何种意图和目的，都可以将其视为一个单独的实体。公开多个功能的设备被恰当地称为多功能设备（MFD）。这意味着它向系统公开两个或多个 PCI 连接，而实际上只有一个设备。我们很快就会看到一个真正的 MFD 示例。</p>
<p>敏锐的读者会注意到，我们已经打破了我之前提到的“规则”：与这个独特的总线 0 相连的设备有很多。这是 PCIe 中“点对点”规则的第一个例外，只有在因为总线 0 物理上位于 CPU 的硅片上的情况下才允许这样做。也就是说，这些设备之间没有电气路径，这是一个想象中的连接。所有这些设备都存在于 CPU 封装内，并使用极高速电气互连进行路由。这些处理器互连使用的是特定于 CPU 供应商的内部协议，尽管这些协议并未公开文档，但我们仍然以 PCIe 的“语言”与它进行通信。这些端点（标记为绿色），由于其特殊性质，将被赋予一个特殊的名称：根复合集成端点（RC Integrated Endpoints，简称 RCIE），因为它们直接集成在 RC 上。</p>
<p>这并不奇怪，你会期望集成 UHD 图形等设备将物理位于 CPU 上（因为它是 CPU 规格的一部分）。但是，我们可以通过观察其他 RCIE 来了解系统的一些更有趣的拓扑结构，例如这里也存在 RAM 控制器（与内存的 DRAM DIMM 通信的硅）和 USB 控制器（与外部 USB 设备通信的硅）。这就是为什么某些 CPU 仅支持某些类型的 RAM 和 USB 规范的原因——因为通信的设备在物理上位于 CPU 上，并且仅支持它们在物理上创建时要支持的规范。</p>
<blockquote>
<p>更新：这种说法是不正确的。一些 IO 控制器仍然可以在称为 PCH（Intel）或也称为芯片组（AMD）的分立芯片上找到，该芯片位于 CPU 附近，并且具有高速链路，使其看起来像是集成到 CPU 芯片中。上面这句话错误地说你可以在物理 CPU 上找到 USB 控制器，而它更有可能在“芯片组”上。但是，为了提高速度，与 RAM 通信的内存控制器位于 CPU 芯片上。</p>
</blockquote>
<p>此图是层次结构第一级的最小化版本，但现在让我们通过在设备管理器中展开其余的 Root Ports 来构建层次结构的其余部分。</p>
<p>这是填充的图表的样子：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8d9ee9bf28e10363f2b2f42b55e2e404.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/8d9ee9bf28e10363f2b2f42b55e2e404.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<blockquote>
<p>注意：我已经标记了 UHD Graphics 设备和总线 0 的 BDF。</p>
</blockquote>
<p>这些 Root Port 物理上位于 CPU 上，但连接到它的设备并不在其中。这台机器的外部 PCIe 插槽上连接了 3 个设备：一块 NVIDIA Quadro P400 图形卡和两个 NVMe 驱动器。通过进入设备管理器中每个设备的属性，我们可以获取并更新它们在视觉上的 BDF(总线、设备、功能) 信息。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/3ad55c7a1e1a1482a63679a009d6c414.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/3ad55c7a1e1a1482a63679a009d6c414.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>在每个 Root Port 下，我们可以看到一个设备已物理连接。但是，我们还可以看到，我们在每个 Bus 下都公开了一个新的 Bus。Root Port 充当了桥，它将我们从总线 0 桥接到新的总线，因此必须为新总线分配一个新的数字 ID，并且该 Port 下的所有设备/功能都将继承该新总线编号。这与 OS/固件在引导期间的总线枚举期间使用的逻辑相同：所有网桥和交换机都公开一条新总线，必须为其分配新的总线 ID 号。</p>
<p>在这种情况下，我们还可以看到一个多功能设备的好例子。Quadro P400 显卡充当具有两种功能的 MFD。第一个函数是 0（BDF 01:00.0），是显卡设备本身。第二个功能是 1（BDF 01:00.1），它是音频控制器，允许从 HDMI 等 Port 播放音频。这两个功能是不同的——它们用于完全不同的目的，并且具有与之关联的单独驱动程序和配置，但它们仍然由相同的物理设备（即设备 0）实现，并且位于同一总线（即总线 1）上。这与 PCIe 的点对点规则是一致的，一个链路上只能连接一个物理设备，因此总线上只能存在一个物理设备（除了例外，总线 0）。</p>
<h2 id="从-windbg-探索-pcie-层次结构和设备">从 WinDbg 探索 PCIe 层次结构和设备</h2>
<p>到目前为止，我们已经通过使用 Device Manager 的“View by Connection”功能看到了标准的 PCI 总线层次结构。还有另一种更详细的方法来调查 PCIe 层次结构：使用 WinDbg 提供的可靠内核调试扩展。</p>
<blockquote>
<p>注意：我们假设你了解如何在一台机器上设置内核调试器来继续下面的操作。你也可以用 LiveKD 来完成大部分练习。如果你并不了解如何设置，可以参考微软提供的指南：<a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-up-a-network-debugging-connection-automatically">设置 KDNET</a>。</p>
</blockquote>
<p>我已经连接到了一台与上述使用的机器不同的新测试机。我们将通过调试器的输出，来演练如何绘制这台机器的层次结构图。我们也将学习如何通过其配置内存来查找设备的信息。</p>
<p>放入调试器后，我们将使用！pcitree 命令开始。这将转储系统上列举的 PCI 设备的文本树形图。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">8: kd&gt; !pcitree
</span></span><span class="line"><span class="cl">Bus 0x0 <span class="o">(</span>FDO Ext ffffdc89b9f75920<span class="o">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>0,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80866f00 devext 0xffffdc89b0759270 devstack 0xffffdc89b0759120 <span class="m">0600</span> Bridge/HOST to PCI
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80866f02 devext 0xffffdc89ba0c74c0 devstack 0xffffdc89ba0c7370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x1 <span class="o">(</span>FDO Ext ffffdc89ba0aa190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    No devices have been enumerated on this bus.
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>2,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80866f04 devext 0xffffdc89ba0c94c0 devstack 0xffffdc89ba0c9370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x2 <span class="o">(</span>FDO Ext ffffdc89ba0a8190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">(</span><span class="nv">d</span><span class="o">=</span>0,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 10de13bb devext 0xffffdc89ba04f270 devstack 0xffffdc89ba04f120 <span class="m">0300</span> Display Controller/VGA
</span></span><span class="line"><span class="cl">    <span class="o">(</span><span class="nv">d</span><span class="o">=</span>0,  <span class="nv">f</span><span class="o">=</span>1<span class="o">)</span> 10de0fbc devext 0xffffdc89ba051270 devstack 0xffffdc89ba051120 <span class="m">0403</span> Multimedia Device/Unknown Sub Class
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>3,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80866f08 devext 0xffffdc89ba0cb4c0 devstack 0xffffdc89ba0cb370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x3 <span class="o">(</span>FDO Ext ffffdc89ba08f190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    No devices have been enumerated on this bus.
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>5,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80866f28 devext 0xffffdc89ba0cd4c0 devstack 0xffffdc89ba0cd370 <span class="m">0880</span> Base System Device/<span class="s1">&#39;Other&#39;</span> base system device
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>5,  <span class="nv">f</span><span class="o">=</span>1<span class="o">)</span> 80866f29 devext 0xffffdc89ba0cf4c0 devstack 0xffffdc89ba0cf370 <span class="m">0880</span> Base System Device/<span class="s1">&#39;Other&#39;</span> base system device
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>5,  <span class="nv">f</span><span class="o">=</span>2<span class="o">)</span> 80866f2a devext 0xffffdc89ba0d14c0 devstack 0xffffdc89ba0d1370 <span class="m">0880</span> Base System Device/<span class="s1">&#39;Other&#39;</span> base system device
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>5,  <span class="nv">f</span><span class="o">=</span>4<span class="o">)</span> 80866f2c devext 0xffffdc89ba0d34c0 devstack 0xffffdc89ba0d3370 <span class="m">0800</span> Base System Device/Interrupt Controller
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>11, <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80868d7c devext 0xffffdc89ba0d84c0 devstack 0xffffdc89ba0d8370 ff00 <span class="o">(</span>Explicitly<span class="o">)</span> Undefined/Unknown Sub Class
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>11, <span class="nv">f</span><span class="o">=</span>4<span class="o">)</span> 80868d62 devext 0xffffdc89ba0da4c0 devstack 0xffffdc89ba0da370 <span class="m">0106</span> Mass Storage Controller/Unknown Sub Class
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>14, <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80868d31 devext 0xffffdc89ba0dc4c0 devstack 0xffffdc89ba0dc370 0c03 Serial Bus Controller/USB
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>16, <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80868d3a devext 0xffffdc89ba0de4c0 devstack 0xffffdc89ba0de370 <span class="m">0780</span> Simple Serial Communications Controller/<span class="s1">&#39;Other&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>16, <span class="nv">f</span><span class="o">=</span>3<span class="o">)</span> 80868d3d devext 0xffffdc89ba0e04c0 devstack 0xffffdc89ba0e0370 <span class="m">0700</span> Simple Serial Communications Controller/Serial Port
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>19, <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 808615a0 devext 0xffffdc89ba0e24c0 devstack 0xffffdc89ba0e2370 <span class="m">0200</span> Network Controller/Ethernet
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1a, <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80868d2d devext 0xffffdc89ba0e44c0 devstack 0xffffdc89ba0e4370 0c03 Serial Bus Controller/USB
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1b, <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80868d20 devext 0xffffdc89ba0254c0 devstack 0xffffdc89ba025370 <span class="m">0403</span> Multimedia Device/Unknown Sub Class
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1c, <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80868d10 devext 0xffffdc89ba0274c0 devstack 0xffffdc89ba027370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x4 <span class="o">(</span>FDO Ext ffffdc89ba0a9190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    No devices have been enumerated on this bus.
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1c, <span class="nv">f</span><span class="o">=</span>1<span class="o">)</span> 80868d12 devext 0xffffdc89ba02c4c0 devstack 0xffffdc89ba02c370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x5 <span class="o">(</span>FDO Ext ffffdc89b9fe6190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    No devices have been enumerated on this bus.
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1c, <span class="nv">f</span><span class="o">=</span>3<span class="o">)</span> 80868d16 devext 0xffffdc89ba02e4c0 devstack 0xffffdc89ba02e370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x6 <span class="o">(</span>FDO Ext ffffdc89ba0a7190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">(</span><span class="nv">d</span><span class="o">=</span>0,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> <span class="m">12838893</span> devext 0xffffdc89ba062270 devstack 0xffffdc89ba062120 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">    Bus 0x7 <span class="o">(</span>FDO Ext ffffdc89ba064250<span class="o">)</span>
</span></span><span class="line"><span class="cl">      No devices have been enumerated on this bus.
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1c, <span class="nv">f</span><span class="o">=</span>4<span class="o">)</span> 80868d18 devext 0xffffdc89ba0304c0 devstack 0xffffdc89ba030370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x8 <span class="o">(</span>FDO Ext ffffdc89ba0b2190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    No devices have been enumerated on this bus.
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1d, <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80868d26 devext 0xffffdc89ba0364c0 devstack 0xffffdc89ba036370 0c03 Serial Bus Controller/USB
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1f, <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80868d44 devext 0xffffdc89ba0384c0 devstack 0xffffdc89ba038370 <span class="m">0601</span> Bridge/PCI to ISA
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1f, <span class="nv">f</span><span class="o">=</span>2<span class="o">)</span> 80868d02 devext 0xffffdc89ba03a4c0 devstack 0xffffdc89ba03a370 <span class="m">0106</span> Mass Storage Controller/Unknown Sub Class
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1f, <span class="nv">f</span><span class="o">=</span>3<span class="o">)</span> 80868d22 devext 0xffffdc89ba03c4c0 devstack 0xffffdc89ba03c370 0c05 Serial Bus Controller/Unknown Sub Class
</span></span></code></pre></div><blockquote>
<p>注意：如果你遇到“无法获取 PciFdoExtensionListHead 地址”的错误，确保你的符号设置正确，并执行.reload pci.sys 操作来重新加载 PCI 的符号。</p>
</blockquote>
<p>当显示此输出时，由于空格的格式设置方式，可能很难直观地看到“tree”。解释此输出的方法是查看 Bus 0x 文本的缩进。任何比 Bus 0x 行进一步缩进一组空格的东西都是该总线上的设备。我们可以看到，在器件的正下方还有其他 Bus 0x 线路。这意味着 Bus 0x 线上方的器件正在向我们公开一条新总线，并且总线编号在那里给出。</p>
<p>让我们看一下此输出的特定部分：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Bus 0x0 <span class="o">(</span>FDO Ext ffffdc89b9f75920<span class="o">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>0,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80866f00 devext 0xffffdc89b0759270 devstack 0xffffdc89b0759120 <span class="m">0600</span> Bridge/HOST to PCI
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>1,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80866f02 devext 0xffffdc89ba0c74c0 devstack 0xffffdc89ba0c7370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x1 <span class="o">(</span>FDO Ext ffffdc89ba0aa190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    No devices have been enumerated on this bus.
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>2,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80866f04 devext 0xffffdc89ba0c94c0 devstack 0xffffdc89ba0c9370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x2 <span class="o">(</span>FDO Ext ffffdc89ba0a8190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">(</span><span class="nv">d</span><span class="o">=</span>0,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 10de13bb devext 0xffffdc89ba04f270 devstack 0xffffdc89ba04f120 <span class="m">0300</span> Display Controller/VGA
</span></span><span class="line"><span class="cl">    <span class="o">(</span><span class="nv">d</span><span class="o">=</span>0,  <span class="nv">f</span><span class="o">=</span>1<span class="o">)</span> 10de0fbc devext 0xffffdc89ba051270 devstack 0xffffdc89ba051120 <span class="m">0403</span> Multimedia Device/Unknown Sub Class
</span></span><span class="line"><span class="cl">  <span class="o">(</span><span class="nv">d</span><span class="o">=</span>3,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 80866f08 devext 0xffffdc89ba0cb4c0 devstack 0xffffdc89ba0cb370 <span class="m">0604</span> Bridge/PCI to PCI
</span></span><span class="line"><span class="cl">  Bus 0x3 <span class="o">(</span>FDO Ext ffffdc89ba08f190<span class="o">)</span>
</span></span><span class="line"><span class="cl">    No devices have been enumerated on this bus.
</span></span></code></pre></div><p>在此输出中，我们可以看到每个设备显示的 BDF。我们还可以看到总线 0 上存在的一组 Root Port，这些 Port 下面没有列举任何设备，这意味着插槽尚未连接到任何设备。</p>
<p>在这里看到树结构应该更容易，但无论如何，让我们把它画出来：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7a507a4048a8a03806ced4cd6714bbf7.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7a507a4048a8a03806ced4cd6714bbf7.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<blockquote>
<p>注意：这只是一个巧合，即公交号恰好与桥梁/PCI 的设备编号匹配到 PCI 端口。</p>
</blockquote>
<p>如你现在所知，标记为 Bridge/PCI to PCI 的设备实际上是 Root Port，而总线 2 上的设备实际上是一个多功能设备。与设备管理器不同，我们看不到！pcitree 中的设备真实名称。相反，我们只得到了一个通用的 PCI 名称，用于设备“类型”将自己宣传为什么。这是因为设备管理器 从驱动程序读取设备名称，而不是直接从 PCI 读取设备名称。</p>
<p>要了解更多关于这个显示控制器设备的信息，我们可以使用命令 <code>！devext [pointer]</code>，其中 <code>[pointer]</code> 是布局中单词 <code>devext</code> 后面的值。在本例中，它是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="o">(</span><span class="nv">d</span><span class="o">=</span>0,  <span class="nv">f</span><span class="o">=</span>0<span class="o">)</span> 10de13bb devext 0xffffdc89ba04f270 devstack 0xffffdc89ba04f120 <span class="m">0300</span> Display Controller/VGA
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">!devext 0xffffdc89ba04f270
</span></span></code></pre></div><p>从这里，我们将从 Windows 中的 PCI 总线驱动程序获得此 PCI 设备的摘要的打印输出，<code>pci.sys</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">8: kd&gt; !devext 0xffffdc89ba04f270
</span></span><span class="line"><span class="cl">PDO Extension, Bus 0x2, Device 0, Function 0.
</span></span><span class="line"><span class="cl">  DevObj 0xffffdc89ba04f120  Parent FDO DevExt 0xffffdc89ba0a8190
</span></span><span class="line"><span class="cl">  Device <span class="nv">State</span> <span class="o">=</span> PciStarted
</span></span><span class="line"><span class="cl">  Vendor ID 10de <span class="o">(</span>NVIDIA CORPORATION<span class="o">)</span>  Device ID 13BB
</span></span><span class="line"><span class="cl">  Subsystem Vendor ID 103c <span class="o">(</span>HEWLETT-PACKARD COMPANY<span class="o">)</span>  Subsystem ID <span class="m">1098</span>
</span></span><span class="line"><span class="cl">  Header Type 0, Class Base/Sub 03/00  <span class="o">(</span>Display Controller/VGA<span class="o">)</span>
</span></span><span class="line"><span class="cl">  Programming Interface: 00, Revision: a2, IntPin: 01, RawLine <span class="m">00</span>
</span></span><span class="line"><span class="cl">  Possible Decodes <span class="o">((</span>cmd <span class="p">&amp;</span> 7<span class="o">)</span> <span class="o">=</span> 7<span class="o">)</span>: BMI
</span></span><span class="line"><span class="cl">  Capabilities: <span class="nv">Ptr</span><span class="o">=</span>60, power msi express 
</span></span><span class="line"><span class="cl">  Express capabilities: <span class="o">(</span>BIOS controlled<span class="o">)</span> 
</span></span><span class="line"><span class="cl">  Logical Device Power State: D0
</span></span><span class="line"><span class="cl">  Device Wake Level:          Unspecified
</span></span><span class="line"><span class="cl">  WaitWakeIrp:                &lt;none&gt;
</span></span><span class="line"><span class="cl">  Requirements:     Alignment Length    Minimum          Maximum
</span></span><span class="line"><span class="cl">    BAR0    Mem:    <span class="m">01000000</span>  <span class="m">01000000</span>  <span class="m">0000000000000000</span> 00000000ffffffff
</span></span><span class="line"><span class="cl">    BAR1    Mem:    <span class="m">10000000</span>  <span class="m">10000000</span>  <span class="m">0000000000000000</span> ffffffffffffffff
</span></span><span class="line"><span class="cl">    BAR3    Mem:    <span class="m">02000000</span>  <span class="m">02000000</span>  <span class="m">0000000000000000</span> ffffffffffffffff
</span></span><span class="line"><span class="cl">    BAR5     Io:    <span class="m">00000080</span>  <span class="m">00000080</span>  <span class="m">0000000000000000</span> 00000000ffffffff
</span></span><span class="line"><span class="cl">      ROM BAR:      <span class="m">00080000</span>  <span class="m">00080000</span>  <span class="m">0000000000000000</span> 00000000ffffffff
</span></span><span class="line"><span class="cl">    VF BAR0 Mem:    <span class="m">00080000</span>  <span class="m">00080000</span>  <span class="m">0000000000000000</span> 00000000ffffffff
</span></span><span class="line"><span class="cl">  Resources:        Start            Length
</span></span><span class="line"><span class="cl">    BAR0    Mem:    00000000f2000000 <span class="m">01000000</span>
</span></span><span class="line"><span class="cl">    BAR1    Mem:    00000000e0000000 <span class="m">10000000</span>
</span></span><span class="line"><span class="cl">    BAR3    Mem:    00000000f0000000 <span class="m">02000000</span>
</span></span><span class="line"><span class="cl">    BAR5     Io:    <span class="m">0000000000001000</span> <span class="m">00000080</span>
</span></span><span class="line"><span class="cl">  Interrupt Requirement:
</span></span><span class="line"><span class="cl">    Line Based - Min <span class="nv">Vector</span> <span class="o">=</span> 0x0, Max <span class="nv">Vector</span> <span class="o">=</span> 0xffffffff
</span></span><span class="line"><span class="cl">    Message Based: Type - Msi, 0x1 messages requested
</span></span><span class="line"><span class="cl">  Interrupt Resource:    Type - MSI, 0x1 Messages Granted
</span></span></code></pre></div><p>这里有很多内核知道的关于这个设备的信息。此信息是通过 配置空间（缩写为“config space”）检索的，配置空间 是系统上的内存部分，允许内核以标准化的方式枚举、查询信息和设置 PCI 设备。软件从设备读取内存以查询供应商 ID 等信息，设备（如果已打开电源）使用该信息进行响应。在下一节中，我将更多地讨论这实际上是如何发生的，但要知道这里查询的信息是从配置空间生成的。</p>
<p>因此，让我们分解一些重要的东西：</p>
<ul>
<li>DevObj：指向 <code>nt！_DEVICE_OBJECT</code> 结构的指针，该结构表示内核中的物理设备。</li>
<li>Vendor ID：注册给特定设备制造商的 16 位 ID 号。此值是标准化的，PCI-SIG 必须为新供应商分配一个唯一 ID，以便它们不会重叠。在本例中，我们看到这是 NVIDIA 显卡。</li>
<li>Device  ID：执行 PCIe 的特定芯片的 16 位 ID 号。类似的想法是，公司必须为其芯片请求一个唯一的 ID，这样它就不会与任何其他芯片冲突。</li>
<li>Subsystem Vendor ID：芯片所在电路板的供应商 ID。在这种情况下，“HP”是显卡的生产商，而“NVIDIA”设计了图形芯片。</li>
<li>Subsystem Device ID：芯片所在电路板的设备 ID。</li>
<li>Logical Device Power State：此设备的电源状态。PCI 中有两种主要的电源状态，D0 = 设备已通电，D3 = 设备处于低功耗状态或完全关闭。</li>
<li>Requirements：设备要求 OS 为其分配的内存要求。稍后会详细介绍。</li>
<li>Resources：操作系统分配给此设备的内存资源。此设备已打开电源并启动，因此已为其分配了资源。</li>
<li>Interrupt Requirement/Resource：与上述相同，但是对于中断则不同。</li>
</ul>
<p>要实际获取有关此设备的完整信息，我们可以使用 <a href="https://pcilookup.com/">PCI Lookup</a> 中的出色工具来查询有关在 PCI-SIG 中注册的 PCI 设备的公共信息。让我们将有关设备和 Vendor ID 的信息放入框中：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/a70cefe8ad2cbb145ce145c1b16e562d.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/a70cefe8ad2cbb145ce145c1b16e562d.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>当我们搜索时，我们得到这个：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/098ff685f1e2ca1d27f87df7378a836a.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/098ff685f1e2ca1d27f87df7378a836a.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>这告诉我们该设备是 NVIDIA 创建的 Quadro K620 显卡。子系统 ID 告诉我们，这个特定的卡 PCB 是由 HP 生产的，该公司已获得 NVIDIA 的许可。</p>
<p>我们在 <code>！devext</code> 中看到的很好地概述了 <code>pci.sys</code> 在摘要中特别关心向我们展示的内容，但它只触及了配置空间中所有信息的皮毛。要将所有信息转储到配置空间中，我们可以使用扩展名 <code>！pci 100 B D F</code>，其中 BDF 是我们相关设备的 BDF。100 是一组标志，指定我们要转储有关设备的所有信息。显示的信息将按照它在设备的 config space 中存在的顺序进行布局。每个部分的前缀是一个偏移量，例如 <code>02</code> 表示 Device ID。这指定了从中读取此值的 config 空间的偏移量。这些偏移量在 PCI 规范中进行了详细说明，并且不会出于向后兼容性目的在 PCI 版本之间更改。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">8: kd&gt; !pci <span class="m">100</span> <span class="m">2</span> <span class="m">0</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">PCI Configuration Space <span class="o">(</span>Segment:0000 Bus:02 Device:00 Function:00<span class="o">)</span>
</span></span><span class="line"><span class="cl">Common Header:
</span></span><span class="line"><span class="cl">    00: VendorID       10de Nvidia Corporation
</span></span><span class="line"><span class="cl">    02: DeviceID       13bb
</span></span><span class="line"><span class="cl">    04: Command        <span class="m">0507</span> IOSpaceEn MemSpaceEn BusInitiate SERREn InterruptDis 
</span></span><span class="line"><span class="cl">    06: Status         <span class="m">0010</span> CapList 
</span></span><span class="line"><span class="cl">    08: RevisionID     a2
</span></span><span class="line"><span class="cl">    09: ProgIF         <span class="m">00</span> VGA
</span></span><span class="line"><span class="cl">    0a: SubClass       <span class="m">00</span> VGA Compatible Controller
</span></span><span class="line"><span class="cl">    0b: BaseClass      <span class="m">03</span> Display Controller
</span></span><span class="line"><span class="cl">    0c: CacheLineSize  <span class="m">0000</span>
</span></span><span class="line"><span class="cl">    0d: LatencyTimer   <span class="m">00</span>
</span></span><span class="line"><span class="cl">    0e: HeaderType     <span class="m">80</span>
</span></span><span class="line"><span class="cl">    0f: BIST           <span class="m">00</span>
</span></span><span class="line"><span class="cl">    10: BAR0           f2000000
</span></span><span class="line"><span class="cl">    14: BAR1           e000000c
</span></span><span class="line"><span class="cl">    18: BAR2           <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    1c: BAR3           f000000c
</span></span><span class="line"><span class="cl">    20: BAR4           <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    24: BAR5           <span class="m">00001001</span>
</span></span><span class="line"><span class="cl">    28: CBCISPtr       <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    2c: SubSysVenID    103c
</span></span><span class="line"><span class="cl">    2e: SubSysID       <span class="m">1098</span>
</span></span><span class="line"><span class="cl">    30: ROMBAR         <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    34: CapPtr         <span class="m">60</span>
</span></span><span class="line"><span class="cl">    3c: IntLine        <span class="m">00</span>
</span></span><span class="line"><span class="cl">    3d: IntPin         <span class="m">01</span>
</span></span><span class="line"><span class="cl">    3e: MinGnt         <span class="m">00</span>
</span></span><span class="line"><span class="cl">    3f: MaxLat         <span class="m">00</span>
</span></span><span class="line"><span class="cl">Device Private:
</span></span><span class="line"><span class="cl">    40: 1098103c <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    50: <span class="m">00000000</span> <span class="m">00000001</span> 0023d6ce <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    60: <span class="m">00036801</span> <span class="m">00000008</span> <span class="m">00817805</span> fee001f8
</span></span><span class="line"><span class="cl">    70: <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00120010</span> 012c8de1
</span></span><span class="line"><span class="cl">    80: <span class="m">00003930</span> 00453d02 <span class="m">11010140</span> <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    90: <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00040013</span>
</span></span><span class="line"><span class="cl">    a0: <span class="m">00000000</span> <span class="m">00000006</span> <span class="m">00000002</span> <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    b0: <span class="m">00000000</span> <span class="m">01140009</span> <span class="m">00000000</span> <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    c0: <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    d0: <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    e0: <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    f0: <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span> <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">Capabilities:
</span></span><span class="line"><span class="cl">    60: CapID          <span class="m">01</span> PwrMgmt Capability
</span></span><span class="line"><span class="cl">    61: NextPtr        <span class="m">68</span>
</span></span><span class="line"><span class="cl">    62: PwrMgmtCap     <span class="m">0003</span> <span class="nv">Version</span><span class="o">=</span><span class="m">3</span>
</span></span><span class="line"><span class="cl">    64: PwrMgmtCtrl    <span class="m">0008</span> DataScale:0 DataSel:0 D0 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    68: CapID          <span class="m">05</span> MSI Capability
</span></span><span class="line"><span class="cl">    69: NextPtr        <span class="m">78</span>
</span></span><span class="line"><span class="cl">    6a: MsgCtrl        64BitCapable MSIEnable MultipleMsgEnable:0 <span class="o">(</span>0x1<span class="o">)</span> MultipleMsgCapable:0 <span class="o">(</span>0x1<span class="o">)</span>
</span></span><span class="line"><span class="cl">    6c: MsgAddrLow     fee001f8
</span></span><span class="line"><span class="cl">    70: MsgAddrHi      <span class="m">0</span>
</span></span><span class="line"><span class="cl">    74: MsgData        <span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    78: CapID          <span class="m">10</span> PCI Express Capability
</span></span><span class="line"><span class="cl">    79: NextPtr        <span class="m">00</span>
</span></span><span class="line"><span class="cl">    7a: Express Caps   <span class="m">0012</span> <span class="o">(</span>ver. 2<span class="o">)</span> Type:LegacyEP
</span></span><span class="line"><span class="cl">    7c: Device Caps    012c8de1
</span></span><span class="line"><span class="cl">    80: Device Control <span class="m">3930</span> bcre/flr MRR:1K NS ap pf ET MP:256 RO ur fe nf ce
</span></span><span class="line"><span class="cl">    82: Device Status  <span class="m">0000</span> tp ap ur fe nf ce
</span></span><span class="line"><span class="cl">    84: Link Caps      00453d02
</span></span><span class="line"><span class="cl">    88: Link Control   <span class="m">0140</span> es CC rl ld RCB:64 ASPM:None 
</span></span><span class="line"><span class="cl">    8a: Link Status    <span class="m">1101</span> SCC lt lte NLW:x16 LS:2.5 
</span></span><span class="line"><span class="cl">    9c: DeviceCaps2    <span class="m">00040013</span> CTR:3 CTDIS arifwd aor aoc32 aoc64 cas128 noro ltr TPH:0 OBFF:1 extfmt eetlp EETLPMax:0
</span></span><span class="line"><span class="cl">    a0: DeviceControl2 <span class="m">0000</span> CTVal:0 ctdis arifwd aor aoeb idoreq idocom ltr OBFF:0 eetlp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Enhanced Capabilities:
</span></span><span class="line"><span class="cl">    100: CapID         <span class="m">0002</span> Virtual Channel Capability
</span></span><span class="line"><span class="cl">         Version       <span class="m">1</span>
</span></span><span class="line"><span class="cl">         NextPtr       <span class="m">258</span>
</span></span><span class="line"><span class="cl">    0104: Port VC Capability <span class="m">1</span>        <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    0108: Port VC Capability <span class="m">2</span>        <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    010c: Port VC Control             <span class="m">0000</span>
</span></span><span class="line"><span class="cl">    010e: Port VC Status              <span class="m">0000</span>
</span></span><span class="line"><span class="cl">    0110: VC Resource<span class="o">[</span>0<span class="o">]</span> Cap          <span class="m">00000000</span>
</span></span><span class="line"><span class="cl">    0114: VC Resource<span class="o">[</span>0<span class="o">]</span> Control      800000ff
</span></span><span class="line"><span class="cl">    011a: VC Resource<span class="o">[</span>0<span class="o">]</span> Status       <span class="m">0000</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    258: CapID         001e L1 PM SS Capability
</span></span><span class="line"><span class="cl">         Version       <span class="m">1</span>
</span></span><span class="line"><span class="cl">         NextPtr       <span class="m">128</span>
</span></span><span class="line"><span class="cl">    25c: Capabilities  0028ff1f  PTPOV:5 PTPOS:0 PCMRT:255 L1PMS ASPML11 ASPML12 PCIPML11 PCIPML12
</span></span><span class="line"><span class="cl">    260: Control1      <span class="m">00000000</span>  LTRL12TS:0 LTRL12TV:0 CMRT:0 aspml11 aspml12 pcipml11 pcipml12
</span></span><span class="line"><span class="cl">    264: Control2      <span class="m">00000028</span>  TPOV:5 TPOS:0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    128: CapID         <span class="m">0004</span> Power Budgeting Capability
</span></span><span class="line"><span class="cl">         Version       <span class="m">1</span>
</span></span><span class="line"><span class="cl">         NextPtr       <span class="m">600</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    600: CapID         000b Vendor Specific Capability
</span></span><span class="line"><span class="cl">         Version       <span class="m">1</span>
</span></span><span class="line"><span class="cl">         NextPtr       <span class="m">000</span>
</span></span><span class="line"><span class="cl">         Vendor Specific ID <span class="m">0001</span> - Ver. <span class="m">1</span>  Length: <span class="m">024</span>
</span></span></code></pre></div><p>这个视图的好处是，我们可以看到有关配置空间的 Capabilities 部分的详细信息。Capabilities 是 config 空间中的一组结构，它准确描述了 device 能够实现的功能。Capabilities 包括链接速度和设备支持的中断类型等信息。PCI 规范中添加的任何新功能都将通过这些结构进行公布，这些结构在配置空间中形成了一个功能链表，可以迭代以发现设备的所有功能。并非所有这些功能都与操作系统相关，有些功能仅与本文未涵盖的硬件方面相关。现在，我不会详细介绍该设备的功能。</p>
<h2 id="pcie一切都与内存相关">PCIe：一切都与内存相关</h2>
<p>现在我们已经研究了几个设备和 PCI 总线的层次结构，让我们谈谈与软件和 PCI 设备的通信实际上是如何运作的。当我第一次学习 PCI 时，我很难理解当软件与 PCI 设备连接时到底发生了什么。因为整个事务对作为软件开发人员的你来说是抽象出来的，所以很难仅通过从调试工具中探入 PCI 内存来构建所发生的事情的心智模型。希望这篇文章能提供比我刚开始时所能得到的更好的概述。</p>
<p>首先，我要做一个大胆的声明：<strong>所有现代 PCIe 通信都是通过内存读写完成的</strong>。如果你了解 PCIe 中的内存如何工作，你就会了解 PCIe 软件通信的工作原理。（是的，在某些平台上还有其他传统的通信方式，但我们不会讨论这些方式，因为它们已被弃用）。</p>
<p>现在，让我们谈谈现代平台上不同类型的内存。在启动的早期，操作系统的 CPU 将使用虚拟内存。也就是说，CPU 看到的内存地址是映射到物理内存世界的内存视图。</p>
<p>就我们的目的而言，系统上有两种类型的物理内存：</p>
<ul>
<li>RAM - 读取或写入时从计算机上的 DRAM DIMM 存储和检索的地址。这就是大多数人在想到“内存”时所想到的。</li>
<li>Device Memory（设备内存） - 在读取或写入时与系统上的设备“对话”的地址。这里的关键词是“对话”。它不会在设备上存储内存，也不会检索设备上的内存（尽管设备可能同时能够同时检索两者）。你可能正在与之通信的地址甚至可能根本不是内存，而是一个更抽象的“device register” ，用于配置设备的内部工作。这种访问会发生什么取决于设备。你所做的只是与设备通信。你通常会看到这称为 MMIO，它全称是 Memory-Mapped I/O。</li>
</ul>
<blockquote>
<p>注意：每当设备不响应设备内存区域中访问的地址时，PCI 的设备内存将始终读取“全 1”或“所有 FF”。这是了解设备何时实际响应的便捷方法。如果你看到所有 FF，则表示你正在读取无效的设备地址。</p>
</blockquote>
<p>初学者认为所有物理内存都是 RAM，这是错误的。当软件与 PCI 区域中的 PCI 设备通信时，它不会从 RAM 读取和写入数据。相反，该设备从 RC 接收一个数据包（TLP，传输层数据包），当 PCI 区域内的地址被读/写时，你的 CPU 会立即自动生成该数据包。你无需在软件中创建这些数据包，所有这些数据包都是在访问此内存后立即完全在后台生成的。在软件中，你甚至无法查看或捕获这些数据包，而需要一个特殊的硬件测试设备来拦截和查看正在发送的数据包。稍后会详细介绍。</p>
<p>如果有帮助，请将物理内存视为设备的映射。RAM 是为你映射到物理内存中的设备。PCI 还会自动为你映射区域。尽管它们截然不同且行为也非常不同，但它们在软件中看起来是相同的。</p>
<p>在下图中，我们可以看到典型系统如何将虚拟内存映射到物理内存。请注意，有两个 RAM 区域和两个 PCI 内存区域。这是因为某些较旧的 PCI 设备只能寻址 32 位内存。因此，如果你的 RAM 不适合 4GB 以下的地址窗口，则一些 RAM 会上移到 4GB 以上。由于你的处理器支持 64 位地址，因此这不是问题。此外，在 4GB 行上方为支持 64 位地址的 PCI 设备创建第二个窗口。由于 4GB 区域可能非常有限，因此设备最好在 4GB 以上移动尽可能多的内存，以免弄乱下面的空间。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/2f1b3cf2195ea201e631c0d29929398b.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/2f1b3cf2195ea201e631c0d29929398b.png" alt=""  title="如何将虚拟地址范围映射到物理地址的非常简化的视图。这忽略了物理内存中的大量 “特殊” 区域，但展示了 RAM 和设备内存是如何不同的。" style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>首先，让我们来谈谈我们已经见过存储器：<strong>配置空间</strong>（Configuration Space）。</p>
<p>配置空间位于一个名为 ECAM（Extended Configuration Access Management，扩展配置访问管理）的内存部分。因为它是一种设备内存，所以要从内核（使用虚拟内存）访问这段内存，内核必须请求内存管理器将这部分物理内存映射到一个虚拟地址上。然后，软件指令可以使用映射的虚拟地址来从物理地址读取和写入。在 Windows 上，定位和映射这段内存的工作部分由<code>pci.sys</code>处理，部分由<code>acpi.sys</code>处理，还有部分由内核（具体来说是 HAL）处理。</p>
<blockquote>
<p>注意：通常，在 Windows 中映射设备内存的方式是通过 <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmmapiospaceex">MmMapIoSpaceEx</a>，这是驱动程序可用于映射物理设备内存的 API。但是，为了进行配置空间访问，软件必须使用 <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-halgetbusdatabyoffset">HalGetBusDataByOffset</a> 和 <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-halsetbusdatabyoffset">HalSetBusDataByOffset</a> 来确保 <code>pci.sys</code> 的内部状态与你正在执行的配置空间读/写保持同步。如果你尝试自己映射和更改配置空间，则可能会使 <code>pci.sys</code> 状态不同步并导致蓝屏死机。</p>
</blockquote>
<blockquote>
<p>注意：ECAM/PCI 区域在物理内存中的位置取决于平台。引导时的固件将分配系统物理内存的所有特殊区域。然后，固件会在引导期间向操作系统公布这些区域的位置。在 x86-64 系统上，ECAM 区域将使用称为 MCFG 的表（结构）通过 ACPI 从固件进行通信。现在知道使用什么特定协议来检索此信息不是很重要吗，只需了解操作系统从固件中检索这些区域的地址，固件决定了将它们放在哪里。</p>
</blockquote>
<p>因此，为了进行配置空间访问，内核必须将配置空间（ECAM）映射到虚拟内存。这是这样的事情会是什么样子：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7bb04dca3ffe48821f8bf9f6f48bc71b.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7bb04dca3ffe48821f8bf9f6f48bc71b.png" alt=""  title="ECAM 到虚拟内存的映射。可怕的是没有规模。" style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>在此之后，内核现在可以使用虚拟映射与设备的配置空间进行通信。但是这个配置空间是什么样的呢？嗯，它只是我们上面讨论的一堆配置空间结构块。设备可能具有的每个可能的 BDF 都在 ECAM 中提供了空间来对其进行配置。它的布局方式是，设备的 BDF 会告诉你其配置空间在 ECAM 中的确切位置。也就是说，给定一个 BDF，我们可以计算要添加到 ECAM 区域基数的偏移量，以便与设备通信，因为每个功能的所有 ECAM 区域的大小都相同。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/54b366fd88125e12c7b1cffd70f8f0bd.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/54b366fd88125e12c7b1cffd70f8f0bd.png" alt=""  title="如果设备不存在，系统将读回所有 FF（二进制中的所有 1）。这将表明设备当前在系统上未处于活动状态" style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>从这张图中，我们可以开始看到 PCIe 的枚举实际上是如何发生的。当我们读回有效的配置空间数据时，我们知道该 BDF 上存在设备。如果我们改为读回 FF，我们知道设备不在该插槽或功能中。当然，我们不会为了枚举所有设备而暴力破解每个地址，因为由于 MMIO 的开销，代价比较大。但是，这种蛮力的高级版本是我们如何快速枚举所有已通电并在配置空间上响应我们的设备。</p>
<h2 id="把它们放在一起---软件配置空间访问">把它们放在一起 - 软件配置空间访问</h2>
<p>现在我们了解了如何访问配置空间，我们可以将两端（层次结构和 MMIO）放在一起，以查看从内核模式读取配置空间的指令的完整路径。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/ed43867c9ad22be7633a41a8cb397d12.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/ed43867c9ad22be7633a41a8cb397d12.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>让我们逐步完成此处采用的整个路径（从左到右）：</p>
<ul>
<li>在内核模式下运行的某些代码从 ECAM 虚拟映射中读取偏移量。</li>
<li>虚拟映射由 CPU 的页表转换为 ECAM 中的物理地址。</li>
<li>读取物理地址，导致内部 CPU 互连中发生操作，以通知RC访问。</li>
<li>RC将请求的数据包化版本生成为 TLP，该 TLP 显示“读取设备 02:00.0 的偏移量 0x0 处的值”，并通过层次结构发送该请求。</li>
<li>TLP 由总线 2 上的此显示控制器接收，并看到它是一个配置空间 TLP。现在，它知道使用包含偏移量 0x0 处的值内容的配置空间响应 TLP 进行响应。</li>
</ul>
<p>现在让我们看看响应：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7636af73b1e906b7cbf1cc44fe620dfc.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/7636af73b1e906b7cbf1cc44fe620dfc.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>响应路径没那么有趣了。设备以含有偏移 0 处的值（我们知道这是供应商 ID）的特殊 TLP 进行响应。这个数据包找到回到请求者（即RC），然后互连通知 CPU 更新 rax 的值为 0x10DE，这是 NVIDIA 显卡的供应商 ID。然后，CPU 开始执行下一条指令。</p>
<p>如你所想那样，通过这种方式进行访问可能比通过全部的 TLP 生成的 RAM 慢很多。这确实是事实，并且这也是存在比这种 MMIO 方法更多的方式去与设备通信的主要原因之一。在接下来的文章中，我将详细介绍另一种方法，即 DMA，以及它对于确保软件能够尽可能快地在 CPU 和设备之间传输内存的至关重要性。</p>
<h2 id="练习通过-windbg-手动访问-ecam">练习：通过 WinDbg 手动访问 ECAM</h2>
<p>我们看了一下 config space access 理论上是如何发生的，但让我们自己用 debugger 做同样的事情。为此，我们希望：</p>
<ul>
<li>找到 ECAM 在系统上的位置。</li>
<li>计算到 ECAM 的偏移量以读取设备的供应商 ID。为此，我选择了 NVIDIA 显卡上的<code>Multimedia Device @ 02:00.1</code></li>
<li>在该地址执行物理内存读取以检索值。</li>
</ul>
<p>第一步是找到 ECAM。鉴于 ECAM 的位置来自 ACPI，特别是 ACPI 中的 MCFG 表，这部分有点棘手。这是 firmware 用来告诉操作系统 ECAM 在系统的物理内存映射中的位置的表。关于 ACPI 以及如何将其与 PCI 结合使用，有很多内容要讨论，但现在，我将快速跳到相关部分以实现我们的目标。</p>
<p>在我们的调试器中，我们可以通过使用<code>!acpicache</code>来转储所有 ACPI 表的缓存副本。要转储 MCFG，请点击链接 MCFG 来转储其内容，或手动键入<code>!acpitable MCFG</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">8: kd&gt; !acpicache
</span></span><span class="line"><span class="cl">Dumping cached ACPI tables...
</span></span><span class="line"><span class="cl">  XSDT @<span class="o">(</span>fffff7b6c0004018<span class="o">)</span> Rev: 0x1 Len: 0x0000bc TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  MCFG @<span class="o">(</span>fffff7b6c0005018<span class="o">)</span> Rev: 0x1 Len: 0x00003c TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  FACP @<span class="o">(</span>fffff7b6c0007018<span class="o">)</span> Rev: 0x4 Len: 0x0000f4 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  APIC @<span class="o">(</span>fffff7b6c0008018<span class="o">)</span> Rev: 0x2 Len: 0x000afc TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  DMAR @<span class="o">(</span>fffff7b6c000a018<span class="o">)</span> Rev: 0x1 Len: 0x0000c0 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  HPET @<span class="o">(</span>fffff7b6c015a018<span class="o">)</span> Rev: 0x1 Len: 0x000038 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  TCPA @<span class="o">(</span>ffffdc89b07209f8<span class="o">)</span> Rev: 0x2 Len: 0x000064 TableID: EDK2    
</span></span><span class="line"><span class="cl">  SSDT @<span class="o">(</span>ffffdc89b0720a88<span class="o">)</span> Rev: 0x2 Len: 0x0003b3 TableID: Tpm2Tabl
</span></span><span class="line"><span class="cl">  TPM2 @<span class="o">(</span>ffffdc89b0720e68<span class="o">)</span> Rev: 0x3 Len: 0x000034 TableID: EDK2    
</span></span><span class="line"><span class="cl">  SSDT @<span class="o">(</span>ffffdc89b07fc018<span class="o">)</span> Rev: 0x1 Len: 0x0013a1 TableID: Plat_Wmi
</span></span><span class="line"><span class="cl">  UEFI @<span class="o">(</span>ffffdc89b07fd3e8<span class="o">)</span> Rev: 0x1 Len: 0x000042 TableID: 
</span></span><span class="line"><span class="cl">  BDAT @<span class="o">(</span>ffffdc89b07fd458<span class="o">)</span> Rev: 0x1 Len: 0x000030 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  MSDM @<span class="o">(</span>ffffdc89b07fd4b8<span class="o">)</span> Rev: 0x3 Len: 0x000055 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  SLIC @<span class="o">(</span>ffffdc89b07fd538<span class="o">)</span> Rev: 0x1 Len: 0x000176 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  WSMT @<span class="o">(</span>ffffdc89b07fd6d8<span class="o">)</span> Rev: 0x1 Len: 0x000028 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  WDDT @<span class="o">(</span>ffffdc89b0721a68<span class="o">)</span> Rev: 0x1 Len: 0x000040 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  SSDT @<span class="o">(</span>ffffdc89b2580018<span class="o">)</span> Rev: 0x2 Len: 0x086372 TableID: SSDT  PM
</span></span><span class="line"><span class="cl">  NITR @<span class="o">(</span>ffffdc89b26063b8<span class="o">)</span> Rev: 0x2 Len: 0x000071 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">  ASF! @<span class="o">(</span>ffffdc89b2606548<span class="o">)</span> Rev: 0x20 Len: 0x000074 TableID:  HCG
</span></span><span class="line"><span class="cl">  BGRT @<span class="o">(</span>ffffdc89b26065e8<span class="o">)</span> Rev: 0x1 Len: 0x000038 TableID: TIANO   
</span></span><span class="line"><span class="cl">  DSDT @<span class="o">(</span>ffffdc89b0e94018<span class="o">)</span> Rev: 0x2 Len: 0x021c89 TableID: SLIC-WKS
</span></span><span class="line"><span class="cl">8: kd&gt; !acpitable MCFG
</span></span><span class="line"><span class="cl">HEADER - fffff7b6c0005018
</span></span><span class="line"><span class="cl">  Signature:               MCFG
</span></span><span class="line"><span class="cl">  Length:                  0x0000003c
</span></span><span class="line"><span class="cl">  Revision:                0x01
</span></span><span class="line"><span class="cl">  Checksum:                0x3c
</span></span><span class="line"><span class="cl">  OEMID:                   HPQOEM
</span></span><span class="line"><span class="cl">  OEMTableID:              SLIC-WKS
</span></span><span class="line"><span class="cl">  OEMRevision:             0x00000001
</span></span><span class="line"><span class="cl">  CreatorID:               INTL
</span></span><span class="line"><span class="cl">  CreatorRev:              0x20091013
</span></span><span class="line"><span class="cl">BODY - fffff7b6c000503c
</span></span><span class="line"><span class="cl">fffff7b6<span class="sb">`</span>c000503c  <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> 00-00 <span class="m">00</span> <span class="m">00</span> d0 <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span>  ................
</span></span><span class="line"><span class="cl">fffff7b6<span class="sb">`</span>c000504c  <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> ff <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span>                          ........
</span></span></code></pre></div><p>要了解如何阅读此表，遗憾的是，我们需要查看 ACPI 规范。与其让你这样做，不如省去你的痛苦，把相关部分拉到这里：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/2e26ba8d45b7e876b78500fa7392440d.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/2e26ba8d45b7e876b78500fa7392440d.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>由于 <code>！acpitable</code> 命令已经解析并显示此表中 <code>Creator Revision</code> 之前的所有内容，因此 <code>BODY</code> 的前 8 个字节将是偏移量 36 处的 8 个字节的 <code>Reserved</code> 内存。因此，我们跳过这 8 个字节并找到以下结构：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/936801aafdfbd7bc6e13e82310ce925f.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/31/936801aafdfbd7bc6e13e82310ce925f.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>此字节的前 8 个字节是 <code>Reserved</code> 后面的区域的 <code>ECAM</code> 区域的地址。这意味着 <code>ECAM</code> 基址的偏移量为偏移量 8。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">BODY - fffff7b6c000503c
</span></span><span class="line"><span class="cl">fffff7b6<span class="sb">`</span>c000503c  <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> 00-00 <span class="m">00</span> <span class="m">00</span> d0 <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span>  ................
</span></span><span class="line"><span class="cl">fffff7b6<span class="sb">`</span>c000504c  <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> ff <span class="m">00</span> <span class="m">00</span> <span class="m">00</span> <span class="m">00</span>                          ........
</span></span></code></pre></div><p>对于这个系统，ECAM 位于地址：<code>0xD0000000</code>。（请别忘了以小端序来读取这个地址）</p>
<p>为了验证我们得到了正确的地址，让我们读取<code>00:00.0</code>的供应商 ID，这也是 ECAM 的前两个字节。我们将使用<code>!dw</code>命令来完成这个操作，该命令代表的<code>dump physical word</code>（感叹号代表物理）。这个命令要求你指定一个缓存类型，在我们的情况下，总是使用<code>[uc]</code>或者说未缓存。它还提供了一个长度，这是由 L1 指定要读取的 word 的数量。</p>
<blockquote>
<p>注意：请务必始终将目标设备内存的大小与我们从软件中读取的大小相匹配。这意味着，如果我们要读取的值是 16 位值（如供应商 ID），则必须执行 16 位读取。执行 32 位读取可能会更改设备响应的结果。对于配置空间，我们可以读取供应商 ID 的更大大小，但并非在所有情况下都是如此。最好养成将读取大小与目标大小匹配的习惯，以避免任何意外结果。请记住：设备内存不是 RAM。</p>
</blockquote>
<p>综上所述，我们读取 <code>00：00.0</code> 的 VendorID，如下所示：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">8: kd&gt; !dw <span class="o">[</span>uc<span class="o">]</span> D0000000 L1
</span></span><span class="line"><span class="cl"><span class="c1">#d0000000 8086</span>
</span></span></code></pre></div><p>我们读取的结果值为 <code>0x8086</code>，它恰好是 <code>Intel</code> 的供应商 ID。为了验证这是正确的，让我们使用 <code>！pci</code> 转储相同的内容。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">8: kd&gt; !pci <span class="m">100</span> <span class="m">0</span> <span class="m">0</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">PCI Configuration Space <span class="o">(</span>Segment:0000 Bus:00 Device:00 Function:00<span class="o">)</span>
</span></span><span class="line"><span class="cl">Common Header:
</span></span><span class="line"><span class="cl">    00: VendorID       <span class="m">8086</span> Intel Corporation
</span></span></code></pre></div><h3 id="从特定函数读取-vendorid">从特定函数读取 VendorID</h3>
<p>现在要计算我们希望与之通信的另一个函数（<code>02：00.1</code> 的 NVIDIA 卡）的 ECAM 地址，我们需要通过使用目标函数的 BDF 和一些位数学计算到 ECAM 的偏移量来手动执行“数组访问”。</p>
<p>计算方法存在于 PCIe 规范中，该规范为总线、器件和函数分配了一定数量的 ECAM 位来计算偏移量：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="p">|</span> <span class="m">27</span> - <span class="m">20</span> <span class="p">|</span> <span class="m">19</span> - <span class="m">15</span> <span class="p">|</span> <span class="m">14</span> - <span class="m">12</span>     <span class="p">|</span>  <span class="m">11</span> - <span class="m">0</span>       <span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span> Bus Nr  <span class="p">|</span> Dev Nr  <span class="p">|</span> Function Nr <span class="p">|</span> Register      <span class="p">|</span>
</span></span></code></pre></div><p>通过填写 BDF 并根据每个元素的位位置对结果进行移位和 OR 运算，我们可以计算出要添加到 ECAM 的偏移量。</p>
<p>我将使用 python，但你可以使用任何你想要的计算器：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">&gt;&gt;&gt; hex<span class="o">(</span>0xD0000000 + <span class="o">((</span><span class="m">2</span> <span class="s">&lt;&lt; 20) | (0 &lt;&lt; 15) | (1 &lt;&lt; 12)))
</span></span></span><span class="line"><span class="cl"><span class="s">&#39;0xd020</span>1000<span class="err">&#39;</span>
</span></span></code></pre></div><p>这意味着 <code>02：00.1</code> 的 ECAM 区域位于 <code>0xD0201000</code>。</p>
<p>现在，要从函数中读取 VendorID 的值：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">8: kd&gt; !dw <span class="o">[</span>uc<span class="o">]</span> D0201000 L1
</span></span><span class="line"><span class="cl"><span class="c1">#d0201000 10de</span>
</span></span></code></pre></div><p>结果是 <code>0x10de</code>，我们从上面知道它是 NVIDIA Corporation！这意味着我们成功地从 ECAM 中读取了此函数的第一个值。</p>
<h2 id="总结">总结</h2>
<p>这篇帖子最终比我预期的要长得多！我不会继续这篇文章，而是将其拆分并随着时间的推移充实该系列。关于 PCIe，我想介绍的主题太多了，但空闲时间却很少，但在下一篇文章中，我将更详细地介绍设备 BAR（一种特定于设备的 MMIO 形式）和 DMA（直接内存访问）。本系列将继续使用与以前相同的租户，更侧重于理解而不是具体细节。</p>
<p>希望你喜欢这个对 PCIe 世界的小小了解！期待更多精彩。</p>
<p><a href="https://lifeislife.cn/posts/%E8%AF%91%E6%96%87-pcie-part2/">单击此处查看第 2 部分！</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>SCP-firmware 源码分析-module 间 api 调用分析</title>
      <link>https://lifeislife.cn/posts/scp-firmware%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-module%E9%97%B4api%E8%B0%83%E7%94%A8%E5%88%86%E6%9E%90/</link>
      <pubDate>Thu, 15 Aug 2024 15:42:13 +0800</pubDate>
      <guid>https://lifeislife.cn/posts/scp-firmware%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-module%E9%97%B4api%E8%B0%83%E7%94%A8%E5%88%86%E6%9E%90/</guid>
      <description>&lt;p&gt;想了解如何提供 API，我们先看看 CMN 模块是如何使用 API 的，在文件&lt;code&gt;module/cmn700/src/mod_cmn700.c&lt;/code&gt;我们可以看到如下代码：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// module/cmn700/src/mod_cmn700.c
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mod_system_info_get_info_api&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;system_info_api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;cmn700_start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;status&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;system_info_api&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;get_system_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;system_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FWK_SUCCESS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;chip_id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;system_info&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;chip_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;multi_chip_mode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;system_info&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;multi_chip_mode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;他调用了&lt;code&gt;system_info_api-&amp;gt;get_system_info&lt;/code&gt;这个函数，实际这就是 mod_system_info 暴露给 mod_cmn700 的一个 API。用于获取系统信息。&lt;/p&gt;
&lt;p&gt;为何这个静态变量&lt;code&gt;system_info_api&lt;/code&gt;就能调用到&lt;code&gt;mod_system_info&lt;/code&gt;的函数呢？我们继续搜索代码，可以看到如下代码：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// module/cmn700/src/mod_cmn700.c
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;cmn700_bind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;round&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;fwk_id_is_type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FWK_ID_TYPE_MODULE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;cm&#34;&gt;/* Bind to system info module to obtain multi-chip info */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;status&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;fwk_module_bind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nf&#34;&gt;FWK_ID_MODULE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_SYSTEM_INFO&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nf&#34;&gt;FWK_ID_API&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_SYSTEM_INFO&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;MOD_SYSTEM_INFO_GET_API_IDX&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;system_info_api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在&lt;code&gt;cmn700_bind&lt;/code&gt;函数中，调用了&lt;code&gt;fwk_module_bind&lt;/code&gt;函数，这个函数的作用是绑定一个模块的 API，这样就可以通过这个 API 调用模块的函数。&lt;code&gt;fwk_module_bind&lt;/code&gt;函数的第一个参数是提供 API 的模块的 ID，比如当前是模块&lt;code&gt;system_info&lt;/code&gt;的 ID，如果是要用 USB 的 API，那么就是 USB 模块的 ID。第二个参数是 API 的 ID，这个 ID 是在模块的头文件中定义的，比如&lt;code&gt;mod_system_info.h&lt;/code&gt;中定义了&lt;code&gt;MOD_SYSTEM_INFO_GET_API_IDX&lt;/code&gt;，这个宏定义的值就是 API 的 ID。第三个参数是 API 的指针，这个指针就是开头提到的静态变量的地址，它是定义在使用 API 的模块中的。这样就可以通过 API 的指针调用模块的函数。对于 USB 模块，我们就需要在使用到 USB 的模块中定义一个静态变量。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fwk_module_bind&lt;/code&gt;做了什么呢？进入该函数，可以看到它回调了&lt;code&gt;mod_system_info&lt;/code&gt;的&lt;code&gt;process_bind_request&lt;/code&gt;函数，也就是&lt;code&gt;system_info_process_bind_request&lt;/code&gt;函数。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// framework/src/fwk_module.c
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;fwk_module_bind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;target_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;api_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;status&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_mod_ctx&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;desc&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;process_bind_request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;fwk_module_ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bind_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;target_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;api_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;system_info_process_bind_request&lt;/code&gt;函数的作用是根据 API 的 ID 返回 API 的指针。它把&lt;code&gt;get_system_info_api&lt;/code&gt;的地址赋值给了&lt;code&gt;api&lt;/code&gt;。这就实现了 API 的暴露。经过这个函数，&lt;code&gt;system_info_api&lt;/code&gt;就指向了&lt;code&gt;get_system_info_api&lt;/code&gt;。实际就是我们在&lt;code&gt;cmn700_bind&lt;/code&gt;函数中使用&lt;code&gt;system_info_api&lt;/code&gt;就是在调用&lt;code&gt;get_system_info_api&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// module/system_info/src/mod_system_info.c
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;system_info_process_bind_request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;requester_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;targer_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;api_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;switch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;fwk_id_get_api_idx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;api_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;MOD_SYSTEM_INFO_GET_API_IDX&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;api&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_system_info_api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FWK_E_PARAM&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FWK_SUCCESS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;解释完这个 bind 函数，它是在哪被调用的？它在框架初始化时就会被调用，分析&lt;code&gt;fwk_arch_init&lt;/code&gt;函数，可以看到在&lt;code&gt;fwk_module_start&lt;/code&gt;函数中会对每一个模块调用&lt;code&gt;bind&lt;/code&gt;函数。函数调用流程参考图片文件。在此就不再赘述了。&lt;/p&gt;
&lt;p&gt;经过以上分析，提供 API 的模块需要做的事情就是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在模块的头文件中定义 API 的 ID，它有一定的格式，具体可以参考 MSCP 文档&lt;code&gt;doc/framework.md&lt;/code&gt;的&lt;code&gt;APIs&lt;/code&gt;章节。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;enum&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mod_modulename_api&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;MOD_MODULENAME_API_A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;MOD_MODULENAME_API_B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在模块的源文件中定义 API 结构体，用于在其他模块中使用。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mod_system_info_get_info_api&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_system_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mod_system_info&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sys_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在模块原文件中提供&lt;code&gt;process_bind_request&lt;/code&gt;函数，用于根据 API 的 ID 返回 API 的指针。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;system_info_process_bind_request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;requester_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;targer_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;api_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;switch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;fwk_id_get_api_idx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;api_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;MOD_SYSTEM_INFO_GET_API_IDX&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;api&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_system_info_api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FWK_E_PARAM&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FWK_SUCCESS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在模块的源文件中定义 API 的指针，用于提供 API 的地址。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mod_system_info_get_info_api&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;get_system_info_api&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_system_info&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;system_info_get_system_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现需要提供的 API 函数。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;system_info_get_system_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mod_system_info&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sys_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sys_info&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;system_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FWK_SUCCESS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上就是大致的移植流程了。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/10/5b3d6d12e4e9e9b2b7aa5cc5fc6c8738.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/10/5b3d6d12e4e9e9b2b7aa5cc5fc6c8738.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<p>想了解如何提供 API，我们先看看 CMN 模块是如何使用 API 的，在文件<code>module/cmn700/src/mod_cmn700.c</code>我们可以看到如下代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// module/cmn700/src/mod_cmn700.c
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">struct</span> <span class="n">mod_system_info_get_info_api</span> <span class="o">*</span><span class="n">system_info_api</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">cmn700_start</span><span class="p">(</span><span class="kt">fwk_id_t</span> <span class="n">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="n">status</span> <span class="o">=</span> <span class="n">system_info_api</span><span class="o">-&gt;</span><span class="nf">get_system_info</span><span class="p">(</span><span class="o">&amp;</span><span class="n">system_info</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">==</span> <span class="n">FWK_SUCCESS</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">chip_id</span> <span class="o">=</span> <span class="n">system_info</span><span class="o">-&gt;</span><span class="n">chip_id</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">multi_chip_mode</span> <span class="o">=</span> <span class="n">system_info</span><span class="o">-&gt;</span><span class="n">multi_chip_mode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>他调用了<code>system_info_api-&gt;get_system_info</code>这个函数，实际这就是 mod_system_info 暴露给 mod_cmn700 的一个 API。用于获取系统信息。</p>
<p>为何这个静态变量<code>system_info_api</code>就能调用到<code>mod_system_info</code>的函数呢？我们继续搜索代码，可以看到如下代码：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// module/cmn700/src/mod_cmn700.c
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">cmn700_bind</span><span class="p">(</span><span class="kt">fwk_id_t</span> <span class="n">id</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">round</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nf">fwk_id_is_type</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">FWK_ID_TYPE_MODULE</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* Bind to system info module to obtain multi-chip info */</span>
</span></span><span class="line"><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="nf">fwk_module_bind</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nf">FWK_ID_MODULE</span><span class="p">(</span><span class="n">FWK_MODULE_IDX_SYSTEM_INFO</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">FWK_ID_API</span><span class="p">(</span><span class="n">FWK_MODULE_IDX_SYSTEM_INFO</span><span class="p">,</span> <span class="n">MOD_SYSTEM_INFO_GET_API_IDX</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="o">&amp;</span><span class="n">system_info_api</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">status</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>在<code>cmn700_bind</code>函数中，调用了<code>fwk_module_bind</code>函数，这个函数的作用是绑定一个模块的 API，这样就可以通过这个 API 调用模块的函数。<code>fwk_module_bind</code>函数的第一个参数是提供 API 的模块的 ID，比如当前是模块<code>system_info</code>的 ID，如果是要用 USB 的 API，那么就是 USB 模块的 ID。第二个参数是 API 的 ID，这个 ID 是在模块的头文件中定义的，比如<code>mod_system_info.h</code>中定义了<code>MOD_SYSTEM_INFO_GET_API_IDX</code>，这个宏定义的值就是 API 的 ID。第三个参数是 API 的指针，这个指针就是开头提到的静态变量的地址，它是定义在使用 API 的模块中的。这样就可以通过 API 的指针调用模块的函数。对于 USB 模块，我们就需要在使用到 USB 的模块中定义一个静态变量。</p>
<p><code>fwk_module_bind</code>做了什么呢？进入该函数，可以看到它回调了<code>mod_system_info</code>的<code>process_bind_request</code>函数，也就是<code>system_info_process_bind_request</code>函数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// framework/src/fwk_module.c
</span></span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">fwk_module_bind</span><span class="p">(</span><span class="kt">fwk_id_t</span> <span class="n">target_id</span><span class="p">,</span> <span class="kt">fwk_id_t</span> <span class="n">api_id</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">api</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="n">status</span> <span class="o">=</span> <span class="n">fwk_mod_ctx</span><span class="o">-&gt;</span><span class="n">desc</span><span class="o">-&gt;</span><span class="nf">process_bind_request</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">fwk_module_ctx</span><span class="p">.</span><span class="n">bind_id</span><span class="p">,</span> <span class="n">target_id</span><span class="p">,</span> <span class="n">api_id</span><span class="p">,</span> <span class="p">(</span><span class="k">const</span> <span class="kt">void</span> <span class="o">**</span><span class="p">)</span><span class="n">api</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>system_info_process_bind_request</code>函数的作用是根据 API 的 ID 返回 API 的指针。它把<code>get_system_info_api</code>的地址赋值给了<code>api</code>。这就实现了 API 的暴露。经过这个函数，<code>system_info_api</code>就指向了<code>get_system_info_api</code>。实际就是我们在<code>cmn700_bind</code>函数中使用<code>system_info_api</code>就是在调用<code>get_system_info_api</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// module/system_info/src/mod_system_info.c
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">system_info_process_bind_request</span><span class="p">(</span><span class="kt">fwk_id_t</span> <span class="n">requester_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">fwk_id_t</span> <span class="n">targer_id</span><span class="p">,</span> <span class="kt">fwk_id_t</span> <span class="n">api_id</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">**</span><span class="n">api</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="nf">fwk_id_get_api_idx</span><span class="p">(</span><span class="n">api_id</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nl">MOD_SYSTEM_INFO_GET_API_IDX</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="o">*</span><span class="n">api</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">get_system_info_api</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">FWK_E_PARAM</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">FWK_SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>解释完这个 bind 函数，它是在哪被调用的？它在框架初始化时就会被调用，分析<code>fwk_arch_init</code>函数，可以看到在<code>fwk_module_start</code>函数中会对每一个模块调用<code>bind</code>函数。函数调用流程参考图片文件。在此就不再赘述了。</p>
<p>经过以上分析，提供 API 的模块需要做的事情就是：</p>
<ol>
<li>
<p>在模块的头文件中定义 API 的 ID，它有一定的格式，具体可以参考 MSCP 文档<code>doc/framework.md</code>的<code>APIs</code>章节。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">enum</span> <span class="n">mod_modulename_api</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">MOD_MODULENAME_API_A</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">MOD_MODULENAME_API_B</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div></li>
<li>
<p>在模块的源文件中定义 API 结构体，用于在其他模块中使用。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">mod_system_info_get_info_api</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">get_system_info</span><span class="p">)(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">mod_system_info</span> <span class="o">**</span><span class="n">sys_info</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div></li>
<li>
<p>在模块原文件中提供<code>process_bind_request</code>函数，用于根据 API 的 ID 返回 API 的指针。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">system_info_process_bind_request</span><span class="p">(</span><span class="kt">fwk_id_t</span> <span class="n">requester_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">fwk_id_t</span> <span class="n">targer_id</span><span class="p">,</span> <span class="kt">fwk_id_t</span> <span class="n">api_id</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">**</span><span class="n">api</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="nf">fwk_id_get_api_idx</span><span class="p">(</span><span class="n">api_id</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nl">MOD_SYSTEM_INFO_GET_API_IDX</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="o">*</span><span class="n">api</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">get_system_info_api</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">FWK_E_PARAM</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">FWK_SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></li>
<li>
<p>在模块的源文件中定义 API 的指针，用于提供 API 的地址。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="k">struct</span> <span class="n">mod_system_info_get_info_api</span> <span class="n">get_system_info_api</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">get_system_info</span> <span class="o">=</span> <span class="n">system_info_get_system_info</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div></li>
<li>
<p>实现需要提供的 API 函数。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">system_info_get_system_info</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">mod_system_info</span> <span class="o">**</span><span class="n">sys_info</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">*</span><span class="n">sys_info</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">system_info</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">FWK_SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></li>
</ol>
<p>以上就是大致的移植流程了。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/10/5b3d6d12e4e9e9b2b7aa5cc5fc6c8738.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/10/5b3d6d12e4e9e9b2b7aa5cc5fc6c8738.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
]]></content:encoded>
    </item>
    <item>
      <title>SCP-firmware 源码分析 - 源码编译以及模块初始化流程</title>
      <link>https://lifeislife.cn/posts/scp-firmware%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E4%BB%A5%E5%8F%8A%E6%A8%A1%E5%9D%97%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B/</link>
      <pubDate>Sat, 10 Aug 2024 15:42:13 +0800</pubDate>
      <guid>https://lifeislife.cn/posts/scp-firmware%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E4%BB%A5%E5%8F%8A%E6%A8%A1%E5%9D%97%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B/</guid>
      <description>&lt;h2 id=&#34;编译源码&#34;&gt;编译源码&lt;/h2&gt;
&lt;h3 id=&#34;下载配置工具链&#34;&gt;下载配置工具链&lt;/h3&gt;
&lt;p&gt;访问&lt;a href=&#34;https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads&#34;&gt;ARM 官网下载工具链&lt;/a&gt;下载自己系统的版本的工具链。如果你也是 x86_64 的 Linux 系统，直接点击&lt;a href=&#34;https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz&#34;&gt;该链接下载 arm-gnu-toolchain-13.3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;解压工具链：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;xz -d arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tar -xvf arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;将工具链路径添加到环境变量中。&lt;/p&gt;
&lt;h3 id=&#34;编译源码-1&#34;&gt;编译源码&lt;/h3&gt;
&lt;p&gt;源码使用 CMake 进行编译，CMake 版本需要 3.18.4 以上。如果你是 Ubuntu 20.04，直接安装即可。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt install cmake
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果你是 Ubuntu 18.04，需要升级 CMAKE。可以通过下面的方式下载源码编译安装 CMAKE。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;wget https://github.com/Kitware/CMake/releases/download/v3.22.0/cmake-3.22.0-linux-x86_64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tar -zxvf cmake-3.22.0-linux-x86_64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mv cmake-3.22.0-linux-x86_64 /usr/local/cmake
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/usr/local/cmake/bin:&lt;span class=&#34;nv&#34;&gt;$PATH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;源码编译：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;make -f Makefile.cmake &lt;span class=&#34;nv&#34;&gt;PRODUCT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;totalcompute/tc2 &lt;span class=&#34;nv&#34;&gt;MODE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;debug firmware-scp_ramfw
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;源码编译成功后，会在&lt;code&gt;build/tc2/GNU/debug/firmware-scp_ramfw/bin&lt;/code&gt;目录下生成二进制文件。具体编译参数，可以参考源码根目录下的&lt;code&gt;user_guide&lt;/code&gt;文档。&lt;/p&gt;
&lt;h2 id=&#34;初始化流程分析&#34;&gt;初始化流程分析&lt;/h2&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/15/9489caa086aadd2299d862a9267ff947.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/15/9489caa086aadd2299d862a9267ff947.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h3 id=&#34;fwk_module_init-初始时module_config_table-是在什么时候被初始化的&#34;&gt;fwk_module_init 初始时，module_config_table 是在什么时候被初始化的？&lt;/h3&gt;
&lt;p&gt;module_config_table 无法在源码中直接搜索到，因为它是在编译过程中生成的，当你编译一次后，就会在 output/build/mpw/GNU/debug/firmware-scp_romfw/framework/src/fwk_module_list.c 找到这个变量。具体它是如何生成的，注意通过 &lt;code&gt;framework/CMakeLists.txt&lt;/code&gt; 文件中的这段代码完成，我们逐行分析这段代码。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cmake&#34; data-lang=&#34;cmake&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;LENGTH&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_IDX_MAX&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;获取模块列表的长度，保存在 &lt;code&gt;SCP_MODULE_IDX_MAX&lt;/code&gt; 变量中。在每个 &lt;code&gt;Firmware.cmake&lt;/code&gt; 文件中，都会有一个 &lt;code&gt;SCP_MODULES&lt;/code&gt; 变量，这个变量保存了该产品所包含的模块。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cmake&#34; data-lang=&#34;cmake&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;# product/juno/scp_romfw/Firmware.cmake
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;uart&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当前我们加入了两个模块，所以 &lt;code&gt;SCP_MODULE_IDX_MAX&lt;/code&gt; 的值为 2。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cmake&#34; data-lang=&#34;cmake&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;foreach&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;idx&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;RANGE&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;SCP_MODULE_IDX_MAX&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;idx&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;EQUAL&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_IDX_MAX&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_IDX_GEN&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;    FWK_MODULE_IDX_COUNT = ${idx},\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;endif&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;遍历模块列表，生成模块索引的定义，如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;enum&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module_idx&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_COUNT&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cmake&#34; data-lang=&#34;cmake&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;GET&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;idx&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;将 &lt;code&gt;SCP_MODULES&lt;/code&gt; 列表中索引为 &lt;code&gt;idx&lt;/code&gt; 的元素赋值给变量 &lt;code&gt;SCP_MODULE&lt;/code&gt;，以便在后续的代码中使用。当前 &lt;code&gt;idx&lt;/code&gt; 为 0，所以 &lt;code&gt;SCP_MODULE&lt;/code&gt; 为 &lt;code&gt;test&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cmake&#34; data-lang=&#34;cmake&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;MAKE_C_IDENTIFIER&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;SCP_MODULE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;TOUPPER&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;SCP_MODULE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_UPPER&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;将 &lt;code&gt;SCP_MODULE&lt;/code&gt; 转换为 C 标识符。将 &lt;code&gt;SCP_MODULE&lt;/code&gt; 转为大写并保存在 &lt;code&gt;SCP_MODULE_UPPER&lt;/code&gt; 中，即 &lt;code&gt;SCP_MODULE_UPPER=&amp;quot;TEST&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cmake&#34; data-lang=&#34;cmake&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_IDX_GEN&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;    FWK_MODULE_IDX_${SCP_MODULE_UPPER} = ${idx},\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_ID_INIT_GEN&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;#define FWK_MODULE_ID_${SCP_MODULE_UPPER}_INIT FWK_ID_MODULE_INIT(FWK_MODULE_IDX_${SCP_MODULE_UPPER})\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_ID_GEN&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;#define FWK_MODULE_ID_${SCP_MODULE_UPPER} FWK_ID_MODULE(FWK_MODULE_IDX_${SCP_MODULE_UPPER})\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_ID_CONST_GEN&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;static const fwk_id_t fwk_module_id_${SCP_MODULE} = FWK_MODULE_ID_${SCP_MODULE_UPPER}_INIT;\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_EXTERN_GEN&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;extern const struct fwk_module module_${SCP_MODULE};\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_EXTERN_CONFIG_GEN&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;extern const struct fwk_module_config config_${SCP_MODULE};\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_GEN&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;    &amp;amp;module_${SCP_MODULE},\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULE_CONFIG_GEN&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;    &amp;amp;config_${SCP_MODULE},\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;进行一些宏替换，替换后的结果如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SCP_MODULE_IDX_GEN: &amp;#34;   FWK_MODULE_IDX_TEST = 0,&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SCP_MODULE_ID_INIT_GEN: &amp;#34;#define FWK_MODULE_ID_TEST_INIT FWK_ID_MODULE_INIT(FWK_MODULE_IDX_TEST)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SCP_MODULE_ID_GEN: &amp;#34;#define FWK_MODULE_ID_TEST FWK_ID_MODULE(FWK_MODULE_IDX_TEST)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SCP_MODULE_ID_CONST_GEN: &amp;#34;static const fwk_id_t fwk_module_id_test = FWK_MODULE_ID_TEST_INIT;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SCP_MODULE_EXTERN_GEN: &amp;#34;extern const struct fwk_module module_test;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SCP_MODULE_EXTERN_CONFIG_GEN: &amp;#34;extern const struct fwk_module_config config_test;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SCP_MODULE_GEN: &amp;#34;&amp;amp;module_test,&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SCP_MODULE_CONFIG_GEN: &amp;#34;&amp;amp;config_test,&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cmake&#34; data-lang=&#34;cmake&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;target_compile_definitions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;framework&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                               &lt;span class=&#34;s&#34;&gt;PUBLIC&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;BUILD_HAS_MOD_${SCP_MODULE_UPPER}=1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;将 &lt;code&gt;BUILD_HAS_MOD_TEST&lt;/code&gt; 定义为 1，以便在后续的代码中使用。&lt;/p&gt;
&lt;p&gt;在源码文件中有两个模板文件，分别为 &lt;code&gt;fwk_module_list.c.in&lt;/code&gt; 和 &lt;code&gt;fwk_module_idx.h.in&lt;/code&gt;，这两个文件中包含了一些宏定义，这些宏定义在编译过程中会被替换为上面生成的宏定义。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;stddef.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SCP_MODULE_EXTERN_GEN&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;module_table&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_COUNT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SCP_MODULE_GEN&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SCP_MODULE_EXTERN_CONFIG_GEN&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module_config&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;module_config_table&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_COUNT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SCP_MODULE_CONFIG_GEN&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;fwk_id.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SCP_MODULE_ID_INIT_GEN&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SCP_MODULE_ID_GEN&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;enum&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module_idx&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SCP_MODULE_IDX_GEN&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SCP_MODULE_ID_CONST_GEN&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在编译过程中就会生成下面两个文件，当使用&lt;code&gt;module_config_table&lt;/code&gt;时，就会引用这两个文件，找到对应的配置。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// build/mpw/GNU/debug/firmware-scp_romfw/framework/include/fwk_module_idx.h
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define FWK_MODULE_ID_TEST_INIT FWK_ID_MODULE_INIT(FWK_MODULE_IDX_TEST)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define FWK_MODULE_ID_UART_INIT FWK_ID_MODULE_INIT(FWK_MODULE_IDX_UART)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define FWK_MODULE_ID_TEST FWK_ID_MODULE(FWK_MODULE_IDX_TEST)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define FWK_MODULE_ID_UART FWK_ID_MODULE(FWK_MODULE_IDX_UART)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;enum&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module_idx&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_TEST&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_UART&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_COUNT&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module_id_test&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FWK_MODULE_ID_TEST_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;fwk_id_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module_id_uart&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FWK_MODULE_ID_UART_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// build/mpw/GNU/debug/firmware-scp_romfw/framework/src/fwk_module_list.c
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;extern&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;module_test&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;extern&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;module_uart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;module_table&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_COUNT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;module_test&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;module_uart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;extern&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module_config&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;config_test&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;extern&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module_config&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;config_uart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fwk_module_config&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;module_config_table&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FWK_MODULE_IDX_COUNT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;config_test&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;config_uart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;如何确定各个模块在初始化过程中的执行顺序&#34;&gt;如何确定各个模块在初始化过程中的执行顺序？&lt;/h3&gt;
&lt;p&gt;在每个产品目录 mscp/product/juno/scp_romfw/Firmware.cmake 中，都会有一个 Firmware.cmake 文件，这个文件中会包含该产品所包含的模块：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cmake&#34; data-lang=&#34;cmake&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;juno-ppu&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;juno-rom&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;gtimer&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;sds&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;bootloader&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;APPEND&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;SCP_MODULES&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;juno-soc-clock&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;编译后会在 output 文件夹中生成&lt;code&gt;fwk_module_idx.h&lt;/code&gt;文件，这个文件中会包含所有模块的索引，如下：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/14-48-59-f81a00af1d7e54bd428cf18f77a3465e-20240729144858-cdaa85.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/14-48-59-f81a00af1d7e54bd428cf18f77a3465e-20240729144858-cdaa85.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h2 id="编译源码">编译源码</h2>
<h3 id="下载配置工具链">下载配置工具链</h3>
<p>访问<a href="https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads">ARM 官网下载工具链</a>下载自己系统的版本的工具链。如果你也是 x86_64 的 Linux 系统，直接点击<a href="https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz">该链接下载 arm-gnu-toolchain-13.3</a></p>
<p>解压工具链：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">xz -d arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
</span></span><span class="line"><span class="cl">tar -xvf arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar
</span></span></code></pre></div><p>将工具链路径添加到环境变量中。</p>
<h3 id="编译源码-1">编译源码</h3>
<p>源码使用 CMake 进行编译，CMake 版本需要 3.18.4 以上。如果你是 Ubuntu 20.04，直接安装即可。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo apt install cmake
</span></span></code></pre></div><p>如果你是 Ubuntu 18.04，需要升级 CMAKE。可以通过下面的方式下载源码编译安装 CMAKE。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">wget https://github.com/Kitware/CMake/releases/download/v3.22.0/cmake-3.22.0-linux-x86_64.tar.gz
</span></span><span class="line"><span class="cl">tar -zxvf cmake-3.22.0-linux-x86_64.tar.gz
</span></span><span class="line"><span class="cl">mv cmake-3.22.0-linux-x86_64 /usr/local/cmake
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span>/usr/local/cmake/bin:<span class="nv">$PATH</span>
</span></span></code></pre></div><p>源码编译：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">make -f Makefile.cmake <span class="nv">PRODUCT</span><span class="o">=</span>totalcompute/tc2 <span class="nv">MODE</span><span class="o">=</span>debug firmware-scp_ramfw
</span></span></code></pre></div><p>源码编译成功后，会在<code>build/tc2/GNU/debug/firmware-scp_ramfw/bin</code>目录下生成二进制文件。具体编译参数，可以参考源码根目录下的<code>user_guide</code>文档。</p>
<h2 id="初始化流程分析">初始化流程分析</h2>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/15/9489caa086aadd2299d862a9267ff947.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/08/15/9489caa086aadd2299d862a9267ff947.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h3 id="fwk_module_init-初始时module_config_table-是在什么时候被初始化的">fwk_module_init 初始时，module_config_table 是在什么时候被初始化的？</h3>
<p>module_config_table 无法在源码中直接搜索到，因为它是在编译过程中生成的，当你编译一次后，就会在 output/build/mpw/GNU/debug/firmware-scp_romfw/framework/src/fwk_module_list.c 找到这个变量。具体它是如何生成的，注意通过 <code>framework/CMakeLists.txt</code> 文件中的这段代码完成，我们逐行分析这段代码。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="s">LENGTH</span> <span class="s">SCP_MODULES</span> <span class="s">SCP_MODULE_IDX_MAX</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>获取模块列表的长度，保存在 <code>SCP_MODULE_IDX_MAX</code> 变量中。在每个 <code>Firmware.cmake</code> 文件中，都会有一个 <code>SCP_MODULES</code> 变量，这个变量保存了该产品所包含的模块。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="c"># product/juno/scp_romfw/Firmware.cmake
</span></span></span><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULES</span> <span class="s2">&#34;test&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULES</span> <span class="s2">&#34;uart&#34;</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>当前我们加入了两个模块，所以 <code>SCP_MODULE_IDX_MAX</code> 的值为 2。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="nb">foreach</span><span class="p">(</span><span class="s">idx</span> <span class="s">RANGE</span> <span class="o">${</span><span class="nv">SCP_MODULE_IDX_MAX</span><span class="o">}</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">if</span><span class="p">(</span><span class="s">idx</span> <span class="s">EQUAL</span> <span class="s">SCP_MODULE_IDX_MAX</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">        <span class="nb">string</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULE_IDX_GEN</span> <span class="s2">&#34;    FWK_MODULE_IDX_COUNT = ${idx},\n&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl">        <span class="nb">break</span><span class="p">()</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">endif</span><span class="p">()</span><span class="err">
</span></span></span></code></pre></div><p>遍历模块列表，生成模块索引的定义，如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">enum</span> <span class="n">fwk_module_idx</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">FWK_MODULE_IDX_COUNT</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl">    <span class="nb">list</span><span class="p">(</span><span class="s">GET</span> <span class="s">SCP_MODULES</span> <span class="o">${</span><span class="nv">idx</span><span class="o">}</span> <span class="s">SCP_MODULE</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>将 <code>SCP_MODULES</code> 列表中索引为 <code>idx</code> 的元素赋值给变量 <code>SCP_MODULE</code>，以便在后续的代码中使用。当前 <code>idx</code> 为 0，所以 <code>SCP_MODULE</code> 为 <code>test</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">MAKE_C_IDENTIFIER</span> <span class="o">${</span><span class="nv">SCP_MODULE</span><span class="o">}</span> <span class="s">SCP_MODULE</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">TOUPPER</span> <span class="o">${</span><span class="nv">SCP_MODULE</span><span class="o">}</span> <span class="s">SCP_MODULE_UPPER</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>将 <code>SCP_MODULE</code> 转换为 C 标识符。将 <code>SCP_MODULE</code> 转为大写并保存在 <code>SCP_MODULE_UPPER</code> 中，即 <code>SCP_MODULE_UPPER=&quot;TEST&quot;</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULE_IDX_GEN</span> <span class="s2">&#34;    FWK_MODULE_IDX_${SCP_MODULE_UPPER} = ${idx},\n&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULE_ID_INIT_GEN</span> <span class="s2">&#34;#define FWK_MODULE_ID_${SCP_MODULE_UPPER}_INIT FWK_ID_MODULE_INIT(FWK_MODULE_IDX_${SCP_MODULE_UPPER})\n&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULE_ID_GEN</span> <span class="s2">&#34;#define FWK_MODULE_ID_${SCP_MODULE_UPPER} FWK_ID_MODULE(FWK_MODULE_IDX_${SCP_MODULE_UPPER})\n&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULE_ID_CONST_GEN</span> <span class="s2">&#34;static const fwk_id_t fwk_module_id_${SCP_MODULE} = FWK_MODULE_ID_${SCP_MODULE_UPPER}_INIT;\n&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULE_EXTERN_GEN</span> <span class="s2">&#34;extern const struct fwk_module module_${SCP_MODULE};\n&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULE_EXTERN_CONFIG_GEN</span> <span class="s2">&#34;extern const struct fwk_module_config config_${SCP_MODULE};\n&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULE_GEN</span> <span class="s2">&#34;    &amp;module_${SCP_MODULE},\n&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl">    <span class="nb">string</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULE_CONFIG_GEN</span> <span class="s2">&#34;    &amp;config_${SCP_MODULE},\n&#34;</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>进行一些宏替换，替换后的结果如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">SCP_MODULE_IDX_GEN: &#34;   FWK_MODULE_IDX_TEST = 0,&#34;
</span></span><span class="line"><span class="cl">SCP_MODULE_ID_INIT_GEN: &#34;#define FWK_MODULE_ID_TEST_INIT FWK_ID_MODULE_INIT(FWK_MODULE_IDX_TEST)&#34;
</span></span><span class="line"><span class="cl">SCP_MODULE_ID_GEN: &#34;#define FWK_MODULE_ID_TEST FWK_ID_MODULE(FWK_MODULE_IDX_TEST)&#34;
</span></span><span class="line"><span class="cl">SCP_MODULE_ID_CONST_GEN: &#34;static const fwk_id_t fwk_module_id_test = FWK_MODULE_ID_TEST_INIT;&#34;
</span></span><span class="line"><span class="cl">SCP_MODULE_EXTERN_GEN: &#34;extern const struct fwk_module module_test;&#34;
</span></span><span class="line"><span class="cl">SCP_MODULE_EXTERN_CONFIG_GEN: &#34;extern const struct fwk_module_config config_test;&#34;
</span></span><span class="line"><span class="cl">SCP_MODULE_GEN: &#34;&amp;module_test,&#34;
</span></span><span class="line"><span class="cl">SCP_MODULE_CONFIG_GEN: &#34;&amp;config_test,&#34;
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl">    <span class="nb">target_compile_definitions</span><span class="p">(</span><span class="s">framework</span>
</span></span><span class="line"><span class="cl">                               <span class="s">PUBLIC</span> <span class="s2">&#34;BUILD_HAS_MOD_${SCP_MODULE_UPPER}=1&#34;</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>将 <code>BUILD_HAS_MOD_TEST</code> 定义为 1，以便在后续的代码中使用。</p>
<p>在源码文件中有两个模板文件，分别为 <code>fwk_module_list.c.in</code> 和 <code>fwk_module_idx.h.in</code>，这两个文件中包含了一些宏定义，这些宏定义在编译过程中会被替换为上面生成的宏定义。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stddef.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">@</span><span class="n">SCP_MODULE_EXTERN_GEN</span><span class="err">@</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="k">struct</span> <span class="n">fwk_module</span> <span class="o">*</span><span class="n">module_table</span><span class="p">[</span><span class="n">FWK_MODULE_IDX_COUNT</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="err">@</span><span class="n">SCP_MODULE_GEN</span><span class="err">@</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">@</span><span class="n">SCP_MODULE_EXTERN_CONFIG_GEN</span><span class="err">@</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="k">struct</span> <span class="n">fwk_module_config</span> <span class="o">*</span><span class="n">module_config_table</span><span class="p">[</span><span class="n">FWK_MODULE_IDX_COUNT</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="err">@</span><span class="n">SCP_MODULE_CONFIG_GEN</span><span class="err">@</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;fwk_id.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">@</span><span class="n">SCP_MODULE_ID_INIT_GEN</span><span class="err">@</span>
</span></span><span class="line"><span class="cl"><span class="err">@</span><span class="n">SCP_MODULE_ID_GEN</span><span class="err">@</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">enum</span> <span class="n">fwk_module_idx</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="err">@</span><span class="n">SCP_MODULE_IDX_GEN</span><span class="err">@</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">@</span><span class="n">SCP_MODULE_ID_CONST_GEN</span><span class="err">@</span>
</span></span></code></pre></div><p>在编译过程中就会生成下面两个文件，当使用<code>module_config_table</code>时，就会引用这两个文件，找到对应的配置。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// build/mpw/GNU/debug/firmware-scp_romfw/framework/include/fwk_module_idx.h
</span></span></span><span class="line"><span class="cl"><span class="cp">#define FWK_MODULE_ID_TEST_INIT FWK_ID_MODULE_INIT(FWK_MODULE_IDX_TEST)
</span></span></span><span class="line"><span class="cl"><span class="cp">#define FWK_MODULE_ID_UART_INIT FWK_ID_MODULE_INIT(FWK_MODULE_IDX_UART)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#define FWK_MODULE_ID_TEST FWK_ID_MODULE(FWK_MODULE_IDX_TEST)
</span></span></span><span class="line"><span class="cl"><span class="cp">#define FWK_MODULE_ID_UART FWK_ID_MODULE(FWK_MODULE_IDX_UART)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">enum</span> <span class="n">fwk_module_idx</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">FWK_MODULE_IDX_TEST</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">FWK_MODULE_IDX_UART</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">FWK_MODULE_IDX_COUNT</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">const</span> <span class="kt">fwk_id_t</span> <span class="n">fwk_module_id_test</span> <span class="o">=</span> <span class="n">FWK_MODULE_ID_TEST_INIT</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">const</span> <span class="kt">fwk_id_t</span> <span class="n">fwk_module_id_uart</span> <span class="o">=</span> <span class="n">FWK_MODULE_ID_UART_INIT</span><span class="p">;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// build/mpw/GNU/debug/firmware-scp_romfw/framework/src/fwk_module_list.c
</span></span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">fwk_module</span> <span class="n">module_test</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">fwk_module</span> <span class="n">module_uart</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="k">struct</span> <span class="n">fwk_module</span> <span class="o">*</span><span class="n">module_table</span><span class="p">[</span><span class="n">FWK_MODULE_IDX_COUNT</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">&amp;</span><span class="n">module_test</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">&amp;</span><span class="n">module_uart</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">fwk_module_config</span> <span class="n">config_test</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">fwk_module_config</span> <span class="n">config_uart</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="k">struct</span> <span class="n">fwk_module_config</span> <span class="o">*</span><span class="n">module_config_table</span><span class="p">[</span><span class="n">FWK_MODULE_IDX_COUNT</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">&amp;</span><span class="n">config_test</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">&amp;</span><span class="n">config_uart</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h3 id="如何确定各个模块在初始化过程中的执行顺序">如何确定各个模块在初始化过程中的执行顺序？</h3>
<p>在每个产品目录 mscp/product/juno/scp_romfw/Firmware.cmake 中，都会有一个 Firmware.cmake 文件，这个文件中会包含该产品所包含的模块：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULES</span> <span class="s2">&#34;juno-ppu&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULES</span> <span class="s2">&#34;juno-rom&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULES</span> <span class="s2">&#34;gtimer&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULES</span> <span class="s2">&#34;sds&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULES</span> <span class="s2">&#34;bootloader&#34;</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span> <span class="s">SCP_MODULES</span> <span class="s2">&#34;juno-soc-clock&#34;</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>编译后会在 output 文件夹中生成<code>fwk_module_idx.h</code>文件，这个文件中会包含所有模块的索引，如下：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/14-48-59-f81a00af1d7e54bd428cf18f77a3465e-20240729144858-cdaa85.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/14-48-59-f81a00af1d7e54bd428cf18f77a3465e-20240729144858-cdaa85.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
]]></content:encoded>
    </item>
    <item>
      <title>Windows挂载NFS网络文件系统实现与开发文件传输</title>
      <link>https://lifeislife.cn/posts/windows%E6%8C%82%E8%BD%BDnfs%E7%BD%91%E7%BB%9C%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%AE%9E%E7%8E%B0%E4%B8%8E%E5%BC%80%E5%8F%91%E6%96%87%E4%BB%B6%E4%BC%A0%E8%BE%93/</link>
      <pubDate>Thu, 25 Jul 2024 21:11:05 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/windows%E6%8C%82%E8%BD%BDnfs%E7%BD%91%E7%BB%9C%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%AE%9E%E7%8E%B0%E4%B8%8E%E5%BC%80%E5%8F%91%E6%96%87%E4%BB%B6%E4%BC%A0%E8%BE%93/</guid>
      <description>&lt;h2 id=&#34;配置windows与开发板网络通信&#34;&gt;配置Windows与开发板网络通信&lt;/h2&gt;
&lt;h3 id=&#34;配置windows侧网卡&#34;&gt;配置Windows侧网卡&lt;/h3&gt;
&lt;p&gt;将Windows与开发板通过USB网卡连接，开发板的网卡插口选择eth0，也就是靠近拨码开关的网口。开发板开机，Windows的设备管理器中出现以下网卡表示网卡功能正常。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/90a28f55cd3ca3039694cb1c16ddff3e.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/90a28f55cd3ca3039694cb1c16ddff3e.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;打开网络和共享中心，选择更改适配器设置，找到USB网卡，右键属性，选择Internet协议版本4（TCP/IPv4），点击属性，选择使用以下IP地址，设置IP地址为以下内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IP地址：192.168.1.111&lt;/li&gt;
&lt;li&gt;子网掩码：255.255.225.0&lt;/li&gt;
&lt;li&gt;默认网关：192.168.1.1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;网卡的IP要和你Window主机自己的IP在一个网段，不能盲目设置为手册里的&lt;code&gt;192.168.5.10&lt;/code&gt;，这样会导致Windows主机无法访问开发板。可以通过在Windows命令行输入&lt;code&gt;ipconfig&lt;/code&gt;查看Windows主机的IP地址。一般来说，如果没有设置过路由器，默认都是在&lt;code&gt;192.168.1.x&lt;/code&gt;网段。那么网关通常就是这个网段的第一个地址，也就是&lt;code&gt;192.168.1.1&lt;/code&gt;，这也是路由器的地址。&lt;/p&gt;
&lt;p&gt;如果你配置完网卡以及开发板的IP后能正常相互ping通，那么说明网络配置成功。如果你很不巧和我一样，USB网卡一直无法通过DHCP自动获取IP地址，并且配置完静态IP后只能单侧ping通，那么也可以和我一样将USB网卡和主机的WIFI网卡桥接，这样就可以实现双向通信。这里有个前提条件就是你的主机原先就有两张网卡，一张用于连接路由器，一张是WIFI网卡。不能直接将USB网卡和连接路由器的网卡桥接。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;万万没想到一张整年闲置的WIFI网卡竟然派上了用场。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;选择WIFI网卡和USB网卡，右键桥接，这样就创建一个网桥，网桥会自动获取IP地址。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/25/07fff7094057ea29a1fd3ace9bdbb33e.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/25/07fff7094057ea29a1fd3ace9bdbb33e.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h3 id=&#34;开发板侧配置网卡&#34;&gt;开发板侧配置网卡&lt;/h3&gt;
&lt;p&gt;配置网卡IP，这个IP也是需要和Windows主机在一个网段的。因为路由器分配IP时会从小到大分配，为了避免和已有的设备IP冲突，我们就可以设置一个比较大的IP，比如112。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 配置网卡ip为192.169.1.112，子网掩码为255.255.255.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ifconfig eth0 192.168.1.112
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;设置默认网关：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ip route add default via 192.168.1.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;注意：更换网口需要重启开发板&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;Match&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;eth0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;Network&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;Address&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;192.168.1.112/24 &lt;span class=&#34;nv&#34;&gt;Gateway&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;192.168.1.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;挂载nsf共享目录&#34;&gt;挂载NSF共享目录&lt;/h2&gt;
&lt;h3 id=&#34;windows侧配置&#34;&gt;Windows侧配置&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;安装haneWIN NFS 服务器（&lt;a href=&#34;https://pan.baidu.com/s/1y2R2aJyEExnCJ7FRqdaNdA&#34;&gt;https://pan.baidu.com/s/1y2R2aJyEExnCJ7FRqdaNdA&lt;/a&gt;  提取码：esdw）&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安装路径一般为：&lt;code&gt;C:\Program Files (x86)\nfsd&lt;/code&gt;。使用VScode打开该目录下的&lt;code&gt;exports&lt;/code&gt;文件，修改内容如下，保存时需要用管理员身份保存：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;d:\nfs -public 192.168.1.112
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;d:\nfs&lt;/code&gt;：共享目录为D盘的nfs，可以根据自己的实际情况修改。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-public&lt;/code&gt;：表示共享目录为公共目录，即所有用户都可以访问。&lt;/li&gt;
&lt;li&gt;IP：为开发板的IP地址，表示要与这个IP进行共享。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;关闭防火墙或者开启必要的通信端口&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果为了省事，可以直接关闭Windows防火墙&lt;/li&gt;
&lt;li&gt;如果不想关防火墙需要开启以下端口的通信：
&lt;ul&gt;
&lt;li&gt;TCP 111&lt;/li&gt;
&lt;li&gt;UDP 111&lt;/li&gt;
&lt;li&gt;TCP 2049&lt;/li&gt;
&lt;li&gt;UDP 2049&lt;/li&gt;
&lt;li&gt;TCP 1058&lt;/li&gt;
&lt;li&gt;UDP 1058&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;开启端口的方法为：&lt;code&gt;控制面板&lt;/code&gt; -&amp;gt; &lt;code&gt;系统和安全&lt;/code&gt; -&amp;gt; &lt;code&gt;Windows Defender 防火墙&lt;/code&gt; -&amp;gt; &lt;code&gt;高级设置&lt;/code&gt; -&amp;gt; &lt;code&gt;入站规则&lt;/code&gt; -&amp;gt; &lt;code&gt;新建规则&lt;/code&gt; -&amp;gt; &lt;code&gt;端口&lt;/code&gt; -&amp;gt; &lt;code&gt;下一步&lt;/code&gt; -&amp;gt; &lt;code&gt;TCP&lt;/code&gt; -&amp;gt; &lt;code&gt;特定本地端口&lt;/code&gt; -&amp;gt; &lt;code&gt;2049&lt;/code&gt; -&amp;gt; &lt;code&gt;下一步&lt;/code&gt; -&amp;gt; &lt;code&gt;允许连接&lt;/code&gt; -&amp;gt; &lt;code&gt;下一步&lt;/code&gt; -&amp;gt; &lt;code&gt;下一步&lt;/code&gt; -&amp;gt; &lt;code&gt;规则名称&lt;/code&gt; -&amp;gt; &lt;code&gt;完成&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;需要注意在出站规则和入站规则都需要添加。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重启Windows NFS Server服务&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;右键Windows徽标键，打开计算机管理，找到服务和应用程序，点击服务，找到&lt;code&gt;NFS Server&lt;/code&gt;，右键重启。
&lt;ol&gt;
&lt;li&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/b73eea915bc0492384cdf55949db30e6.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/b73eea915bc0492384cdf55949db30e6.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;点击软件的输出选项卡，点击重启服务器，如果出现如图配置表示正确启动服务：


&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/8003d546a8f10b949a43f8856d6b07d6.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/8003d546a8f10b949a43f8856d6b07d6.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;开发板侧配置&#34;&gt;开发板侧配置&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;新建一个目录，用于挂载共享目录&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;mkdir /nfs&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;挂载共享目录&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mount -vvv  -t nfs -o nolock -o &lt;span class=&#34;nv&#34;&gt;vers&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;3,port&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2049&lt;/span&gt; 192.168.1.7:/d/nfs  /nfs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;-vvv：表示挂载时输出详细信息，可省略&lt;/li&gt;
&lt;li&gt;-t nfs：表示挂载的文件系统类型为nfs&lt;/li&gt;
&lt;li&gt;-o nolock：表示不使用锁定机制&lt;/li&gt;
&lt;li&gt;-o vers=3：表示使用NFSv3版本&lt;/li&gt;
&lt;li&gt;-o port=2049：表示NFS服务端口号&lt;/li&gt;
&lt;li&gt;192.168.1.7:/d/nfs：表示NFS服务器的IP地址和共享目录&lt;/li&gt;
&lt;li&gt;/nfs: 表示开发板上的共享目录&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;常见错误&#34;&gt;常见错误&lt;/h3&gt;
&lt;h4 id=&#34;挂载时报错portmap-query-failed-rpc-timed-out&#34;&gt;挂载时报错：portmap query failed: RPC: Timed out&lt;/h4&gt;
&lt;p&gt;点击NFS服务的端口映射页面，可以查看RPC使用的所有端口，需要将这些端口都开放，或者直接关闭防火墙。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/b73ecc06edeb5e50f42ee12768fd1f68.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/b73ecc06edeb5e50f42ee12768fd1f68.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;一些可能用到的命令：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ip route add default via 192.168.1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ip route del 0.0.0.0/0 via 192.168.1.1 dev eth0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ip route del 192.168.1.0/24 dev eth0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;vi  /etc/systemd/network/50-static.network
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;systemctl &lt;span class=&#34;nb&#34;&gt;enable&lt;/span&gt; systemd-networkd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href=&#34;https://gitcode.csdn.net/65e93d3c1a836825ed78e9ea.html?dp_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTE3MDk0NywiZXhwIjoxNzIxOTEyNTYxLCJpYXQiOjE3MjEzMDc3NjEsInVzZXJuYW1lIjoiRHVua3laIn0.7EEwv32DoA7Vdm-XwdAd3_0P8hEsWarq71nSo5HWTrA&#34;&gt;如何在Windows上搭建NFS服务器实现开发板与Windows之间的文件共享_opencv_Jack_小明-GitCode 开源社区&lt;/a&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h2 id="配置windows与开发板网络通信">配置Windows与开发板网络通信</h2>
<h3 id="配置windows侧网卡">配置Windows侧网卡</h3>
<p>将Windows与开发板通过USB网卡连接，开发板的网卡插口选择eth0，也就是靠近拨码开关的网口。开发板开机，Windows的设备管理器中出现以下网卡表示网卡功能正常。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/90a28f55cd3ca3039694cb1c16ddff3e.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/90a28f55cd3ca3039694cb1c16ddff3e.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>打开网络和共享中心，选择更改适配器设置，找到USB网卡，右键属性，选择Internet协议版本4（TCP/IPv4），点击属性，选择使用以下IP地址，设置IP地址为以下内容：</p>
<ul>
<li>IP地址：192.168.1.111</li>
<li>子网掩码：255.255.225.0</li>
<li>默认网关：192.168.1.1</li>
</ul>
<p>网卡的IP要和你Window主机自己的IP在一个网段，不能盲目设置为手册里的<code>192.168.5.10</code>，这样会导致Windows主机无法访问开发板。可以通过在Windows命令行输入<code>ipconfig</code>查看Windows主机的IP地址。一般来说，如果没有设置过路由器，默认都是在<code>192.168.1.x</code>网段。那么网关通常就是这个网段的第一个地址，也就是<code>192.168.1.1</code>，这也是路由器的地址。</p>
<p>如果你配置完网卡以及开发板的IP后能正常相互ping通，那么说明网络配置成功。如果你很不巧和我一样，USB网卡一直无法通过DHCP自动获取IP地址，并且配置完静态IP后只能单侧ping通，那么也可以和我一样将USB网卡和主机的WIFI网卡桥接，这样就可以实现双向通信。这里有个前提条件就是你的主机原先就有两张网卡，一张用于连接路由器，一张是WIFI网卡。不能直接将USB网卡和连接路由器的网卡桥接。</p>
<blockquote>
<p>万万没想到一张整年闲置的WIFI网卡竟然派上了用场。</p>
</blockquote>
<p>选择WIFI网卡和USB网卡，右键桥接，这样就创建一个网桥，网桥会自动获取IP地址。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/25/07fff7094057ea29a1fd3ace9bdbb33e.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/25/07fff7094057ea29a1fd3ace9bdbb33e.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h3 id="开发板侧配置网卡">开发板侧配置网卡</h3>
<p>配置网卡IP，这个IP也是需要和Windows主机在一个网段的。因为路由器分配IP时会从小到大分配，为了避免和已有的设备IP冲突，我们就可以设置一个比较大的IP，比如112。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 配置网卡ip为192.169.1.112，子网掩码为255.255.255.0</span>
</span></span><span class="line"><span class="cl">ifconfig eth0 192.168.1.112
</span></span></code></pre></div><p>设置默认网关：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ip route add default via 192.168.1.1
</span></span></code></pre></div><blockquote>
<p>注意：更换网口需要重启开发板</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="o">[</span>Match<span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="nv">Name</span><span class="o">=</span>eth0
</span></span><span class="line"><span class="cl"><span class="o">[</span>Network<span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="nv">Address</span><span class="o">=</span>192.168.1.112/24 <span class="nv">Gateway</span><span class="o">=</span>192.168.1.1
</span></span></code></pre></div><h2 id="挂载nsf共享目录">挂载NSF共享目录</h2>
<h3 id="windows侧配置">Windows侧配置</h3>
<ol>
<li>
<p>安装haneWIN NFS 服务器（<a href="https://pan.baidu.com/s/1y2R2aJyEExnCJ7FRqdaNdA">https://pan.baidu.com/s/1y2R2aJyEExnCJ7FRqdaNdA</a>  提取码：esdw）</p>
<ol>
<li>安装路径一般为：<code>C:\Program Files (x86)\nfsd</code>。使用VScode打开该目录下的<code>exports</code>文件，修改内容如下，保存时需要用管理员身份保存：</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">d:\nfs -public 192.168.1.112
</span></span></code></pre></div><ul>
<li><code>d:\nfs</code>：共享目录为D盘的nfs，可以根据自己的实际情况修改。</li>
<li><code>-public</code>：表示共享目录为公共目录，即所有用户都可以访问。</li>
<li>IP：为开发板的IP地址，表示要与这个IP进行共享。</li>
</ul>
</li>
<li>
<p>关闭防火墙或者开启必要的通信端口</p>
<ol>
<li>如果为了省事，可以直接关闭Windows防火墙</li>
<li>如果不想关防火墙需要开启以下端口的通信：
<ul>
<li>TCP 111</li>
<li>UDP 111</li>
<li>TCP 2049</li>
<li>UDP 2049</li>
<li>TCP 1058</li>
<li>UDP 1058</li>
</ul>
</li>
<li>开启端口的方法为：<code>控制面板</code> -&gt; <code>系统和安全</code> -&gt; <code>Windows Defender 防火墙</code> -&gt; <code>高级设置</code> -&gt; <code>入站规则</code> -&gt; <code>新建规则</code> -&gt; <code>端口</code> -&gt; <code>下一步</code> -&gt; <code>TCP</code> -&gt; <code>特定本地端口</code> -&gt; <code>2049</code> -&gt; <code>下一步</code> -&gt; <code>允许连接</code> -&gt; <code>下一步</code> -&gt; <code>下一步</code> -&gt; <code>规则名称</code> -&gt; <code>完成</code>。</li>
<li>需要注意在出站规则和入站规则都需要添加。</li>
</ol>
</li>
<li>
<p>重启Windows NFS Server服务</p>
<ol>
<li>右键Windows徽标键，打开计算机管理，找到服务和应用程序，点击服务，找到<code>NFS Server</code>，右键重启。
<ol>
<li>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/b73eea915bc0492384cdf55949db30e6.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/b73eea915bc0492384cdf55949db30e6.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></li>
</ol>
</li>
<li>点击软件的输出选项卡，点击重启服务器，如果出现如图配置表示正确启动服务：


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/8003d546a8f10b949a43f8856d6b07d6.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/8003d546a8f10b949a43f8856d6b07d6.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></li>
</ol>
</li>
</ol>
<h3 id="开发板侧配置">开发板侧配置</h3>
<ol>
<li>
<p>新建一个目录，用于挂载共享目录</p>
<ol>
<li><code>mkdir /nfs</code></li>
</ol>
</li>
<li>
<p>挂载共享目录</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mount -vvv  -t nfs -o nolock -o <span class="nv">vers</span><span class="o">=</span>3,port<span class="o">=</span><span class="m">2049</span> 192.168.1.7:/d/nfs  /nfs
</span></span></code></pre></div><ul>
<li>-vvv：表示挂载时输出详细信息，可省略</li>
<li>-t nfs：表示挂载的文件系统类型为nfs</li>
<li>-o nolock：表示不使用锁定机制</li>
<li>-o vers=3：表示使用NFSv3版本</li>
<li>-o port=2049：表示NFS服务端口号</li>
<li>192.168.1.7:/d/nfs：表示NFS服务器的IP地址和共享目录</li>
<li>/nfs: 表示开发板上的共享目录</li>
</ul>
</li>
</ol>
<h3 id="常见错误">常见错误</h3>
<h4 id="挂载时报错portmap-query-failed-rpc-timed-out">挂载时报错：portmap query failed: RPC: Timed out</h4>
<p>点击NFS服务的端口映射页面，可以查看RPC使用的所有端口，需要将这些端口都开放，或者直接关闭防火墙。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/b73ecc06edeb5e50f42ee12768fd1f68.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2024/07/21/b73ecc06edeb5e50f42ee12768fd1f68.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>一些可能用到的命令：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ip route add default via 192.168.1.1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ip route del 0.0.0.0/0 via 192.168.1.1 dev eth0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ip route del 192.168.1.0/24 dev eth0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">vi  /etc/systemd/network/50-static.network
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">systemctl <span class="nb">enable</span> systemd-networkd
</span></span></code></pre></div><p><a href="https://gitcode.csdn.net/65e93d3c1a836825ed78e9ea.html?dp_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTE3MDk0NywiZXhwIjoxNzIxOTEyNTYxLCJpYXQiOjE3MjEzMDc3NjEsInVzZXJuYW1lIjoiRHVua3laIn0.7EEwv32DoA7Vdm-XwdAd3_0P8hEsWarq71nSo5HWTrA">如何在Windows上搭建NFS服务器实现开发板与Windows之间的文件共享_opencv_Jack_小明-GitCode 开源社区</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>BusyBox 构建并启动 RISC-V Linux 内核</title>
      <link>https://lifeislife.cn/posts/busybox-%E6%9E%84%E5%BB%BA%E5%B9%B6%E5%90%AF%E5%8A%A8-risc-v-linux-%E5%86%85%E6%A0%B8/</link>
      <pubDate>Thu, 20 Jun 2024 21:10:49 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/busybox-%E6%9E%84%E5%BB%BA%E5%B9%B6%E5%90%AF%E5%8A%A8-risc-v-linux-%E5%86%85%E6%A0%B8/</guid>
      <description>&lt;h1 id=&#34;根文件系统&#34;&gt;根文件系统&lt;/h1&gt;
&lt;h2 id=&#34;文件系统与根文件系统&#34;&gt;文件系统与根文件系统&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;文件系统&lt;/strong&gt;（File System）是操作系统用于管理和存储数据的一种方式。它定义了如何在存储设备（如硬盘、SSD、USB 驱动器等）上组织文件和目录，以及如何进行数据的读写操作。&lt;/p&gt;
&lt;p&gt;常见的文件系统类型有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ext4：Linux 最常用的文件系统，支持大文件和大分区。&lt;/li&gt;
&lt;li&gt;NTFS：Windows 操作系统常用的文件系统，支持文件加密和权限控制。&lt;/li&gt;
&lt;li&gt;FAT32：一种兼容性广泛的文件系统，常用于 USB 驱动器和内存卡。&lt;/li&gt;
&lt;li&gt;XFS：适用于高性能和高容量存储需求的文件系统。&lt;/li&gt;
&lt;li&gt;btrfs：一种现代 Linux 文件系统，支持快照、压缩和多设备管理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;根文件系统&lt;/strong&gt;（Root File System，通常简称为 rootfs）是文件系统层次结构中的顶级文件系统。它包含了操作系统启动和运行所需的所有基本文件和目录。根文件系统是整个文件系统层次的起点，在 Linux 中由单个斜杠（&lt;code&gt;/&lt;/code&gt;）表示。&lt;strong&gt;根文件系统首先是内核启动时所 mount 的第一个文件系统&lt;/strong&gt;，内核代码映像文件保存在根文件系统中，而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;根文件系统是建立在文件系统之上的。根文件系统使用某种具体的文件系统类型（如 ext4）来管理和存储其内容。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;基于内存的文件系统&#34;&gt;基于内存的文件系统&lt;/h2&gt;
&lt;p&gt;ramdisk 是一种基于内存的文件系统，它将内存的一部分用作硬盘驱动器，这样就可以在内存中创建一个文件系统。ramdisk 是一个虚拟磁盘，它的大小和硬盘驱动器的大小一样。ramdisk 的优点是速度快，缺点是断电后数据丢失。&lt;/p&gt;
&lt;h2 id=&#34;根文件系统中各种配置文件的作用以及配置文件的格式介绍&#34;&gt;根文件系统中各种配置文件的作用以及配置文件的格式介绍&lt;/h2&gt;
&lt;h3 id=&#34;etcinittab&#34;&gt;/etc/inittab&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/etc/inittab&lt;/code&gt; 是 Linux 系统中的一个配置文件，它是 init 程序的配置文件，用于配置系统的运行级别和 init 程序的行为。在 Linux 系统中，init 程序是系统的第一个进程，它负责启动系统中的所有其他进程。&lt;code&gt;/etc/inittab&lt;/code&gt; 文件中的每一行都是一个配置项，每个配置项由四个字段组成，字段之间用空格或制表符分隔。&lt;code&gt;/etc/inittab&lt;/code&gt; 文件的格式如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;id:runlevels:action:process
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每个字段用冒号分隔，可以缺省。各字段的含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;id：配置项的标识符，用于标识配置项。&lt;/li&gt;
&lt;li&gt;runlevels：配置项所对应的运行级别，可以是一个或多个运行级别的组合。&lt;/li&gt;
&lt;li&gt;action：配置项的动作，可以是以下几种动作之一：
&lt;ul&gt;
&lt;li&gt;sysinit：系统初始化时运行。&lt;/li&gt;
&lt;li&gt;respawn：如果进程终止，立即重新启动。&lt;/li&gt;
&lt;li&gt;askfirst：在运行 process 之前询问用户。并在控制台上显示 Please press Enter to active this console。&lt;/li&gt;
&lt;li&gt;wait：等待进程终止，然后继续执行下一个配置项。&lt;/li&gt;
&lt;li&gt;once：只运行一次，进程终止后不会重新启动。&lt;/li&gt;
&lt;li&gt;boot：在系统引导时运行。&lt;/li&gt;
&lt;li&gt;bootwait：在系统引导时运行，等待进程终止后继续引导。&lt;/li&gt;
&lt;li&gt;initdefault：设置默认运行级别。&lt;/li&gt;
&lt;li&gt;shutdown：在系统关机时运行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;process：要执行的进程或脚本的路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /etc/inittab&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#系统开机或重新启动，执行rcS文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;::sysinit:/etc/init.d/rcS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#系统启动后，运行登录程序  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;::askfirst:-/bin/login
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#按下组合键“ctrl+alt+del”，重启Linux系统&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;::ctrlaltdel:-/sbin/reboot   
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#系统关机时，卸载所有文件系统&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;::shutdown:/bin/umount -a -r
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#重启init进程&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;::restart:/sbin/init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;etcfstab&#34;&gt;/etc/fstab&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/etc/fstab&lt;/code&gt; 是 Linux 系统中的一个配置文件，用于配置文件系统的挂载信息。在 Linux 系统中，文件系统是通过挂载的方式来访问的，&lt;code&gt;/etc/fstab&lt;/code&gt; 文件中记录了系统中所有文件系统的挂载信息。&lt;code&gt;/etc/fstab&lt;/code&gt; 文件的格式如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;device mount_point fs_type options dump pass
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;各字段的含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;device：设备文件或 UUID。&lt;/li&gt;
&lt;li&gt;mount_point：挂载点。&lt;/li&gt;
&lt;li&gt;fs_type：文件系统类型。&lt;/li&gt;
&lt;li&gt;options：挂载选项。&lt;/li&gt;
&lt;li&gt;dump：备份标志，用于备份工具。&lt;/li&gt;
&lt;li&gt;pass：文件系统检查顺序。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /etc/fstab&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# &amp;lt;文件系统&amp;gt;  &amp;lt;挂载点&amp;gt;  &amp;lt;类型&amp;gt;  &amp;lt;选项&amp;gt;        &amp;lt;dump&amp;gt;  &amp;lt;fsck&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 根文件系统&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/dev/sda1       /           ext4    defaults        &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# 根分区，使用 ext4 文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /boot 分区，用于存放引导加载程序和内核镜像&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/dev/sda2       /boot       ext4    defaults        &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# /boot 分区，使用 ext4 文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 交换分区，用作虚拟内存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/dev/sda3       none        swap    sw              &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# 交换分区，不需要挂载点，使用 swap 类型&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /home 分区，用于存放用户的个人数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/dev/sda4       /home       ext4    defaults        &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# /home 分区，使用 ext4 文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /tmp 分区，用于存放临时文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/dev/sda5       /tmp        ext4    defaults        &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# /tmp 分区，使用 ext4 文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /var 分区，用于存放可变数据文件，如日志、邮件、缓存等&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/dev/sda6       /var        ext4    defaults        &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# /var 分区，使用 ext4 文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /mnt/data 分区，用于存放额外的数据文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/dev/sda7       /mnt/data   ext4    defaults        &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# /mnt/data 分区，使用 ext4 文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 挂载 proc 文件系统，用于访问进程和系统信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;proc            /proc       proc    defaults        &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# proc 虚拟文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 挂载 sysfs 文件系统，用于访问设备和内核信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sysfs           /sys        sysfs   defaults        &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# sysfs 虚拟文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 挂载 tmpfs 文件系统，用于临时文件存储&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tmpfs           /dev/shm    tmpfs   defaults        &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# tmpfs 虚拟文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 挂载 devpts 文件系统，用于伪终端设备&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;devpts          /dev/pts    devpts  &lt;span class=&#34;nv&#34;&gt;gid&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;5,mode&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;620&lt;/span&gt;  &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;       &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# devpts 虚拟文件系统，默认挂载选项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;etcinitdrcs&#34;&gt;/etc/init.d/rcS&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/etc/init.d/rcS&lt;/code&gt; 是 Linux 系统中的一个初始化脚本，用于系统初始化时运行。在 Linux 系统中，&lt;code&gt;/etc/init.d/rcS&lt;/code&gt; 脚本是系统初始化时运行的第一个脚本，它负责初始化系统中的各种服务和配置。&lt;code&gt;/etc/init.d/rcS&lt;/code&gt; 脚本通常包含了一些初始化命令，如加载模块、挂载文件系统、启动服务等。&lt;code&gt;/etc/init.d/rcS&lt;/code&gt; 脚本的格式如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 这行是 shebang，用于指定这个脚本将由 /bin/sh 解释执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 挂载 proc 文件系统到 /proc 目录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mount -t proc none /proc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# proc 文件系统是一个虚拟文件系统，用于提供内核和进程的信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 挂载 sysfs 文件系统到 /sys 目录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mount -t sysfs none /sys
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# sysfs 文件系统是一个虚拟文件系统，用于提供设备和内核信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 挂载 devtmpfs 文件系统到 /dev 目录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mount -t devtmpfs none /dev
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# devtmpfs 是一个用于设备节点的临时文件系统&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 运行 mdev 以创建设备节点&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/sbin/mdev -s
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# mdev 是一个轻量级的设备管理工具，用于创建和管理 /dev 目录中的设备节点&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 设置主机名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;myhostname&amp;#34;&lt;/span&gt; &amp;gt; /proc/sys/kernel/hostname
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 将主机名设置为 &amp;#34;myhostname&amp;#34;，这个名字可以根据需要更改&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 启动网络接口配置脚本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ifconfig lo up
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 启动回环接口 lo，这是一个特殊的网络接口，用于网络软件本地通信&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 运行所有位于 /etc/init.d/ 目录中的初始化脚本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; script in /etc/init.d/S*&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; -x &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$script&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$script&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; start
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 遍历 /etc/init.d/ 目录中的所有以 S 开头的脚本，并执行它们以启动相应的服务&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# -x 选项用于检查文件是否可执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 挂载用户文件系统（可选，根据实际需要）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mount -a
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 挂载 /etc/fstab 文件中列出的所有文件系统&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 打印启动完成信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;System initialization complete.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 输出系统初始化完成信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;etcprofile&#34;&gt;/etc/profile&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/etc/profile&lt;/code&gt; 是 Linux 系统中的一个全局配置文件，用于配置系统的环境变量和用户的 shell 环境。在 Linux 系统中，&lt;code&gt;/etc/profile&lt;/code&gt; 文件是系统启动时加载的第一个配置文件，它包含了系统的全局环境变量和用户的 shell 环境配置。&lt;code&gt;/etc/profile&lt;/code&gt; 文件的格式如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /etc/profile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#!/bin/sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 这行是 shebang，用于指定这个脚本将由 /bin/sh 解释执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 设置 PATH 环境变量，定义系统命令的搜索路径&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; PATH
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /usr/local/sbin:/usr/local/bin 是系统管理员和用户安装的程序路径&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /sbin:/bin:/usr/sbin:/usr/bin 是系统默认的命令路径&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 设置用户 umask，定义新文件和目录的默认权限掩码&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;umask&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;022&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# umask 022 表示新文件的默认权限是 755，新目录的默认权限是 644&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 设置系统语言和区域&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;LANG&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;en_US.UTF-8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; LANG
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# LANG 变量定义了系统的语言环境，这里设置为美国英语 UTF-8 编码&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 设置历史记录文件和大小&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;HISTSIZE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;HISTFILESIZE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; HISTSIZE HISTFILESIZE
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# HISTSIZE 定义了 shell 会话历史记录的条目数，HISTFILESIZE 定义了历史记录文件的最大条目数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 系统启动信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Welcome to your Linux system!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 在用户登录时显示欢迎信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 读取并执行 /etc/bashrc（如果存在）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; -f /etc/bashrc &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    . /etc/bashrc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /etc/bashrc 是另一个全局配置文件，通常包含 bash shell 的配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 使用 . /etc/bashrc 命令将其内容导入当前 shell 环境&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 配置别名（示例）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;alias&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;ll&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ls -l&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;alias&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;la&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ls -A&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;alias&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ls -CF&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 定义一些常用命令的别名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ll 代表 ls -l，显示详细列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# la 代表 ls -A，显示所有文件包括隐藏文件（但不包括 . 和 ..）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# l 代表 ls -CF，以分类格式显示文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 读取用户特定的配置文件（如果存在）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; -f &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.profile&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    . &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;/.profile&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# .profile 是用户的个人配置文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 使用 . $HOME/.profile 命令将其内容导入当前 shell 环境&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 设置编辑器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;EDITOR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;vim
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; EDITOR
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 定义默认的文本编辑器为 vim&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 设置主机名显示，也就是终端最前面提示符的样式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;PS1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\u@\h:\w\$ &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; PS1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# PS1 定义了 shell 提示符的样式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# \u 代表用户名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# \h 代表主机名&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# \w 代表当前工作目录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;busybox-简介以及如何制作-busybox-文件系统&#34;&gt;Busybox 简介以及如何制作 Busybox 文件系统&lt;/h2&gt;
&lt;h3 id=&#34;准备-qemu&#34;&gt;准备 QEMU&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-Bash&#34; data-lang=&#34;Bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; mkdir -p /home/user/program/riscv64-qemu
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; workspace
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone https://github.com/qemu/qemu.git
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; qemu
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; qemu &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./configure --target-list&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv32-softmmu,riscv32-linux-user,riscv64-linux-user,riscv64-softmmu &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;               --enable-kvm --enable-sdl &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;               --prefix&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/home/user/program/riscv64-qemu
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;make install -j &lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;nproc&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;准备-opensbi&#34;&gt;准备 OpenSBI&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-Bash&#34; data-lang=&#34;Bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; workspace
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone https://github.com/riscv-software-src/opensbi.git
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; opensbi
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;make &lt;span class=&#34;nv&#34;&gt;ARCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv &lt;span class=&#34;nv&#34;&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv64-unknown-linux-gnu- &lt;span class=&#34;nv&#34;&gt;PLATFORM&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;generic
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;准备-linux-内核&#34;&gt;准备 Linux 内核&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-Bash&#34; data-lang=&#34;Bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; workspace
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone https://github.com/torvalds/linux.git
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;make &lt;span class=&#34;nv&#34;&gt;ARCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv &lt;span class=&#34;nv&#34;&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv64-unknown-linux-gnu- defconfig
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;make &lt;span class=&#34;nv&#34;&gt;ARCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv &lt;span class=&#34;nv&#34;&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv64-unknown-linux-gnu- -j &lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;nproc&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;准备-busybox&#34;&gt;准备 Busybox&lt;/h3&gt;
&lt;p&gt;进入下载软件包，&lt;a href=&#34;https://busybox.net/downloads/&#34;&gt;https://busybox.net/downloads/&lt;/a&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tar -xvf busybox-1.36.1.tar.bz2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mv busybox-1.36.1 busybox
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; busybox
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置编译选项&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;make &lt;span class=&#34;nv&#34;&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv64-unknown-linux-gnu- menuconfig
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;选择静态库模式，设置路径：Settings -&amp;gt; Build static binary (no shared libs)&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-51-50-e83454415403aacffad1e9320676356e-20240607105149-9cc407.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-51-50-e83454415403aacffad1e9320676356e-20240607105149-9cc407.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-52-13-e92a2a3f782236215a33b93161226d99-20240607105212-428b5c.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-52-13-e92a2a3f782236215a33b93161226d99-20240607105212-428b5c.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;编译 Busybox&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;make &lt;span class=&#34;nv&#34;&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv64-unknown-linux-gnu- -j &lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;nproc&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;如果需要频繁编译，也可以在 Menuconfig 中设置交叉编译器路径，这样就不需要每次都指定了。设置路径为：Settings -&amp;gt; Build Options -&amp;gt; Cross Compiler prefix。将其设置为 riscv64-unknown-linux-gnu-。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;安装 Busybox：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;make &lt;span class=&#34;nv&#34;&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;riscv64-unknown-linux-gnu- install
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;Busybox 默认安装在当前目录的_install 目录下，也可以在 Menuconfig 中设置安装目录，路径为：Settings -&amp;gt; Build Options -&amp;gt; Destination path for &amp;lsquo;make install&amp;rsquo;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;查看安装目录&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ls _install 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;bin  linuxrc  sbin  usr
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;制作根文件系统&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;qemu-img create -f raw rootfs.img 256M
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkfs.ext4 rootfs.img
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir rootfs
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo mount -o loop rootfs.img rootfs
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo cp -r _install/* rootfs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; rootfs
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo mkdir proc sys dev etc etc/init.d
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; etc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; init.d
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo touch rcS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo chmod +x rcS
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo vim rcS
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;写入以下内容：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/sh  
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mount -t proc none /proc  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mount -t sysfs none /sys  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/sbin/mdev -s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo umount rootfs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;qemu 启动内核：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; -en &lt;span class=&#34;s1&#34;&gt;&amp;#39;\001\033[0m\002&amp;#39;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; -en &lt;span class=&#34;s1&#34;&gt;&amp;#39;\001\033[00;33m\002&amp;#39;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;## Configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;vcpu&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;memory&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;drive&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/home/user/workspace/rootfs.img&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;kernel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34; /home/user/workspace/linux-stable/arch/riscv/boot/Image&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;fw&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/home/user/workspace/opensbi/build/platform/generic/firmware/fw_jump.bin&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;ssh_port&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;12070&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;cmd&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/home/user/program/riscv64-qemu/bin/qemu-system-riscv64 \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  -nographic -machine virt \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  -smp &amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$vcpu&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34; -m &amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$memory&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;G \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  -cpu rv64\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  -bios &amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$fw&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  -kernel &amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$kernel&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  -drive file=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$drive&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;,format=raw,id=hd0 \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  -device virtio-blk-device,drive=hd0 \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  -append &amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;root&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/dev/vda &lt;span class=&#34;nv&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;ttyS0&lt;span class=&#34;s2&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;:: Starting VM...&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;:: Using following configuration&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;vCPU Cores: &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$vcpu&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;Memory: &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$memory&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;G&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;Disk: &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$drive&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;SSH Port: &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ssh_port&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;:: NOTE: Make sure ONLY ONE .qcow2 file is&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;in the current directory&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;:: Tip: Try setting DNS manually &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; QEMU user network doesn&lt;span class=&#34;se&#34;&gt;\&amp;#39;&lt;/span&gt;t work well. &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;YELLOW&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;:: HOWTO -&lt;span class=&#34;se&#34;&gt;\&amp;gt;&lt;/span&gt; https://serverfault.com/a/810639 &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;RESTORE&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sleep &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;eval&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$cmd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/17-24-09-3656ec35f835ab8650a4628ceac4dad8-20240618172408-48a595.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/17-24-09-3656ec35f835ab8650a4628ceac4dad8-20240618172408-48a595.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h1 id="根文件系统">根文件系统</h1>
<h2 id="文件系统与根文件系统">文件系统与根文件系统</h2>
<p><strong>文件系统</strong>（File System）是操作系统用于管理和存储数据的一种方式。它定义了如何在存储设备（如硬盘、SSD、USB 驱动器等）上组织文件和目录，以及如何进行数据的读写操作。</p>
<p>常见的文件系统类型有：</p>
<ul>
<li>ext4：Linux 最常用的文件系统，支持大文件和大分区。</li>
<li>NTFS：Windows 操作系统常用的文件系统，支持文件加密和权限控制。</li>
<li>FAT32：一种兼容性广泛的文件系统，常用于 USB 驱动器和内存卡。</li>
<li>XFS：适用于高性能和高容量存储需求的文件系统。</li>
<li>btrfs：一种现代 Linux 文件系统，支持快照、压缩和多设备管理。</li>
</ul>
<p><strong>根文件系统</strong>（Root File System，通常简称为 rootfs）是文件系统层次结构中的顶级文件系统。它包含了操作系统启动和运行所需的所有基本文件和目录。根文件系统是整个文件系统层次的起点，在 Linux 中由单个斜杠（<code>/</code>）表示。<strong>根文件系统首先是内核启动时所 mount 的第一个文件系统</strong>，内核代码映像文件保存在根文件系统中，而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。</p>
<p><strong>根文件系统是建立在文件系统之上的。根文件系统使用某种具体的文件系统类型（如 ext4）来管理和存储其内容。</strong></p>
<h2 id="基于内存的文件系统">基于内存的文件系统</h2>
<p>ramdisk 是一种基于内存的文件系统，它将内存的一部分用作硬盘驱动器，这样就可以在内存中创建一个文件系统。ramdisk 是一个虚拟磁盘，它的大小和硬盘驱动器的大小一样。ramdisk 的优点是速度快，缺点是断电后数据丢失。</p>
<h2 id="根文件系统中各种配置文件的作用以及配置文件的格式介绍">根文件系统中各种配置文件的作用以及配置文件的格式介绍</h2>
<h3 id="etcinittab">/etc/inittab</h3>
<p><code>/etc/inittab</code> 是 Linux 系统中的一个配置文件，它是 init 程序的配置文件，用于配置系统的运行级别和 init 程序的行为。在 Linux 系统中，init 程序是系统的第一个进程，它负责启动系统中的所有其他进程。<code>/etc/inittab</code> 文件中的每一行都是一个配置项，每个配置项由四个字段组成，字段之间用空格或制表符分隔。<code>/etc/inittab</code> 文件的格式如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">id:runlevels:action:process
</span></span></code></pre></div><p>每个字段用冒号分隔，可以缺省。各字段的含义如下：</p>
<ul>
<li>id：配置项的标识符，用于标识配置项。</li>
<li>runlevels：配置项所对应的运行级别，可以是一个或多个运行级别的组合。</li>
<li>action：配置项的动作，可以是以下几种动作之一：
<ul>
<li>sysinit：系统初始化时运行。</li>
<li>respawn：如果进程终止，立即重新启动。</li>
<li>askfirst：在运行 process 之前询问用户。并在控制台上显示 Please press Enter to active this console。</li>
<li>wait：等待进程终止，然后继续执行下一个配置项。</li>
<li>once：只运行一次，进程终止后不会重新启动。</li>
<li>boot：在系统引导时运行。</li>
<li>bootwait：在系统引导时运行，等待进程终止后继续引导。</li>
<li>initdefault：设置默认运行级别。</li>
<li>shutdown：在系统关机时运行。</li>
</ul>
</li>
<li>process：要执行的进程或脚本的路径。</li>
</ul>
<p>示例：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># /etc/inittab</span>
</span></span><span class="line"><span class="cl"><span class="c1">#系统开机或重新启动，执行rcS文件</span>
</span></span><span class="line"><span class="cl">::sysinit:/etc/init.d/rcS
</span></span><span class="line"><span class="cl"><span class="c1">#系统启动后，运行登录程序  </span>
</span></span><span class="line"><span class="cl">::askfirst:-/bin/login
</span></span><span class="line"><span class="cl"><span class="c1">#按下组合键“ctrl+alt+del”，重启Linux系统</span>
</span></span><span class="line"><span class="cl">::ctrlaltdel:-/sbin/reboot   
</span></span><span class="line"><span class="cl"><span class="c1">#系统关机时，卸载所有文件系统</span>
</span></span><span class="line"><span class="cl">::shutdown:/bin/umount -a -r
</span></span><span class="line"><span class="cl"><span class="c1">#重启init进程</span>
</span></span><span class="line"><span class="cl">::restart:/sbin/init
</span></span></code></pre></div><h3 id="etcfstab">/etc/fstab</h3>
<p><code>/etc/fstab</code> 是 Linux 系统中的一个配置文件，用于配置文件系统的挂载信息。在 Linux 系统中，文件系统是通过挂载的方式来访问的，<code>/etc/fstab</code> 文件中记录了系统中所有文件系统的挂载信息。<code>/etc/fstab</code> 文件的格式如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">device mount_point fs_type options dump pass
</span></span></code></pre></div><p>各字段的含义如下：</p>
<ul>
<li>device：设备文件或 UUID。</li>
<li>mount_point：挂载点。</li>
<li>fs_type：文件系统类型。</li>
<li>options：挂载选项。</li>
<li>dump：备份标志，用于备份工具。</li>
<li>pass：文件系统检查顺序。</li>
</ul>
<p>示例：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># /etc/fstab</span>
</span></span><span class="line"><span class="cl"><span class="c1"># &lt;文件系统&gt;  &lt;挂载点&gt;  &lt;类型&gt;  &lt;选项&gt;        &lt;dump&gt;  &lt;fsck&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 根文件系统</span>
</span></span><span class="line"><span class="cl">/dev/sda1       /           ext4    defaults        <span class="m">1</span>       <span class="m">1</span>  <span class="c1"># 根分区，使用 ext4 文件系统，默认挂载选项</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># /boot 分区，用于存放引导加载程序和内核镜像</span>
</span></span><span class="line"><span class="cl">/dev/sda2       /boot       ext4    defaults        <span class="m">1</span>       <span class="m">2</span>  <span class="c1"># /boot 分区，使用 ext4 文件系统，默认挂载选项</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 交换分区，用作虚拟内存</span>
</span></span><span class="line"><span class="cl">/dev/sda3       none        swap    sw              <span class="m">0</span>       <span class="m">0</span>  <span class="c1"># 交换分区，不需要挂载点，使用 swap 类型</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># /home 分区，用于存放用户的个人数据</span>
</span></span><span class="line"><span class="cl">/dev/sda4       /home       ext4    defaults        <span class="m">1</span>       <span class="m">2</span>  <span class="c1"># /home 分区，使用 ext4 文件系统，默认挂载选项</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># /tmp 分区，用于存放临时文件</span>
</span></span><span class="line"><span class="cl">/dev/sda5       /tmp        ext4    defaults        <span class="m">1</span>       <span class="m">2</span>  <span class="c1"># /tmp 分区，使用 ext4 文件系统，默认挂载选项</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># /var 分区，用于存放可变数据文件，如日志、邮件、缓存等</span>
</span></span><span class="line"><span class="cl">/dev/sda6       /var        ext4    defaults        <span class="m">1</span>       <span class="m">2</span>  <span class="c1"># /var 分区，使用 ext4 文件系统，默认挂载选项</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># /mnt/data 分区，用于存放额外的数据文件</span>
</span></span><span class="line"><span class="cl">/dev/sda7       /mnt/data   ext4    defaults        <span class="m">1</span>       <span class="m">2</span>  <span class="c1"># /mnt/data 分区，使用 ext4 文件系统，默认挂载选项</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 挂载 proc 文件系统，用于访问进程和系统信息</span>
</span></span><span class="line"><span class="cl">proc            /proc       proc    defaults        <span class="m">0</span>       <span class="m">0</span>  <span class="c1"># proc 虚拟文件系统，默认挂载选项</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 挂载 sysfs 文件系统，用于访问设备和内核信息</span>
</span></span><span class="line"><span class="cl">sysfs           /sys        sysfs   defaults        <span class="m">0</span>       <span class="m">0</span>  <span class="c1"># sysfs 虚拟文件系统，默认挂载选项</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 挂载 tmpfs 文件系统，用于临时文件存储</span>
</span></span><span class="line"><span class="cl">tmpfs           /dev/shm    tmpfs   defaults        <span class="m">0</span>       <span class="m">0</span>  <span class="c1"># tmpfs 虚拟文件系统，默认挂载选项</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 挂载 devpts 文件系统，用于伪终端设备</span>
</span></span><span class="line"><span class="cl">devpts          /dev/pts    devpts  <span class="nv">gid</span><span class="o">=</span>5,mode<span class="o">=</span><span class="m">620</span>  <span class="m">0</span>       <span class="m">0</span>  <span class="c1"># devpts 虚拟文件系统，默认挂载选项</span>
</span></span></code></pre></div><h3 id="etcinitdrcs">/etc/init.d/rcS</h3>
<p><code>/etc/init.d/rcS</code> 是 Linux 系统中的一个初始化脚本，用于系统初始化时运行。在 Linux 系统中，<code>/etc/init.d/rcS</code> 脚本是系统初始化时运行的第一个脚本，它负责初始化系统中的各种服务和配置。<code>/etc/init.d/rcS</code> 脚本通常包含了一些初始化命令，如加载模块、挂载文件系统、启动服务等。<code>/etc/init.d/rcS</code> 脚本的格式如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="c1"># 这行是 shebang，用于指定这个脚本将由 /bin/sh 解释执行</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 挂载 proc 文件系统到 /proc 目录</span>
</span></span><span class="line"><span class="cl">mount -t proc none /proc
</span></span><span class="line"><span class="cl"><span class="c1"># proc 文件系统是一个虚拟文件系统，用于提供内核和进程的信息</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 挂载 sysfs 文件系统到 /sys 目录</span>
</span></span><span class="line"><span class="cl">mount -t sysfs none /sys
</span></span><span class="line"><span class="cl"><span class="c1"># sysfs 文件系统是一个虚拟文件系统，用于提供设备和内核信息</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 挂载 devtmpfs 文件系统到 /dev 目录</span>
</span></span><span class="line"><span class="cl">mount -t devtmpfs none /dev
</span></span><span class="line"><span class="cl"><span class="c1"># devtmpfs 是一个用于设备节点的临时文件系统</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 运行 mdev 以创建设备节点</span>
</span></span><span class="line"><span class="cl">/sbin/mdev -s
</span></span><span class="line"><span class="cl"><span class="c1"># mdev 是一个轻量级的设备管理工具，用于创建和管理 /dev 目录中的设备节点</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 设置主机名</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;myhostname&#34;</span> &gt; /proc/sys/kernel/hostname
</span></span><span class="line"><span class="cl"><span class="c1"># 将主机名设置为 &#34;myhostname&#34;，这个名字可以根据需要更改</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 启动网络接口配置脚本</span>
</span></span><span class="line"><span class="cl">ifconfig lo up
</span></span><span class="line"><span class="cl"><span class="c1"># 启动回环接口 lo，这是一个特殊的网络接口，用于网络软件本地通信</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 运行所有位于 /etc/init.d/ 目录中的初始化脚本</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> script in /etc/init.d/S*<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[</span> -x <span class="s2">&#34;</span><span class="nv">$script</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;</span><span class="nv">$script</span><span class="s2">&#34;</span> start
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 遍历 /etc/init.d/ 目录中的所有以 S 开头的脚本，并执行它们以启动相应的服务</span>
</span></span><span class="line"><span class="cl"><span class="c1"># -x 选项用于检查文件是否可执行</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 挂载用户文件系统（可选，根据实际需要）</span>
</span></span><span class="line"><span class="cl">mount -a
</span></span><span class="line"><span class="cl"><span class="c1"># 挂载 /etc/fstab 文件中列出的所有文件系统</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 打印启动完成信息</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;System initialization complete.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 输出系统初始化完成信息</span>
</span></span></code></pre></div><h3 id="etcprofile">/etc/profile</h3>
<p><code>/etc/profile</code> 是 Linux 系统中的一个全局配置文件，用于配置系统的环境变量和用户的 shell 环境。在 Linux 系统中，<code>/etc/profile</code> 文件是系统启动时加载的第一个配置文件，它包含了系统的全局环境变量和用户的 shell 环境配置。<code>/etc/profile</code> 文件的格式如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># /etc/profile</span>
</span></span><span class="line"><span class="cl"><span class="c1">#!/bin/sh</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 这行是 shebang，用于指定这个脚本将由 /bin/sh 解释执行</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 设置 PATH 环境变量，定义系统命令的搜索路径</span>
</span></span><span class="line"><span class="cl"><span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> PATH
</span></span><span class="line"><span class="cl"><span class="c1"># /usr/local/sbin:/usr/local/bin 是系统管理员和用户安装的程序路径</span>
</span></span><span class="line"><span class="cl"><span class="c1"># /sbin:/bin:/usr/sbin:/usr/bin 是系统默认的命令路径</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 设置用户 umask，定义新文件和目录的默认权限掩码</span>
</span></span><span class="line"><span class="cl"><span class="nb">umask</span> <span class="m">022</span>
</span></span><span class="line"><span class="cl"><span class="c1"># umask 022 表示新文件的默认权限是 755，新目录的默认权限是 644</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 设置系统语言和区域</span>
</span></span><span class="line"><span class="cl"><span class="nv">LANG</span><span class="o">=</span><span class="s2">&#34;en_US.UTF-8&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> LANG
</span></span><span class="line"><span class="cl"><span class="c1"># LANG 变量定义了系统的语言环境，这里设置为美国英语 UTF-8 编码</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 设置历史记录文件和大小</span>
</span></span><span class="line"><span class="cl"><span class="nv">HISTSIZE</span><span class="o">=</span><span class="m">1000</span>
</span></span><span class="line"><span class="cl"><span class="nv">HISTFILESIZE</span><span class="o">=</span><span class="m">2000</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> HISTSIZE HISTFILESIZE
</span></span><span class="line"><span class="cl"><span class="c1"># HISTSIZE 定义了 shell 会话历史记录的条目数，HISTFILESIZE 定义了历史记录文件的最大条目数</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 系统启动信息</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Welcome to your Linux system!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 在用户登录时显示欢迎信息</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 读取并执行 /etc/bashrc（如果存在）</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f /etc/bashrc <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    . /etc/bashrc
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="c1"># /etc/bashrc 是另一个全局配置文件，通常包含 bash shell 的配置</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 使用 . /etc/bashrc 命令将其内容导入当前 shell 环境</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 配置别名（示例）</span>
</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">ll</span><span class="o">=</span><span class="s1">&#39;ls -l&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">la</span><span class="o">=</span><span class="s1">&#39;ls -A&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">l</span><span class="o">=</span><span class="s1">&#39;ls -CF&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 定义一些常用命令的别名</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ll 代表 ls -l，显示详细列表</span>
</span></span><span class="line"><span class="cl"><span class="c1"># la 代表 ls -A，显示所有文件包括隐藏文件（但不包括 . 和 ..）</span>
</span></span><span class="line"><span class="cl"><span class="c1"># l 代表 ls -CF，以分类格式显示文件</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 读取用户特定的配置文件（如果存在）</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.profile&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    . <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.profile&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="c1"># .profile 是用户的个人配置文件</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 使用 . $HOME/.profile 命令将其内容导入当前 shell 环境</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 设置编辑器</span>
</span></span><span class="line"><span class="cl"><span class="nv">EDITOR</span><span class="o">=</span>vim
</span></span><span class="line"><span class="cl"><span class="nb">export</span> EDITOR
</span></span><span class="line"><span class="cl"><span class="c1"># 定义默认的文本编辑器为 vim</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 设置主机名显示，也就是终端最前面提示符的样式</span>
</span></span><span class="line"><span class="cl"><span class="nv">PS1</span><span class="o">=</span><span class="s1">&#39;\u@\h:\w\$ &#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> PS1
</span></span><span class="line"><span class="cl"><span class="c1"># PS1 定义了 shell 提示符的样式</span>
</span></span><span class="line"><span class="cl"><span class="c1"># \u 代表用户名</span>
</span></span><span class="line"><span class="cl"><span class="c1"># \h 代表主机名</span>
</span></span><span class="line"><span class="cl"><span class="c1"># \w 代表当前工作目录</span>
</span></span></code></pre></div><h2 id="busybox-简介以及如何制作-busybox-文件系统">Busybox 简介以及如何制作 Busybox 文件系统</h2>
<h3 id="准备-qemu">准备 QEMU</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl"><span class="nb">cd</span> <span class="o">&amp;&amp;</span> mkdir -p /home/user/program/riscv64-qemu
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> workspace
</span></span><span class="line"><span class="cl">git clone https://github.com/qemu/qemu.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> qemu
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> qemu <span class="o">&amp;&amp;</span> ./configure --target-list<span class="o">=</span>riscv32-softmmu,riscv32-linux-user,riscv64-linux-user,riscv64-softmmu <span class="se">\
</span></span></span><span class="line"><span class="cl">               --enable-kvm --enable-sdl <span class="se">\
</span></span></span><span class="line"><span class="cl">               --prefix<span class="o">=</span>/home/user/program/riscv64-qemu
</span></span><span class="line"><span class="cl">make install -j <span class="k">$(</span>nproc<span class="k">)</span>
</span></span></code></pre></div><h3 id="准备-opensbi">准备 OpenSBI</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl"><span class="nb">cd</span> workspace
</span></span><span class="line"><span class="cl">git clone https://github.com/riscv-software-src/opensbi.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> opensbi
</span></span><span class="line"><span class="cl">make <span class="nv">ARCH</span><span class="o">=</span>riscv <span class="nv">CROSS_COMPILE</span><span class="o">=</span>riscv64-unknown-linux-gnu- <span class="nv">PLATFORM</span><span class="o">=</span>generic
</span></span></code></pre></div><h3 id="准备-linux-内核">准备 Linux 内核</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl"><span class="nb">cd</span> workspace
</span></span><span class="line"><span class="cl">git clone https://github.com/torvalds/linux.git
</span></span><span class="line"><span class="cl">make <span class="nv">ARCH</span><span class="o">=</span>riscv <span class="nv">CROSS_COMPILE</span><span class="o">=</span>riscv64-unknown-linux-gnu- defconfig
</span></span><span class="line"><span class="cl">make <span class="nv">ARCH</span><span class="o">=</span>riscv <span class="nv">CROSS_COMPILE</span><span class="o">=</span>riscv64-unknown-linux-gnu- -j <span class="k">$(</span>nproc<span class="k">)</span>
</span></span></code></pre></div><h3 id="准备-busybox">准备 Busybox</h3>
<p>进入下载软件包，<a href="https://busybox.net/downloads/">https://busybox.net/downloads/</a></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
</span></span><span class="line"><span class="cl">tar -xvf busybox-1.36.1.tar.bz2
</span></span><span class="line"><span class="cl">mv busybox-1.36.1 busybox
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> busybox
</span></span></code></pre></div><p>配置编译选项</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">make <span class="nv">CROSS_COMPILE</span><span class="o">=</span>riscv64-unknown-linux-gnu- menuconfig
</span></span></code></pre></div><p>选择静态库模式，设置路径：Settings -&gt; Build static binary (no shared libs)</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-51-50-e83454415403aacffad1e9320676356e-20240607105149-9cc407.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-51-50-e83454415403aacffad1e9320676356e-20240607105149-9cc407.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-52-13-e92a2a3f782236215a33b93161226d99-20240607105212-428b5c.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-52-13-e92a2a3f782236215a33b93161226d99-20240607105212-428b5c.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>编译 Busybox</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">make <span class="nv">CROSS_COMPILE</span><span class="o">=</span>riscv64-unknown-linux-gnu- -j <span class="k">$(</span>nproc<span class="k">)</span>
</span></span></code></pre></div><blockquote>
<p>如果需要频繁编译，也可以在 Menuconfig 中设置交叉编译器路径，这样就不需要每次都指定了。设置路径为：Settings -&gt; Build Options -&gt; Cross Compiler prefix。将其设置为 riscv64-unknown-linux-gnu-。</p>
</blockquote>
<p>安装 Busybox：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">make <span class="nv">CROSS_COMPILE</span><span class="o">=</span>riscv64-unknown-linux-gnu- install
</span></span></code></pre></div><blockquote>
<p>Busybox 默认安装在当前目录的_install 目录下，也可以在 Menuconfig 中设置安装目录，路径为：Settings -&gt; Build Options -&gt; Destination path for &lsquo;make install&rsquo;。</p>
</blockquote>
<p>查看安装目录</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ls _install 
</span></span><span class="line"><span class="cl">bin  linuxrc  sbin  usr
</span></span></code></pre></div><p>制作根文件系统</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">qemu-img create -f raw rootfs.img 256M
</span></span><span class="line"><span class="cl">mkfs.ext4 rootfs.img
</span></span><span class="line"><span class="cl">mkdir rootfs
</span></span><span class="line"><span class="cl">sudo mount -o loop rootfs.img rootfs
</span></span><span class="line"><span class="cl">sudo cp -r _install/* rootfs
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> rootfs
</span></span><span class="line"><span class="cl">sudo mkdir proc sys dev etc etc/init.d
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> etc
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> init.d
</span></span><span class="line"><span class="cl">sudo touch rcS
</span></span><span class="line"><span class="cl">sudo chmod +x rcS
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo vim rcS
</span></span></code></pre></div><p>写入以下内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/bin/sh  
</span></span></span><span class="line"><span class="cl">mount -t proc none /proc  
</span></span><span class="line"><span class="cl">mount -t sysfs none /sys  
</span></span><span class="line"><span class="cl">/sbin/mdev -s
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo umount rootfs
</span></span></code></pre></div><p>qemu 启动内核：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="nv">RESTORE</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> -en <span class="s1">&#39;\001\033[0m\002&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">YELLOW</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> -en <span class="s1">&#39;\001\033[00;33m\002&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## Configuration</span>
</span></span><span class="line"><span class="cl"><span class="nv">vcpu</span><span class="o">=</span><span class="m">8</span>
</span></span><span class="line"><span class="cl"><span class="nv">memory</span><span class="o">=</span><span class="m">16</span>
</span></span><span class="line"><span class="cl"><span class="nv">drive</span><span class="o">=</span><span class="s2">&#34;/home/user/workspace/rootfs.img&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">kernel</span><span class="o">=</span><span class="s2">&#34; /home/user/workspace/linux-stable/arch/riscv/boot/Image&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">fw</span><span class="o">=</span><span class="s2">&#34;/home/user/workspace/opensbi/build/platform/generic/firmware/fw_jump.bin&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">ssh_port</span><span class="o">=</span><span class="m">12070</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">cmd</span><span class="o">=</span><span class="s2">&#34;/home/user/program/riscv64-qemu/bin/qemu-system-riscv64 \
</span></span></span><span class="line"><span class="cl"><span class="s2">  -nographic -machine virt \
</span></span></span><span class="line"><span class="cl"><span class="s2">  -smp &#34;</span><span class="nv">$vcpu</span><span class="s2">&#34; -m &#34;</span><span class="nv">$memory</span><span class="s2">&#34;G \
</span></span></span><span class="line"><span class="cl"><span class="s2">  -cpu rv64\
</span></span></span><span class="line"><span class="cl"><span class="s2">  -bios &#34;</span><span class="nv">$fw</span><span class="s2">&#34; \
</span></span></span><span class="line"><span class="cl"><span class="s2">  -kernel &#34;</span><span class="nv">$kernel</span><span class="s2">&#34; \
</span></span></span><span class="line"><span class="cl"><span class="s2">  -drive file=</span><span class="nv">$drive</span><span class="s2">,format=raw,id=hd0 \
</span></span></span><span class="line"><span class="cl"><span class="s2">  -device virtio-blk-device,drive=hd0 \
</span></span></span><span class="line"><span class="cl"><span class="s2">  -append &#34;</span><span class="nv">root</span><span class="o">=</span>/dev/vda <span class="nv">console</span><span class="o">=</span>ttyS0<span class="s2">&#34; &#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>:: Starting VM...<span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>:: Using following configuration<span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>vCPU Cores: <span class="s2">&#34;</span><span class="nv">$vcpu</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>Memory: <span class="s2">&#34;</span><span class="nv">$memory</span><span class="s2">&#34;</span>G<span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>Disk: <span class="s2">&#34;</span><span class="nv">$drive</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>SSH Port: <span class="s2">&#34;</span><span class="nv">$ssh_port</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>:: NOTE: Make sure ONLY ONE .qcow2 file is<span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>in the current directory<span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>:: Tip: Try setting DNS manually <span class="k">if</span> QEMU user network doesn<span class="se">\&#39;</span>t work well. <span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span>:: HOWTO -<span class="se">\&gt;</span> https://serverfault.com/a/810639 <span class="si">${</span><span class="nv">RESTORE</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sleep <span class="m">2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">eval</span> <span class="nv">$cmd</span>
</span></span></code></pre></div><p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/17-24-09-3656ec35f835ab8650a4628ceac4dad8-20240618172408-48a595.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/17-24-09-3656ec35f835ab8650a4628ceac4dad8-20240618172408-48a595.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
]]></content:encoded>
    </item>
    <item>
      <title>BootROM 中 SPI 如何适配不同厂家的 Flash 芯片</title>
      <link>https://lifeislife.cn/posts/bootrom%E4%B8%ADspi%E5%A6%82%E4%BD%95%E9%80%82%E9%85%8D%E4%B8%8D%E5%90%8C%E5%8E%82%E5%AE%B6%E7%9A%84flash%E8%8A%AF%E7%89%87/</link>
      <pubDate>Sat, 30 Mar 2024 21:38:10 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/bootrom%E4%B8%ADspi%E5%A6%82%E4%BD%95%E9%80%82%E9%85%8D%E4%B8%8D%E5%90%8C%E5%8E%82%E5%AE%B6%E7%9A%84flash%E8%8A%AF%E7%89%87/</guid>
      <description>&lt;h1 id=&#34;问题背景&#34;&gt;问题背景&lt;/h1&gt;
&lt;p&gt;一款 SoC 芯片，会支持多种方式启动，比如从 NAND Flash、SPI Flash、eMMC、USB、UART 启动。对于 SPI Flash 启动，BootROM 需要知道 SPI Flash 的型号、容量、页大小、擦除大小等信息，以便正确读取、写入、擦除 SPI Flash。但是，不同厂家的 SPI Flash，这些参数可能不同，而 ROM 中的代码是无法修改的，并且容量有限，如何以最小的代码量，适配不同厂家的 SPI Flash 呢？&lt;/p&gt;
&lt;h1 id=&#34;spi-开发流程&#34;&gt;SPI 开发流程&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SPI 控制器初始&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SPI 控制器有自己的寄存器，可以配置 SPI 的页，块的擦除时间，是否需要 DMA，是否开启中断，单次读写的大小等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 SPI 对 Flash 进行配置&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BootROM 的 SPI 支持三种模式：Single、Dual、Quad，这不仅要配置 SPI 本身的寄存器，也需要配置 Flash 的寄存器。配置 Flash 的寄存器就需要使用 SPI 来完成。
&lt;ul&gt;
&lt;li&gt;BootROM 中配置 SPI 控制器的 &lt;code&gt;SPIC_CSR_01&lt;/code&gt; 寄存器的&lt;code&gt;spi_bus_mode&lt;/code&gt; 位可以配置 SPI 的模式；&lt;/li&gt;
&lt;li&gt;查阅 Flash 手册，找到配置总线模式的寄存器以及操作它的命令，如 WINBOND-W25Q128JW 这款 Flash 的配置寄存器为 &lt;code&gt;Status Register-2&lt;/code&gt;，Flash 的寄存器是无法直接读写的，所以需要在 Flash 手册中找到操作这个寄存器的命令，在手册的 Instructions 章节可以找到这款 Flash 的配置命令为 &lt;code&gt;Write Status Register-2&lt;/code&gt;，命令码为 &lt;code&gt;0x31&lt;/code&gt;，只要我们向 Flash 发送 &lt;code&gt;0x31&lt;/code&gt;，就可以配置 Flash 的总线模式了。


&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2Fe7024b05e381b724d3471744f323de76.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2Fe7024b05e381b724d3471744f323de76.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;


&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2F350d91ef1d63ed0c5650e4dfd2c72917.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2F350d91ef1d63ed0c5650e4dfd2c72917.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/li&gt;
&lt;li&gt;如何向 Flash 发送指令？需要将真正要发送的指令写入 SPI 的寄存器&lt;code&gt;SPIC_CSR_06&lt;/code&gt;，该寄存器的各个 Bit 可以配置命令码，命令类型，命令是否有效等信息。我们将之前的命令码&lt;code&gt;0x31&lt;/code&gt;写入到&lt;code&gt;SPIC_CSR_06&lt;/code&gt;的&lt;code&gt;command_code&lt;/code&gt;位，然后将&lt;code&gt;SPIC_CSR_06&lt;/code&gt;的&lt;code&gt;command_type&lt;/code&gt;位设置为&lt;code&gt;write status register&lt;/code&gt;，SPI 就会自动将这些信息发送给 Flash。Flash 接收到命令后，会根据命令码执行配置寄存器的操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SPI 读写 Flash&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;和配置 Flash 总线模式一样，读写 Flash 也需要配置 SPI 控制器的寄存器，以及配置 Flash 的寄存器。SPI 控制器的寄存器配置和配置 Flash 总线模式一样，只是命令码和命令类型不同。稍有不同的是需要先配置好 SPI 寄存器，配置读写的大小以及读写的地址，然后再配置&lt;code&gt;SPIC_CSR_06&lt;/code&gt;。Flash接收到命令后，会根据命令码执行读写操作。主设备只需要读写 Flash 的指定地址即可读写到数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;spi-驱动如何适配不同的-flash&#34;&gt;SPI 驱动如何适配不同的 Flash&lt;/h1&gt;
&lt;p&gt;在开发流程中我们并未考虑不同厂家的 Flash 如何操作，只是关心了 WINBOND-W25Q128JW 这款 Flash 的操作。在操作过程中我们可以发现，不同厂家的 Flash 的操作基本一致，只是命令码、页大小、擦除大小等参数不同。我们只要针对不同的 Flash，配置不同的参数即可。但是，如何知道当前 Flash 的参数呢？这就需要用到 SFDP 了。&lt;/p&gt;
&lt;p&gt;SFDP 是 JEDEC 发布的 JESD216 的一个新标准，目前的版本号是 V1.0。简而言之，SFDP（Serial Flash Discoverable Parameters）就相当于一张存储了 FLASH 部分属性的表，此表是不占用 FLASH 本身的存储空间的。SFDP 中的信息自出厂就被固定，只供读取，开发人员可通过发送操作指令 0x5A 来读取当前 FLASH 的 SFDP 相关内容，这有利于开发人员了解 FLASH 之间的差异，提高开发效率，缩短整个开发周期。&lt;/p&gt;
&lt;p&gt;也就是只要 Flash 研发时遵循了 JESD216 这个标准，我们都可以通过发送 0x5A 这个命令码，去获取Flash相关参数。最为关键的就是其中会保存 Flash 厂商的 ID，当我们获取到 Flash 厂商的 ID 时，我们就可以根据 ID 执行不同的配置操作。&lt;/p&gt;
&lt;p&gt;对于 WINBOND-W25Q128JW 这款 Flash，在数据手册的Feature里就可以看到它是支持 SFDP 的：


&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2Fbe54a549cc9266d120b1e2db37d68555.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2Fbe54a549cc9266d120b1e2db37d68555.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h1 id="问题背景">问题背景</h1>
<p>一款 SoC 芯片，会支持多种方式启动，比如从 NAND Flash、SPI Flash、eMMC、USB、UART 启动。对于 SPI Flash 启动，BootROM 需要知道 SPI Flash 的型号、容量、页大小、擦除大小等信息，以便正确读取、写入、擦除 SPI Flash。但是，不同厂家的 SPI Flash，这些参数可能不同，而 ROM 中的代码是无法修改的，并且容量有限，如何以最小的代码量，适配不同厂家的 SPI Flash 呢？</p>
<h1 id="spi-开发流程">SPI 开发流程</h1>
<ul>
<li>
<p>SPI 控制器初始</p>
<ul>
<li>SPI 控制器有自己的寄存器，可以配置 SPI 的页，块的擦除时间，是否需要 DMA，是否开启中断，单次读写的大小等等。</li>
</ul>
</li>
<li>
<p>使用 SPI 对 Flash 进行配置</p>
<ul>
<li>BootROM 的 SPI 支持三种模式：Single、Dual、Quad，这不仅要配置 SPI 本身的寄存器，也需要配置 Flash 的寄存器。配置 Flash 的寄存器就需要使用 SPI 来完成。
<ul>
<li>BootROM 中配置 SPI 控制器的 <code>SPIC_CSR_01</code> 寄存器的<code>spi_bus_mode</code> 位可以配置 SPI 的模式；</li>
<li>查阅 Flash 手册，找到配置总线模式的寄存器以及操作它的命令，如 WINBOND-W25Q128JW 这款 Flash 的配置寄存器为 <code>Status Register-2</code>，Flash 的寄存器是无法直接读写的，所以需要在 Flash 手册中找到操作这个寄存器的命令，在手册的 Instructions 章节可以找到这款 Flash 的配置命令为 <code>Write Status Register-2</code>，命令码为 <code>0x31</code>，只要我们向 Flash 发送 <code>0x31</code>，就可以配置 Flash 的总线模式了。


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2Fe7024b05e381b724d3471744f323de76.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2Fe7024b05e381b724d3471744f323de76.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html>


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2F350d91ef1d63ed0c5650e4dfd2c72917.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2F350d91ef1d63ed0c5650e4dfd2c72917.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></li>
<li>如何向 Flash 发送指令？需要将真正要发送的指令写入 SPI 的寄存器<code>SPIC_CSR_06</code>，该寄存器的各个 Bit 可以配置命令码，命令类型，命令是否有效等信息。我们将之前的命令码<code>0x31</code>写入到<code>SPIC_CSR_06</code>的<code>command_code</code>位，然后将<code>SPIC_CSR_06</code>的<code>command_type</code>位设置为<code>write status register</code>，SPI 就会自动将这些信息发送给 Flash。Flash 接收到命令后，会根据命令码执行配置寄存器的操作。</li>
</ul>
</li>
</ul>
</li>
<li>
<p>SPI 读写 Flash</p>
<ul>
<li>和配置 Flash 总线模式一样，读写 Flash 也需要配置 SPI 控制器的寄存器，以及配置 Flash 的寄存器。SPI 控制器的寄存器配置和配置 Flash 总线模式一样，只是命令码和命令类型不同。稍有不同的是需要先配置好 SPI 寄存器，配置读写的大小以及读写的地址，然后再配置<code>SPIC_CSR_06</code>。Flash接收到命令后，会根据命令码执行读写操作。主设备只需要读写 Flash 的指定地址即可读写到数据。</li>
</ul>
</li>
</ul>
<h1 id="spi-驱动如何适配不同的-flash">SPI 驱动如何适配不同的 Flash</h1>
<p>在开发流程中我们并未考虑不同厂家的 Flash 如何操作，只是关心了 WINBOND-W25Q128JW 这款 Flash 的操作。在操作过程中我们可以发现，不同厂家的 Flash 的操作基本一致，只是命令码、页大小、擦除大小等参数不同。我们只要针对不同的 Flash，配置不同的参数即可。但是，如何知道当前 Flash 的参数呢？这就需要用到 SFDP 了。</p>
<p>SFDP 是 JEDEC 发布的 JESD216 的一个新标准，目前的版本号是 V1.0。简而言之，SFDP（Serial Flash Discoverable Parameters）就相当于一张存储了 FLASH 部分属性的表，此表是不占用 FLASH 本身的存储空间的。SFDP 中的信息自出厂就被固定，只供读取，开发人员可通过发送操作指令 0x5A 来读取当前 FLASH 的 SFDP 相关内容，这有利于开发人员了解 FLASH 之间的差异，提高开发效率，缩短整个开发周期。</p>
<p>也就是只要 Flash 研发时遵循了 JESD216 这个标准，我们都可以通过发送 0x5A 这个命令码，去获取Flash相关参数。最为关键的就是其中会保存 Flash 厂商的 ID，当我们获取到 Flash 厂商的 ID 时，我们就可以根据 ID 执行不同的配置操作。</p>
<p>对于 WINBOND-W25Q128JW 这款 Flash，在数据手册的Feature里就可以看到它是支持 SFDP 的：


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2Fbe54a549cc9266d120b1e2db37d68555.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/%2F2024%2F03%2F30%2Fbe54a549cc9266d120b1e2db37d68555.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
]]></content:encoded>
    </item>
    <item>
      <title>嵌入式 Shell 基础</title>
      <link>https://lifeislife.cn/posts/%E5%B5%8C%E5%85%A5%E5%BC%8Fshell%E5%9F%BA%E7%A1%80/</link>
      <pubDate>Sun, 25 Sep 2022 22:35:16 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/%E5%B5%8C%E5%85%A5%E5%BC%8Fshell%E5%9F%BA%E7%A1%80/</guid>
      <description>&lt;h2 id=&#34;脚本语言&#34;&gt;脚本语言&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;常用的脚本语言有 sh、bash、csh、ksh、perl、python；&lt;/li&gt;
&lt;li&gt;在 Linux 下常用的脚本语言其实就是 bash、sh；&lt;/li&gt;
&lt;li&gt;脚本语言一般在嵌入式中应用，主要是用来做配置。（一个复杂的嵌入式程序都是可配置的，配置过程就是用脚本语言来实现的）自然不会使用过于复杂的脚本语言特性，因此只需要针对性的学习即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;shell-脚本的运行机制&#34;&gt;shell 脚本的运行机制&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;C/C++ 语言这种编写过程是：编写出源代码（源代码是不能直接运行的）然后编译链接形成可执行二进制程序，然后才能运行；而脚本程序不同，脚本程序编写好后源代码即可直接运行（没有编译链接过程）；&lt;/li&gt;
&lt;li&gt;shell 程序是解释运行的，所谓解释运行就是说当我们执行一个 shell 程序时，shell 解析器会逐行的解释 shell 程序代码，然后一行一行的去运行。（顺序结构）&lt;/li&gt;
&lt;li&gt;CPU 实际只认识二进制代码，根本不认识源代码。脚本程序源代码其实也不是二进制代码，CPU 也不认识，也不能直接执行。只不过脚本程序的编译链接过程不是以脚本程序源代码为单位进行的，而是在脚本运行过程中逐行的解释执行时才去完成脚本程序源代码转成二进制的过程（不一定是编译链接，因为这行脚本程序可能早就编译连接好了，这里我们只是调用它）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;动手写第一个-shell&#34;&gt;动手写第一个 shell&lt;/h2&gt;
&lt;h3 id=&#34;编辑器与编译器&#34;&gt;编辑器与编译器&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;shell 程序是文本格式的，只要是文本编辑器都可以。但是因为我们的 shell 是要在 Linux 系统下运行的，所以换行符必须是&lt;code&gt;\n&lt;/code&gt;，而 Windows 下的换行符是&lt;code&gt;\r\n&lt;/code&gt;，因此 Windows 中的编辑器写的 shell 不能在 Linux 下运行。&lt;/li&gt;
&lt;li&gt;编译器不涉及，因为 shell 是解释性语言，直接编辑完就可以运行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;shell-程序运行的运行的三种方法&#34;&gt;shell 程序运行的运行的三种方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;./xx.sh&lt;/code&gt;，和运行二进制可执行程序方法一样。这样运行 shell 要求 shell 程序必须具有可执行权限。&lt;code&gt;chmod a+x xx.sh&lt;/code&gt; 来添加可执行权限。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source xx.sh&lt;/code&gt;，&lt;code&gt;source&lt;/code&gt; 是 Linux 的一个命令，这个命令就是用来执行脚本程序的。这样运行不需要脚本具有可执行权限。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bash xx.sh&lt;/code&gt;，&lt;code&gt;bash&lt;/code&gt; 是一个脚本程序解释器，本质上是一个可执行程序。这样执行相当于我们执行了 &lt;code&gt;bash&lt;/code&gt; 程序，然后把 &lt;code&gt;xx.sh&lt;/code&gt; 作为 &lt;code&gt;argv[1]&lt;/code&gt; 传给他运行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;hello-world-程序和解释&#34;&gt;hello world 程序和解释&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;shell 程序的第一行一般都是以&lt;code&gt;#!/bin/sh&lt;/code&gt;开始，这行话的意思就是指定 shell 程序执行时被哪个解释器解释执行。所以我们这里写上&lt;code&gt;/bin/sh&lt;/code&gt;意思就是这个&lt;code&gt;shell&lt;/code&gt;将来被当前机器中&lt;code&gt;/bin&lt;/code&gt;目录下的&lt;code&gt;sh&lt;/code&gt;可执行程序执行。可以将第一行写为&lt;code&gt;#!/bin/bash&lt;/code&gt;来指定使用&lt;code&gt;bash&lt;/code&gt;执行该脚本。&lt;/li&gt;
&lt;li&gt;脚本中的注释使用&lt;code&gt;#&lt;/code&gt;，&lt;code&gt;#&lt;/code&gt;开头的行是注释行。如果有多行需要注释，每行前面都要加&lt;code&gt;#&lt;/code&gt;。（&lt;code&gt;#&lt;/code&gt;就相当于是 C 语言中的&lt;code&gt;//&lt;/code&gt;）;&lt;/li&gt;
&lt;li&gt;shell 程序的正文，由很多行 shell 语句构成。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;shell-语法&#34;&gt;shell 语法&lt;/h2&gt;
&lt;p&gt;shell 就是把以前命令行中键入执行的命令写成了程序。shell 其实就是为了避免反复的在命令行下手工输入而发明的一种把手工输入步骤记录下来，然后通过执行 shell 脚本程序就能再次复述原来记录的手工输入过程的一种技术。&lt;/p&gt;
&lt;h3 id=&#34;shell-中的变量定义和引用&#34;&gt;shell 中的变量定义和引用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;变量定义和初始化。shell 是弱类型语言（语言中的变量如果有明确的类型则属于强类型语言；变量没有明确类型就是弱类型语言），和 C 语言不同。在 shell 编程中定义变量不需要制定类型，也没有类型这个概念。&lt;/li&gt;
&lt;li&gt;变量定义时可以初始化，使用&lt;code&gt;=&lt;/code&gt;进行初始化赋值。在 shell 中赋值的=两边是不能有空格的。
注意：shell 对语法非常在意，非常严格。很多地方空格都是必须没有或者必须有，而且不能随意有没有空格。&lt;/li&gt;
&lt;li&gt;变量赋值，变量定义后可以再次赋值，新的赋值会覆盖老的赋值。shell 中并不刻意区分变量的定义和赋值，反正每个变量就是一个符号，这个符号的值就是最后一个给他赋值时的值。&lt;/li&gt;
&lt;li&gt;变量引用。shell 中引用一个变量必须使用&lt;code&gt;$&lt;/code&gt;符号，&lt;code&gt;$&lt;/code&gt;符号就是变量解引用符号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：&lt;code&gt;$&lt;/code&gt;符号后面跟一个字符串，这个字符串就会被当作变量去解析。如果这个字符串本身没有定义，执行时并不会报错，而是把这个变量解析为空。也就是说在 shell 中没有被定义的变量其实就相当于是一个定义并赋值为空的变量。&lt;/p&gt;
&lt;p&gt;注意：变量引用的时候可以&lt;code&gt;$var&lt;/code&gt;，也可以&lt;code&gt;${var}&lt;/code&gt;。这两种的区别是在某些情况下只能用&lt;code&gt;${var}&lt;/code&gt;而不能简单的&lt;code&gt;$var&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;shell-中无引用单引号和双引号的区别&#34;&gt;shell 中无引用、单引号和双引号的区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;shell 中使用字符串可以不加双引号，直接使用。而且有空格时也可以，但是缺陷是不能输出&lt;code&gt;&amp;quot;&lt;/code&gt;或者其他转义字符。&lt;/li&gt;
&lt;li&gt;shell 中也可以使用单引号来表示字符串，也是直接使用的，不能输出转义字符。&lt;/li&gt;
&lt;li&gt;单引号中：完全字面替换（不可包含单引号本身）&lt;/li&gt;
&lt;li&gt;双引号中：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$&lt;/code&gt;加变量名可以取变量的值&lt;/li&gt;
&lt;li&gt;反引号仍表示命令替换&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\$&lt;/code&gt;表示&lt;code&gt;$&lt;/code&gt;的字面值（输出$符号）&lt;/li&gt;
&lt;li&gt;`表示`的字面值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\&amp;quot;&lt;/code&gt;表示&lt;code&gt;&amp;quot;&lt;/code&gt;的字面值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\\&lt;/code&gt;表示&lt;code&gt;\&lt;/code&gt;的字面值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除以上情况之外，在其它字符前面的\无特殊含义，只表示字面值。&lt;/p&gt;
&lt;p&gt;单引号会原样输出，双引号可以调用命令：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-Bash&#34; data-lang=&#34;Bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;PATH_A&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;`pwd`/include&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;PATH_B&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;`pwd`/include&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$PATH_A&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# /home/a/b/include&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$PATH_B&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# `pwd`/include&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;shell-中调用-linux-命令&#34;&gt;shell 中调用 Linux 命令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;直接执行&lt;/li&gt;
&lt;li&gt;反引号括起来执行。有时候我们在 shell 中调用 Linux 命令是为了得到这个命令的返回值（结果值），这时候就适合用一对反引号 (键盘上 ESC 按键下面的那个按键，和&lt;code&gt;~&lt;/code&gt;在一个按键上) 来调用执行命令。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;shell-中的选择分支结构&#34;&gt;shell 中的选择分支结构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;shell 的 &lt;code&gt;if&lt;/code&gt; 语言用法很多，在此只介绍常用的，其他感兴趣可以自己去学&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;典型&lt;code&gt;if&lt;/code&gt;语言格式：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-Bash&#34; data-lang=&#34;Bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 表达式 &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  xxx
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  yyy
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  zzz
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  xxx
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ddd
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  uuu
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;if-的典型应用&#34;&gt;if 的典型应用&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-f&lt;/code&gt;判断文件是否存在，注意&lt;code&gt;[]&lt;/code&gt;里面&lt;strong&gt;前后都有空格&lt;/strong&gt;，不能省略&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-d&lt;/code&gt;判断目录是否存在&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;str1&amp;quot; = &amp;quot;str2&amp;quot;&lt;/code&gt;判断字符串是否相等，注意&lt;strong&gt;用一个等号而不是两个&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;判断数字是否相等&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-eq&lt;/code&gt;等于&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-gt&lt;/code&gt;大于&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-lt&lt;/code&gt;小于&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-ge&lt;/code&gt;大于等于&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-le&lt;/code&gt;小于等于&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-z&lt;/code&gt;判断字符串是否为空，注意&lt;code&gt;-z&lt;/code&gt;判断时如果变量本身没定义也是不成立（也就是说-z 认为没定义不等于为空）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;-o&lt;/code&gt;表示逻辑或，连接两个表达式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if [ 10 -eq 10 -o  ]; then&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; &lt;code&gt;||&lt;/code&gt;表示逻辑与和逻辑或&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;shell-中的循环结构&#34;&gt;shell 中的循环结构&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;for&lt;/code&gt; 循环，要求能看懂、能改即可。不要求能够完全不参考写出来。因为毕竟嵌入式并不需要完全重新手写。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;while&lt;/code&gt; 循环，和 C 语言的循环在逻辑上无差别，要注意很多格式要求，譬如：&lt;code&gt;while&lt;/code&gt; 后面的 &lt;code&gt;[]&lt;/code&gt; 两边都有空格，&lt;code&gt;[]&lt;/code&gt; 后面有分号（如果 do 放在一行的话），&lt;code&gt;i++&lt;/code&gt;的写法中有两层括号。&lt;/p&gt;
&lt;h3 id=&#34;echo-的创建和追加输入文件&#34;&gt;echo 的创建和追加输入文件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在 shell 中可以直接使用 &lt;code&gt;echo&lt;/code&gt; 指令新建一个文件，并且将一些内容传入这个文件中。创建文件并输入内容的关键就是&lt;code&gt;&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;还可以使用 &lt;code&gt;echo&lt;/code&gt; 指令配合追加符号&lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; 向一个已经存在的文件末尾追加输入内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;shell-中其他值得关注的知识点&#34;&gt;shell 中其他值得关注的知识点&lt;/h3&gt;
&lt;h4 id=&#34;case-语句&#34;&gt;case 语句&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;shell 中的 case 语句和 C 语言中的 switch case 语句作用一样，格式有差异&lt;/li&gt;
&lt;li&gt;shell 中的 case 语句天生没有 &lt;code&gt;break&lt;/code&gt;，也不需要&lt;code&gt;break&lt;/code&gt;，和 C 语言中的 switch case 不同。shell 中的 case 默认就是匹配上哪个执行哪个，不会说执行完了还去执行后面的其他 case&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;调用-shell-程序的传参&#34;&gt;调用 shell 程序的传参&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;C 语言中可以通过 &lt;code&gt;main&lt;/code&gt; 函数的 &lt;code&gt;argc&lt;/code&gt; 和 &lt;code&gt;argv&lt;/code&gt; 给程序传参&lt;/li&gt;
&lt;li&gt;shell 程序本身也可以在调用时传参给他。在 shell 程序内部使用传参也是使用的一些特定符号来表示的，包括：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$#&lt;/code&gt;表示调用该 shell 时传参的个数。（&lt;code&gt;$#&lt;/code&gt;计数时只考虑真正的参数个数）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$0、$1、$2·····&lt;/code&gt;则依次表示传参的各个参数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./a.out aa bb cc     
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# argc = 4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# argv[0] = ./a.out&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# argv[1] 是第一个有效参数····&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;source&lt;/span&gt; a.sh aa bb cc  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# $# = 3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# $0是执行这个 shell 程序的解析程序的名字&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# $1是第一个有效参数的值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# $2是第 2 个有效参数的值·····&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;while-循环和-case-语言和传参结合&#34;&gt;while 循环和 case 语言和传参结合&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;shell 中的 &lt;code&gt;break&lt;/code&gt; 关键字和 &lt;code&gt;C&lt;/code&gt; 语言中意义相同（都是跳出）但是用法不同。因为 shell 中 &lt;code&gt;case&lt;/code&gt; 语句默认不用 &lt;code&gt;break&lt;/code&gt; 的，因此在 &lt;code&gt;shell&lt;/code&gt; 中 &lt;code&gt;break&lt;/code&gt; 只用于循环跳出。所以当 &lt;code&gt;while&lt;/code&gt; 中内嵌 &lt;code&gt;case&lt;/code&gt; 语句时，&lt;code&gt;case&lt;/code&gt; 中的 &lt;code&gt;break&lt;/code&gt; 是跳出外层的 &lt;code&gt;while&lt;/code&gt; 循环的，不是用来跳出 &lt;code&gt;case&lt;/code&gt; 语句的。&lt;/li&gt;
&lt;li&gt;shell 中的&lt;code&gt;$# $1&lt;/code&gt;等内置变量的值是可以被改变，被 &lt;code&gt;shift&lt;/code&gt; 指令改变。&lt;code&gt;shift&lt;/code&gt; 指令有点像左移运算符，把我们给 shell 程序的传参左移了一个移出去了，原来的&lt;code&gt;$2&lt;/code&gt;变成了新的&lt;code&gt;$1&lt;/code&gt;，原来的&lt;code&gt;$#&lt;/code&gt;少了 1 个。&lt;/li&gt;
&lt;/ul&gt;
</description>
      <content:encoded><![CDATA[<h2 id="脚本语言">脚本语言</h2>
<ul>
<li>常用的脚本语言有 sh、bash、csh、ksh、perl、python；</li>
<li>在 Linux 下常用的脚本语言其实就是 bash、sh；</li>
<li>脚本语言一般在嵌入式中应用，主要是用来做配置。（一个复杂的嵌入式程序都是可配置的，配置过程就是用脚本语言来实现的）自然不会使用过于复杂的脚本语言特性，因此只需要针对性的学习即可。</li>
</ul>
<h2 id="shell-脚本的运行机制">shell 脚本的运行机制</h2>
<ul>
<li>C/C++ 语言这种编写过程是：编写出源代码（源代码是不能直接运行的）然后编译链接形成可执行二进制程序，然后才能运行；而脚本程序不同，脚本程序编写好后源代码即可直接运行（没有编译链接过程）；</li>
<li>shell 程序是解释运行的，所谓解释运行就是说当我们执行一个 shell 程序时，shell 解析器会逐行的解释 shell 程序代码，然后一行一行的去运行。（顺序结构）</li>
<li>CPU 实际只认识二进制代码，根本不认识源代码。脚本程序源代码其实也不是二进制代码，CPU 也不认识，也不能直接执行。只不过脚本程序的编译链接过程不是以脚本程序源代码为单位进行的，而是在脚本运行过程中逐行的解释执行时才去完成脚本程序源代码转成二进制的过程（不一定是编译链接，因为这行脚本程序可能早就编译连接好了，这里我们只是调用它）。</li>
</ul>
<h2 id="动手写第一个-shell">动手写第一个 shell</h2>
<h3 id="编辑器与编译器">编辑器与编译器</h3>
<ul>
<li>shell 程序是文本格式的，只要是文本编辑器都可以。但是因为我们的 shell 是要在 Linux 系统下运行的，所以换行符必须是<code>\n</code>，而 Windows 下的换行符是<code>\r\n</code>，因此 Windows 中的编辑器写的 shell 不能在 Linux 下运行。</li>
<li>编译器不涉及，因为 shell 是解释性语言，直接编辑完就可以运行。</li>
</ul>
<h3 id="shell-程序运行的运行的三种方法">shell 程序运行的运行的三种方法</h3>
<ul>
<li><code>./xx.sh</code>，和运行二进制可执行程序方法一样。这样运行 shell 要求 shell 程序必须具有可执行权限。<code>chmod a+x xx.sh</code> 来添加可执行权限。</li>
<li><code>source xx.sh</code>，<code>source</code> 是 Linux 的一个命令，这个命令就是用来执行脚本程序的。这样运行不需要脚本具有可执行权限。</li>
<li><code>bash xx.sh</code>，<code>bash</code> 是一个脚本程序解释器，本质上是一个可执行程序。这样执行相当于我们执行了 <code>bash</code> 程序，然后把 <code>xx.sh</code> 作为 <code>argv[1]</code> 传给他运行。</li>
</ul>
<h3 id="hello-world-程序和解释">hello world 程序和解释</h3>
<ul>
<li>shell 程序的第一行一般都是以<code>#!/bin/sh</code>开始，这行话的意思就是指定 shell 程序执行时被哪个解释器解释执行。所以我们这里写上<code>/bin/sh</code>意思就是这个<code>shell</code>将来被当前机器中<code>/bin</code>目录下的<code>sh</code>可执行程序执行。可以将第一行写为<code>#!/bin/bash</code>来指定使用<code>bash</code>执行该脚本。</li>
<li>脚本中的注释使用<code>#</code>，<code>#</code>开头的行是注释行。如果有多行需要注释，每行前面都要加<code>#</code>。（<code>#</code>就相当于是 C 语言中的<code>//</code>）;</li>
<li>shell 程序的正文，由很多行 shell 语句构成。</li>
</ul>
<h2 id="shell-语法">shell 语法</h2>
<p>shell 就是把以前命令行中键入执行的命令写成了程序。shell 其实就是为了避免反复的在命令行下手工输入而发明的一种把手工输入步骤记录下来，然后通过执行 shell 脚本程序就能再次复述原来记录的手工输入过程的一种技术。</p>
<h3 id="shell-中的变量定义和引用">shell 中的变量定义和引用</h3>
<ul>
<li>变量定义和初始化。shell 是弱类型语言（语言中的变量如果有明确的类型则属于强类型语言；变量没有明确类型就是弱类型语言），和 C 语言不同。在 shell 编程中定义变量不需要制定类型，也没有类型这个概念。</li>
<li>变量定义时可以初始化，使用<code>=</code>进行初始化赋值。在 shell 中赋值的=两边是不能有空格的。
注意：shell 对语法非常在意，非常严格。很多地方空格都是必须没有或者必须有，而且不能随意有没有空格。</li>
<li>变量赋值，变量定义后可以再次赋值，新的赋值会覆盖老的赋值。shell 中并不刻意区分变量的定义和赋值，反正每个变量就是一个符号，这个符号的值就是最后一个给他赋值时的值。</li>
<li>变量引用。shell 中引用一个变量必须使用<code>$</code>符号，<code>$</code>符号就是变量解引用符号。</li>
</ul>
<p>注意：<code>$</code>符号后面跟一个字符串，这个字符串就会被当作变量去解析。如果这个字符串本身没有定义，执行时并不会报错，而是把这个变量解析为空。也就是说在 shell 中没有被定义的变量其实就相当于是一个定义并赋值为空的变量。</p>
<p>注意：变量引用的时候可以<code>$var</code>，也可以<code>${var}</code>。这两种的区别是在某些情况下只能用<code>${var}</code>而不能简单的<code>$var</code>。</p>
<h3 id="shell-中无引用单引号和双引号的区别">shell 中无引用、单引号和双引号的区别</h3>
<ul>
<li>shell 中使用字符串可以不加双引号，直接使用。而且有空格时也可以，但是缺陷是不能输出<code>&quot;</code>或者其他转义字符。</li>
<li>shell 中也可以使用单引号来表示字符串，也是直接使用的，不能输出转义字符。</li>
<li>单引号中：完全字面替换（不可包含单引号本身）</li>
<li>双引号中：
<ul>
<li><code>$</code>加变量名可以取变量的值</li>
<li>反引号仍表示命令替换</li>
<li><code>\$</code>表示<code>$</code>的字面值（输出$符号）</li>
<li>`表示`的字面值</li>
<li><code>\&quot;</code>表示<code>&quot;</code>的字面值</li>
<li><code>\\</code>表示<code>\</code>的字面值</li>
</ul>
</li>
</ul>
<p>除以上情况之外，在其它字符前面的\无特殊含义，只表示字面值。</p>
<p>单引号会原样输出，双引号可以调用命令：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl"><span class="nv">PATH_A</span><span class="o">=</span><span class="s2">&#34;`pwd`/include&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">PATH_B</span><span class="o">=</span><span class="s1">&#39;`pwd`/include&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$PATH_A</span>
</span></span><span class="line"><span class="cl"><span class="c1"># /home/a/b/include</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$PATH_B</span>
</span></span><span class="line"><span class="cl"><span class="c1"># `pwd`/include</span>
</span></span></code></pre></div><h3 id="shell-中调用-linux-命令">shell 中调用 Linux 命令</h3>
<ul>
<li>直接执行</li>
<li>反引号括起来执行。有时候我们在 shell 中调用 Linux 命令是为了得到这个命令的返回值（结果值），这时候就适合用一对反引号 (键盘上 ESC 按键下面的那个按键，和<code>~</code>在一个按键上) 来调用执行命令。</li>
</ul>
<h3 id="shell-中的选择分支结构">shell 中的选择分支结构</h3>
<ul>
<li>shell 的 <code>if</code> 语言用法很多，在此只介绍常用的，其他感兴趣可以自己去学</li>
</ul>
<p>典型<code>if</code>语言格式：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Bash" data-lang="Bash"><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> 表达式 <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  xxx
</span></span><span class="line"><span class="cl">  yyy
</span></span><span class="line"><span class="cl">  zzz
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  xxx
</span></span><span class="line"><span class="cl">  ddd
</span></span><span class="line"><span class="cl">  uuu
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></div><h4 id="if-的典型应用">if 的典型应用</h4>
<ul>
<li>
<p><code>-f</code>判断文件是否存在，注意<code>[]</code>里面<strong>前后都有空格</strong>，不能省略</p>
</li>
<li>
<p><code>-d</code>判断目录是否存在</p>
</li>
<li>
<p><code>&quot;str1&quot; = &quot;str2&quot;</code>判断字符串是否相等，注意<strong>用一个等号而不是两个</strong></p>
</li>
<li>
<p>判断数字是否相等</p>
<ul>
<li><code>-eq</code>等于</li>
<li><code>-gt</code>大于</li>
<li><code>-lt</code>小于</li>
<li><code>-ge</code>大于等于</li>
<li><code>-le</code>小于等于</li>
</ul>
</li>
<li>
<p><code>-z</code>判断字符串是否为空，注意<code>-z</code>判断时如果变量本身没定义也是不成立（也就是说-z 认为没定义不等于为空）</p>
</li>
<li>
<p><code>-o</code>表示逻辑或，连接两个表达式</p>
<ul>
<li><code>if [ 10 -eq 10 -o  ]; then</code></li>
</ul>
</li>
<li>
<p><code>&amp;&amp;</code> <code>||</code>表示逻辑与和逻辑或</p>
</li>
</ul>
<h3 id="shell-中的循环结构">shell 中的循环结构</h3>
<p><code>for</code> 循环，要求能看懂、能改即可。不要求能够完全不参考写出来。因为毕竟嵌入式并不需要完全重新手写。</p>
<p><code>while</code> 循环，和 C 语言的循环在逻辑上无差别，要注意很多格式要求，譬如：<code>while</code> 后面的 <code>[]</code> 两边都有空格，<code>[]</code> 后面有分号（如果 do 放在一行的话），<code>i++</code>的写法中有两层括号。</p>
<h3 id="echo-的创建和追加输入文件">echo 的创建和追加输入文件</h3>
<ul>
<li>在 shell 中可以直接使用 <code>echo</code> 指令新建一个文件，并且将一些内容传入这个文件中。创建文件并输入内容的关键就是<code>&gt;</code>。</li>
<li>还可以使用 <code>echo</code> 指令配合追加符号<code>&gt;&gt;</code> 向一个已经存在的文件末尾追加输入内容。</li>
</ul>
<h3 id="shell-中其他值得关注的知识点">shell 中其他值得关注的知识点</h3>
<h4 id="case-语句">case 语句</h4>
<ul>
<li>shell 中的 case 语句和 C 语言中的 switch case 语句作用一样，格式有差异</li>
<li>shell 中的 case 语句天生没有 <code>break</code>，也不需要<code>break</code>，和 C 语言中的 switch case 不同。shell 中的 case 默认就是匹配上哪个执行哪个，不会说执行完了还去执行后面的其他 case</li>
</ul>
<h4 id="调用-shell-程序的传参">调用 shell 程序的传参</h4>
<ul>
<li>C 语言中可以通过 <code>main</code> 函数的 <code>argc</code> 和 <code>argv</code> 给程序传参</li>
<li>shell 程序本身也可以在调用时传参给他。在 shell 程序内部使用传参也是使用的一些特定符号来表示的，包括：
<ul>
<li><code>$#</code>表示调用该 shell 时传参的个数。（<code>$#</code>计数时只考虑真正的参数个数）</li>
<li><code>$0、$1、$2·····</code>则依次表示传参的各个参数</li>
</ul>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./a.out aa bb cc     
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># argc = 4</span>
</span></span><span class="line"><span class="cl"><span class="c1"># argv[0] = ./a.out</span>
</span></span><span class="line"><span class="cl"><span class="c1"># argv[1] 是第一个有效参数····</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">source</span> a.sh aa bb cc  
</span></span><span class="line"><span class="cl"><span class="c1"># $# = 3</span>
</span></span><span class="line"><span class="cl"><span class="c1"># $0是执行这个 shell 程序的解析程序的名字</span>
</span></span><span class="line"><span class="cl"><span class="c1"># $1是第一个有效参数的值</span>
</span></span><span class="line"><span class="cl"><span class="c1"># $2是第 2 个有效参数的值·····</span>
</span></span></code></pre></div><h4 id="while-循环和-case-语言和传参结合">while 循环和 case 语言和传参结合</h4>
<ul>
<li>shell 中的 <code>break</code> 关键字和 <code>C</code> 语言中意义相同（都是跳出）但是用法不同。因为 shell 中 <code>case</code> 语句默认不用 <code>break</code> 的，因此在 <code>shell</code> 中 <code>break</code> 只用于循环跳出。所以当 <code>while</code> 中内嵌 <code>case</code> 语句时，<code>case</code> 中的 <code>break</code> 是跳出外层的 <code>while</code> 循环的，不是用来跳出 <code>case</code> 语句的。</li>
<li>shell 中的<code>$# $1</code>等内置变量的值是可以被改变，被 <code>shift</code> 指令改变。<code>shift</code> 指令有点像左移运算符，把我们给 shell 程序的传参左移了一个移出去了，原来的<code>$2</code>变成了新的<code>$1</code>，原来的<code>$#</code>少了 1 个。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>IIC 协议</title>
      <link>https://lifeislife.cn/posts/iic%E5%8D%8F%E8%AE%AE/</link>
      <pubDate>Fri, 19 Aug 2022 14:16:29 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/iic%E5%8D%8F%E8%AE%AE/</guid>
      <description>&lt;h1 id=&#34;iic-概述&#34;&gt;IIC 概述&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;IIC（Inter-Integrated Circuit）&lt;/strong&gt;，也叫 I2C（Inter-IC Communication）总线，是一种串行通信协议，由 Philips 公司在 1980 年代初开发。IIC 总线用于连接微控制器、传感器和其他集成电路，它具有以下特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;双线制结构简单：由一条数据线（SDA）和一条时钟线（SCL）组成。&lt;/li&gt;
&lt;li&gt;多主机并行通信：多个 Master 设备可同时接入同一条 IIC 总线上进行数据交换。&lt;/li&gt;
&lt;li&gt;硬件资源占用少：只要两根线就可以连接多个器件。&lt;/li&gt;
&lt;li&gt;数据传输速率快：现代 IIC 总线的最高传输速率可达到 400Kbps。&lt;/li&gt;
&lt;li&gt;低功耗设计：使用者可以通过软件控制设备进入睡眠模式以减少功耗。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;传输协议&#34;&gt;传输协议&lt;/h1&gt;
&lt;h2 id=&#34;写操作&#34;&gt;写操作&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;主机要发出一个&lt;strong&gt;起始信号&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;主机发出一个&lt;strong&gt;设备地址&lt;/strong&gt;用来确定是往那一个芯片写数据，以及写标记（0）&lt;/li&gt;
&lt;li&gt;从设备回应（用来确定这个设备是香存在，然后就可以传输数据）&lt;/li&gt;
&lt;li&gt;主设备发送一个字节数据给从设备，并等待回应&lt;/li&gt;
&lt;li&gt;每传输一字节故据，接收方要有一个回应信号（确定故据是否接受完成），然后再传输下一个故据。&lt;/li&gt;
&lt;li&gt;数据发送完之后，主机就会发送一个停止信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;读操作&#34;&gt;读操作&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;主机要发出一个&lt;strong&gt;起始信号&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;主机发出一个&lt;strong&gt;设备地址&lt;/strong&gt;用来确定是往那一个芯片读数据，以及读标记（1）&lt;/li&gt;
&lt;li&gt;从设备回应（用来确定这个设备是否存在），然后就可以传输数据&lt;/li&gt;
&lt;li&gt;从设备发送一个字节放据给主设备，并等待回应&lt;/li&gt;
&lt;li&gt;每传输一字节数据，接收方要有一个回应信号（确定数据是否接受完成），然后再传输下一个放据。&lt;/li&gt;
&lt;li&gt;数据发送完之后，主芯片就会发送一个停止信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;状态&#34;&gt;状态&lt;/h1&gt;
&lt;h2 id=&#34;空闲状态&#34;&gt;空闲状态&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SCL 和 SDA 都为高电平&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此时各个器件的输出级场效应管均处在截止状态，即释放总线，由两条信号线各自的上拉电阻把电平拉高。&lt;/p&gt;
&lt;h2 id=&#34;起始状态&#34;&gt;起始状态&lt;/h2&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2023/08/19/e9d2b88fa97d66c8a93d089299d6f636.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2023/08/19/e9d2b88fa97d66c8a93d089299d6f636.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SCL 为高电平，SDA 由高电平变为低电平&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;标志着一次数据传输的开始。起始信号是由主控器主动建立的，在建立该信号之前 I2C 总线必须处于空闲状态。&lt;/p&gt;
&lt;h2 id=&#34;结束状态&#34;&gt;结束状态&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SCL 为高电平，SDA 由低电平变为高电平&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;数据传输状态&#34;&gt;数据传输状态&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SCL 高电平期间，SDL 保持稳定
&lt;ul&gt;
&lt;li&gt;SDL 为高电平表示 1，低电平表示 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 IIC 总线上传送的每一位数据都有一个时钟脉冲相对应 (或同步控制)，即在 SCL 串行时钟的配合下，数据在 SDA 上从高位向低位依次串行传送每一位的数据。进行数据传送时，在 SCL 呈现高电平期间，SDA 上的电平必须保持稳定，低电平为数据 0，高电平为数据 1。只有在 SCL 为低电平期间，才允许 SDA 上的电平改变状态。下图是 &lt;code&gt;0xaa&lt;/code&gt; 在 IIC 总线上有效传输 (有效传输是指第 9 个时钟的高电平期间，从机给主机反馈了一个有效的应答位 0) 的图示&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2023/08/19/d851ded7e34114e925809dbcf33fa894.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2023/08/19/d851ded7e34114e925809dbcf33fa894.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在时序图中，MSB 代表的是 Most Significant Bit（最高位）。它表示二进制数中最左边的一位，也就是最高位。在一个 n 位的二进制数中，最高位的权值为 2^(n-1)。因此，MSB 在时序图中通常用来指示二进制数或数据字的最高位。同理，LSB 代表的是 Least Significant Bit（最低位）。它表示二进制数中最右边的一位，也就是最低位。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;应答状态&#34;&gt;应答状态&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SCL 为高电平，SDA 由高电平变为低电平（上图最后的 ACK 标记）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I2C 总线上的所有数据都是以 8 位字节传送的，发送器 (主机) 每发送一个字节，就在&lt;strong&gt;第 9 个时钟&lt;/strong&gt;脉冲期间释放数据线，由从设备反馈一个应答信号。应答信号为&lt;strong&gt;低电平&lt;/strong&gt;时，规定为&lt;strong&gt;有效应答位&lt;/strong&gt; (ACK 简称应答位)，表示接收器已经成功地接收了该字节；应答信号为&lt;strong&gt;高电平时&lt;/strong&gt;，规定为&lt;strong&gt;非应答位&lt;/strong&gt; (NACK)，一般表示接收器接收该字节没有成功。对于反馈有效应答位 ACK 的要求是，接收器在第 9 个时钟脉冲之前的低电平期间将 &lt;strong&gt;SDA&lt;/strong&gt; 线拉低，并且确保在该&lt;strong&gt;时钟的高电平期间为稳定的低电平&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以下四种情况 IIC 通信过程中会产生非应答位（NACK）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从机正在处理某些实时的操作无法与主机实现 IIC 通信的时候，从机会给主机反馈一个非应答 (NACK)&lt;/li&gt;
&lt;li&gt;主机发送数据的过程中，从机无法解析发送的数据，从机也会给主机反馈一个非应答位 (NACK)&lt;/li&gt;
&lt;li&gt;主机发送数据的过程中，从机无法再继续接收数据，从机也会给主机反馈一个非应答位 (NACK)&lt;/li&gt;
&lt;li&gt;主机从从机中读取数据的过程中，主机不想再接收数据，主机会给从机反馈一个非应答位 (NACK)，注意，这种情况是主机给从机反馈一个非应答位 (NACK)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;参考资料&#34;&gt;参考资料&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/liujinggang/p/9656358.html&#34;&gt;IIC总线的原理与Verilog实现 - jgliu - 博客园&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/xuyan123/p/14134246.html&#34;&gt;IIC时序分析 - 夏天师妹 - 博客园&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
      <content:encoded><![CDATA[<h1 id="iic-概述">IIC 概述</h1>
<p><strong>IIC（Inter-Integrated Circuit）</strong>，也叫 I2C（Inter-IC Communication）总线，是一种串行通信协议，由 Philips 公司在 1980 年代初开发。IIC 总线用于连接微控制器、传感器和其他集成电路，它具有以下特点：</p>
<ol>
<li>双线制结构简单：由一条数据线（SDA）和一条时钟线（SCL）组成。</li>
<li>多主机并行通信：多个 Master 设备可同时接入同一条 IIC 总线上进行数据交换。</li>
<li>硬件资源占用少：只要两根线就可以连接多个器件。</li>
<li>数据传输速率快：现代 IIC 总线的最高传输速率可达到 400Kbps。</li>
<li>低功耗设计：使用者可以通过软件控制设备进入睡眠模式以减少功耗。</li>
</ol>
<h1 id="传输协议">传输协议</h1>
<h2 id="写操作">写操作</h2>
<ul>
<li>主机要发出一个<strong>起始信号</strong></li>
<li>主机发出一个<strong>设备地址</strong>用来确定是往那一个芯片写数据，以及写标记（0）</li>
<li>从设备回应（用来确定这个设备是香存在，然后就可以传输数据）</li>
<li>主设备发送一个字节数据给从设备，并等待回应</li>
<li>每传输一字节故据，接收方要有一个回应信号（确定故据是否接受完成），然后再传输下一个故据。</li>
<li>数据发送完之后，主机就会发送一个停止信号。</li>
</ul>
<h2 id="读操作">读操作</h2>
<ul>
<li>主机要发出一个<strong>起始信号</strong></li>
<li>主机发出一个<strong>设备地址</strong>用来确定是往那一个芯片读数据，以及读标记（1）</li>
<li>从设备回应（用来确定这个设备是否存在），然后就可以传输数据</li>
<li>从设备发送一个字节放据给主设备，并等待回应</li>
<li>每传输一字节数据，接收方要有一个回应信号（确定数据是否接受完成），然后再传输下一个放据。</li>
<li>数据发送完之后，主芯片就会发送一个停止信号。</li>
</ul>
<h1 id="状态">状态</h1>
<h2 id="空闲状态">空闲状态</h2>
<ul>
<li>SCL 和 SDA 都为高电平</li>
</ul>
<p>此时各个器件的输出级场效应管均处在截止状态，即释放总线，由两条信号线各自的上拉电阻把电平拉高。</p>
<h2 id="起始状态">起始状态</h2>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2023/08/19/e9d2b88fa97d66c8a93d089299d6f636.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2023/08/19/e9d2b88fa97d66c8a93d089299d6f636.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<ul>
<li>SCL 为高电平，SDA 由高电平变为低电平</li>
</ul>
<p>标志着一次数据传输的开始。起始信号是由主控器主动建立的，在建立该信号之前 I2C 总线必须处于空闲状态。</p>
<h2 id="结束状态">结束状态</h2>
<ul>
<li>SCL 为高电平，SDA 由低电平变为高电平</li>
</ul>
<h2 id="数据传输状态">数据传输状态</h2>
<ul>
<li>SCL 高电平期间，SDL 保持稳定
<ul>
<li>SDL 为高电平表示 1，低电平表示 0</li>
</ul>
</li>
</ul>
<p>在 IIC 总线上传送的每一位数据都有一个时钟脉冲相对应 (或同步控制)，即在 SCL 串行时钟的配合下，数据在 SDA 上从高位向低位依次串行传送每一位的数据。进行数据传送时，在 SCL 呈现高电平期间，SDA 上的电平必须保持稳定，低电平为数据 0，高电平为数据 1。只有在 SCL 为低电平期间，才允许 SDA 上的电平改变状态。下图是 <code>0xaa</code> 在 IIC 总线上有效传输 (有效传输是指第 9 个时钟的高电平期间，从机给主机反馈了一个有效的应答位 0) 的图示</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2023/08/19/d851ded7e34114e925809dbcf33fa894.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img//2023/08/19/d851ded7e34114e925809dbcf33fa894.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<blockquote>
<p>在时序图中，MSB 代表的是 Most Significant Bit（最高位）。它表示二进制数中最左边的一位，也就是最高位。在一个 n 位的二进制数中，最高位的权值为 2^(n-1)。因此，MSB 在时序图中通常用来指示二进制数或数据字的最高位。同理，LSB 代表的是 Least Significant Bit（最低位）。它表示二进制数中最右边的一位，也就是最低位。</p>
</blockquote>
<h2 id="应答状态">应答状态</h2>
<ul>
<li>SCL 为高电平，SDA 由高电平变为低电平（上图最后的 ACK 标记）</li>
</ul>
<p>I2C 总线上的所有数据都是以 8 位字节传送的，发送器 (主机) 每发送一个字节，就在<strong>第 9 个时钟</strong>脉冲期间释放数据线，由从设备反馈一个应答信号。应答信号为<strong>低电平</strong>时，规定为<strong>有效应答位</strong> (ACK 简称应答位)，表示接收器已经成功地接收了该字节；应答信号为<strong>高电平时</strong>，规定为<strong>非应答位</strong> (NACK)，一般表示接收器接收该字节没有成功。对于反馈有效应答位 ACK 的要求是，接收器在第 9 个时钟脉冲之前的低电平期间将 <strong>SDA</strong> 线拉低，并且确保在该<strong>时钟的高电平期间为稳定的低电平</strong>。</p>
<p>以下四种情况 IIC 通信过程中会产生非应答位（NACK）：</p>
<ul>
<li>从机正在处理某些实时的操作无法与主机实现 IIC 通信的时候，从机会给主机反馈一个非应答 (NACK)</li>
<li>主机发送数据的过程中，从机无法解析发送的数据，从机也会给主机反馈一个非应答位 (NACK)</li>
<li>主机发送数据的过程中，从机无法再继续接收数据，从机也会给主机反馈一个非应答位 (NACK)</li>
<li>主机从从机中读取数据的过程中，主机不想再接收数据，主机会给从机反馈一个非应答位 (NACK)，注意，这种情况是主机给从机反馈一个非应答位 (NACK)</li>
</ul>
<h1 id="参考资料">参考资料</h1>
<ol>
<li><a href="https://www.cnblogs.com/liujinggang/p/9656358.html">IIC总线的原理与Verilog实现 - jgliu - 博客园</a></li>
<li><a href="https://www.cnblogs.com/xuyan123/p/14134246.html">IIC时序分析 - 夏天师妹 - 博客园</a></li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>SPI 协议</title>
      <link>https://lifeislife.cn/posts/spi%E5%8D%8F%E8%AE%AE/</link>
      <pubDate>Tue, 19 Jul 2022 14:23:40 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/spi%E5%8D%8F%E8%AE%AE/</guid>
      <description>&lt;h1 id=&#34;spi-概述&#34;&gt;SPI 概述&lt;/h1&gt;
&lt;p&gt;SPI 是一种同步的、全双工的、高速的串行通信总线。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/19-17-11-68acbe1aebbf2173bbc3401dffd69c5a-20230814191709-b01232.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/19-17-11-68acbe1aebbf2173bbc3401dffd69c5a-20230814191709-b01232.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;4 线制 SPI 四个信号：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SCLK：串行时钟信号，由主设备产生，用于同步数据传输。&lt;/li&gt;
&lt;li&gt;CS：片选信号，由主设备产生，用于选择从设备。&lt;/li&gt;
&lt;li&gt;MOSI：主设备输出，从设备输入，用于主设备向从设备传输数据。&lt;/li&gt;
&lt;li&gt;MISO：主设备输入，从设备输出，用于从设备向主设备传输数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了四线式 SPI 总线之外，还有三线式 SPI 总线和双线式 SPI 总线。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;四线式 SPI 总线，也称为标准 SPI 总线，由 SCLK、MOSI、MISO 和 SS（Slave Select）四个信号线组成。其中，SCLK 是时钟信号线；MOSI 是主设备向从设备发送数据的信号线；MISO 是从设备向主设备发送数据的信号线；SS 是从设备的片选信号线，用于选择要通信的从设备。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;三线式 SPI 总线将 MOSI 和 MISO 合并为单一的信号线。这种 SPI 总线有一个专门的叫做 MOMI 的信号线，既可以作为主设备向从设备发送数据的信号线，又可以作为从设备向主设备发送数据的信号线。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;双线式 SPI 总线（也称为 MICROWIRE 或 uWire），由一个串行数据线和一个时钟线组成。在这种 SPI 总线上，没有单独的片选信号线，而是使用一个帧选择控制位来选择相应的从设备。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;他们传输速率有差别吗？
是的，SPI 总线的不同类型之间存在传输速率上的差别。一般情况下，四线式 SPI 总线的传输速度最快，而双线式 SPI 总线的传输速度最慢。但具体的传输速度会受到很多因素的影响，例如工作频率、数据线长度等等。如果要在实际应用中选择合适的 SPI 总线类型，需要考虑诸如这些因素的影响，并根据具体情况进行权衡取舍。
SPI 总线的传输速度快慢与其信号线数量有关。双线式 SPI 总线一共只有两条信号线：一个主设备 (Master) 输出时钟信号 (SCLK)，一个主设备通过该信号线读取从设备 (Slave) 的应答信号。而四线式 SPI 总线除了上述两条信号线外，还有两条用于数据传输的信号线：主设备通过 MOSI 信号线向从设备发送数据，从设备则通过 MISO 信号线将数据返回给主设备。由于四线式 SPI 总线有专门的数据传输信号线，故可以通过同时在这两条信号线上传输数据来实现更高的传输速率，从而比双线式 SPI 总线快一些。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;时钟极性和时钟相位&#34;&gt;时钟极性和时钟相位&lt;/h3&gt;
&lt;p&gt;在 SPI 中，主机可以选择时钟极性和时钟相位。在空闲状态期间，CPOL 位设置时钟信号的极性。空闲状态是指传输开始时 CS 为高电平且在向低电平转变的期间，以及传输结束时 CS 为低电平且在向高电平转变的期间。CPHA 位选择时钟相位。根据 CPHA 位的状态，使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据 CPOL 和 CPHA 位的选择，有四种 SPI 模式可用。表 1 显示了这 4 种 SPI 模式。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;SPI 模式&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;CPOL(Serial Clock Polarity)&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;CPHA(Serial Clock Phase)&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;空闲状态下的时钟极性&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;采样时的时钟相位&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;移位数据的时钟相位&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;0&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;0&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;0&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;逻辑低电平&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;上升沿&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;下降沿&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;1&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;0&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;1&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;逻辑低电平&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;下降沿&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;上升沿&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;2&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;1&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;1&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;高电平&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;下降沿&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;上升沿&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;3&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;1&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;0&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;高电平&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;上升沿&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;下降沿&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;CPOL 和 CPHA 可以通过 SPI 的状态寄存器设置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;显示了四种 SPI 模式下的通信示例。在这些示例中，数据显示在 MOSI 和 MISO 线上。传输的开始和结束用绿色虚线表示，采样边沿用橙色虚线表示，移位边沿用蓝色虚线表示。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-15-5c6c2c600194fb53f85df52262dad7ef-20230815085012-956ab0.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-15-5c6c2c600194fb53f85df52262dad7ef-20230815085012-956ab0.png&#34; alt=&#34;&#34;  title=&#34; SPI 模式 0 ，CPOL=0 ，CPHA=0 ： CLK 空闲状态 =低电平，数据在上升沿采样，并在下降沿移出。&#34; style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;下图给出了 SPI 模式 1 的时序图。在此模式下，时钟极性为 0，表示时钟信号的空闲状态为低电平。此模式下的时钟相位为 1，表示数据在下降沿采样（由橙色虚线显示），并且数据在时钟信号的上升沿移出（由蓝色虚线显示）。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-28-a01e2572490707cc3b3a2cbad4e05533-20230815085026-b1efe3.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-28-a01e2572490707cc3b3a2cbad4e05533-20230815085026-b1efe3.png&#34; alt=&#34;&#34;  title=&#34;SPI 模式 1 ， CPOL = 0 ， CPHA = 1 ： CLK 空闲状态  = 低电平，数据在下降沿采样， 并在上升沿移出。&#34; style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-42-dba0e3a19915b00ed54953a271095f0a-20230815085039-cbaff8.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-42-dba0e3a19915b00ed54953a271095f0a-20230815085039-cbaff8.png&#34; alt=&#34;&#34;  title=&#34;SPI 模式 2 ， CPOL = 1 ， CPHA = 1 ： CLK 空闲状态  = 高电平，数据在下降沿采样， 并在上升沿移出&#34; style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-57-3256fcb2422ad57e875cfbeb87d64b7b-20230815085056-9b7476.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-57-3256fcb2422ad57e875cfbeb87d64b7b-20230815085056-9b7476.png&#34; alt=&#34;&#34;  title=&#34;SPI 模式 3 ， CPOL=1 ， CPHA=0：CLK空闲状态 =高电平，数据在上升沿采样，并在下降沿移出。&#34; style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h1 id=&#34;多从机配置&#34;&gt;多从机配置&lt;/h1&gt;
&lt;h2 id=&#34;标准-spi-模式&#34;&gt;标准 SPI 模式&lt;/h2&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-59-19-58306989e27c67dfcffff44c1ed40855-20230815085917-6b0c7d.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-59-19-58306989e27c67dfcffff44c1ed40855-20230815085917-6b0c7d.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在常规模式下，主机需要为每个从机提供单独的片选信号。一旦主机使能（拉低）片选信号，MOSI/MISO 线上的时钟和数据便可用于所选的从机。如果使能多个片选信号，则 MISO 线上的数据会被破坏，因为主机无法识别哪个从机正在传输数据。&lt;/p&gt;
&lt;p&gt;从上图可以看出，随着从机数量的增加，来自主机的片选线的数量也增加。这会快速增加主机需要提供的输入和输出数量，并限制可以使用的从机数量。可以使用其他技术来增加常规模式下的从机数量，例如使用多路复用器产生片选信号。&lt;/p&gt;
&lt;h2 id=&#34;菊花链模式&#34;&gt;菊花链模式&lt;/h2&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/09-00-50-8735cd63c5a6e4f29f2c92bef5391529-20230815090047-fb7f9d.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/09-00-50-8735cd63c5a6e4f29f2c92bef5391529-20230815090047-fb7f9d.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在菊花链模式下，所有从机的片选信号连接在一起，数据从一个从机传播到下一个从机。在此配置中，所有从机同时接收同一 SPI 时钟。来自主机的数据直接送到第一个从机，该从机将数据提供给下一个从机，依此类推。&lt;/p&gt;
&lt;p&gt;使用该方法时，由于数据是从一个从机传播到下一个从机，所以传输数据所需的时钟周期数与菊花链中的从机位置成比例。为使第 3 个从机能够获得数据，需要 24 个时钟脉冲，而常规 SPI 模式下只需 8 个时钟脉冲。下图显示了时钟周期和通过菊花链的数据传播。并非所有 SPI 器件都支持菊花链模式。请参阅产品数据手册以确认菊花链是否可用。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-38-26-69352144e54b0d62f2077c7e94a2a32a-20230815103825-b99e54.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-38-26-69352144e54b0d62f2077c7e94a2a32a-20230815103825-b99e54.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h1 id=&#34;spi-实现&#34;&gt;SPI 实现&lt;/h1&gt;
&lt;h1 id=&#34;参考资料&#34;&gt;参考资料&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://www.analog.com/cn/analog-dialogue/articles/introduction-to-spi-interface.html&#34;&gt;SPI 接口简介 | 亚德诺半导体&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://jia.je/hardware/2023/04/26/spi/#sd-%E5%8D%A1&#34;&gt;SPI 协议 - 杰哥的{运维，编程，调板子}小笔记&lt;/a&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h1 id="spi-概述">SPI 概述</h1>
<p>SPI 是一种同步的、全双工的、高速的串行通信总线。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/19-17-11-68acbe1aebbf2173bbc3401dffd69c5a-20230814191709-b01232.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/19-17-11-68acbe1aebbf2173bbc3401dffd69c5a-20230814191709-b01232.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>4 线制 SPI 四个信号：</p>
<ul>
<li>SCLK：串行时钟信号，由主设备产生，用于同步数据传输。</li>
<li>CS：片选信号，由主设备产生，用于选择从设备。</li>
<li>MOSI：主设备输出，从设备输入，用于主设备向从设备传输数据。</li>
<li>MISO：主设备输入，从设备输出，用于从设备向主设备传输数据。</li>
</ul>
<p>除了四线式 SPI 总线之外，还有三线式 SPI 总线和双线式 SPI 总线。</p>
<ul>
<li>
<p>四线式 SPI 总线，也称为标准 SPI 总线，由 SCLK、MOSI、MISO 和 SS（Slave Select）四个信号线组成。其中，SCLK 是时钟信号线；MOSI 是主设备向从设备发送数据的信号线；MISO 是从设备向主设备发送数据的信号线；SS 是从设备的片选信号线，用于选择要通信的从设备。</p>
</li>
<li>
<p>三线式 SPI 总线将 MOSI 和 MISO 合并为单一的信号线。这种 SPI 总线有一个专门的叫做 MOMI 的信号线，既可以作为主设备向从设备发送数据的信号线，又可以作为从设备向主设备发送数据的信号线。</p>
</li>
<li>
<p>双线式 SPI 总线（也称为 MICROWIRE 或 uWire），由一个串行数据线和一个时钟线组成。在这种 SPI 总线上，没有单独的片选信号线，而是使用一个帧选择控制位来选择相应的从设备。</p>
</li>
</ul>
<blockquote>
<p>他们传输速率有差别吗？
是的，SPI 总线的不同类型之间存在传输速率上的差别。一般情况下，四线式 SPI 总线的传输速度最快，而双线式 SPI 总线的传输速度最慢。但具体的传输速度会受到很多因素的影响，例如工作频率、数据线长度等等。如果要在实际应用中选择合适的 SPI 总线类型，需要考虑诸如这些因素的影响，并根据具体情况进行权衡取舍。
SPI 总线的传输速度快慢与其信号线数量有关。双线式 SPI 总线一共只有两条信号线：一个主设备 (Master) 输出时钟信号 (SCLK)，一个主设备通过该信号线读取从设备 (Slave) 的应答信号。而四线式 SPI 总线除了上述两条信号线外，还有两条用于数据传输的信号线：主设备通过 MOSI 信号线向从设备发送数据，从设备则通过 MISO 信号线将数据返回给主设备。由于四线式 SPI 总线有专门的数据传输信号线，故可以通过同时在这两条信号线上传输数据来实现更高的传输速率，从而比双线式 SPI 总线快一些。</p>
</blockquote>
<h3 id="时钟极性和时钟相位">时钟极性和时钟相位</h3>
<p>在 SPI 中，主机可以选择时钟极性和时钟相位。在空闲状态期间，CPOL 位设置时钟信号的极性。空闲状态是指传输开始时 CS 为高电平且在向低电平转变的期间，以及传输结束时 CS 为低电平且在向高电平转变的期间。CPHA 位选择时钟相位。根据 CPHA 位的状态，使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据 CPOL 和 CPHA 位的选择，有四种 SPI 模式可用。表 1 显示了这 4 种 SPI 模式。</p>
<table>
	<thead>
			<tr>
					<th style="text-align: center">SPI 模式</th>
					<th style="text-align: center">CPOL(Serial Clock Polarity)</th>
					<th style="text-align: center">CPHA(Serial Clock Phase)</th>
					<th style="text-align: center">空闲状态下的时钟极性</th>
					<th style="text-align: center">采样时的时钟相位</th>
					<th style="text-align: center">移位数据的时钟相位</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: center">0</td>
					<td style="text-align: center">0</td>
					<td style="text-align: center">0</td>
					<td style="text-align: center">逻辑低电平</td>
					<td style="text-align: center">上升沿</td>
					<td style="text-align: center">下降沿</td>
			</tr>
			<tr>
					<td style="text-align: center">1</td>
					<td style="text-align: center">0</td>
					<td style="text-align: center">1</td>
					<td style="text-align: center">逻辑低电平</td>
					<td style="text-align: center">下降沿</td>
					<td style="text-align: center">上升沿</td>
			</tr>
			<tr>
					<td style="text-align: center">2</td>
					<td style="text-align: center">1</td>
					<td style="text-align: center">1</td>
					<td style="text-align: center">高电平</td>
					<td style="text-align: center">下降沿</td>
					<td style="text-align: center">上升沿</td>
			</tr>
			<tr>
					<td style="text-align: center">3</td>
					<td style="text-align: center">1</td>
					<td style="text-align: center">0</td>
					<td style="text-align: center">高电平</td>
					<td style="text-align: center">上升沿</td>
					<td style="text-align: center">下降沿</td>
			</tr>
	</tbody>
</table>
<blockquote>
<p>CPOL 和 CPHA 可以通过 SPI 的状态寄存器设置。</p>
</blockquote>
<p>显示了四种 SPI 模式下的通信示例。在这些示例中，数据显示在 MOSI 和 MISO 线上。传输的开始和结束用绿色虚线表示，采样边沿用橙色虚线表示，移位边沿用蓝色虚线表示。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-15-5c6c2c600194fb53f85df52262dad7ef-20230815085012-956ab0.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-15-5c6c2c600194fb53f85df52262dad7ef-20230815085012-956ab0.png" alt=""  title=" SPI 模式 0 ，CPOL=0 ，CPHA=0 ： CLK 空闲状态 =低电平，数据在上升沿采样，并在下降沿移出。" style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>下图给出了 SPI 模式 1 的时序图。在此模式下，时钟极性为 0，表示时钟信号的空闲状态为低电平。此模式下的时钟相位为 1，表示数据在下降沿采样（由橙色虚线显示），并且数据在时钟信号的上升沿移出（由蓝色虚线显示）。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-28-a01e2572490707cc3b3a2cbad4e05533-20230815085026-b1efe3.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-28-a01e2572490707cc3b3a2cbad4e05533-20230815085026-b1efe3.png" alt=""  title="SPI 模式 1 ， CPOL = 0 ， CPHA = 1 ： CLK 空闲状态  = 低电平，数据在下降沿采样， 并在上升沿移出。" style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-42-dba0e3a19915b00ed54953a271095f0a-20230815085039-cbaff8.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-42-dba0e3a19915b00ed54953a271095f0a-20230815085039-cbaff8.png" alt=""  title="SPI 模式 2 ， CPOL = 1 ， CPHA = 1 ： CLK 空闲状态  = 高电平，数据在下降沿采样， 并在上升沿移出" style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-57-3256fcb2422ad57e875cfbeb87d64b7b-20230815085056-9b7476.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-50-57-3256fcb2422ad57e875cfbeb87d64b7b-20230815085056-9b7476.png" alt=""  title="SPI 模式 3 ， CPOL=1 ， CPHA=0：CLK空闲状态 =高电平，数据在上升沿采样，并在下降沿移出。" style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h1 id="多从机配置">多从机配置</h1>
<h2 id="标准-spi-模式">标准 SPI 模式</h2>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-59-19-58306989e27c67dfcffff44c1ed40855-20230815085917-6b0c7d.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/08-59-19-58306989e27c67dfcffff44c1ed40855-20230815085917-6b0c7d.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>在常规模式下，主机需要为每个从机提供单独的片选信号。一旦主机使能（拉低）片选信号，MOSI/MISO 线上的时钟和数据便可用于所选的从机。如果使能多个片选信号，则 MISO 线上的数据会被破坏，因为主机无法识别哪个从机正在传输数据。</p>
<p>从上图可以看出，随着从机数量的增加，来自主机的片选线的数量也增加。这会快速增加主机需要提供的输入和输出数量，并限制可以使用的从机数量。可以使用其他技术来增加常规模式下的从机数量，例如使用多路复用器产生片选信号。</p>
<h2 id="菊花链模式">菊花链模式</h2>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/09-00-50-8735cd63c5a6e4f29f2c92bef5391529-20230815090047-fb7f9d.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/09-00-50-8735cd63c5a6e4f29f2c92bef5391529-20230815090047-fb7f9d.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>在菊花链模式下，所有从机的片选信号连接在一起，数据从一个从机传播到下一个从机。在此配置中，所有从机同时接收同一 SPI 时钟。来自主机的数据直接送到第一个从机，该从机将数据提供给下一个从机，依此类推。</p>
<p>使用该方法时，由于数据是从一个从机传播到下一个从机，所以传输数据所需的时钟周期数与菊花链中的从机位置成比例。为使第 3 个从机能够获得数据，需要 24 个时钟脉冲，而常规 SPI 模式下只需 8 个时钟脉冲。下图显示了时钟周期和通过菊花链的数据传播。并非所有 SPI 器件都支持菊花链模式。请参阅产品数据手册以确认菊花链是否可用。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-38-26-69352144e54b0d62f2077c7e94a2a32a-20230815103825-b99e54.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/10-38-26-69352144e54b0d62f2077c7e94a2a32a-20230815103825-b99e54.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h1 id="spi-实现">SPI 实现</h1>
<h1 id="参考资料">参考资料</h1>
<p><a href="https://www.analog.com/cn/analog-dialogue/articles/introduction-to-spi-interface.html">SPI 接口简介 | 亚德诺半导体</a></p>
<p><a href="https://jia.je/hardware/2023/04/26/spi/#sd-%E5%8D%A1">SPI 协议 - 杰哥的{运维，编程，调板子}小笔记</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>SoC 存储器比较</title>
      <link>https://lifeislife.cn/posts/soc%E5%AD%98%E5%82%A8%E5%99%A8%E6%AF%94%E8%BE%83/</link>
      <pubDate>Sat, 21 May 2022 17:13:33 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/soc%E5%AD%98%E5%82%A8%E5%99%A8%E6%AF%94%E8%BE%83/</guid>
      <description>&lt;h2 id=&#34;内存&#34;&gt;内存&lt;/h2&gt;
&lt;p&gt;也就是内部存储器，主要用来运行程序的，典型的就是 RAM 随机存储器（Random Access Memory），那么随机是什么意思？所谓随机，指的是当存储器中的数据被读取或写入时，所需要的时间与这段信息所在的位置无关（任何位置读写速度一样）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DRAM&lt;/strong&gt;（Dynamic Random Access Memory，动态随机存储器）是最为常见的系统内存。我们使用的电脑和手机的运行内存都是 DRAM。DRAM 使用电容存储，DRAM 只能将数据保持很短的时间。为了保持数据，所以必须隔一段时间刷新（refresh）一次，如果存储单元没有被刷新，存储的信息就会丢失。数据的存储，请参考数据存储模型。我们知道，电容中的电荷很容易变化，所以随着时间推移，电容中的电荷数会增加或减少，为了确保数据不会丢失，DRAM 每隔一段时间会给电容刷新（充电或放电）。动态：定时刷新数据&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SRAM&lt;/strong&gt;（Static Random Access Memory，静态随机存储器），它是一种具有静止存取功能的内存，其内部机构比 DRAM 复杂，可以做到不刷新电路即能保存它内部存储的数据。&lt;strong&gt;静态：不需要刷新&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DDR SDRAM&lt;/strong&gt;（Double Data Rate SDRAM）：为双信道同步动态随机存取内存，是新一代的 SDRAM 技术。DDR 内存芯片的数据预取宽度（Prefetch）为 2 bit（SDRAM 的两倍）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DDR2 SDRAM&lt;/strong&gt;（Double Data Rate Two SDRAM）：为双信道两次同步动态随机存取内存。DDR2 内存 Prefetch 又再度提升至 4 bit（DDR 的两倍）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DDR3 SDRAM&lt;/strong&gt;（Double Data Rate Three SDRAM）：为双信道三次同步动态随机存取内存。DDR3 内存 Prefetch 提升至 8 bit，即每次会存取 8 bits 为一组的数据。运算频率介于 800MHz -1600MHz 之间。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205211606655.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205211606655.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&#34;外存&#34;&gt;外存&lt;/h2&gt;
&lt;p&gt;外部存储器，通常用来存储文件的，一般也叫 ROM（&lt;strong&gt;Read-only memory&lt;/strong&gt;）只读存储器。&lt;/p&gt;
&lt;p&gt;CPU 连接内存和外存的连接方式不同。内存需要直接地址访问，所以是通过地址总线&amp;amp;数据总线的总线式访问方式连接的（好处是直接访问，随机访问；坏处是占用 CPU 的地址空间，大小受限）；外存是通过 CPU 的外存接口来连接的（好处是不占用 CPU 的地址空间，坏处是访问速度没有总线式快，访问时序较复杂）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们平时用的硬盘，SD 卡都属于 ROM，但是他们却可以写入？ROM 严格意义来讲确实是只读的，但是随着储存器的发展，出现了可擦可编程只读存储器（EPROM）、电可擦可编程只读存储器（EEPROM）形式的半导体存储器，以及 flash。他们都是可写的。ROM 就不再单单只表示只读存储器了，一般来说与 RAM 相对，掉电不易失的存储器都被当做 ROM。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;rom&#34;&gt;ROM&lt;/h3&gt;
&lt;p&gt;ROM（Read Only Memory）只读存储器，这种存储器（Memory）的内容任何情况下都不会改变，电脑与用户只能读取保存在这里的指令，和使用存储在 ROM 的资料，但不能变更或存入资料。ROM 被存储在一个非易失性芯片上，也就是说，即使在关机之后记忆的内容仍可以被保存，所以这种存储器多用来存储特定功能的程序，如&lt;a href=&#34;https://zh.wikipedia.org/wiki/%E5%9B%BA%E4%BB%B6&#34;&gt;固件&lt;/a&gt;。ROM 存储用来启动电脑的程序（如&lt;a href=&#34;https://zh.wikipedia.org/wiki/BIOS&#34;&gt;BIOS&lt;/a&gt;），电脑引导的时候 BIOS 提供一连串的指令对中央处理器（&lt;a href=&#34;https://zh.wikipedia.org/wiki/CPU&#34;&gt;CPU&lt;/a&gt;）等组件进行初始化，在初始化过程中，BIOS 程序初始化并检查&lt;a href=&#34;https://zh.wikipedia.org/wiki/%E9%9A%8F%E6%9C%BA%E5%AD%98%E5%8F%96%E5%AD%98%E5%82%A8%E5%99%A8&#34;&gt;RAM&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&#34;norflash&#34;&gt;NorFlash&lt;/h3&gt;
&lt;p&gt;总线式访问，接到 SROM bank，优点是可以直接总线访问，一般用来启动。&lt;/p&gt;
&lt;h3 id=&#34;nandflash&#34;&gt;NandFlash&lt;/h3&gt;
&lt;p&gt;SLC：容量小，价格高，稳定性高&lt;/p&gt;
&lt;p&gt;MLC：容量大，价格低，稳定性差，易出坏块&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;iNand&lt;/strong&gt;
SanDisk 公司出产的 eMMC&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;moviNand&lt;/strong&gt;
三星公司出产的 eMMC&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;oneNAND&lt;/strong&gt;
三星公司出的一种 Nand，价格贵，用的少&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SD 卡（Secure Digital Memory Card）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192308956.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192308956.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TF 卡（TransFLash Card, MicroSD）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192309547.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192309547.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MMC 卡&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192312777.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192312777.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;eMMC 卡（embeded MMC）&lt;/strong&gt;
嵌入式的 MMC，可以当成一种芯片，内部做了坏块处理&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SATA 硬盘&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;特点：机械式访问、磁存储原理、SATA 是接口。&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h2 id="内存">内存</h2>
<p>也就是内部存储器，主要用来运行程序的，典型的就是 RAM 随机存储器（Random Access Memory），那么随机是什么意思？所谓随机，指的是当存储器中的数据被读取或写入时，所需要的时间与这段信息所在的位置无关（任何位置读写速度一样）。</p>
<p><strong>DRAM</strong>（Dynamic Random Access Memory，动态随机存储器）是最为常见的系统内存。我们使用的电脑和手机的运行内存都是 DRAM。DRAM 使用电容存储，DRAM 只能将数据保持很短的时间。为了保持数据，所以必须隔一段时间刷新（refresh）一次，如果存储单元没有被刷新，存储的信息就会丢失。数据的存储，请参考数据存储模型。我们知道，电容中的电荷很容易变化，所以随着时间推移，电容中的电荷数会增加或减少，为了确保数据不会丢失，DRAM 每隔一段时间会给电容刷新（充电或放电）。动态：定时刷新数据</p>
<p><strong>SRAM</strong>（Static Random Access Memory，静态随机存储器），它是一种具有静止存取功能的内存，其内部机构比 DRAM 复杂，可以做到不刷新电路即能保存它内部存储的数据。<strong>静态：不需要刷新</strong></p>
<p><strong>DDR SDRAM</strong>（Double Data Rate SDRAM）：为双信道同步动态随机存取内存，是新一代的 SDRAM 技术。DDR 内存芯片的数据预取宽度（Prefetch）为 2 bit（SDRAM 的两倍）。</p>
<p><strong>DDR2 SDRAM</strong>（Double Data Rate Two SDRAM）：为双信道两次同步动态随机存取内存。DDR2 内存 Prefetch 又再度提升至 4 bit（DDR 的两倍）</p>
<p><strong>DDR3 SDRAM</strong>（Double Data Rate Three SDRAM）：为双信道三次同步动态随机存取内存。DDR3 内存 Prefetch 提升至 8 bit，即每次会存取 8 bits 为一组的数据。运算频率介于 800MHz -1600MHz 之间。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205211606655.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205211606655.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h2 id="外存">外存</h2>
<p>外部存储器，通常用来存储文件的，一般也叫 ROM（<strong>Read-only memory</strong>）只读存储器。</p>
<p>CPU 连接内存和外存的连接方式不同。内存需要直接地址访问，所以是通过地址总线&amp;数据总线的总线式访问方式连接的（好处是直接访问，随机访问；坏处是占用 CPU 的地址空间，大小受限）；外存是通过 CPU 的外存接口来连接的（好处是不占用 CPU 的地址空间，坏处是访问速度没有总线式快，访问时序较复杂）</p>
<blockquote>
<p>我们平时用的硬盘，SD 卡都属于 ROM，但是他们却可以写入？ROM 严格意义来讲确实是只读的，但是随着储存器的发展，出现了可擦可编程只读存储器（EPROM）、电可擦可编程只读存储器（EEPROM）形式的半导体存储器，以及 flash。他们都是可写的。ROM 就不再单单只表示只读存储器了，一般来说与 RAM 相对，掉电不易失的存储器都被当做 ROM。</p>
</blockquote>
<h3 id="rom">ROM</h3>
<p>ROM（Read Only Memory）只读存储器，这种存储器（Memory）的内容任何情况下都不会改变，电脑与用户只能读取保存在这里的指令，和使用存储在 ROM 的资料，但不能变更或存入资料。ROM 被存储在一个非易失性芯片上，也就是说，即使在关机之后记忆的内容仍可以被保存，所以这种存储器多用来存储特定功能的程序，如<a href="https://zh.wikipedia.org/wiki/%E5%9B%BA%E4%BB%B6">固件</a>。ROM 存储用来启动电脑的程序（如<a href="https://zh.wikipedia.org/wiki/BIOS">BIOS</a>），电脑引导的时候 BIOS 提供一连串的指令对中央处理器（<a href="https://zh.wikipedia.org/wiki/CPU">CPU</a>）等组件进行初始化，在初始化过程中，BIOS 程序初始化并检查<a href="https://zh.wikipedia.org/wiki/%E9%9A%8F%E6%9C%BA%E5%AD%98%E5%8F%96%E5%AD%98%E5%82%A8%E5%99%A8">RAM</a>。</p>
<h3 id="norflash">NorFlash</h3>
<p>总线式访问，接到 SROM bank，优点是可以直接总线访问，一般用来启动。</p>
<h3 id="nandflash">NandFlash</h3>
<p>SLC：容量小，价格高，稳定性高</p>
<p>MLC：容量大，价格低，稳定性差，易出坏块</p>
<p><strong>iNand</strong>
SanDisk 公司出产的 eMMC</p>
<p><strong>moviNand</strong>
三星公司出产的 eMMC</p>
<p><strong>oneNAND</strong>
三星公司出的一种 Nand，价格贵，用的少</p>
<p><strong>SD 卡（Secure Digital Memory Card）</strong></p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192308956.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192308956.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p><strong>TF 卡（TransFLash Card, MicroSD）</strong></p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192309547.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192309547.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p><strong>MMC 卡</strong></p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192312777.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205192312777.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p><strong>eMMC 卡（embeded MMC）</strong>
嵌入式的 MMC，可以当成一种芯片，内部做了坏块处理</p>
<p><strong>SATA 硬盘</strong></p>
<p>特点：机械式访问、磁存储原理、SATA 是接口。</p>
]]></content:encoded>
    </item>
    <item>
      <title>芯片启动过程全解析</title>
      <link>https://lifeislife.cn/posts/%E8%8A%AF%E7%89%87%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B%E5%85%A8%E8%A7%A3%E6%9E%90/</link>
      <pubDate>Sat, 18 Dec 2021 22:32:27 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/%E8%8A%AF%E7%89%87%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B%E5%85%A8%E8%A7%A3%E6%9E%90/</guid>
      <description>&lt;p&gt;内容总结自 B 站 Up&lt;a href=&#34;https://www.bilibili.com/video/BV1AN411R7Be/?spm_id_from=333.788.recommend_more_video.1&#34;&gt;【蛋饼嵌入式】我提着鞋带拎自己？嵌入式芯片启动过程全解析，彻底理解 bootloader&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;当你按下电源开关的那一瞬间，第一行代码如何在芯片上运行起来的呢？嵌入式软件代码需要一定的方式烧录到芯片中才能运行，除了物理刻蚀，无论是通讯端口的传输或者调试端口的烧录，都需要驱动程序的支持。所以说是&lt;strong&gt;程序烧录了程序，软件启动了软件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这就像自己提着自己的鞋带，把自己拎起来。靴子（Boot）,鞋带（Strap），提鞋带（Loader）。这就是&lt;code&gt;Boot Strap Loader&lt;/code&gt;的命名来源。通常称&lt;code&gt;BootLoader&lt;/code&gt;，中文翻译为&lt;strong&gt;自举&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BootLoader&lt;/code&gt;是芯片最初运行的代码吗？当然不是，其实每一块芯片在出厂时都在其内部的&lt;code&gt;ROM&lt;/code&gt;中，烧录了它最基础的软件。CPU 搬运并运行的第一条代码的默认位置，就在&lt;code&gt;ROM&lt;/code&gt;的地址空间。所以一切的起始都在硬件上。&lt;/p&gt;
&lt;p&gt;以 X86 架构的鼻祖 8086 芯片为例，按下开关的一瞬间，芯片 Reset 引脚接收到了电平跳变，在一连串电路的作用下，代码段寄存器&lt;code&gt;CS&lt;/code&gt;恢复成&lt;code&gt;0XFFFF&lt;/code&gt;，指令指针寄存器&lt;code&gt;IP&lt;/code&gt;恢复成&lt;code&gt;0X0000&lt;/code&gt;，他们组合成 20 位的地址正好等于 ROM 中存放第一条代码的位置。之后取出这里的指令在跳转到别处。&lt;/p&gt;
&lt;p&gt;ARM 架构的芯片也是类似的过程，对于 32 位的芯片，通电后，&lt;code&gt;PC&lt;/code&gt;指针寄存器复位至零地址，随后从中断向量表表头的 reset 向量处获取下一个跳转的地址。这时候的代码已经以二进制形式存储，处理器可以直接搬到自身缓存中运行。有了这部分代码，就能跳转到存放有更多更复杂的代码的地址。执行硬件自检，基本的初始化操作，提供基础的输入输出支持。之后可以将操作系统从外部的存储空间加载到内部。代码就这样接力式的流转起来。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191002002.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191002002.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;所以我们把出厂就写在&lt;code&gt;ROM&lt;/code&gt;里，负责启动后续用户软件的软件，称为&lt;code&gt;Boot ROM&lt;/code&gt;或者&lt;code&gt;ROM Code&lt;/code&gt;。现在不一定是用只读存储器（Read Only Memory），但是至少是一块掉电不易失的存储器，现在主要用&lt;code&gt;EEPROM&lt;/code&gt;，&lt;code&gt;NOR Flash&lt;/code&gt;。我们一般没有权限修改它，但是它也不完全是黑盒，大部分芯片都会有外部启动配置引脚，通常是以拨码快关的形式。对于 PC 机来说，&lt;code&gt;Boot ROM&lt;/code&gt;就是我们常说的&lt;code&gt;BIOS&lt;/code&gt;，它也有启动配置途径。而且提供了交互界面，用于配置部分功能和选择后续的引导设备。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191009985.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191009985.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;除了芯片自带的&lt;code&gt;Boot ROM&lt;/code&gt;，还需要再给自己实际的应用程序，写一个二次引导代码或者 N 次引导代码，用作操作系统，文件系统加载等等。我们所说的&lt;code&gt;Bootloader&lt;/code&gt;时，其实大多数就是这样的二次引导代码。&lt;/p&gt;
&lt;p&gt;这些事其实&lt;code&gt;Boot ROM&lt;/code&gt;它也能做，但是&lt;code&gt;Boot ROM&lt;/code&gt;实现的功能和配置方法不灵活，但是&lt;code&gt;Bootloader&lt;/code&gt;是开发人员可以而完全控制的引导代码。&lt;/p&gt;
&lt;p&gt;在设计&lt;code&gt;Bootloader&lt;/code&gt;时，&lt;code&gt;MCU&lt;/code&gt;的引导步骤就开始和嵌入式 Linux 或者 PC 有所不同。这一定程度与芯片架构所采用的的存储方案有关。&lt;/p&gt;
&lt;p&gt;先来说&lt;code&gt;MCU&lt;/code&gt;，与&lt;code&gt;SOC&lt;/code&gt;相比&lt;code&gt;MCU&lt;/code&gt;的主要特征是单核和或多核同构的微处理器，单核或多核同构，主频 &amp;lt; 1GHz，没有&lt;code&gt;MMU&lt;/code&gt;内存管理单元，只能运行实时操作系统。常见&lt;code&gt;MCU&lt;/code&gt;内核：&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191051828.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191051828.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;程序的主要运行介质为&lt;code&gt;NOR Flash&lt;/code&gt;，因为和&lt;code&gt;RAM&lt;/code&gt;一样有分离的地址线和数据线。并且可以以字节长度精确寻址，所以程序不需要拷贝到&lt;code&gt;RAM&lt;/code&gt;中运行的。&lt;/p&gt;
&lt;p&gt;以英飞凌家的 TC27x 系列 MCU 为例，上电后的默认取址位置是&lt;code&gt;0x8FFF 8000&lt;/code&gt;，这就是他的&lt;code&gt;Boot ROM&lt;/code&gt;在&lt;code&gt;NorFlash&lt;/code&gt;中的地址。并且这块&lt;code&gt;Boot Rom&lt;/code&gt;分为&lt;code&gt;SSW&lt;/code&gt;，&lt;code&gt;BSL&lt;/code&gt;，&lt;code&gt;TF&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191055339.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191055339.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;SSW 每次上电必须运行，他会根据写在&lt;code&gt;program flash&lt;/code&gt;，&lt;code&gt;PFO&lt;/code&gt;地址的前 32byte 中的配置字，来决定&lt;code&gt;SSW&lt;/code&gt;执行完的跳转地址。我们可以选择一个合适的跳转地址，比如&lt;code&gt;0x80000020&lt;/code&gt;，放上自己写的&lt;code&gt;Bootloader&lt;/code&gt;。也可以选择不跳转，运行厂家提供的&lt;code&gt;Bootloader&lt;/code&gt;（BSL）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MCU&lt;/code&gt;下的&lt;code&gt;Bootloader&lt;/code&gt;主要完成的事情有以下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关闭看门狗，初始化中断和 trap 向量表，进行时钟和外设初始化，让芯片正常运行起来。&lt;/li&gt;
&lt;li&gt;提供&lt;code&gt;CAN&lt;/code&gt;,&lt;code&gt;UART&lt;/code&gt;, &lt;code&gt;ETH&lt;/code&gt;等用于通讯功能的驱动，能够接收外部数据传输请求。&lt;/li&gt;
&lt;li&gt;提供&lt;code&gt;FLASH&lt;/code&gt;的读写与擦除驱动，设计服务来对通讯端口接收到的更新代码进行校验、存储，以及跳转操作系统或后续应用程序代码。&lt;/li&gt;
&lt;li&gt;如有必要，还会开发一些基础诊断服务，串口交互程序等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么运行 Linux 的&lt;code&gt;SOC&lt;/code&gt;和 PC 的这一过程有何不同呢。还是先看存储方案，运行嵌入式 Linux 的 SoC。一般将它的操作系统，文件系统和他的应用程序放在&lt;code&gt;nand flash&lt;/code&gt;中。运行代码前，现将代码搬运到&lt;code&gt;SRAM&lt;/code&gt;中，相比&lt;code&gt;MCU&lt;/code&gt;多了一道步骤。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191101930.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191101930.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;对于&lt;code&gt;SOC&lt;/code&gt;的&lt;code&gt;Boot ROM&lt;/code&gt; 和 PC 的&lt;code&gt;BIOS&lt;/code&gt;而言，他们结束运行前的最终任务，是将某些代码从&lt;code&gt;nand flash&lt;/code&gt;搬运到&lt;code&gt;SRAM&lt;/code&gt;中，其中最重要的内容就是&lt;code&gt;Boot Loader&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;而一般&lt;code&gt;SOC&lt;/code&gt;的&lt;code&gt;Bootloader&lt;/code&gt;，又分为&lt;code&gt;SPL&lt;/code&gt;（Secondary Program Loader）和&lt;code&gt;uBOOT&lt;/code&gt;两个阶段。&lt;code&gt;SPL&lt;/code&gt;的 Secondary 就是相对于&lt;code&gt;BootROM&lt;/code&gt;而言，他就像是接力赛中的第二棒选手。&lt;code&gt;SPL&lt;/code&gt;会初始化更大空间的外部&lt;code&gt;DRAM&lt;/code&gt;，再把&lt;code&gt;uBoot&lt;/code&gt;搬运到外部&lt;code&gt;DRAM&lt;/code&gt;中去运行。&lt;code&gt;uBoot&lt;/code&gt;作为第三棒选手，开始运行它的初始化程序。之后再根据系统环境变量，将 OS 内核搬运到外部&lt;code&gt;DRAM&lt;/code&gt;中去运行。OS 再完成根文件系统的加载等等等等。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191116399.gif&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191116399.gif&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<p>内容总结自 B 站 Up<a href="https://www.bilibili.com/video/BV1AN411R7Be/?spm_id_from=333.788.recommend_more_video.1">【蛋饼嵌入式】我提着鞋带拎自己？嵌入式芯片启动过程全解析，彻底理解 bootloader</a></p>
<p>当你按下电源开关的那一瞬间，第一行代码如何在芯片上运行起来的呢？嵌入式软件代码需要一定的方式烧录到芯片中才能运行，除了物理刻蚀，无论是通讯端口的传输或者调试端口的烧录，都需要驱动程序的支持。所以说是<strong>程序烧录了程序，软件启动了软件</strong>。</p>
<p>这就像自己提着自己的鞋带，把自己拎起来。靴子（Boot）,鞋带（Strap），提鞋带（Loader）。这就是<code>Boot Strap Loader</code>的命名来源。通常称<code>BootLoader</code>，中文翻译为<strong>自举</strong>。</p>
<p><code>BootLoader</code>是芯片最初运行的代码吗？当然不是，其实每一块芯片在出厂时都在其内部的<code>ROM</code>中，烧录了它最基础的软件。CPU 搬运并运行的第一条代码的默认位置，就在<code>ROM</code>的地址空间。所以一切的起始都在硬件上。</p>
<p>以 X86 架构的鼻祖 8086 芯片为例，按下开关的一瞬间，芯片 Reset 引脚接收到了电平跳变，在一连串电路的作用下，代码段寄存器<code>CS</code>恢复成<code>0XFFFF</code>，指令指针寄存器<code>IP</code>恢复成<code>0X0000</code>，他们组合成 20 位的地址正好等于 ROM 中存放第一条代码的位置。之后取出这里的指令在跳转到别处。</p>
<p>ARM 架构的芯片也是类似的过程，对于 32 位的芯片，通电后，<code>PC</code>指针寄存器复位至零地址，随后从中断向量表表头的 reset 向量处获取下一个跳转的地址。这时候的代码已经以二进制形式存储，处理器可以直接搬到自身缓存中运行。有了这部分代码，就能跳转到存放有更多更复杂的代码的地址。执行硬件自检，基本的初始化操作，提供基础的输入输出支持。之后可以将操作系统从外部的存储空间加载到内部。代码就这样接力式的流转起来。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191002002.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191002002.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>所以我们把出厂就写在<code>ROM</code>里，负责启动后续用户软件的软件，称为<code>Boot ROM</code>或者<code>ROM Code</code>。现在不一定是用只读存储器（Read Only Memory），但是至少是一块掉电不易失的存储器，现在主要用<code>EEPROM</code>，<code>NOR Flash</code>。我们一般没有权限修改它，但是它也不完全是黑盒，大部分芯片都会有外部启动配置引脚，通常是以拨码快关的形式。对于 PC 机来说，<code>Boot ROM</code>就是我们常说的<code>BIOS</code>，它也有启动配置途径。而且提供了交互界面，用于配置部分功能和选择后续的引导设备。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191009985.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191009985.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>除了芯片自带的<code>Boot ROM</code>，还需要再给自己实际的应用程序，写一个二次引导代码或者 N 次引导代码，用作操作系统，文件系统加载等等。我们所说的<code>Bootloader</code>时，其实大多数就是这样的二次引导代码。</p>
<p>这些事其实<code>Boot ROM</code>它也能做，但是<code>Boot ROM</code>实现的功能和配置方法不灵活，但是<code>Bootloader</code>是开发人员可以而完全控制的引导代码。</p>
<p>在设计<code>Bootloader</code>时，<code>MCU</code>的引导步骤就开始和嵌入式 Linux 或者 PC 有所不同。这一定程度与芯片架构所采用的的存储方案有关。</p>
<p>先来说<code>MCU</code>，与<code>SOC</code>相比<code>MCU</code>的主要特征是单核和或多核同构的微处理器，单核或多核同构，主频 &lt; 1GHz，没有<code>MMU</code>内存管理单元，只能运行实时操作系统。常见<code>MCU</code>内核：</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191051828.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191051828.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>程序的主要运行介质为<code>NOR Flash</code>，因为和<code>RAM</code>一样有分离的地址线和数据线。并且可以以字节长度精确寻址，所以程序不需要拷贝到<code>RAM</code>中运行的。</p>
<p>以英飞凌家的 TC27x 系列 MCU 为例，上电后的默认取址位置是<code>0x8FFF 8000</code>，这就是他的<code>Boot ROM</code>在<code>NorFlash</code>中的地址。并且这块<code>Boot Rom</code>分为<code>SSW</code>，<code>BSL</code>，<code>TF</code>。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191055339.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191055339.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>SSW 每次上电必须运行，他会根据写在<code>program flash</code>，<code>PFO</code>地址的前 32byte 中的配置字，来决定<code>SSW</code>执行完的跳转地址。我们可以选择一个合适的跳转地址，比如<code>0x80000020</code>，放上自己写的<code>Bootloader</code>。也可以选择不跳转，运行厂家提供的<code>Bootloader</code>（BSL）。</p>
<p><code>MCU</code>下的<code>Bootloader</code>主要完成的事情有以下：</p>
<ul>
<li>关闭看门狗，初始化中断和 trap 向量表，进行时钟和外设初始化，让芯片正常运行起来。</li>
<li>提供<code>CAN</code>,<code>UART</code>, <code>ETH</code>等用于通讯功能的驱动，能够接收外部数据传输请求。</li>
<li>提供<code>FLASH</code>的读写与擦除驱动，设计服务来对通讯端口接收到的更新代码进行校验、存储，以及跳转操作系统或后续应用程序代码。</li>
<li>如有必要，还会开发一些基础诊断服务，串口交互程序等等。</li>
</ul>
<p>那么运行 Linux 的<code>SOC</code>和 PC 的这一过程有何不同呢。还是先看存储方案，运行嵌入式 Linux 的 SoC。一般将它的操作系统，文件系统和他的应用程序放在<code>nand flash</code>中。运行代码前，现将代码搬运到<code>SRAM</code>中，相比<code>MCU</code>多了一道步骤。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191101930.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191101930.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>对于<code>SOC</code>的<code>Boot ROM</code> 和 PC 的<code>BIOS</code>而言，他们结束运行前的最终任务，是将某些代码从<code>nand flash</code>搬运到<code>SRAM</code>中，其中最重要的内容就是<code>Boot Loader</code>。</p>
<p>而一般<code>SOC</code>的<code>Bootloader</code>，又分为<code>SPL</code>（Secondary Program Loader）和<code>uBOOT</code>两个阶段。<code>SPL</code>的 Secondary 就是相对于<code>BootROM</code>而言，他就像是接力赛中的第二棒选手。<code>SPL</code>会初始化更大空间的外部<code>DRAM</code>，再把<code>uBoot</code>搬运到外部<code>DRAM</code>中去运行。<code>uBoot</code>作为第三棒选手，开始运行它的初始化程序。之后再根据系统环境变量，将 OS 内核搬运到外部<code>DRAM</code>中去运行。OS 再完成根文件系统的加载等等等等。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191116399.gif">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202112191116399.gif" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
]]></content:encoded>
    </item>
    <item>
      <title>定时器 Timer 基础</title>
      <link>https://lifeislife.cn/posts/%E5%AE%9A%E6%97%B6%E5%99%A8timer%E5%9F%BA%E7%A1%80/</link>
      <pubDate>Wed, 15 Dec 2021 12:22:18 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/%E5%AE%9A%E6%97%B6%E5%99%A8timer%E5%9F%BA%E7%A1%80/</guid>
      <description>&lt;h2 id=&#34;概念&#34;&gt;概念&lt;/h2&gt;
&lt;p&gt;定时器（Timer），又叫计时器，顾名思义，它的主要功能就是计时。因为 CPU 计时会占用大量资源，而定时器独立于 CPU，专门用来计时。单核 CPU 好比人的大脑，一心不可二用，它只能知道自己当前要干什么。人可以用闹钟来提醒自己某个时间需要做某件事，而 CPU 就需要定时器来完成这样的工作。&lt;/p&gt;
&lt;p&gt;当定时器被开启后，里面的计数器就以计数器时钟的频率开始运行，内部的计数值不断增加。例如一个时钟为&lt;code&gt;1MHz&lt;/code&gt;的定时器，被开启后每隔&lt;code&gt;1us&lt;/code&gt;计数值就会加 1。但计数值不可能无限增加，最大值比如&lt;code&gt;65535&lt;/code&gt;。将这个十进制数转为二进制数后应该是一个 16 位的二进制数&lt;code&gt;1111 1111 1111 1111&lt;/code&gt;。所以我们需要有一个 16 位大小的存储空间来存储它。那这就是一个 16 位定时器。&lt;/p&gt;
&lt;h2 id=&#34;功能&#34;&gt;功能&lt;/h2&gt;
&lt;p&gt;定时器可以让 SoC 在执行主程序的同时，可以 (通过定时器) 具有计时功能，到了一定时间 (计时结束) 后，定时器会产生中断提醒 CPU，CPU 会去处理中断并执行定时器中断的 ISR，从而去执行预先设定好的事件。打个比方，定时器就像一个秘书，CPU 就是老板。老板每天都有很多事要做，具体时间安排不想操心，就安排给秘书。秘书每天就是盯着表，到点就提醒老板要做某事。&lt;/p&gt;
&lt;h2 id=&#34;原理&#34;&gt;原理&lt;/h2&gt;
&lt;p&gt;外设的工作频率是与它所挂载在的外设总线的时钟频率相同的。但工作频率不是时钟频率，工作频率到时钟频率需要进行一次分频。这个可调节的分频值使得定时器的计时更加灵活。这个分频值就是需要设置的第一个参数&lt;strong&gt;预分频系数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;$$
计数器时钟频率 = 工作频率/(预分频系数+1)
$$&lt;/p&gt;
&lt;p&gt;$$
定时频率 = 计时器时钟频率/(自动重载值+1)
$$&lt;/p&gt;
&lt;p&gt;假设定时器时钟频率为&lt;code&gt;1MHz&lt;/code&gt;，那定时&lt;code&gt;1ms&lt;/code&gt;该如何做？计数 1000 次即可。最大的计数值就是&lt;strong&gt;自动重载值&lt;/strong&gt;，是我们需要设置的第二个参数。定时器被打开后，计数值就增加，一旦达到自动重载值就会出发定时器溢出中断，就实现了定时&lt;code&gt;1ms&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id=&#34;计数模式&#34;&gt;计数模式&lt;/h2&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211214100956.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211214100956.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;中心计数：计数器从 0 开始计数到自动装入的值 -1，产生一个计数器溢出事件，0 然后向下计数到 1 并且产生一个计数器溢出事件，然后再从 0 开始重新计数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;向上计数：计数器从 0 计数到自动加载值 (TIMx_ARR) ，然后重新从 0 开始计数并且产生一个计数器溢出事件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;向下计数：计数器从自动装入的值 (TIMx_ARR) 开始向下计数到 0，然后从自动装入的值重新开始，并产生一个计数器向下溢出事件。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211214140256.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211214140256.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h2 id="概念">概念</h2>
<p>定时器（Timer），又叫计时器，顾名思义，它的主要功能就是计时。因为 CPU 计时会占用大量资源，而定时器独立于 CPU，专门用来计时。单核 CPU 好比人的大脑，一心不可二用，它只能知道自己当前要干什么。人可以用闹钟来提醒自己某个时间需要做某件事，而 CPU 就需要定时器来完成这样的工作。</p>
<p>当定时器被开启后，里面的计数器就以计数器时钟的频率开始运行，内部的计数值不断增加。例如一个时钟为<code>1MHz</code>的定时器，被开启后每隔<code>1us</code>计数值就会加 1。但计数值不可能无限增加，最大值比如<code>65535</code>。将这个十进制数转为二进制数后应该是一个 16 位的二进制数<code>1111 1111 1111 1111</code>。所以我们需要有一个 16 位大小的存储空间来存储它。那这就是一个 16 位定时器。</p>
<h2 id="功能">功能</h2>
<p>定时器可以让 SoC 在执行主程序的同时，可以 (通过定时器) 具有计时功能，到了一定时间 (计时结束) 后，定时器会产生中断提醒 CPU，CPU 会去处理中断并执行定时器中断的 ISR，从而去执行预先设定好的事件。打个比方，定时器就像一个秘书，CPU 就是老板。老板每天都有很多事要做，具体时间安排不想操心，就安排给秘书。秘书每天就是盯着表，到点就提醒老板要做某事。</p>
<h2 id="原理">原理</h2>
<p>外设的工作频率是与它所挂载在的外设总线的时钟频率相同的。但工作频率不是时钟频率，工作频率到时钟频率需要进行一次分频。这个可调节的分频值使得定时器的计时更加灵活。这个分频值就是需要设置的第一个参数<strong>预分频系数</strong>。</p>
<p>$$
计数器时钟频率 = 工作频率/(预分频系数+1)
$$</p>
<p>$$
定时频率 = 计时器时钟频率/(自动重载值+1)
$$</p>
<p>假设定时器时钟频率为<code>1MHz</code>，那定时<code>1ms</code>该如何做？计数 1000 次即可。最大的计数值就是<strong>自动重载值</strong>，是我们需要设置的第二个参数。定时器被打开后，计数值就增加，一旦达到自动重载值就会出发定时器溢出中断，就实现了定时<code>1ms</code>。</p>
<h2 id="计数模式">计数模式</h2>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211214100956.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211214100956.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<ul>
<li>
<p>中心计数：计数器从 0 开始计数到自动装入的值 -1，产生一个计数器溢出事件，0 然后向下计数到 1 并且产生一个计数器溢出事件，然后再从 0 开始重新计数。</p>
</li>
<li>
<p>向上计数：计数器从 0 计数到自动加载值 (TIMx_ARR) ，然后重新从 0 开始计数并且产生一个计数器溢出事件。</p>
</li>
<li>
<p>向下计数：计数器从自动装入的值 (TIMx_ARR) 开始向下计数到 0，然后从自动装入的值重新开始，并产生一个计数器向下溢出事件。</p>
</li>
</ul>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211214140256.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211214140256.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
