<?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>DMA on 夜云泊</title>
    <link>https://lifeislife.cn/tags/dma/</link>
    <description>feedId:57980998056508425+userId:73222296380546048 Recent content in DMA on 夜云泊</description>
    <generator>Hugo -- 0.163.1</generator>
    <language>zh</language>
    <lastBuildDate>Sat, 31 Aug 2024 17:00:13 +0800</lastBuildDate>
    <atom:link href="https://lifeislife.cn/tags/dma/index.xml" rel="self" type="application/rss+xml" />
    <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>计算机组成原理-存储与 IO 系统</title>
      <link>https://lifeislife.cn/posts/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86-%E5%AD%98%E5%82%A8%E4%B8%8Eio%E7%B3%BB%E7%BB%9F/</link>
      <pubDate>Sun, 08 May 2022 10:48:23 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86-%E5%AD%98%E5%82%A8%E4%B8%8Eio%E7%B3%BB%E7%BB%9F/</guid>
      <description>&lt;h2 id=&#34;存储器&#34;&gt;存储器&lt;/h2&gt;
&lt;h3 id=&#34;存储器的层次结构&#34;&gt;存储器的层次结构&lt;/h3&gt;
&lt;h4 id=&#34;sramstatic-random-access-memory静态随机存取存储器&#34;&gt;SRAM（Static Random-Access Memory，静态随机存取存储器）&lt;/h4&gt;
&lt;p&gt;CPU 如果形容成人的大脑的话，那么 CPU Cache (高速缓存) 就好比人的记忆。它用的是 SRAM 芯片。&lt;/p&gt;
&lt;p&gt;SRAM 的“静态”的意思是，只要处于通电状态，里面的数据就保持存在，一旦断电，数据就会丢失。SRAM 里 1bit 数据需要 6-8 个晶体管，所以 SRAM 的存储密度不高，同样的物理空间，能够存的数据有限。因为其电路简单，访问速度非常快。&lt;/p&gt;
&lt;p&gt;在 CPU 里，通常会有 L1、L2、L3 这样三层高速缓存。每个 CPU 核心都有一块属于自己的 L1 高速缓存，通常分成指令缓存和数据缓存，分开存放 CPU 使用的指令和数据。&lt;/p&gt;
&lt;p&gt;L2 的 Cache 同样是每个 CPU 核心都有的，不过它往往不在 CPU 核心的内部。所以，L2 Cache 的访问速度会比 L1 稍微慢一些。而 L3Cache，则通常是多个 CPU 核心共用的，尺寸会更大一些，访问速度自然也就更慢一些。&lt;/p&gt;
&lt;p&gt;你可以把 CPU 中的 L1Cache 理解为我们的短期记忆，把 L2/L3Cache 理解成长期记忆，把内存当成我们拥有的书架或者书桌。当我们自己记忆中没有资料的时候，可以从书桌或者书架上拿书来翻阅。这个过程中就相当于，数据从内存中加载到 CPU 的寄存器和 Cache 中，然后通过“大脑”，也就是 CPU，进行处理和运算。&lt;/p&gt;
&lt;h4 id=&#34;dramdynamic-random-access-memory动态随机存取存储器&#34;&gt;DRAM（Dynamic Random Access Memory，动态随机存取存储器）&lt;/h4&gt;
&lt;p&gt;内存用的芯片和 Cache 有所不同，它用的是一种叫作 DRAM 的芯片，比起 SRAM 来说，它的密度更高，有更大的容量，而且它也比 SRAM 芯片便宜不少。&lt;/p&gt;
&lt;p&gt;DRAM 被称为“动态”存储器，是因为 DRAM 需要靠不断地“刷新”，才能保持数据被存储起来。DRAM 的一个比特，只需要一个晶体管和一个电容就能存储。所以，DRAM 在同样的物理空间下，能够存储的数据也就更多，也就是存储的“密度”更大。但是，因为数据是存储在电容里的，电容会不断漏电，所以需要定时刷新充电，才能保持数据不丢失。DRAM 的数据访问电路和刷新电路都比 SRAM 更复杂，所以访问延时也就更长。&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/202205081652018.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081652018.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;从 Cache、内存，到 SSD 和 HDD 硬盘，一台现代计算机中，就用上了所有这些存储器设备。其中，容量越小的设备速度越快，而且，CPU 并不是直接和每一种存储器设备打交道，而是每一种存储器设备，只和它相邻的存储设备打交道。比如，CPUCache 是从内存里加载而来的，或者需要写回内存，并不会直接写回数据到硬盘，也不会直接从硬盘加载数据到 CPUCache 中，而是先加载到内存，再从内存加载到 Cache 中。&lt;/p&gt;
&lt;p&gt;这样，各个存储器只和相邻的一层存储器打交道，并且随着一层层向下，存储器的容量逐层增大，访问速度逐层变慢，而单位存储成本也逐层下降，也就构成了我们日常所说的存储器层次结构。&lt;/p&gt;
&lt;h2 id=&#34;缓存&#34;&gt;缓存&lt;/h2&gt;
&lt;h3 id=&#34;cpu-cache&#34;&gt;CPU cache&lt;/h3&gt;
&lt;h3 id=&#34;高速缓存&#34;&gt;高速缓存&lt;/h3&gt;
&lt;p&gt;缓存不是 CPU 的专属功能，可以把它当成一种策略，任何时候想要增加数据传输性能，都可以通过加一层缓存试试。&lt;/p&gt;
&lt;p&gt;存储器层次结构的中心思想是，对于每个$k$，位于$k$层的更快更小的存储设备作为位于$k+1$层的更大更慢的存储设备的缓存。&lt;a href=&#34;#%E5%AD%98%E5%82%A8%E5%99%A8%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E4%B8%AD%E5%9F%BA%E6%9C%AC%E7%9A%84%E7%BC%93%E5%AD%98%E5%8E%9F%E7%90%86&#34;&gt;下图&lt;/a&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/20220711145943.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711145943.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;div id=&#34;存储器层次结构中基本的缓存原理&#34;&gt;&lt;/div&gt;
&lt;p&gt;数据总是以块&lt;code&gt;block&lt;/code&gt;为单位，在层与层之间来回复制。&lt;/p&gt;
&lt;p&gt;说回高速缓存，按照摩尔定律，CPU 的访问速度每 18 个月便会翻一翻，相当于每年增长 60%。内存的访问速度虽然不断增长，却远没有那么快，每年只增长 7% 左右。这样就导致 CPU 性能和内存访问的差距不断拉大。为了弥补两者之间差异，现代 CPU 引入了&lt;strong&gt;高速缓存&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/20220708092012.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220708092012.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;CPU 的读（load）实质上就是从缓存中读取数据到寄存器（register）里，在多级缓存的架构中，如果缓存中找不到数据（Cache miss），就会层层读取二级缓存三级缓存，一旦所有的缓存里都找不到对应的数据，就要去内存里寻址了。寻址到的数据首先放到寄存器里，其副本会驻留到 CPU 的缓存中。&lt;/p&gt;
&lt;p&gt;CPU 的写（store）也是针对缓存作写入。并不会直接和内存打交道，而是通过某种机制实现数据从缓存到内存的写回（write back）。&lt;/p&gt;
&lt;p&gt;缓存到底如何与 CPU 和主存数据交换的？CPU 如何从缓存中读写数据的？缓存中没有读的数据，或者缓存写满了怎么办？我们先从 CPU 如何读取数据说起。&lt;/p&gt;
&lt;h4 id=&#34;缓存读取&#34;&gt;缓存读取&lt;/h4&gt;
&lt;p&gt;CPU 发起一个读取请求后，返回的结果会有如下几种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存命中 (cache hit)
要读取的数据刚好在缓存中，叫做&lt;strong&gt;缓存命中&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;缓存不命中 (cache miss)
发送缓存不命中，缓存就得执行一直&lt;strong&gt;放置策略&lt;/strong&gt;(placement policy)，比如 LRU。来决定从主存中取出的数据放到哪里。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;强制性不命中&lt;/strong&gt;(compulsory miss)/冷不命中(cold miss)：缓存中没有要读取的数据，需要从主存读取数据，并将数据放入缓存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;冲突不命中&lt;/strong&gt;(conflict miss)：缓存中有要读的数据，在采取放置策略时，从主存中取数据放到缓存时发生了冲突，这叫做冲突不命中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;高速缓存存储器组织结构&#34;&gt;高速缓存存储器组织结构&lt;/h4&gt;
&lt;p&gt;整个 Cache 被划分为 1 个或多个&lt;strong&gt;组&lt;/strong&gt; (Set)，$S$ 表示组的个数。每个组包含 1 个或多个&lt;strong&gt;缓存行&lt;/strong&gt;(Cache line)，$E$ 表示一个组中缓存行的行数。每个缓存行由三部分组成：&lt;strong&gt;有效位&lt;/strong&gt;(valid)，&lt;strong&gt;标记位&lt;/strong&gt;（tag），&lt;strong&gt;数据块&lt;/strong&gt;（cache block）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有效位：该位等于 1，表示这个行数据有效。&lt;/li&gt;
&lt;li&gt;标记位：唯一的标识了存储在高速缓存中的块，标识目标数据是否存在当前的缓存行中。&lt;/li&gt;
&lt;li&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/20220711171136.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711171136.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;Cache 的结构可以由元组$(S,E,B,m)$表示。不包括有效位和标记位。Cache 的大小为 $C=S \times E \times B$.&lt;/p&gt;
&lt;p&gt;接下来看看 Cache 是如何工作的，当 CPU 执行数据加载指令，从内存地址 A 读取数据时，根据存储器层次原理，如果 Cache 中保存着目标数据的副本，那么就立即将数据返回给 CPU。那么 Cache 如何知道自己保存了目标数据的副本呢？&lt;/p&gt;
&lt;p&gt;假设目标地址的数据长度为$m$位，这个地址被参数 $S$ 和 $B$ 分成了三个字段：&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/20220711174416.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711174416.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;首先通过长度为$s$的&lt;strong&gt;组索引&lt;/strong&gt;，确定目标数据保存在哪一个组 (Set) 中，其次通过长度为$t$的&lt;strong&gt;标记&lt;/strong&gt;，确定在哪一行，需要注意的是此时有效位必须等于 1，最后根据长度为$b$的&lt;strong&gt;块偏移&lt;/strong&gt;，来确定目标数据在数据块中的确切位置。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Q：既然读取 Cache 第一步是组选择，为什么不用高位作为组索引，而使用中间的为作为组索引？
A：如果使用了高位作索引，那么一些连续的内存块就会映射到相同的高速缓存块。如图前四个块映射到第一个缓存组，第二个四个块映射到第二个组，依次类推。如果一个程序有良好的空间局部性，顺序扫描一个数组的元素，那么在任何时候，缓存中都只保存在一个块大小的数组内容。这样对缓存的使用率很低。相比而言，如果使用中间的位作为组索引，那么相邻的块总是映射到不同的组，图中的情况能够存放整个大小的数组片。


