
动态随机存取存储器(DRAM)是为几乎所有现代计算设备(从智能手机到超级计算机)提供动力的高速易失性存储器。然而,其惊人的速度和密度是以复杂性为代价的。访问数据并非瞬时事件,而是一场由一系列严格规则(称为时序参数)精心编排的舞蹈。理解这些时序是解锁系统性能、确保可靠性,乃至理解微妙安全漏洞的关键。本文旨在通过剖析定义其操作的复杂信号交响曲,弥合“仅知内存存在”与“真正理解其工作原理”之间的知识鸿沟。
在接下来的章节中,您将深入了解 DRAM 的内部工作原理。我们首先将探讨这些时序的物理基础,从内存单元的“漏桶”特性到导致复杂访问周期的破坏性读取过程。然后,我们将研究这些基本规则如何在系统层面应用,影响从内存控制器设计和性能优化到系统可靠性和计算本身的未来等方方面面。
要理解 DRAM 时序的复杂舞蹈,我们必须从一个难以想象的小桶开始,而不是时钟。这不仅仅是任何一个桶;它是一个漏水的桶。这就是动态随机存取存储器(DRAM)的核心:一个晶体管和一个电容器(一个 1T1C 单元),现代内存最基本的单位。
想象一下,你想存储一位信息,一个‘1’或一个‘0’。在 DRAM 的世界里,‘1’是一个充满电荷的微小电容器,而‘0’则是一个空的电容器。晶体管充当一个门,通过一个称为字线的信号控制对这个电容器的访问。当门关闭时,电荷被困住。但这里有一个问题,也是 DRAM 中“动态”一词的由来:电容器并不完美。它会漏电。一个存储的‘1’会在非常短的时间内逐渐消失,其电荷会泄漏殆尽。这就是为什么 DRAM 需要一个持续的刷新过程,内存控制器会周期性地读取数据并将其写回,在所有漏桶干涸之前将其重新充满。
这与其表亲静态 RAM (SRAM) 形成鲜明对比,SRAM 使用一对交叉耦合的反相器形成一个双稳态锁存器。SRAM 单元就像一个电灯开关:它要么明确地处于打开状态,要么明确地处于关闭状态,并且只要供电就会保持该状态。它不需要刷新,但这种稳定性是有代价的:一个 SRAM 单元远比一个简单的 DRAM 单元复杂,并且占用更多的空间。这种权衡是根本性的:DRAM 给了我们惊人的密度——在微小的芯片上集成数十亿位——但作为交换,我们必须掌握其动态且有些脆弱的特性。
DRAM 的第二个巨大挑战出现在我们试图读取信息时。单元电容器非常微小,仅持有飞库仑级别的电荷。它连接到一根叫做位线的长导线,位线的电容要大得多——想象一下将我们的小桶连接到一个大水槽。在读取之前,位线被小心地预充电到一个中间电压,比如 。为了读取单元,字线打开晶体管门,单元电容器中的电荷溢出并与位线上的电荷混合。
会发生什么?位线电压的变化小得令人痛苦。如果单元存储的是‘1’(电压为 ),位线上的最终电压可能只会上升几十毫伏——在嘈杂的背景中仅仅是一声耳语。对于位线电容是单元电容十倍的典型参数,一个存储‘1’的单元可能只会将位线电压从 推高到约 。读取‘0’会导致类似微小的下降。
为了检测这声耳语,每个位线都配备了一个精巧的设备,称为感测放大器。你可以把它想象成一个完美平衡的跷跷板。这个微小的电压差刚好足以给跷跷板一个轻微的推动,然后一个正反馈机制启动,使其猛地倒向一边——要么是满电压(),要么是地()。耳语被放大成了明确的呐喊。
但请注意发生了什么。在读取过程中,我们小桶里的原始电荷被倾倒进了水槽。单元的状态被破坏了。这是 DRAM 的基本规则:每一次读取都是破坏性的。感测放大器在判断它看到的是‘1’还是‘0’之后,必须立即执行写回(或恢复)操作,用其现在全强度的信号将单元电容器重新充电到其原始状态。这整个精细的序列——激活、电荷共享、感测、放大和恢复——是支配所有 DRAM 操作的时序参数的物理基础。
内存控制器看到的不仅仅是漏桶;它看到的是一个由数十亿个漏桶组成的高度组织化的网格,排列在 bank、行和列中。要访问单个位,它不能只是伸进去抓住它。它必须指挥一场由控制信号组成的精确交响乐,这个序列由一套严格的时序规则支配。让我们跟随单次访问的乐谱。
想象一个庞大图书馆(一个 DRAM bank)里的图书管理员。要取一本书(一条数据),你给他一个地址,这个地址被分成行和列。
激活 (ACT):第一个命令告诉图书管理员去哪个过道。该命令激活一条字线,打开一整行数千个单元,并将它们全部连接到各自的位线。这启动了我们刚才描述的电荷共享和感测过程。整行的数据现在被保存在感测放大器阵列中,这些感测放大器共同构成一个行缓冲区。
(RAS-to-CAS 延迟):这是图书管理员把整架书拿下来并摆在桌子上所需的时间。它是从 ACTIVATE 命令到感测放大器稳定并且行缓冲区持有有效数据之间的最小延迟。只有在 之后,控制器才能请求特定的列。这个延迟由字线电压上升、电荷共享以及敏感的放大器解析微小信号所需的物理时间决定。
读/写 (CAS):现在行在行缓冲区中是“打开”的,控制器发出带有列地址的 READ 或 WRITE 命令。这就像指向桌子上的一本特定的书。CAS 代表列地址选通。
(CAS 延迟):在你指向书之后,这是图书管理员实际拿起书并递给你所需的时间。它是从 READ 命令到第一份数据出现在数据总线上的延迟。
(行有效时间):这是一个微妙但关键的参数。它是行必须保持活动状态的最小时间(从 ACTIVATE 开始),然后才能被关闭。为什么?因为感测放大器不仅需要足够的时间来放大信号以供读取,还需要足够的时间将恢复的电荷完全驱动回微小的单元电容器中。如果你过早关闭行,写回可能不完整,从而损坏内存中的数据。在 结束前发出 PRECHARGE 命令是一个严重的时序违规。
预充电 (PRE):一旦从行中读取了所有需要的数据(或者 接近其极限),就会发出 PRECHARGE 命令。图书管理员把书架放回原处。该命令停用字线,关闭感测放大器,并将位线恢复到其就绪状态的预充电电压(),为下一次访问准备好 bank。
(行预充电时间):这是“清理”所需的时间。在这个预充电操作完成之前,你不能在同一个 bank 中激活另一行。
这些参数定义了单个 DRAM bank 的基本操作序列。一个完整周期的总时间——激活一行、访问它、并预充电以准备好在同一个 bank 中进行下一次激活——被称为行周期时间 ()。自然地,这是行必须活动的时间和预充电所需时间的总和:。从一个新激活的行中进行简单的 4 字突发读取将花费总时间 ,其中 是连续列命令之间的时间。
理解这些时序不仅仅是一个学术练习;它是实现高性能计算的关键。一个在不同行之间随机跳转的天真访问模式效率极低,每次访问都要付出完整的 代价。内存控制器采用两种强大的策略来克服这一点:利用局部性和并行性。
页面模式与行缓冲命中:回想一下我们的图书管理员。一旦一架子书(一行)被摆在桌子上,从中拿多本书就会非常快。这就是页面模式访问的精髓。对一个已经打开的行的访问被称为行缓冲命中。它跳过了冗长的 PRECHARGE 和 ACTIVATE 步骤,只产生短暂的 延迟。对不同行的访问是行缓冲未命中(或冲突),这迫使旧行预充电并激活新行,代价是完整的 。性能差异是惊人的;一系列随机访问的时间可能几乎是来自同一行的顺序突发访问的两倍。
这导致内存控制器面临一个关键决策:一次访问后该做什么?开放页面策略保持行打开,赌下一次访问将是命中。封闭页面策略立即预充电该行,采取保守做法,但保证每次访问都是未命中。哪种更好完全取决于工作负载的局部性。对于具有高行命中概率()的请求流,开放页面策略完胜。对于随机请求(低 ),其优势消失,甚至可能成为一种惩罚。这种权衡可以进行量化分析。对于行命中概率为 的情况,开放页面策略相比于封闭页面策略的平均延迟节省大约为 ,因为每次命中都避免了一个预充电和激活周期。因此,当工作负载表现出高局部性(高 )时,开放页面策略更优。
Bank 交错:一个 DRAM 芯片不是一个巨大的图书馆;它是由几个更小的、独立的图书馆组成的集合,这些图书馆被称为 bank。当一个 bank 忙于其完整的 周期时,内存控制器可以向另一个 bank 发出命令。这就是bank 交错,一种隐藏延迟的并行形式。通过在不同 bank 之间轮换请求——ACT 到 bank 0,ACT 到 bank 1,ACT 到 bank 2,依此类推——控制器可以使数据总线持续繁忙,实现比单个 bank 所能提供的更高的吞吐量。现代 DRAM 通过bank 组进一步发展了这一点,提供了更多并行操作的机会。
这种并行性并非毫无约束。当控制器在多个 bank 之间处理操作时,它必须遵守确保整个芯片保持稳定的全局流量规则。这些规则主要与功耗管理有关。
ACTIVATE 命令是 DRAM 中最耗电的操作,因为它涉及驱动一条长字线并同时启动数千个感测放大器。过快地发出太多的 ACTIVATE 命令会产生巨大的瞬时电流尖峰,可能导致芯片的供电电压下降,从而可能导致错误。为了防止这种情况,JEDEC 标准规定了两个关键约束:
(行到行激活延迟):这设置了对不同 bank(在同一 rank 内)发出 ACTIVATE 命令之间的最短时间。这是一个短期的时间间隔规则,以防止背对背的电流尖峰。在现代 DDR4 中,这甚至被分为对同一 bank 组内激活的较短延迟()和对不同 bank 组激活的较长延迟()。
(四激活窗口):这是一个更长期的功耗完整性规则。它规定在任何持续时间为 的滑动时间窗口内,不能发出超过四个 ACTIVATE 命令。这可以防止在中等时间尺度上电流消耗的累积。
这些约束共同限制了行激活的最大速率。例如,如果一个控制器正在激活不同 bank 组中的 bank,那么激活之间的真实最小时间是 和 中的较大值。这确保了短期和长期的功耗约束都得到满足,从而限制了内存系统的峰值激活吞吐量。甚至还有关于行可以保持打开多长时间的约束,由 指定,以防止长时间激活引起的微妙问题,这可能迫使控制器用预充电-激活周期来中断一长串的行命中。
最后,我们必须面对一个关键事实:所有这些时序参数——、、——都不是自然界中不可改变的常数。它们是制造商做出的承诺,保证芯片在特定操作条件下能正常工作。物理世界是混乱的,晶体管的性能对其环境极其敏感。这就是 PVT(工艺、电压、温度)变化的挑战。
DRAM 的时序规格是为最差情况角保证的:通常是最慢的工艺变体、最低的供电电压和最高的运行温度。例如,一个 的 可能是在 和 下指定的。但系统设计者必须更加悲观。他们必须考虑来自其特定供电网络的额外电压骤降和来自其时钟源的时序不确定性(抖动)。仔细计算可能会表明,为了在这些额外压力下安全地满足 18 ns 的要求,控制器必须被编程为等待总共 24 个时钟周期,即使这相当于近 20 ns——为安全起见增加了一个保护带。
温度对漏电和刷新的影响尤为深远。晶体管中的漏电流大约每升高 就会翻一番。这意味着单元的保持时间减半。在 下运行的 DRAM 可能需要比在 下运行的 DRAM 刷新频率高一倍。从凉爽的 移动到炎热的 可能需要刷新频率增加 64 倍 ()!这就是为什么现代系统对不同温度范围有不同的刷新率,通常使用片上温度传感器动态调整。
因此,DRAM 时序的世界是基础物理、巧妙电路设计和复杂系统级编排之间迷人的相互作用。这是一场持续的协商,在不懈追求性能与物理世界不屈不挠的约束之间取得平衡。每一纳秒都是在一场无声的、微观的交响乐中赢得的艰苦胜利。
在熟悉了 DRAM 的基本时序参数——其内部时钟的断奏节奏,激活、访问和预充电的停顿之后——我们可能会觉得已经学会了一门新语言的基本语法。但仅有语法并不能构成诗歌。真正的魔力在于看到这种语法如何被用来创作现代计算中错综复杂的散文和十四行诗。这些时序参数不仅仅是抽象的约束;它们是内存控制器编排令人惊叹的数据之舞的规则本身,是系统可靠性赖以建立的基础,甚至,正如我们将看到的,是幽灵般漏洞的来源和未来计算范式的蓝图。
想象一位交响乐指挥家——内存控制器——站在由内存 bank 组成的庞大管弦乐队面前。这位指挥家面前的乐谱是用时序参数的语言写成的。做出的每一个决定都是一种权衡,一种选择,即强调一个乐句而非另一个,一切都为了最终的演出效果。
最根本的决定是页面管理策略。当一个行被访问时,其数据被复制到一个称为行缓冲区的快速临时存储区域。指挥家是否应该让这“一页”乐谱保持打开状态,赌下一次请求会是同一页上的音符?这就是“开放页面”策略。如果赌注成功(一次“行命中”),访问会非常快,仅需列地址选通延迟 的成本。但如果赌注失败(一次“行未命中”),就必须付出代价:必须收起错误的页面(),并拿出新的页面(),然后才能演奏那个音符。另一种选择是“封闭页面”策略,即指挥家在每次使用后都勤勉地收起每一页。这避免了最坏情况的惩罚,但确保了每次访问都是一个相对较慢、完整的预充电、激活和访问周期。
哪种策略更好?这完全取决于正在演奏的音乐——工作负载。像观看视频这样的流式工作负载具有很高的“行缓冲局部性”,在开放页面策略下蓬勃发展,实现高吞吐量。而一个随机访问的工作负载,请求在内存中到处跳转,可能会遭受如此多的未命中,以至于谨慎的封闭页面策略平均下来反而更有效率。
为了进一步增加胜算,聪明的控制器采用预取技术。它们试图扮演读心者的角色,猜测处理器接下来会需要哪些数据,并在其被请求之前就将其取回。一次成功的预取神奇地将一次缓慢的行未命中转变为一次闪电般的行命中。但这是一个危险的游戏。一次错误的猜测不仅无济于事,还会产生无用的内存流量,消耗宝贵的带宽。决定是否启用预取器归结为一个仔细的计算:由命中率提高带来的延迟节省是否超过了因额外、可能无用的请求而浪费的时间?[@problem_d:3636997]。
控制器的任务更加复杂,因为计算的乐章不仅涉及读取数据,还涉及写入数据。读取通常是紧急的——处理器在停顿,等待数据。写入通常可以被缓冲并在稍后处理。这导致了“读优先”调度器。然而,写缓冲区不能被永远忽略。当它填满时,控制器必须进入“写排空”模式,暂停读取以清空待处理的写入。这种切换不是没有代价的。“写到读”转换惩罚 在数据总线上引入了一个不可避免的空闲气泡,这是数据流节奏中的一个静默节拍。
为了管理这种复杂性,控制器利用了现代 DRAM 内部的大规模并行性。通过在不同的 bank 和 rank(芯片组)之间交错请求,控制器可以将一个操作的延迟隐藏在另一个操作的执行之后。例如,通过在两个不同的 rank 之间交替请求,访问一个 rank 后所需的长恢复时间()几乎可以被完全隐藏,取而代之的是一个短得多的 rank 到 rank 切换惩罚()。这种rank 交错技术可以显著提高持续带宽。然而,即使是这个优雅的解决方案也有其局限性。所有通道和 bank 最终共享像命令总线这样的资源。一个顶层调度器可能会以轮询方式公平地授予每个通道访问权,但这可能导致某个通道被迫闲置,发出“无操作”(NOP)命令,即使另一个通道有工作准备就绪。这种对共享资源的争用揭示了内存系统中性能瓶颈的深层、层次化性质。
DRAM 时序的作用远不止于追求速度。它与整个系统的物理完整性、安全性和能耗紧密相连。
DRAM 中的“D”代表“动态”是有原因的:存储数据位的微小电容器是会漏电的。如果不加理会,它们的电荷——以及你的数据——将在毫秒内消失。为了防止这种情况,内存必须被周期性地刷新。这是一项看不见的、持续的劳动。内存的一部分时间,由刷新周期时间()与刷新间隔()的比率决定,被用于这项至关重要的任务,使内存无法用于正常操作。这种与物理世界的联系是直接且不容置疑的。例如,在更高温度下运行系统会增加电容器的漏电率,迫使刷新间隔 缩短。例如,将刷新间隔减半,会使刷新操作造成的性能开销加倍。现代系统采用复杂的策略,如per-bank 刷新,即一次刷新一个 bank 而不是所有 bank。这种更细粒度的方法可以显著减少性能冲击,特别是对于活动集中在少数“热”bank 的工作负载,让控制器可以自由地与其他 bank 协作。
如果尽管进行了刷新,一个位还是翻转了,会发生什么?为了防范这种情况,高可靠性系统使用纠错码(ECC)。在这里我们发现了一个优美而微妙的权衡。一个标准的 ECC 可以纠正单位错误。但如果整个内存芯片发生故障呢?为了在这种情况下幸存,系统可以使用“Chipkill”ECC,这是一种更强大的方案,它将数据条带化到多个芯片上。然而,这种条带化模式可能迫使连续的内存访问目标是同一个 bank 组。这阻止了控制器使用最快的命令间距(),并迫使其使用更长的延迟(),在数据突发之间产生一个小气泡。结果是一个直接的选择:以牺牲峰值内存带宽为代价,换取对物理故障的更大恢复能力。
也许 DRAM 时序最迷人、最令人不安的应用是在计算机安全领域。这些精确、可预测的时序特性可以被用来对付系统,创建一个时序侧信道。现代处理器使用“推测执行”来提高性能,在知道路径是否正确之前,沿着预测的路径执行指令。如果预测错误,结果将被丢弃,就好像这些指令从未运行过一样——在体系结构上是这样。但在微体系结构上,它们可能留下了痕迹。想象一个推测性指令访问了某个内存地址处的秘密数据。尽管数据本身从未被泄露,但访问它的行为可能会打开 DRAM 行缓冲区中相应的行。如果攻击者能在此之后立即对同一行发出合法请求,他们将观察到一次快速的“行命中”。如果他们访问不同的行,他们将观察到一次缓慢的“行未命中”。这两种结果之间的时间差,一个精确为 的微小延迟,是可以被测量的。通过精心设计这样的实验,攻击者可以利用这个幽灵般的时序信号来推断推测代码触及了哪些秘密地址,将一个性能优化变成了一个强大的安全漏洞。
从更宏观的视角看,处理器与 DRAM 时序之间的关系定义了计算机体系结构中最重大的挑战之一:“内存墙”。随着处理器通过动态电压与频率调节(DVFS)等技术变得更加节能,一个奇怪的倒置发生了。当处理器核心降低其频率时,其内部操作在绝对时间上变慢了。相比之下,以纳秒为单位测量的 DRAM 延迟却顽固地保持不变。这意味着,对于较慢的核心来说,以核心周期为单位测量的内存延迟实际上减少了。虽然这看起来可能是有益的,但它凸显了一个更深层次的问题。随着核心变得更快或更高效,对 DRAM 数据的漫长等待在总执行时间中所占的比例越来越大。性能可能会变得“核心分发受限”,即核心本身甚至无法足够快地生成内存请求来保持高带宽内存系统的繁忙。
这把我们带到了一个最终的、有远见的问题。如果访问内存是瓶颈,那么如果内存本身可以计算呢?这就是内存计算的核心思想,一个试图通过将内存阵列转变为计算引擎来打破内存墙的范式。通过操纵 DRAM 的基本操作,我们可以直接在数据所在的位置执行逻辑。例如,通过同时激活三行——一次“三行激活”——可以使感测放大器计算这三行的按位多数,结果会自动写回。这种大规模并行计算的吞吐量由最基本的时序决定:行周期时间,。这个激进的想法重新构想了 DRAM,不再将其视为被动的数据存储库,而是计算的积极参与者,预示着一个未来,我们所探讨的时序语法将被用来书写一种全新的诗篇。