Appearance
主存储器
概念
主存储器(main memory,简称主存/内存)是 CPU 能通过地址总线直接寻址访问的存储器。它夹在 Cache 与辅存之间:对 Cache 是"慢而大的后备",对辅存是"快而小的前台"。
主存的物理实现主要是 DRAM(Dynamic Random Access Memory,动态随机存取存储器)。它便宜、集成度高,代价是必须不断刷新——"动态"两个字就指这件事。
本章的三个计算主线:刷新开销、芯片扩展的片数与地址、交叉存储的带宽。
原理
一、存储芯片的内部结构
地址线 A0~A(n-1)
│
┌──────┴──────┐
│ 地址译码器 │
└──────┬──────┘
│ 选中某一行 / 某个单元
┌──────▼──────────────┐
│ 存储阵列 │ ← 2^n 个存储元
│ (行 × 列的位单元) │
└──────┬──────────────┘
│
┌──────▼──────┐
│ 读写电路 │──→ 数据线 D0~D(w-1)
│ 数据寄存器 │←── 控制信号 CS/WE/OE
└─────────────┘两个关键参数决定芯片规格:
例如
二维译码:若把
二、SRAM 与 DRAM 对比(必背表)
| 对比项 | SRAM(静态) | DRAM(动态) |
|---|---|---|
| 存储元 | 双稳态触发器(6 个晶体管) | 电容(1 管 1 容) |
| 数据保存 | 通电即保持 | 电荷会泄漏,需定期刷新 |
| 读出 | 非破坏性 | 破坏性,读出后必须重写 |
| 存取周期 vs 存取时间 | ||
| 速度 | 快 | 慢 |
| 集成度 | 低(每个元 6 管) | 高(每个元 1 管) |
| 功耗 | 大 | 小 |
| 每位成本 | 高 | 低 |
| 典型用途 | Cache | 主存 |
一句话记忆:SRAM 用管子换速度,DRAM 用电容换容量。
三、DRAM 的刷新
为什么刷新:DRAM 用电容存电荷,电容会漏电,一般几十毫秒就会丢数据。必须在电荷彻底漏光之前读出来再写回去(刷新)。
刷新以"行"为单位:因为 DRAM 的一次读出会整行取出并整行重写,所以刷新一次就能保住一整行。若阵列有 128 行,刷完一遍要 128 次。
刷新周期的含义:题目里的"刷新周期 2 ms"指必须在 2 ms 内把每一行都刷一遍,不是"每 2 ms 刷一次"。刷新由刷新计数器产生行地址,与 CPU 给出的地址无关——刷新不是 CPU 发起的。
三种刷新方式:
| 方式 | 做法 | 死时间 | 特点 |
|---|---|---|---|
| 集中刷新 | 在刷新周期末尾集中刷完所有行 | 有一块连续死区,期间 CPU 完全不能访存 | 存取周期不变,但死区集中,对实时性差 |
| 分散刷新 | 每个存取周期后都跟一个刷新周期 | 没有死区,但系统存取周期翻倍 | 速度恒定下降,实为"用一半速度换无死区" |
| 异步刷新 | 把 128 次刷新均匀分散到整个刷新周期内 | 没有死区,只有零星短暂占用 | 折中方案,实际最常用 |
关键结论:集中刷新与异步刷新的"总死时间"完全一样,差别只在分布。 因为要刷的行数一样、每次耗时一样,总量必然相等;分散刷新的总量也一样,只是表现为"每个周期都变慢"。
刷新优先级高于 CPU 访存:刷新期间 CPU 只能等待(DRAM 的刷新信号会占用存储器)。
四、存储芯片的扩展
单片芯片容量有限,需要多片拼成所需规格。三种扩展方式:
| 方式 | 做法 | 地址线 | 数据线 | 片数 |
|---|---|---|---|---|
| 位扩展 | 地址线共用,数据线拼接 | 不变 | 增加 | 目标位数 / 单片位数 |
| 字扩展 | 数据线共用,地址线增加(高位做片选) | 增加 | 不变 | 目标字数 / 单片字数 |
| 字位同时扩展 | 先位扩展成一组,再字扩展成多组 | 增加 | 增加 | 组数 × 每组片数 |
字扩展的地址划分:新增的高位地址线送到译码器,译码器的每个输出接一片的片选(
片选逻辑的要点:
五、多模块交叉编址
把主存分成
| 方式 | 地址分配 | 效果 |
|---|---|---|
| 高位交叉(顺序方式) | 高位地址选模块,低位是模块内地址 | 连续地址落在同一模块 → 不能流水,带宽不提升 |
| 低位交叉(交叉方式) | 低位地址选模块,高位是模块内地址 | 连续地址分散到不同模块 → 可以流水,带宽成倍提升 |
低位交叉为什么快?因为
连续读
其中
完全掩盖条件:
即模块数不少于"存取周期 / 传送周期",才能让总线一刻不停。模块数不足时总线要空等。
示例
例 1:位扩展—— 位 → 位
完整计算过程:
第一步,判断扩展类型:字数不变(都是
第二步,算片数:
第三步,确定连线方式:
- 地址线:
片全部并接在同一组地址线上。字数 决定地址线条数:
- 数据线:每片贡献 1 位,8 片拼成 8 位。第
片的唯一数据线接到 。 - 片选:8 片同时被选中(它们共同构成一个字),所以
并接在一起。
结论:需要 8 片,地址线 13 根(共用),每片 1 根数据线,8 片同时工作。
例 2:字扩展—— 位 → 位
完整计算过程:
第一步,判断类型:位数不变(都是 8),字数从
第二步,算片数:
第三步,算总地址位数:
第四步,划分地址:
第五步,画地址映射(用
| 高位地址 | 选中片 | 片内地址范围 | 对应全局地址 |
|---|---|---|---|
00 | 第 0 片 | ||
01 | 第 1 片 | ||
10 | 第 2 片 | ||
11 | 第 3 片 |
结论:需要 4 片,16 根地址线(14 根片内共用 + 2 根片选用),数据线 8 根共用。每片负责
例 3:字位同时扩展——用 位芯片组成 位
完整计算过程:
第一步,拆成两级:先把"位数"补齐,再把"字数"补齐。
第二步,位扩展:要把 4 位凑成 8 位,每组需要
第三步,字扩展:要把
第四步,总片数:
第五步,连线方式:
- 同一组的 2 片:地址线并接、
并接,各贡献 4 位数据(一片接 ,一片接 )。 - 4 组之间:数据线并接(都接同一组 8 根数据线),由
根高位地址A15 A14经译码器产生 4 个组选信号。
结论:需要 8 片,地址线 16 根,数据线 8 根。
例 4:DRAM 三种刷新方式的完整时间计算
某 DRAM 芯片的存储阵列为
(128 行、128 列),存取周期 ,刷新周期 。分别计算三种刷新方式的开销。
完整计算过程:
第一步,确定要刷多少行:阵列有 128 行,每行刷一次,共 128 次。每次刷新占用 1 个存取周期:
第二步,算刷完一遍的总时间:
(1)集中刷新
在
(2)分散刷新
每个存取周期后面都跟一个刷新周期,即原来
刷完 128 行所需时间:
时间上完全够,而且没有死区——但代价是系统速度降低一半(存取周期翻倍)。
(3)异步刷新
把 128 次刷新均匀铺在
即每隔
(4)三种方式对比:
| 方式 | 系统存取周期 | 死区形式 | 总死时间占比 | 评价 |
|---|---|---|---|---|
| 集中刷新 | 每 | 死区集中,实时性差 | ||
| 分散刷新 | 变为 | 无死区 | 隐含在周期里(速度降 50%) | 简单但慢 |
| 异步刷新 | 无死区,每 | 折中最好 |
关键结论:集中刷新与异步刷新的死时间总量严格相等(都是
例 5:低位交叉存储的带宽提升
主存分成 4 个模块,采用低位交叉编址。单个模块的存取周期
,总线的传送周期 。求连续读取 4 个字的时间,并与高位交叉对照。
完整计算过程:
第一步,高位交叉(顺序方式):连续 4 个字落在同一个模块里,后一个字必须等前一个完成存取周期:
第二步,低位交叉(交叉方式):4 个模块的地址低位分别是
第三步,算提升倍数:
第四步,算平均每字耗时:
第五步,验证能否"完全掩盖"。条件:
现在
结论:顺序方式
为什么没有跑满 4 倍:交叉方式的总时间被公式里的
例 6:加大模块数会怎样
仍用
、 ,改成 8 个模块连续读 8 个字。
完整计算过程:
第一步,顺序方式:
第二步,低位交叉:
第三步,平均每字:
第四步,对照:
| 模块数 | 读字数 | 顺序 | 交叉 | 提速 | 平均每字 |
|---|---|---|---|---|---|
| 4 | 4 | ||||
| 8 | 8 |
结论:模块数从 4 增到 8,提升倍数从
例 7:C 代码——刷新调度与交叉读取时序
本机不提供代码运行能力,runnable 标记仅为将来接入运行件预留;请自行在本地编译验证。
#include <stdio.h>
#define ROWS 128 /* 阵列行数 = 需刷新次数 */
#define TCYCLE 0.5 /* 存取周期 us */
#define REFRESH 2000.0 /* 刷新周期 us */
/* 三种刷新方式的开销 */
static void refresh_report(void) {
double once = ROWS * TCYCLE; /* 刷一遍总耗时 */
printf("== 刷新开销 ==\n");
printf("刷一遍 = %d 行 x %.1f us = %.1f us\n", ROWS, TCYCLE, once);
/* 集中刷新:末尾连续死区 */
printf("集中刷新: 死区 %.1f us, 死时间率 %.2f%%\n",
once, once / REFRESH * 100);
/* 分散刷新:周期翻倍 */
printf("分散刷新: 系统存取周期变为 %.1f us, 刷完需 %.1f us (仅占刷新周期的 %.4f%%)\n",
TCYCLE * 2, ROWS * TCYCLE * 2, ROWS * TCYCLE * 2 / REFRESH * 100);
/* 异步刷新:均匀分布 */
double gap = REFRESH / ROWS;
printf("异步刷新: 间隔 %.3f us 刷一行, 死时间率 %.2f%%\n",
gap, TCYCLE / gap * 100);
}
/* 低位交叉 vs 高位交叉:连续读 m 个字 */
static void interleave_report(int m, double T, double tau) {
double seq = m * T;
double ilv = T + (m - 1) * tau;
printf("m=%d T=%.0fns tau=%.0fns -> 顺序 %.0fns, 交叉 %.0fns, 提速 %.2f 倍, 平均 %.3f ns/字\n",
m, T, tau, seq, ilv, seq / ilv, ilv / m);
printf(" 完全掩盖条件 m >= T/tau = %.0f : %s\n",
T / tau, (m >= T / tau) ? "满足" : "不满足");
}
int main(void) {
refresh_report();
printf("\n== 交叉存储 ==\n");
interleave_report(4, 100, 25);
interleave_report(8, 100, 25);
/* 模块数不足的反例 */
interleave_report(2, 100, 25);
return 0;
}
c 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
预期输出:
== 刷新开销 ==
刷一遍 = 128 行 x 0.5 us = 64.0 us
集中刷新: 死区 64.0 us, 死时间率 3.20%
分散刷新: 系统存取周期变为 1.0 us, 刷完需 128.0 us (仅占刷新周期的 6.4000%)
异步刷新: 间隔 15.625 us 刷一行, 死时间率 3.20%
== 交叉存储 ==
m=4 T=100ns tau=25ns -> 顺序 400ns, 交叉 175ns, 提速 2.29 倍, 平均 43.750 ns/字
完全掩盖条件 m >= T/tau = 4 : 满足
m=8 T=100ns tau=25ns -> 顺序 800ns, 交叉 275ns, 提速 2.91 倍, 平均 34.375 ns/字
完全掩盖条件 m >= T/tau = 4 : 满足
m=2 T=100ns tau=25ns -> 顺序 200ns, 交叉 125ns, 提速 1.60 倍, 平均 62.500 ns/字
完全掩盖条件 m >= T/tau = 4 : 不满足最后一行是反例:
例 8:Python 对照与验算
# ---- 刷新开销 ----
ROWS, TCYCLE, REFRESH = 128, 0.5, 2000.0
once = ROWS * TCYCLE
print('刷一遍 = %g us' % once)
print('集中刷新死时间率 = %.2f%%' % (once / REFRESH * 100))
print('分散刷新: 周期 %.1f us, 刷完 %.1f us' % (TCYCLE * 2, ROWS * TCYCLE * 2))
gap = REFRESH / ROWS
print('异步刷新: 间隔 %g us, 死时间率 %.2f%%' % (gap, TCYCLE / gap * 100))
print('集中与异步死时间总量相等? %s' % ('是' if abs(once - (TCYCLE / gap) * REFRESH) < 1e-9 else '否'))
# ---- 芯片扩展 ----
def extend(one_words, one_bits, want_words, want_bits):
bits = want_bits // one_bits # 每组片数(位扩展)
groups = want_words // one_words # 组数(字扩展)
return bits * groups, bits, groups
print('8Kx1 -> 8Kx8 :', extend(8192, 1, 8192, 8))
print('16Kx8 -> 64Kx8:', extend(16384, 8, 65536, 8))
print('16Kx4 -> 64Kx8:', extend(16384, 4, 65536, 8))
# ---- 交叉存储 ----
import math
for m in (2, 4, 8):
T, tau = 100, 25
seq, ilv = m * T, T + (m - 1) * tau
print('m=%d: 顺序 %d ns, 交叉 %d ns, 提速 %.2f, 平均 %.3f ns/字, 完全掩盖=%s'
% (m, seq, ilv, seq / ilv, ilv / m, m >= T / tau))
# 地址位数划分
print('64Kx8: 总地址 %d 位 = 片内 %d 位 + 片选 %d 位'
% (int(math.log2(65536)), int(math.log2(16384)), int(math.log2(4))))
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
输出对照:与例 4、例 5、例 6 的手算一致——刷新死时间率
考点
考点
1. 必背结论
- SRAM 用触发器、非破坏性读出、不需刷新、快、贵、集成度低 → 用于 Cache
- DRAM 用电容、破坏性读出、需刷新、慢、便宜、集成度高 → 用于 主存
- 刷新以"行"为单位,刷新地址由刷新计数器产生,与 CPU 无关
- 三种刷新:集中(有连续死区)/分散(无死区,周期翻倍)/异步(无死区,均匀分布)
- 交叉存储:
,交 叉 ,完全掩盖条件顺 序
2. 高频陷阱
- 集中刷新和异步刷新的总死时间完全相同。很多人误以为异步刷新"更省时间",其实省不下来——刷的行数固定,总量就固定,异步只是把它摊开。
- 分散刷新没有死区,但代价是存取周期翻倍,别把它当成"零成本"方案。
- 刷新单位是行,不是列、也不是整个芯片。
的阵列刷一遍是 128 次,不是 128×128 次。 - 刷新周期 2 ms 的含义是"2 ms 内必须刷完一遍",不是"每 2 ms 刷一次全表"以外的含义。
- 位扩展时 8 片同时被选中(它们合起来构成一个字),字扩展时同一时刻只选中 1 片。这条决定连线方式,连错就全错。
- 低位交叉才提升带宽,高位交叉不提升。判断方法看地址:连续地址落在不同模块才是低位交叉。
- 交叉存储的分母别搞错:
是单个模块的存取周期(100 ns), 是总线传送周期(25 ns)。两者不是一回事, 才可能有 4 倍以上的理论提升。 时总线会空等,提升倍数达不到 。例 7 里 只得 倍就是反例。- 片选信号低电平有效,未被选中的芯片数据线为高阻态(不是输出 0)。
3. "求片数与地址"解题模板
① 看字数变没变 → 变 = 字扩展;看位数变没变 → 变 = 位扩展;都变 = 字位同时扩展
② 片数:位扩展 = 目标位/单片位;字扩展 = 目标字数/单片字数
③ 总地址线 = log2(目标字数)
④ 片内地址线 = log2(单片字数);片选地址线 = 总地址线 − 片内地址线
⑤ 写地址范围:按片选码顺序切分地址空间,每片占 单片字数 × 字长4. 与后续章节的接口
- 例 4 的刷新开销与例 5 的交叉带宽,是第 12 篇 Cache 为什么必须存在的量化依据。
- 主存的存取周期
是第 13 篇虚拟存储器里"缺页代价"的一部分。 - 与操作系统交叉命题:刷新与 DMA 争用存储器、内存对齐都从本节的芯片结构来。
小结
- 主存的主要实现是 DRAM,它便宜、集成度高,但破坏性读出 + 电容漏电带来两个后果:存取周期大于存取时间、必须定期刷新。
- SRAM 与 DRAM 的十项对比要背;记住"SRAM 用管子换速度,DRAM 用电容换容量"。
- 刷新以行为单位,有集中/分散/异步三种方式;集中与异步总死时间相同,只是分布不同;分散无死区但周期翻倍。
- 芯片扩展分位扩展(拼数据线)、字扩展(加高地址 + 译码器做片选)、字位同时扩展(两者组合);片数与地址划分有固定模板。
- 多模块交叉编址中只有低位交叉能提升带宽,连续读
个字耗时 ,完全掩盖条件 。
下一篇:Cache 与映射方式
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。