&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/20220711185819.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711185819.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;h5 id=&#34;直接映射高速缓存-direct-mapped-cache&#34;&gt;直接映射高速缓存 Direct Mapped Cache&lt;/h5&gt;
&lt;p&gt;根据每个组的缓存行数 $E$ 的不同，Cache 被分为不同的类。每个组只有一行$E=1$的高速缓存被称为&lt;strong&gt;直接映射高速缓存&lt;/strong&gt;(direct-mapped cache)。&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/20220711190845.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711190845.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;当一条加载指令指示 CPU 从主存地址 A 中读取一个字 w 时，会将该主存地址 A 发送到高速缓存中，则高速缓存会根据&lt;strong&gt;组选择&lt;/strong&gt;，&lt;strong&gt;行匹配&lt;/strong&gt;和&lt;strong&gt;字抽取&lt;/strong&gt;三步来判断地址 A 是否命中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;组选择&lt;/strong&gt;(set selection)：根据组索引值来确定属于哪一个组，如图中索引长度为 5 位，可以检索 32 个组 ($2^5=32$)。当$s=0$时，此时组选择的结果为&lt;code&gt;set 0&lt;/code&gt;，当$s=1$时，此时组选择的结果为&lt;code&gt;set 1&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/20220711191708.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711191708.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;行匹配 (line match)&lt;/strong&gt;：首先看缓存行的有效位，此时有效位为 1，表示当前数据有效。然后对比缓存行的标记&lt;code&gt;0110&lt;/code&gt;与地址中的标记&lt;code&gt;0110&lt;/code&gt;是否相等，如果相等，则表示目标数据在当前的缓存行中（缓存命中）。如果不一致或者有效位为 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/20220711192435.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711192435.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;字抽取 (word extraction)&lt;/strong&gt;：根据偏移量$b$确定目标数据的确切位置，通俗来说就是从数据块的什么位置开始抽取位置。如当偏移块等于&lt;code&gt;100&lt;/code&gt;时，表示目标数据起始地址位于字节 4 处。&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/20220711192757.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711192757.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;下面通过一个例子来解释清除这个过程。假设我们有一个直接映射高速缓存，描述为$(S,E,B,m) = (4,1,2,4)$。换句话说，高速缓存有 4 个组，每个组 1 行，每个数据块 2 个字节，地址长度为 4 位。&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/20220711194256.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711194256.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 个内存块，但只有 4 个高速缓存组，所以会有多个块映射到同一个高速缓存组中。例如，块 0 和块 4 都会被映射到组 0。&lt;/p&gt;
&lt;p&gt;下面我们来模拟当 CPU 执行一系列读的时候，高速缓存的执行情况，我们假设每次 CPU 读 1 个字节的字。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;读地址 0(0000) 的字：&lt;/strong&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/20220711193901.gif&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711193901.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;
&lt;p&gt;&lt;strong&gt;读地址 1(0001) 的字：&lt;/strong&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/20220711194838.gif&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711194838.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;
&lt;p&gt;&lt;strong&gt;读地址 13(1101) 的字：&lt;/strong&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/20220711195108.gif&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711195108.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;
&lt;p&gt;&lt;strong&gt;读地址 8(1000) 的字：&lt;/strong&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/20220711200054.gif&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711200054.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;
&lt;p&gt;&lt;strong&gt;读地址 0(0000) 的字：&lt;/strong&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/20220711200409.gif&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711200409.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;
&lt;h5 id=&#34;组相联高速缓存-set-associative-cache&#34;&gt;组相联高速缓存 Set Associative Cache&lt;/h5&gt;
&lt;p&gt;由于直接映射高速缓存的组中只有一行，所以容易发生冲突不命中。组相联高速缓存 (Set associative cache) 运行有多行缓存行。但是缓存行最大不能超过 $C/B$。&lt;/p&gt;
&lt;p&gt;如图一个组中包含了两行缓存行，这种我们称为 2 路相联高速缓存。&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/20220712160513.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220712160513.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;组选择&lt;/strong&gt;：与直接映射高速缓存的组选择过程一样。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;行匹配&lt;/strong&gt;：因为一个组有多行，所以需要遍历所有行，找到一个有效位为 1，并且标记为与地址中的标记位相匹配的一行。如果找到了，表示缓存命中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;字抽取&lt;/strong&gt;：根据偏移量$b$确定目标数据的确切位置，通俗来说就是从数据块的什么位置开始抽取位置。如当偏移块等于&lt;code&gt;100&lt;/code&gt;时，表示目标数据起始地址位于字节 4 处。&lt;/p&gt;
&lt;p&gt;如果不命中，那么就需要从主存中取出需要的数据块，但是将数据块放在哪一行缓存行呢？如果存在空行 ($valid=0$)，那就放到空行里。如果没有空行，就得选择一个非空行来替换，同时希望 CPU 不会很快引用这个被替换的行。这里介绍几个替换策略。&lt;/p&gt;
&lt;p&gt;最简单的方式就是随机选择一行来替换，其他复杂的方式就是利用局部性原理，使得接下来 CPU 引用替换的行概率最小。如&lt;/p&gt;
&lt;h3 id=&#34;缓存一致性协议-mesi&#34;&gt;缓存一致性协议 MESI&lt;/h3&gt;
&lt;h4 id=&#34;为什么需要缓存一致&#34;&gt;为什么需要缓存一致&lt;/h4&gt;
&lt;p&gt;目前主流电脑的 CPU 都是多核心的，多核心的有点就是在不能提升 CPU 主频后，通过增加核心来提升 CPU 吞吐量。每个核心都有自己的 L1 Cache 和 L2 Cache，只是共用 L3 Cache 和主内存。每个核心操作是独立的，每个核心的 Cache 就不是同步更新的，这样就会带来缓存一致性（Cache Coherence）的问题。&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/202205291536919.gif&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205291536919.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;
&lt;p&gt;有 2 个 CPU，主内存里有个变量&lt;code&gt;x=0&lt;/code&gt;。CPU A 中有个需要将变量&lt;code&gt;x&lt;/code&gt;加&lt;code&gt;1&lt;/code&gt;。CPU A 就将变量&lt;code&gt;x&lt;/code&gt;加载到自己的缓存中，然后将变量&lt;code&gt;x&lt;/code&gt;加&lt;code&gt;1&lt;/code&gt;。因为此时 CPU A 还未将缓存数据写回主内存，CPU B 再读取变量&lt;code&gt;x&lt;/code&gt;时，变量&lt;code&gt;x&lt;/code&gt;的值依然是&lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这里的问题就是所谓的缓存一致性问题，因为 CPU A 的缓存与 CPU B 的缓存是不一致的。&lt;/p&gt;
&lt;h4 id=&#34;如何解决缓存一致性问题&#34;&gt;如何解决缓存一致性问题&lt;/h4&gt;
&lt;h5 id=&#34;通过在总线加-lock-锁的方式&#34;&gt;通过在总线加 LOCK 锁的方式&lt;/h5&gt;
&lt;p&gt;在锁住总线上加一个 LOCK 标识，CPU A 进行读写操作时，锁住总线，其他 CPU 此时无法进行内存读写操作，只有等解锁了才能进行操作。&lt;/p&gt;
&lt;p&gt;该方式因为锁住了整个总线，所以效率低。&lt;/p&gt;
&lt;h5 id=&#34;缓存一致性协议-mesi-1&#34;&gt;缓存一致性协议 MESI&lt;/h5&gt;
&lt;p&gt;该方式对单个缓存行的数据进行加锁，不会影响到内存其他数据的读写。&lt;/p&gt;
&lt;p&gt;在学习 MESI 协议之前，简单了解一下总线嗅探机制（Bus Snooping）。要对自己的缓存加锁，需要通知其他 CPU，多个 CPU 核心之间的数据传播问题。最常见的一种解决方案就是总线嗅探。&lt;/p&gt;
&lt;p&gt;这个策略，本质上就是把所有的读写请求都通过总线广播给所有的 CPU 核心，然后让各个核心去“嗅探”这些请求，再根据本地的情况进行响应。MESI 就是基于总线嗅探机制的缓存一致性协议。&lt;/p&gt;
&lt;p&gt;MESI 协议的由来是对 Cache Line 的四个不同的标记，分别是：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:50px&#34;&gt;状态&lt;/div&gt;&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:100px&#34;&gt;状态&lt;/div&gt;&lt;/th&gt;
					&lt;th&gt;&lt;div style=&#34;width:200px&#34;&gt;描述&lt;/div&gt;&lt;/th&gt;
					&lt;th&gt;&lt;div style=&#34;width:200px&#34;&gt;监听任务&lt;/div&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;Modified&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;已修改&lt;/td&gt;
					&lt;td&gt;该 Cache Line 有效，数据被修改了，和内存中的数据不一致，数据只存在于本 Cache 中&lt;/td&gt;
					&lt;td&gt;Cache Line 必须时刻监听所有试图读该 Cache Line 相对于主存的操作，这种操作必须在缓存将该 Cache Line 写回主存并将状态改为 S 状态之前，被延迟执行&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Exclusive&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;独享，互斥&lt;/td&gt;
					&lt;td&gt;该 Cache Line 有效，数据和内存中的数据一直，数据只存在于本 Cache&lt;/td&gt;
					&lt;td&gt;Cache Line 必须监听其他缓存读主存中该 Cache Line 的操作，一旦有这种操作，该 Cache Line 需要改为 S 状态&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Shared&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;共享的&lt;/td&gt;
					&lt;td&gt;该 Cache Line 有效，数据和内存中的数据一直，数据存在于很多个 Cache 中&lt;/td&gt;
					&lt;td&gt;Cache Line 必须监听其他  Cache Line 使该 Cache Line 无效或者独享该 Cache Line 的请求，并将 Cache Line 改为 I 状态&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Invalid&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;无效的&lt;/td&gt;
					&lt;td&gt;该 Cache Line 无效&lt;/td&gt;
					&lt;td&gt;无&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;整个 MESI 的状态，可以用一个有限状态机来表示它的状态流转。需要注意的是，对于不同状态触发的事件操作，可能来自于当前 CPU 核心，也可能来自总线里其他 CPU 核心广播出来的信号。我把各个状态之间的流转用表格总结了一下：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;当前状态&lt;/div&gt;&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;事件&lt;/div&gt;&lt;/th&gt;
					&lt;th&gt;&lt;div style=&#34;width:300px,center&#34;&gt;行为&lt;/div&gt;&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;下个状态&lt;/div&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;M&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Local Read&lt;/td&gt;
					&lt;td&gt;从 Cache 中读，状态不变&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;M&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;M&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Local Write&lt;/td&gt;
					&lt;td&gt;修改 cache 数据，状态不变&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;M&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;M&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Remote Read&lt;/td&gt;
					&lt;td&gt;这行数据被写到内存中，使其他核能使用到最新数据，状态变为 S&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;S&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;M&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Remote Write&lt;/td&gt;
					&lt;td&gt;这行数据被写入内存中，其他核可以获取到最新数据，由于其他 CPU 修改该条数据，则本地 Cache 变为 I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;I&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;当前状态&lt;/div&gt;&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;事件&lt;/div&gt;&lt;/th&gt;
					&lt;th&gt;&lt;div style=&#34;width:200px,center&#34;&gt;行为&lt;/div&gt;&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;下个状态&lt;/div&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;E&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Local Read&lt;/td&gt;
					&lt;td&gt;从 Cache 中读，状态不变&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;E&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;E&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Local Write&lt;/td&gt;
					&lt;td&gt;修改数据，状态改为 M&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;M&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;E&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Remote Read&lt;/td&gt;
					&lt;td&gt;数据和其他 CPU 共享，变为 S&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;S&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;E&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Remote Write&lt;/td&gt;
					&lt;td&gt;数据被修改，本地缓存失效，变为 I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;I&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;当前状态&lt;/div&gt;&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;事件&lt;/div&gt;&lt;/th&gt;
					&lt;th&gt;&lt;div style=&#34;width:200px,text-align: center&#34;&gt;行为&lt;/div&gt;&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;下个状态&lt;/div&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;S&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Local Read&lt;/td&gt;
					&lt;td&gt;从 Cache 中读，状态不变&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;S&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;S&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Local Write&lt;/td&gt;
					&lt;td&gt;修改数据，状态改为 M，其他 CPU 的 Cache Line 状态改为 I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;M&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;S&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Remote Read&lt;/td&gt;
					&lt;td&gt;数据和其他 CPU 共享，状态不变&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;S&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;S&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Remote Write&lt;/td&gt;
					&lt;td&gt;数据被修改，本地缓存失效，变为 I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;I&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;当前状态&lt;/div&gt;&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;事件&lt;/div&gt;&lt;/th&gt;
					&lt;th&gt;&lt;div style=&#34;width:200px,center&#34;&gt;行为&lt;/div&gt;&lt;/th&gt;
					&lt;th style=&#34;text-align: center&#34;&gt;&lt;div style=&#34;width:80px&#34;&gt;下个状态&lt;/div&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;I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Local Read&lt;/td&gt;
					&lt;td&gt;1. 如果其他 CPU 没有这份数据，直接从内存中加载数据，状态变为 E；&lt;br&gt; 2. 如果其他 CPU 有这个数据，且 Cache Line 状态为 M，则先把 Cache Line 中的内容写回到主存。本地 Cache 再从内存中读取数据，这时两个 Cache Line 的状态都变为 S；&lt;br&gt;3. 如果其他 Cache Line 有这份数据，并且状态为 S 或者 E，则本地 Cache Line 从主存读取数据，并将这些 Cache Line 状态改为 S&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;E 或者 S&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Local Write&lt;/td&gt;
					&lt;td&gt;1. 先从内存中读取数据，如果其他 Cache Line 中有这份数据，且状态为 M，则现将数据更新到主存再读取，将 Cache Line 状态改为 M；&lt;br&gt; 2. 如果其他 Cache Line 有这份数据，且状态为 E 或者 S，则其他 Cache Line 状态改为 I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;M&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Remote Read&lt;/td&gt;
					&lt;td&gt;数据和其他 CPU 共享，状态不变&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;S&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;Remote Write&lt;/td&gt;
					&lt;td&gt;数据被修改，本地缓存失效，变为 I&lt;/td&gt;
					&lt;td style=&#34;text-align: center&#34;&gt;I&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;内存&#34;&gt;内存&lt;/h2&gt;
&lt;p&gt;计算机有五大组成部分，分别是：运算器、控制器、存储器、输入设备和输出设备。而内存就是其中的存储器。我们的数据和指令都需要先放到内存中，然后再被 CPU 执行。&lt;/p&gt;
&lt;p&gt;操作系统中程序并不能直接访问物理内存，我们的内存需要被分成固定大小的页（Page），然后再通过&lt;strong&gt;虚拟内存地址（Virtual Address）到物理内存地址（Physical Address）的地址转换（Address Translation）&lt;/strong&gt;，才能到达实际存放数据的物理内存位置。而我们的程序看到的内存地址，都是虚拟内存地址。那么如何进行转换的呢？&lt;/p&gt;
&lt;h3 id=&#34;简单页表&#34;&gt;简单页表&lt;/h3&gt;
&lt;p&gt;最简单的方式，就是建立一张虚拟内存到物理内存的映射表，在计算机里叫做页表（Page Table）。页表这个地址转换的办法，会把一个内存地址分成页号（Directory）和偏移量（Offset）两个部分，是不是似曾相识，因为在前面的高速缓存里，缓存的结构也是这样的。&lt;/p&gt;
&lt;p&gt;以一个 32 位地址举例，高 20 位是虚拟页号，可以从虚拟页表中找到物理页号的信息，低 12 位是偏移量，可以准确获得物理地址。


&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/202207161640968.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202207161640968.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;把虚拟内存地址，切分成页号和偏移量的组合；&lt;/li&gt;
&lt;li&gt;从页表里面，查询出虚拟页号，对应的物理页号；&lt;/li&gt;
&lt;li&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/202207161645714.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202207161645714.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;但是这样的页表有个问题，它需要记录$2^{20}$个物理页表，这个存储关系，就好比一个 $2^{20}$大小的数组。一个页号是完整的 32 位的 4 字节（Byte），这样一个页表就需要 4MB 的空间。并且每个进程都会有这样一个页表，现代电脑正常都有成百上千个进程，如果用这样的页表肯定行不通的。&lt;/p&gt;
&lt;h3 id=&#34;多级页表&#34;&gt;多级页表&lt;/h3&gt;
&lt;p&gt;所以，&lt;strong&gt;在一个实际的程序进程里面，虚拟内存占用的地址空间，通常是两段连续的空间。而不是完全散落的随机的内存地址&lt;/strong&gt;。而多级页表，就特别适合这样的内存地址分布。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/357648933&#34;&gt;谈一谈内存管理，虚拟内存，多级页表 - 知乎&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;tlb&#34;&gt;TLB&lt;/h3&gt;
&lt;h3 id=&#34;内存保护---可执行空间保护&#34;&gt;内存保护 - 可执行空间保护&lt;/h3&gt;
&lt;h3 id=&#34;内存保护---地址空间布局随机化&#34;&gt;内存保护 - 地址空间布局随机化&lt;/h3&gt;
&lt;p&gt;Address Space Layout Randomization&lt;/p&gt;
&lt;h2 id=&#34;总线计算机内部的高速公路&#34;&gt;总线：计算机内部的高速公路&lt;/h2&gt;
&lt;p&gt;计算机由控制器、运算器、存储器、输入设备以及输出设备五大部分组成。CPU 所代表的控制器和运算器，要和存储器，也就是我们的主内存，以及输入和输出设备进行通信。那么计算机是用什么样的方式来完成，CPU 和内存、以及外部输入输出设备的通信呢？答案就是通过总线来通信。&lt;/p&gt;
&lt;p&gt;计算机里有不同的硬件设备，如果设备与设备之间都单独连接，那么就需要 N*N 的连线。那么怎么降低复杂度呢？与其让各个设备之间互相单独通信，不如我们去设计一个公用的线路。CPU 想要和什么设备通信，通信的指令是什么，对应的数据是什么，都发送到这个线路上；设备要向 CPU 发送什么信息呢，也发送到这个线路上。这个线路就好像一个高速公路，各个设备和其他设备之间，不需要单独建公路，只建一条小路通向这条高速公路就好了。&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/202205081510203.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081510203.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/202205081510711.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081510711.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;首先，CPU 和内存以及高速缓存通信的总线，这里面通常有两种总线。这种方式，我们称之为双独立总线（Dual Independent Bus，缩写为 DIB）。CPU 里，有一个快速的本地总线（Local Bus），以及一个速度相对较慢的前端总线（Front-side Bus）。&lt;/p&gt;
&lt;p&gt;现代的 CPU 里，通常有专门的高速缓存芯片。这里的高速本地总线，就是用来和高速缓存通信的。而前端总线，则是用来和主内存以及输入输出设备通信的。有时候，我们会把本地总线也叫作后端总线（Back-sideBus），和前面的前端总线对应起来。&lt;/p&gt;
&lt;p&gt;除了前端总线呢，我们常常还会听到 PCI 总线、I/O 总线或者系统总线（System Bus）。看到这么多总线的名字，你是不是已经有点晕了。这些名词确实容易混为一谈。其实各种总线的命名一直都很混乱，我们不如直接来看一看 CPU 的硬件架构图。对照图来看，一切问题就都清楚了。&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/202205081513938.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081513938.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;CPU 里面的北桥芯片，把我们上面说的前端总线，一分为二，变成了三个总线。我们的前端总线，其实就是系统总线。CPU 里面的内存接口，直接和系统总线通信，然后系统总线再接入一个 I/O 桥接器（I/OBridge）。这个 I/O 桥接器，一边接入了我们的内存总线，使得我们的 CPU 和内存通信；另一边呢，又接入了一个 I/O 总线，用来连接 I/O 设备。&lt;/p&gt;
&lt;p&gt;事实上，真实的计算机里，这个总线层面拆分得更细。根据不同的设备，还会分成独立的 PCI 总线、ISA 总线等等。


&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/202205081516341.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081516341.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;ol&gt;
&lt;li&gt;数据线（Data Bus），用来传输实际的数据信息，也就是实际上了公交车的“人”。&lt;/li&gt;
&lt;li&gt;地址线（Address Bus），用来确定到底把数据传输到哪里去，是内存的某个位置，还是某一个 I/O 设备。这个其实就相当于拿了个纸条，写下了上面的人要下车的站点。&lt;/li&gt;
&lt;li&gt;控制线（ControlBus），用来控制对于总线的访问。虽然我们把总线比喻成了一辆公交车。那么有人想要做公交车的时候，需要告诉公交车司机，这个就是我们的控制信号。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;尽管总线减少了设备之间的耦合，也降低了系统设计的复杂度，但同时也带来了一个新问题，那就是&lt;strong&gt;总线不能同时给多个设备提供通信功能&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们的总线是很多个设备公用的，那多个设备都想要用总线，我们就需要有一个机制，去决定这种情况下，到底把总线给哪一个设备用。这个机制，就叫作&lt;strong&gt;总线裁决&lt;/strong&gt;（Bus Arbitraction）&lt;/p&gt;
&lt;h2 id=&#34;硬盘&#34;&gt;硬盘&lt;/h2&gt;
&lt;h2 id=&#34;dma&#34;&gt;DMA&lt;/h2&gt;
&lt;p&gt;过去几年，计算机产业一直在为提升 I/O 设备的速度而努力，从机械硬盘 HDD 到固态硬盘 SSD，从 SATA 协议到 PCIE 协议，虽然速度都几十上百倍的增加，但是仍然不够快。因为相比于 CPU 基本都是 2GHz 的频率（每秒会有 20 亿次的操作），SSD 硬盘的 IOPS 的 2 万次操作就显得微不足道。&lt;/p&gt;
&lt;p&gt;如果我们对于 I/O 的操作，都是由 CPU 发出对应的指令，然后等待 I/O 设备完成操作之后返回，那 CPU 有大量的时间其实都是在等待 I/O 设备完成操作。特别是当传输的数据量比较大的时候，比如进行大文件复制，如果所有数据都要经过 CPU，实在是有点儿太浪费时间了。&lt;/p&gt;
&lt;p&gt;因此，计算机工程师们，就发明了&lt;strong&gt;DMA 技术&lt;/strong&gt;，也就是&lt;strong&gt;直接内存访问（Direct Memory Access）技术&lt;/strong&gt;，来减少 CPU 等待的时间。&lt;/p&gt;
&lt;h3 id=&#34;什么是-dma&#34;&gt;什么是 DMA&lt;/h3&gt;
&lt;p&gt;本质上，DMA 技术就是我们在主板上放一块&lt;strong&gt;独立的芯片&lt;/strong&gt;。在进行内存和 I/O 设备的数据传输的时候，我们不再通过 CPU 来控制数据传输，而直接通过 DMA 控制器（DMA Controller，简称 DMAC）。这块芯片，我们可以认为它其实就是一个&lt;strong&gt;协处理器&lt;/strong&gt;（Co-Processor）。&lt;/p&gt;
&lt;p&gt;DMAC 最有价值的地方体现在，当我们要传输的数据特别大、速度特别快，或者传输的数据特别小、速度特别慢的时候。&lt;/p&gt;
&lt;p&gt;比如说，我们用千兆网卡或者硬盘传输大量数据的时候，如果都用 CPU 来搬运的话，肯定忙不过来，所以可以选择 DMAC。而当数据传输很慢的时候，DMAC 可以等数据到齐了，再向 CPU 发起中断，让 CPU 去处理，而不是让 CPU 在那里忙等待。&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/202208152252016.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202208152252016.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;ol&gt;
&lt;li&gt;首先，CPU 还是作为一个主设备，向 DMAC 设备发起请求。这个请求，其实就是在 DMAC 里面修改配置寄存器。&lt;/li&gt;
&lt;li&gt;CPU 修改 DMAC 的配置的时候，会告诉 DMAC 这样几个信息：
&lt;ul&gt;
&lt;li&gt;源地址的初始值：数据要从哪里传输过来。如果我们要从内存里面写入数据到硬盘上，那么就是要读取的数据在内存里面的地址&lt;/li&gt;
&lt;li&gt;传输时候的地址增减方式：数据是从大的地址向小的地址传输，还是从小的地址往大的地址传输&lt;/li&gt;
&lt;li&gt;传输的数据长度：也就是我们一共要传输多少数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;设置完这些信息之后，DMAC 就会变成一个空闲的状态（Idle）。&lt;/li&gt;
&lt;li&gt;如果我们要从硬盘上往内存里面加载数据，这个时候，硬盘就会向 DMAC 发起一个数据传输请求。这个请求并不是通过总线，而是通过一个额外的连线。&lt;/li&gt;
&lt;li&gt;然后，我们的 DMAC 需要再通过一个额外的连线响应这个申请。&lt;/li&gt;
&lt;li&gt;DMAC 这个芯片，就向硬盘的接口发起要总线读的传输请求。数据就从硬盘里面，读到了 DMAC 的控制器里面。&lt;/li&gt;
&lt;li&gt;DMAC 再向我们的内存发起总线写的数据传输请求，把数据写入到内存里面。&lt;/li&gt;
&lt;li&gt;DMAC 会反复进行上面第 6、7 步的操作，直到 DMAC 的寄存器里面设置的数据长度传输完成。&lt;/li&gt;
&lt;li&gt;数据传输完成之后，DMAC 重新回到第 3 步的空闲状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以，整个数据传输的过程中，我们不是通过 CPU 来搬运数据，而是由 DMAC 这个芯片来搬运数据。但是 CPU 在这个过程中也是必不可少的。因为传输什么数据，从哪里传输到哪里，其实还是由 CPU 来设置的。这也是为什么，DMAC 被叫作 &lt;strong&gt;协处理器&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bilibili.com/video/BV1cJ411K7HW?spm_id_from=333.999.0.0&#34;&gt;【硬件科普】电脑主板右下角的散热片下面究竟隐藏着什么？详解主板南桥芯片组的功能和作用_哔哩哔哩_bilibili&lt;/a&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h2 id="存储器">存储器</h2>
<h3 id="存储器的层次结构">存储器的层次结构</h3>
<h4 id="sramstatic-random-access-memory静态随机存取存储器">SRAM（Static Random-Access Memory，静态随机存取存储器）</h4>
<p>CPU 如果形容成人的大脑的话，那么 CPU Cache (高速缓存) 就好比人的记忆。它用的是 SRAM 芯片。</p>
<p>SRAM 的“静态”的意思是，只要处于通电状态，里面的数据就保持存在，一旦断电，数据就会丢失。SRAM 里 1bit 数据需要 6-8 个晶体管，所以 SRAM 的存储密度不高，同样的物理空间，能够存的数据有限。因为其电路简单，访问速度非常快。</p>
<p>在 CPU 里，通常会有 L1、L2、L3 这样三层高速缓存。每个 CPU 核心都有一块属于自己的 L1 高速缓存，通常分成指令缓存和数据缓存，分开存放 CPU 使用的指令和数据。</p>
<p>L2 的 Cache 同样是每个 CPU 核心都有的，不过它往往不在 CPU 核心的内部。所以，L2 Cache 的访问速度会比 L1 稍微慢一些。而 L3Cache，则通常是多个 CPU 核心共用的，尺寸会更大一些，访问速度自然也就更慢一些。</p>
<p>你可以把 CPU 中的 L1Cache 理解为我们的短期记忆，把 L2/L3Cache 理解成长期记忆，把内存当成我们拥有的书架或者书桌。当我们自己记忆中没有资料的时候，可以从书桌或者书架上拿书来翻阅。这个过程中就相当于，数据从内存中加载到 CPU 的寄存器和 Cache 中，然后通过“大脑”，也就是 CPU，进行处理和运算。</p>
<h4 id="dramdynamic-random-access-memory动态随机存取存储器">DRAM（Dynamic Random Access Memory，动态随机存取存储器）</h4>
<p>内存用的芯片和 Cache 有所不同，它用的是一种叫作 DRAM 的芯片，比起 SRAM 来说，它的密度更高，有更大的容量，而且它也比 SRAM 芯片便宜不少。</p>
<p>DRAM 被称为“动态”存储器，是因为 DRAM 需要靠不断地“刷新”，才能保持数据被存储起来。DRAM 的一个比特，只需要一个晶体管和一个电容就能存储。所以，DRAM 在同样的物理空间下，能够存储的数据也就更多，也就是存储的“密度”更大。但是，因为数据是存储在电容里的，电容会不断漏电，所以需要定时刷新充电，才能保持数据不丢失。DRAM 的数据访问电路和刷新电路都比 SRAM 更复杂，所以访问延时也就更长。</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/202205081652018.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081652018.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>从 Cache、内存，到 SSD 和 HDD 硬盘，一台现代计算机中，就用上了所有这些存储器设备。其中，容量越小的设备速度越快，而且，CPU 并不是直接和每一种存储器设备打交道，而是每一种存储器设备，只和它相邻的存储设备打交道。比如，CPUCache 是从内存里加载而来的，或者需要写回内存，并不会直接写回数据到硬盘，也不会直接从硬盘加载数据到 CPUCache 中，而是先加载到内存，再从内存加载到 Cache 中。</p>
<p>这样，各个存储器只和相邻的一层存储器打交道，并且随着一层层向下，存储器的容量逐层增大，访问速度逐层变慢，而单位存储成本也逐层下降，也就构成了我们日常所说的存储器层次结构。</p>
<h2 id="缓存">缓存</h2>
<h3 id="cpu-cache">CPU cache</h3>
<h3 id="高速缓存">高速缓存</h3>
<p>缓存不是 CPU 的专属功能，可以把它当成一种策略，任何时候想要增加数据传输性能，都可以通过加一层缓存试试。</p>
<p>存储器层次结构的中心思想是，对于每个$k$，位于$k$层的更快更小的存储设备作为位于$k+1$层的更大更慢的存储设备的缓存。<a href="#%E5%AD%98%E5%82%A8%E5%99%A8%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E4%B8%AD%E5%9F%BA%E6%9C%AC%E7%9A%84%E7%BC%93%E5%AD%98%E5%8E%9F%E7%90%86">下图</a>展示了存储器层次结构中缓存的一般性概念。</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/20220711145943.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711145943.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>
<div id="存储器层次结构中基本的缓存原理"></div>
<p>数据总是以块<code>block</code>为单位，在层与层之间来回复制。</p>
<p>说回高速缓存，按照摩尔定律，CPU 的访问速度每 18 个月便会翻一翻，相当于每年增长 60%。内存的访问速度虽然不断增长，却远没有那么快，每年只增长 7% 左右。这样就导致 CPU 性能和内存访问的差距不断拉大。为了弥补两者之间差异，现代 CPU 引入了<strong>高速缓存</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/20220708092012.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220708092012.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>CPU 的读（load）实质上就是从缓存中读取数据到寄存器（register）里，在多级缓存的架构中，如果缓存中找不到数据（Cache miss），就会层层读取二级缓存三级缓存，一旦所有的缓存里都找不到对应的数据，就要去内存里寻址了。寻址到的数据首先放到寄存器里，其副本会驻留到 CPU 的缓存中。</p>
<p>CPU 的写（store）也是针对缓存作写入。并不会直接和内存打交道，而是通过某种机制实现数据从缓存到内存的写回（write back）。</p>
<p>缓存到底如何与 CPU 和主存数据交换的？CPU 如何从缓存中读写数据的？缓存中没有读的数据，或者缓存写满了怎么办？我们先从 CPU 如何读取数据说起。</p>
<h4 id="缓存读取">缓存读取</h4>
<p>CPU 发起一个读取请求后，返回的结果会有如下几种情况：</p>
<ul>
<li>缓存命中 (cache hit)
要读取的数据刚好在缓存中，叫做<strong>缓存命中</strong>。</li>
<li>缓存不命中 (cache miss)
发送缓存不命中，缓存就得执行一直<strong>放置策略</strong>(placement policy)，比如 LRU。来决定从主存中取出的数据放到哪里。
<ul>
<li><strong>强制性不命中</strong>(compulsory miss)/冷不命中(cold miss)：缓存中没有要读取的数据，需要从主存读取数据，并将数据放入缓存。</li>
<li><strong>冲突不命中</strong>(conflict miss)：缓存中有要读的数据，在采取放置策略时，从主存中取数据放到缓存时发生了冲突，这叫做冲突不命中。</li>
</ul>
</li>
</ul>
<h4 id="高速缓存存储器组织结构">高速缓存存储器组织结构</h4>
<p>整个 Cache 被划分为 1 个或多个<strong>组</strong> (Set)，$S$ 表示组的个数。每个组包含 1 个或多个<strong>缓存行</strong>(Cache line)，$E$ 表示一个组中缓存行的行数。每个缓存行由三部分组成：<strong>有效位</strong>(valid)，<strong>标记位</strong>（tag），<strong>数据块</strong>（cache block）。</p>
<ul>
<li>有效位：该位等于 1，表示这个行数据有效。</li>
<li>标记位：唯一的标识了存储在高速缓存中的块，标识目标数据是否存在当前的缓存行中。</li>
<li>数据块：一部分内存数据的副本。</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/20220711171136.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711171136.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>Cache 的结构可以由元组$(S,E,B,m)$表示。不包括有效位和标记位。Cache 的大小为 $C=S \times E \times B$.</p>
<p>接下来看看 Cache 是如何工作的，当 CPU 执行数据加载指令，从内存地址 A 读取数据时，根据存储器层次原理，如果 Cache 中保存着目标数据的副本，那么就立即将数据返回给 CPU。那么 Cache 如何知道自己保存了目标数据的副本呢？</p>
<p>假设目标地址的数据长度为$m$位，这个地址被参数 $S$ 和 $B$ 分成了三个字段：</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/20220711174416.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711174416.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>首先通过长度为$s$的<strong>组索引</strong>，确定目标数据保存在哪一个组 (Set) 中，其次通过长度为$t$的<strong>标记</strong>，确定在哪一行，需要注意的是此时有效位必须等于 1，最后根据长度为$b$的<strong>块偏移</strong>，来确定目标数据在数据块中的确切位置。</p>
<blockquote>
<p>Q：既然读取 Cache 第一步是组选择，为什么不用高位作为组索引，而使用中间的为作为组索引？
A：如果使用了高位作索引，那么一些连续的内存块就会映射到相同的高速缓存块。如图前四个块映射到第一个缓存组，第二个四个块映射到第二个组，依次类推。如果一个程序有良好的空间局部性，顺序扫描一个数组的元素，那么在任何时候，缓存中都只保存在一个块大小的数组内容。这样对缓存的使用率很低。相比而言，如果使用中间的位作为组索引，那么相邻的块总是映射到不同的组，图中的情况能够存放整个大小的数组片。


<!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/20220711185819.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711185819.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>
<h5 id="直接映射高速缓存-direct-mapped-cache">直接映射高速缓存 Direct Mapped Cache</h5>
<p>根据每个组的缓存行数 $E$ 的不同，Cache 被分为不同的类。每个组只有一行$E=1$的高速缓存被称为<strong>直接映射高速缓存</strong>(direct-mapped cache)。</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/20220711190845.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711190845.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>当一条加载指令指示 CPU 从主存地址 A 中读取一个字 w 时，会将该主存地址 A 发送到高速缓存中，则高速缓存会根据<strong>组选择</strong>，<strong>行匹配</strong>和<strong>字抽取</strong>三步来判断地址 A 是否命中。</p>
<p><strong>组选择</strong>(set selection)：根据组索引值来确定属于哪一个组，如图中索引长度为 5 位，可以检索 32 个组 ($2^5=32$)。当$s=0$时，此时组选择的结果为<code>set 0</code>，当$s=1$时，此时组选择的结果为<code>set 1</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/20220711191708.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711191708.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>行匹配 (line match)</strong>：首先看缓存行的有效位，此时有效位为 1，表示当前数据有效。然后对比缓存行的标记<code>0110</code>与地址中的标记<code>0110</code>是否相等，如果相等，则表示目标数据在当前的缓存行中（缓存命中）。如果不一致或者有效位为 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/20220711192435.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711192435.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>字抽取 (word extraction)</strong>：根据偏移量$b$确定目标数据的确切位置，通俗来说就是从数据块的什么位置开始抽取位置。如当偏移块等于<code>100</code>时，表示目标数据起始地址位于字节 4 处。</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/20220711192757.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711192757.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>下面通过一个例子来解释清除这个过程。假设我们有一个直接映射高速缓存，描述为$(S,E,B,m) = (4,1,2,4)$。换句话说，高速缓存有 4 个组，每个组 1 行，每个数据块 2 个字节，地址长度为 4 位。</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/20220711194256.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711194256.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 个内存块，但只有 4 个高速缓存组，所以会有多个块映射到同一个高速缓存组中。例如，块 0 和块 4 都会被映射到组 0。</p>
<p>下面我们来模拟当 CPU 执行一系列读的时候，高速缓存的执行情况，我们假设每次 CPU 读 1 个字节的字。</p>
<p><strong>读地址 0(0000) 的字：</strong>


<!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/20220711193901.gif">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711193901.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>
<p><strong>读地址 1(0001) 的字：</strong>


<!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/20220711194838.gif">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711194838.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>
<p><strong>读地址 13(1101) 的字：</strong>


<!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/20220711195108.gif">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711195108.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>
<p><strong>读地址 8(1000) 的字：</strong>


<!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/20220711200054.gif">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711200054.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>
<p><strong>读地址 0(0000) 的字：</strong>


<!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/20220711200409.gif">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220711200409.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>
<h5 id="组相联高速缓存-set-associative-cache">组相联高速缓存 Set Associative Cache</h5>
<p>由于直接映射高速缓存的组中只有一行，所以容易发生冲突不命中。组相联高速缓存 (Set associative cache) 运行有多行缓存行。但是缓存行最大不能超过 $C/B$。</p>
<p>如图一个组中包含了两行缓存行，这种我们称为 2 路相联高速缓存。</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/20220712160513.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20220712160513.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>组选择</strong>：与直接映射高速缓存的组选择过程一样。</p>
<p><strong>行匹配</strong>：因为一个组有多行，所以需要遍历所有行，找到一个有效位为 1，并且标记为与地址中的标记位相匹配的一行。如果找到了，表示缓存命中。</p>
<p><strong>字抽取</strong>：根据偏移量$b$确定目标数据的确切位置，通俗来说就是从数据块的什么位置开始抽取位置。如当偏移块等于<code>100</code>时，表示目标数据起始地址位于字节 4 处。</p>
<p>如果不命中，那么就需要从主存中取出需要的数据块，但是将数据块放在哪一行缓存行呢？如果存在空行 ($valid=0$)，那就放到空行里。如果没有空行，就得选择一个非空行来替换，同时希望 CPU 不会很快引用这个被替换的行。这里介绍几个替换策略。</p>
<p>最简单的方式就是随机选择一行来替换，其他复杂的方式就是利用局部性原理，使得接下来 CPU 引用替换的行概率最小。如</p>
<h3 id="缓存一致性协议-mesi">缓存一致性协议 MESI</h3>
<h4 id="为什么需要缓存一致">为什么需要缓存一致</h4>
<p>目前主流电脑的 CPU 都是多核心的，多核心的有点就是在不能提升 CPU 主频后，通过增加核心来提升 CPU 吞吐量。每个核心都有自己的 L1 Cache 和 L2 Cache，只是共用 L3 Cache 和主内存。每个核心操作是独立的，每个核心的 Cache 就不是同步更新的，这样就会带来缓存一致性（Cache Coherence）的问题。</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/202205291536919.gif">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205291536919.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>
<p>有 2 个 CPU，主内存里有个变量<code>x=0</code>。CPU A 中有个需要将变量<code>x</code>加<code>1</code>。CPU A 就将变量<code>x</code>加载到自己的缓存中，然后将变量<code>x</code>加<code>1</code>。因为此时 CPU A 还未将缓存数据写回主内存，CPU B 再读取变量<code>x</code>时，变量<code>x</code>的值依然是<code>0</code>。</p>
<p>这里的问题就是所谓的缓存一致性问题，因为 CPU A 的缓存与 CPU B 的缓存是不一致的。</p>
<h4 id="如何解决缓存一致性问题">如何解决缓存一致性问题</h4>
<h5 id="通过在总线加-lock-锁的方式">通过在总线加 LOCK 锁的方式</h5>
<p>在锁住总线上加一个 LOCK 标识，CPU A 进行读写操作时，锁住总线，其他 CPU 此时无法进行内存读写操作，只有等解锁了才能进行操作。</p>
<p>该方式因为锁住了整个总线，所以效率低。</p>
<h5 id="缓存一致性协议-mesi-1">缓存一致性协议 MESI</h5>
<p>该方式对单个缓存行的数据进行加锁，不会影响到内存其他数据的读写。</p>
<p>在学习 MESI 协议之前，简单了解一下总线嗅探机制（Bus Snooping）。要对自己的缓存加锁，需要通知其他 CPU，多个 CPU 核心之间的数据传播问题。最常见的一种解决方案就是总线嗅探。</p>
<p>这个策略，本质上就是把所有的读写请求都通过总线广播给所有的 CPU 核心，然后让各个核心去“嗅探”这些请求，再根据本地的情况进行响应。MESI 就是基于总线嗅探机制的缓存一致性协议。</p>
<p>MESI 协议的由来是对 Cache Line 的四个不同的标记，分别是：</p>
<table>
	<thead>
			<tr>
					<th style="text-align: center"><div style="width:50px">状态</div></th>
					<th style="text-align: center"><div style="width:100px">状态</div></th>
					<th><div style="width:200px">描述</div></th>
					<th><div style="width:200px">监听任务</div></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: center">Modified</td>
					<td style="text-align: center">已修改</td>
					<td>该 Cache Line 有效，数据被修改了，和内存中的数据不一致，数据只存在于本 Cache 中</td>
					<td>Cache Line 必须时刻监听所有试图读该 Cache Line 相对于主存的操作，这种操作必须在缓存将该 Cache Line 写回主存并将状态改为 S 状态之前，被延迟执行</td>
			</tr>
			<tr>
					<td style="text-align: center">Exclusive</td>
					<td style="text-align: center">独享，互斥</td>
					<td>该 Cache Line 有效，数据和内存中的数据一直，数据只存在于本 Cache</td>
					<td>Cache Line 必须监听其他缓存读主存中该 Cache Line 的操作，一旦有这种操作，该 Cache Line 需要改为 S 状态</td>
			</tr>
			<tr>
					<td style="text-align: center">Shared</td>
					<td style="text-align: center">共享的</td>
					<td>该 Cache Line 有效，数据和内存中的数据一直，数据存在于很多个 Cache 中</td>
					<td>Cache Line 必须监听其他  Cache Line 使该 Cache Line 无效或者独享该 Cache Line 的请求，并将 Cache Line 改为 I 状态</td>
			</tr>
			<tr>
					<td style="text-align: center">Invalid</td>
					<td style="text-align: center">无效的</td>
					<td>该 Cache Line 无效</td>
					<td>无</td>
			</tr>
	</tbody>
</table>
<p>整个 MESI 的状态，可以用一个有限状态机来表示它的状态流转。需要注意的是，对于不同状态触发的事件操作，可能来自于当前 CPU 核心，也可能来自总线里其他 CPU 核心广播出来的信号。我把各个状态之间的流转用表格总结了一下：</p>
<table>
	<thead>
			<tr>
					<th style="text-align: center"><div style="width:80px">当前状态</div></th>
					<th style="text-align: center"><div style="width:80px">事件</div></th>
					<th><div style="width:300px,center">行为</div></th>
					<th style="text-align: center"><div style="width:80px">下个状态</div></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: center">M</td>
					<td style="text-align: center">Local Read</td>
					<td>从 Cache 中读，状态不变</td>
					<td style="text-align: center">M</td>
			</tr>
			<tr>
					<td style="text-align: center">M</td>
					<td style="text-align: center">Local Write</td>
					<td>修改 cache 数据，状态不变</td>
					<td style="text-align: center">M</td>
			</tr>
			<tr>
					<td style="text-align: center">M</td>
					<td style="text-align: center">Remote Read</td>
					<td>这行数据被写到内存中，使其他核能使用到最新数据，状态变为 S</td>
					<td style="text-align: center">S</td>
			</tr>
			<tr>
					<td style="text-align: center">M</td>
					<td style="text-align: center">Remote Write</td>
					<td>这行数据被写入内存中，其他核可以获取到最新数据，由于其他 CPU 修改该条数据，则本地 Cache 变为 I</td>
					<td style="text-align: center">I</td>
			</tr>
	</tbody>
</table>
<table>
	<thead>
			<tr>
					<th style="text-align: center"><div style="width:80px">当前状态</div></th>
					<th style="text-align: center"><div style="width:80px">事件</div></th>
					<th><div style="width:200px,center">行为</div></th>
					<th style="text-align: center"><div style="width:80px">下个状态</div></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: center">E</td>
					<td style="text-align: center">Local Read</td>
					<td>从 Cache 中读，状态不变</td>
					<td style="text-align: center">E</td>
			</tr>
			<tr>
					<td style="text-align: center">E</td>
					<td style="text-align: center">Local Write</td>
					<td>修改数据，状态改为 M</td>
					<td style="text-align: center">M</td>
			</tr>
			<tr>
					<td style="text-align: center">E</td>
					<td style="text-align: center">Remote Read</td>
					<td>数据和其他 CPU 共享，变为 S</td>
					<td style="text-align: center">S</td>
			</tr>
			<tr>
					<td style="text-align: center">E</td>
					<td style="text-align: center">Remote Write</td>
					<td>数据被修改，本地缓存失效，变为 I</td>
					<td style="text-align: center">I</td>
			</tr>
	</tbody>
</table>
<table>
	<thead>
			<tr>
					<th style="text-align: center"><div style="width:80px">当前状态</div></th>
					<th style="text-align: center"><div style="width:80px">事件</div></th>
					<th><div style="width:200px,text-align: center">行为</div></th>
					<th style="text-align: center"><div style="width:80px">下个状态</div></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: center">S</td>
					<td style="text-align: center">Local Read</td>
					<td>从 Cache 中读，状态不变</td>
					<td style="text-align: center">S</td>
			</tr>
			<tr>
					<td style="text-align: center">S</td>
					<td style="text-align: center">Local Write</td>
					<td>修改数据，状态改为 M，其他 CPU 的 Cache Line 状态改为 I</td>
					<td style="text-align: center">M</td>
			</tr>
			<tr>
					<td style="text-align: center">S</td>
					<td style="text-align: center">Remote Read</td>
					<td>数据和其他 CPU 共享，状态不变</td>
					<td style="text-align: center">S</td>
			</tr>
			<tr>
					<td style="text-align: center">S</td>
					<td style="text-align: center">Remote Write</td>
					<td>数据被修改，本地缓存失效，变为 I</td>
					<td style="text-align: center">I</td>
			</tr>
	</tbody>
</table>
<table>
	<thead>
			<tr>
					<th style="text-align: center"><div style="width:80px">当前状态</div></th>
					<th style="text-align: center"><div style="width:80px">事件</div></th>
					<th><div style="width:200px,center">行为</div></th>
					<th style="text-align: center"><div style="width:80px">下个状态</div></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: center">I</td>
					<td style="text-align: center">Local Read</td>
					<td>1. 如果其他 CPU 没有这份数据，直接从内存中加载数据，状态变为 E；<br> 2. 如果其他 CPU 有这个数据，且 Cache Line 状态为 M，则先把 Cache Line 中的内容写回到主存。本地 Cache 再从内存中读取数据，这时两个 Cache Line 的状态都变为 S；<br>3. 如果其他 Cache Line 有这份数据，并且状态为 S 或者 E，则本地 Cache Line 从主存读取数据，并将这些 Cache Line 状态改为 S</td>
					<td style="text-align: center">E 或者 S</td>
			</tr>
			<tr>
					<td style="text-align: center">I</td>
					<td style="text-align: center">Local Write</td>
					<td>1. 先从内存中读取数据，如果其他 Cache Line 中有这份数据，且状态为 M，则现将数据更新到主存再读取，将 Cache Line 状态改为 M；<br> 2. 如果其他 Cache Line 有这份数据，且状态为 E 或者 S，则其他 Cache Line 状态改为 I</td>
					<td style="text-align: center">M</td>
			</tr>
			<tr>
					<td style="text-align: center">I</td>
					<td style="text-align: center">Remote Read</td>
					<td>数据和其他 CPU 共享，状态不变</td>
					<td style="text-align: center">S</td>
			</tr>
			<tr>
					<td style="text-align: center">I</td>
					<td style="text-align: center">Remote Write</td>
					<td>数据被修改，本地缓存失效，变为 I</td>
					<td style="text-align: center">I</td>
			</tr>
	</tbody>
</table>
<h2 id="内存">内存</h2>
<p>计算机有五大组成部分，分别是：运算器、控制器、存储器、输入设备和输出设备。而内存就是其中的存储器。我们的数据和指令都需要先放到内存中，然后再被 CPU 执行。</p>
<p>操作系统中程序并不能直接访问物理内存，我们的内存需要被分成固定大小的页（Page），然后再通过<strong>虚拟内存地址（Virtual Address）到物理内存地址（Physical Address）的地址转换（Address Translation）</strong>，才能到达实际存放数据的物理内存位置。而我们的程序看到的内存地址，都是虚拟内存地址。那么如何进行转换的呢？</p>
<h3 id="简单页表">简单页表</h3>
<p>最简单的方式，就是建立一张虚拟内存到物理内存的映射表，在计算机里叫做页表（Page Table）。页表这个地址转换的办法，会把一个内存地址分成页号（Directory）和偏移量（Offset）两个部分，是不是似曾相识，因为在前面的高速缓存里，缓存的结构也是这样的。</p>
<p>以一个 32 位地址举例，高 20 位是虚拟页号，可以从虚拟页表中找到物理页号的信息，低 12 位是偏移量，可以准确获得物理地址。


<!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/202207161640968.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202207161640968.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>把虚拟内存地址，切分成页号和偏移量的组合；</li>
<li>从页表里面，查询出虚拟页号，对应的物理页号；</li>
<li>直接拿物理页号，加上前面的偏移量，就得到了物理内存地址。</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/202207161645714.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202207161645714.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>但是这样的页表有个问题，它需要记录$2^{20}$个物理页表，这个存储关系，就好比一个 $2^{20}$大小的数组。一个页号是完整的 32 位的 4 字节（Byte），这样一个页表就需要 4MB 的空间。并且每个进程都会有这样一个页表，现代电脑正常都有成百上千个进程，如果用这样的页表肯定行不通的。</p>
<h3 id="多级页表">多级页表</h3>
<p>所以，<strong>在一个实际的程序进程里面，虚拟内存占用的地址空间，通常是两段连续的空间。而不是完全散落的随机的内存地址</strong>。而多级页表，就特别适合这样的内存地址分布。</p>
<p><a href="https://zhuanlan.zhihu.com/p/357648933">谈一谈内存管理，虚拟内存，多级页表 - 知乎</a></p>
<h3 id="tlb">TLB</h3>
<h3 id="内存保护---可执行空间保护">内存保护 - 可执行空间保护</h3>
<h3 id="内存保护---地址空间布局随机化">内存保护 - 地址空间布局随机化</h3>
<p>Address Space Layout Randomization</p>
<h2 id="总线计算机内部的高速公路">总线：计算机内部的高速公路</h2>
<p>计算机由控制器、运算器、存储器、输入设备以及输出设备五大部分组成。CPU 所代表的控制器和运算器，要和存储器，也就是我们的主内存，以及输入和输出设备进行通信。那么计算机是用什么样的方式来完成，CPU 和内存、以及外部输入输出设备的通信呢？答案就是通过总线来通信。</p>
<p>计算机里有不同的硬件设备，如果设备与设备之间都单独连接，那么就需要 N*N 的连线。那么怎么降低复杂度呢？与其让各个设备之间互相单独通信，不如我们去设计一个公用的线路。CPU 想要和什么设备通信，通信的指令是什么，对应的数据是什么，都发送到这个线路上；设备要向 CPU 发送什么信息呢，也发送到这个线路上。这个线路就好像一个高速公路，各个设备和其他设备之间，不需要单独建公路，只建一条小路通向这条高速公路就好了。</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/202205081510203.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081510203.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/202205081510711.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081510711.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>首先，CPU 和内存以及高速缓存通信的总线，这里面通常有两种总线。这种方式，我们称之为双独立总线（Dual Independent Bus，缩写为 DIB）。CPU 里，有一个快速的本地总线（Local Bus），以及一个速度相对较慢的前端总线（Front-side Bus）。</p>
<p>现代的 CPU 里，通常有专门的高速缓存芯片。这里的高速本地总线，就是用来和高速缓存通信的。而前端总线，则是用来和主内存以及输入输出设备通信的。有时候，我们会把本地总线也叫作后端总线（Back-sideBus），和前面的前端总线对应起来。</p>
<p>除了前端总线呢，我们常常还会听到 PCI 总线、I/O 总线或者系统总线（System Bus）。看到这么多总线的名字，你是不是已经有点晕了。这些名词确实容易混为一谈。其实各种总线的命名一直都很混乱，我们不如直接来看一看 CPU 的硬件架构图。对照图来看，一切问题就都清楚了。</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/202205081513938.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081513938.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>CPU 里面的北桥芯片，把我们上面说的前端总线，一分为二，变成了三个总线。我们的前端总线，其实就是系统总线。CPU 里面的内存接口，直接和系统总线通信，然后系统总线再接入一个 I/O 桥接器（I/OBridge）。这个 I/O 桥接器，一边接入了我们的内存总线，使得我们的 CPU 和内存通信；另一边呢，又接入了一个 I/O 总线，用来连接 I/O 设备。</p>
<p>事实上，真实的计算机里，这个总线层面拆分得更细。根据不同的设备，还会分成独立的 PCI 总线、ISA 总线等等。


<!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/202205081516341.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202205081516341.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>
<ol>
<li>数据线（Data Bus），用来传输实际的数据信息，也就是实际上了公交车的“人”。</li>
<li>地址线（Address Bus），用来确定到底把数据传输到哪里去，是内存的某个位置，还是某一个 I/O 设备。这个其实就相当于拿了个纸条，写下了上面的人要下车的站点。</li>
<li>控制线（ControlBus），用来控制对于总线的访问。虽然我们把总线比喻成了一辆公交车。那么有人想要做公交车的时候，需要告诉公交车司机，这个就是我们的控制信号。</li>
</ol>
<p>尽管总线减少了设备之间的耦合，也降低了系统设计的复杂度，但同时也带来了一个新问题，那就是<strong>总线不能同时给多个设备提供通信功能</strong>。</p>
<p>我们的总线是很多个设备公用的，那多个设备都想要用总线，我们就需要有一个机制，去决定这种情况下，到底把总线给哪一个设备用。这个机制，就叫作<strong>总线裁决</strong>（Bus Arbitraction）</p>
<h2 id="硬盘">硬盘</h2>
<h2 id="dma">DMA</h2>
<p>过去几年，计算机产业一直在为提升 I/O 设备的速度而努力，从机械硬盘 HDD 到固态硬盘 SSD，从 SATA 协议到 PCIE 协议，虽然速度都几十上百倍的增加，但是仍然不够快。因为相比于 CPU 基本都是 2GHz 的频率（每秒会有 20 亿次的操作），SSD 硬盘的 IOPS 的 2 万次操作就显得微不足道。</p>
<p>如果我们对于 I/O 的操作，都是由 CPU 发出对应的指令，然后等待 I/O 设备完成操作之后返回，那 CPU 有大量的时间其实都是在等待 I/O 设备完成操作。特别是当传输的数据量比较大的时候，比如进行大文件复制，如果所有数据都要经过 CPU，实在是有点儿太浪费时间了。</p>
<p>因此，计算机工程师们，就发明了<strong>DMA 技术</strong>，也就是<strong>直接内存访问（Direct Memory Access）技术</strong>，来减少 CPU 等待的时间。</p>
<h3 id="什么是-dma">什么是 DMA</h3>
<p>本质上，DMA 技术就是我们在主板上放一块<strong>独立的芯片</strong>。在进行内存和 I/O 设备的数据传输的时候，我们不再通过 CPU 来控制数据传输，而直接通过 DMA 控制器（DMA Controller，简称 DMAC）。这块芯片，我们可以认为它其实就是一个<strong>协处理器</strong>（Co-Processor）。</p>
<p>DMAC 最有价值的地方体现在，当我们要传输的数据特别大、速度特别快，或者传输的数据特别小、速度特别慢的时候。</p>
<p>比如说，我们用千兆网卡或者硬盘传输大量数据的时候，如果都用 CPU 来搬运的话，肯定忙不过来，所以可以选择 DMAC。而当数据传输很慢的时候，DMAC 可以等数据到齐了，再向 CPU 发起中断，让 CPU 去处理，而不是让 CPU 在那里忙等待。</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/202208152252016.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/202208152252016.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>
<ol>
<li>首先，CPU 还是作为一个主设备，向 DMAC 设备发起请求。这个请求，其实就是在 DMAC 里面修改配置寄存器。</li>
<li>CPU 修改 DMAC 的配置的时候，会告诉 DMAC 这样几个信息：
<ul>
<li>源地址的初始值：数据要从哪里传输过来。如果我们要从内存里面写入数据到硬盘上，那么就是要读取的数据在内存里面的地址</li>
<li>传输时候的地址增减方式：数据是从大的地址向小的地址传输，还是从小的地址往大的地址传输</li>
<li>传输的数据长度：也就是我们一共要传输多少数据</li>
</ul>
</li>
<li>设置完这些信息之后，DMAC 就会变成一个空闲的状态（Idle）。</li>
<li>如果我们要从硬盘上往内存里面加载数据，这个时候，硬盘就会向 DMAC 发起一个数据传输请求。这个请求并不是通过总线，而是通过一个额外的连线。</li>
<li>然后，我们的 DMAC 需要再通过一个额外的连线响应这个申请。</li>
<li>DMAC 这个芯片，就向硬盘的接口发起要总线读的传输请求。数据就从硬盘里面，读到了 DMAC 的控制器里面。</li>
<li>DMAC 再向我们的内存发起总线写的数据传输请求，把数据写入到内存里面。</li>
<li>DMAC 会反复进行上面第 6、7 步的操作，直到 DMAC 的寄存器里面设置的数据长度传输完成。</li>
<li>数据传输完成之后，DMAC 重新回到第 3 步的空闲状态。</li>
</ol>
<p>所以，整个数据传输的过程中，我们不是通过 CPU 来搬运数据，而是由 DMAC 这个芯片来搬运数据。但是 CPU 在这个过程中也是必不可少的。因为传输什么数据，从哪里传输到哪里，其实还是由 CPU 来设置的。这也是为什么，DMAC 被叫作 <strong>协处理器</strong>。</p>
<h2 id="参考资料">参考资料</h2>
<p><a href="https://www.bilibili.com/video/BV1cJ411K7HW?spm_id_from=333.999.0.0">【硬件科普】电脑主板右下角的散热片下面究竟隐藏着什么？详解主板南桥芯片组的功能和作用_哔哩哔哩_bilibili</a></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
