408 系统知识手册
从基础概念走向机制、算法与计算:按四科知识依赖重新组织讲解,保留关键表述、适用条件、步骤、例子与对照。系统学习与针对性纠错分开,阅读时可以交叉参照。
从基础概念走向机制、算法与计算:按四科知识依赖重新组织讲解,保留关键表述、适用条件、步骤、例子与对照。系统学习与针对性纠错分开,阅读时可以交叉参照。
共 295 条知识卡片
点击卡片查看完整内容。标记保存在当前浏览器;再次点击同一标记可取消。随机抽查只从当前筛选结果中选取。
没有匹配的知识点。请减少关键词或重置筛选。
数据的逻辑结构描述数据元素之间的关系,不依赖具体存储方式。集合中元素之间仅有同属一个集合的关系;线性结构为一对一关系;树为一对多关系;图可表示多对多关系。
非空线性结构有唯一首元素、唯一尾元素;首元素没有直接前驱,尾元素没有直接后继,其余元素各有唯一直接前驱和直接后继。空结构需单独处理。
存储结构把元素及其关系映射到内存:顺序存储通过位置相邻体现关系,链式存储通过指针或游标体现关系;索引存储另建索引表,散列存储通过散列函数计算位置。
结构体可以把不同类型的成员组织成一种复合数据类型。typedef 是类型别名,不会创建变量;#define 是预处理阶段的文本替换,二者的作用不能互换。
指针变量保存地址。若 p=&x,则 p 指向 x,*p 访问 x 的内容;&x 是取地址。把地址赋给指针和给指针所指对象赋值是两个不同操作。
结构体对象使用“.”访问成员,结构体指针使用“->”访问成员;p->data 与 (*p).data 等价。解引用前必须保证地址有效。
链表结点由数据域和链接域组成。声明指针只得到保存地址的变量;malloc 申请结点空间后,指针才可以指向这片空间。分配失败时返回空指针,不能继续访问成员。释放结点前应保存后继地址,释放后不能再通过原指针读写结点。
typedef struct Node {
int data;
struct Node *next;
} Node;
Node *p = (Node *)malloc(sizeof(Node));
if (p != NULL) {
p->data = 0;
p->next = NULL;
}时间复杂度由基本操作执行次数 T(n) 的增长阶刻画。保留最高阶项并忽略常数因子,例如 3n²+10n+8 属于 Θ(n²),也常记为 O(n²)。必须说明讨论的是最坏、平均还是最好情况,不能把这三种情况混为一谈。
顺序执行的两段取增长更快的一段;嵌套执行则累计内层次数。只有各层次数独立时,才可直接相乘;若内层上界随外层变化,应求和。例如内层执行 1、2、…、n 次,总量为 n(n+1)/2。
循环变量倍增或缩减通常产生对数层数;递归需同时考虑分支数、子问题规模与合并工作。空间复杂度通常统计额外辅助空间,递归调用栈也应计入。
对足够大的 n,常见次序为:1 < log₂n < n < nlog₂n < n² < n³ < 2ⁿ < n! < nⁿ。比较时关注渐近增长,不能用某个很小的 n 代替结论。
线性表是 n 个相同数据类型的数据元素组成的有限序列。n 是表长,n=0 时为空表;逻辑顺序由前驱与后继关系确定,并不意味着物理地址也相邻。元素类型可以是基本类型,也可以是结构体。
| 操作 | 语义与注意事项 |
|---|---|
| InitList、Empty、Length | 初始化空表、判断是否为空、返回有效元素个数。容量与表长是不同概念。 |
| GetElem、LocateElem | 前者按位序取元素,后者按给定值查找;位序通常从 1 开始,数组下标通常从 0 开始。 |
| ListInsert、ListDelete | 插入位置允许位于当前表尾之后;删除位置必须已有元素。删除通常通过输出参数返回被删值。 |
| Clear、Destroy | 清空使有效元素数变成 0;销毁还要终止对象或释放其资源。动态空间必须按具体实现释放,不能只改 length。 |
| PrintList | 按照逻辑顺序访问并输出所有有效元素。 |
顺序表包含连续数组和有效长度 length。按位序访问第 i 个元素时,数组下标为 i−1,地址可由起始地址加固定偏移直接算出,所以按位访问为 O(1)。顺序存储本身不保证按值查找快,未排序时仍需顺序扫描。
静态分配的容量通常固定;动态分配允许扩容,但重新分配和搬移数据可能花费 O(n)。初始化 length=0 不代表分配的每个单元都成为有效元素。
在第 i 位插入时,从尾向前搬移,避免覆盖尚未移动的元素,需移动 n−i+1 个元素;删除第 i 位后,从前向后补空位,需移动 n−i 个元素。插删平均和最坏时间均为 O(n),在表尾操作可为 O(1)。
bool insert(List &L, int i, Elem e) {
if (i < 1 || i > L.length + 1 || L.length == L.capacity) return false;
for (int j = L.length; j >= i; --j) L.data[j] = L.data[j-1];
L.data[i-1] = e;
++L.length;
return true;
}
bool erase(List &L, int i, Elem &e) {
if (i < 1 || i > L.length) return false;
e = L.data[i-1];
for (int j = i; j < L.length; ++j) L.data[j-1] = L.data[j];
--L.length;
return true;
}顺序查找逐个比较,第一个等于目标值的元素可返回其位序,失败返回约定标记。未排序表按值查找通常 O(n);已经有序且支持随机访问时,可以采用折半查找 O(log n)。
顺序表插入:为什么从后往前搬
头指针保存链表入口地址;头结点是可选的附加结点,不计入有效表长。带头结点时,首个数据结点为 L->next,空表条件通常为 L->next==NULL;不带头结点时,空表条件为 L==NULL。若接口允许 L 本身为空,访问 L->next 前还须检查 L。
结点可以分散存放,next 指向逻辑后继,末结点 next 为 NULL。随机访问第 i 个结点通常不能由地址公式得到,必须沿链查找。
| 方法 | 链接步骤 | 得到的逻辑顺序 |
|---|---|---|
| 头插法 | 新结点 s->next=L->next,再令 L->next=s。 | 与输入顺序相反;每次插入 O(1),总计 O(n)。 |
| 尾插法 | 保持尾指针 r;令 r->next=s,随后 r=s,末尾置空。 | 与输入顺序相同;有尾指针时每次插入 O(1),总计 O(n)。 |
单链表增加指针域,存储密度低于只存数据的紧凑顺序表;优势是结点空间可按需申请、插删无需整体搬移。能否减少实际耗时取决于定位方式和实现,不能仅凭二者都为 O(n) 就断言某种结构总是更快。
头插建表:为什么输入顺序会反过来
表长统计只数数据结点,带头结点时从 L->next 开始。清空带头结点的表,应逐个释放数据结点并令 L->next=NULL;销毁整个表还应释放头结点,并按接口将头指针置空。
逐个释放时先保存 next,再释放当前结点;不能 free(p) 后再读取 p->next。把一个指针置 NULL 只会改变该指针变量,不会自动释放原来指向的空间。
Node *q = p->next;
if (q != NULL) {
Elem removed = q->data;
p->next = q->next;
free(q);
}单链表插入:先接后继,再改前驱
双链表每个结点含 prior 和 next,已知结点时可直接访问前驱、后继。在 p 后插入 s,应设置 s->next=p->next、s->prior=p,并修正原后继的 prior 和 p->next;非循环表遇到尾结点时,原后继可能为空,须先判空。删除 q 时要同时修正 q 前后两侧链接,再释放 q。
| 结构 | 空表与结束判定 | 操作特点 |
|---|---|---|
| 带头结点循环单链表 | 空表 L->next==L;遍历回到头结点结束。 | 末结点 next 指向头结点,没有普通的 NULL 终止标记。 |
| 只设尾指针的循环单链表 | 尾指针 r 的 next 给出头结点或首结点,取决于是否带头结点。 | 定位表头与表尾可 O(1),但删除尾结点仍可能需寻找前驱。 |
| 带头结点循环双链表 | 空表时 head->next==head 且 head->prior==head。 | 双向均可循环遍历;正常结构中 p->next->prior==p、p->prior->next==p。 |
静态链表在预先分配的数组中存结点,每个单元保存 data 和 next。next 存的是数组下标(游标),而非普通指针地址;逻辑后继由游标决定,数据不必按逻辑顺序紧邻。
终止标记与空闲链表约定由实现决定,可用 −1,也可用保留下标 0;不能把不同教材的标记混用。插删通常只修改游标,不整体移动元素;但数组容量仍有界,需维护空闲单元。
若数据逻辑序列为 1→2→3,可以让值 1 存在 a[1] 且 next=3,值 2 存在 a[3] 且 next=5,值 3 存在 a[5] 且 next=0。这里 0 作为结束标记,逻辑遍历按 1、3、5 这些下标链接,与数组物理相邻位置不同。备用单元可另串成空闲链,插入时取空闲单元,删除后归还空闲链。
双链表删除:两条链接都必须绕过p
| 结构 | 插入与删除 | 常用操作 |
|---|---|---|
| 栈 | 同一端进行插入、删除;栈顶可变,栈底相对固定,后进先出。 | Push、Pop、GetTop;GetTop 只读取,不删除。 |
| 队列 | 队尾入队、队头出队,先进先出。 | EnQueue、DeQueue、GetHead;GetHead 不移动队头。 |
链栈通常把单链表表头作为栈顶,插入与删除栈顶为 O(1)。带头结点与不带头结点的空栈判断不同;出栈时先保存数据和后继,再释放原栈顶。动态链栈没有数组固定容量的“满”,但仍可能内存分配失败。
链队同时设 front 和 rear,分别定位队头入口与队尾。带头结点时,空队通常 front==rear,且头结点 next 为空;不带头结点时,空队通常 front==rear==NULL。入队接到 rear 后并更新 rear;删除最后一个数据结点后,还必须同步调整 rear,防止悬空。
初始化建立与约定相符的空结构;清空删除有效元素但可保留头结点;销毁释放全部动态结点。初始化时“已分配头结点”与“两个指针均为空”属于不同实现,不能组合成同一段操作。
同样输入A,B,C:栈和队列先取出谁
容量为 M、数组下标 0…M−1、top 指向当前栈顶时:空栈 top=−1,满栈 top=M−1,元素数 top+1。入栈先检查未满,再执行 data[++top]=x;出栈先检查非空,再执行 x=data[top--]。逻辑出栈不要求清除原数组字节。
若 top 指向下一空位,空栈可为 top=0,入栈可写 data[top++]=x,出栈可读 data[--top]。指针型 base、top 的差表示元素数时,也要确认 top 是否指向下一空位。前置、后置自增表达不同取值时机,不能据此断言其中一种在所有编译环境下效率都更高。
令 0 号栈由左向右增长,1 号栈由右向左增长,并令 top0、top1 指向各自栈顶。初始化 top0=−1、top1=M;两者相邻,即 top1−top0=1 时共享空间已满。0 号栈入栈先 ++top0,1 号栈入栈先 --top1。
共享栈能让两个栈动态利用同一片剩余空间,避免固定切半造成一边满、一边空。只要还有空间,基本入栈和出栈仍为 O(1)。
共享栈:两边向中间生长怎样判满
普通顺序队列出队后队头前面的数组单元闲置,rear 到达数组末端时可能出现“假溢出”。循环队列用模运算把下标首尾连接,使已释放单元可重新利用。
数组容量为 M,采用牺牲一个存储单元的约定:空队 front==rear;满队 (rear+1)%M==front;元素数 (rear−front+M)%M,最多保存 M−1 个元素。入队先写 data[rear] 再推进 rear,出队先读 data[front] 再推进 front。
也可以规定指针指向首元素之前或末元素本身,相应读写顺序和公式会改变。不能把“先移后存”的代码直接套入“rear 指向下一空位”的定义。
双端队列两端都允许插入、删除。输入受限表示只允许一端插入而两端删除;输出受限表示两端插入而只允许一端删除。判断可能输出序列时,应按允许操作逐步模拟,不能把双端队列等同于普通栈或普通队列。
例:5个槽位,留空一格的循环队列
数组是固定下标范围内、同类型元素的集合,可以看作线性表的推广。通常只讨论按下标取值与修改,不像一般线性表那样频繁改变元素数量。物理空间连续,按合法下标可随机访问。
设 A 的行下界为 r₀、列下界为 c₀,共 R 行 C 列,每元素占 w 个存储单元,首地址为 base。行优先先存较小行号,同一行再存较小列号;列优先则交换行列角色。
计算偏移前先把实际下标减去对应下界,再乘前面完整行或列的元素数。多维数组同理:某维下标的权重是其后各维长度的乘积(行优先)。
行优先;括号中是从首元素起经过的元素个数。
列优先;行数 R 是相邻两列首元素的间隔。
二维数组:行优先与列优先怎样映射到内存
对重复元素只保存一份,对规定的零元素可不分配位置,从而减少空间。特殊矩阵具有明确的分布规律;只要保留元素的位置能由公式求出,仍可由下标计算压缩地址。
n 阶对称矩阵满足 aᵢⱼ=aⱼᵢ,只需保存主对角线及一个三角区域,共 n(n+1)/2 个元素。以原矩阵下标从 1 开始、下三角按行存入 B[0…] 为例:i≥j 时 k=i(i−1)/2+j−1;i<j 时交换 i、j 后使用公式。
上、下三角矩阵的一侧严格三角区域为同一个常量 c 时,保存另一侧连同主对角线,再保存一个 c,共 n(n+1)/2+1 个位置;若题目规定零常量无需单独保存,应按题设处理。上三角按行存储时,先累计前面各行的 n、n−1、… 个元素,再加本行偏移。
原矩阵下标从 1 开始、B 从 0 开始时,上三角按行存储的非恒定项(i≤j)映射 k=(i−1)(2n−i+2)/2+(j−i),严格下三角常量统一映射到 B[n(n+1)/2]。下三角对应 i≥j 用 k=i(i−1)/2+j−1,严格上三角常量同样映射到最后一个位置;这与对称矩阵把另一侧交换下标的办法不同。
三对角矩阵仅主对角线及相邻上下两条对角线可能非零,非零带外满足 |i−j|>1。n≥2 时带内元素共有 3n−2 个。以 i、j 从 1 开始、按行压入 B[0…] 为例,带内元素下标 k=2i+j−3。
反求行列时要利用每行带内元素数:首末行各两个,中间行各三个。带外元素取零或指定常量,不能套用带内地址公式。
对称矩阵压缩:k=4究竟对应哪一个元素
当非零元素数 t 远小于总元素数 m×n 时,可只记录非零元素。三元组记录(行、列、值),通常按行优先或列优先排序;矩阵总行数、总列数、非零元素数也应保留。根据任意 (i,j) 找元素通常需查找,不能像普通二维数组那样一次偏移直接取值。
伪地址表示把元素在展开矩阵中的相对位置与值成对保存。对 m×n 矩阵、下标从 1 开始时,行优先的 1 起始位置为 n(i−1)+j;如果位置从 0 开始,则再减 1。由伪地址可反求行列。
三元组的行号、列号本质为整数。若为了演示而把值、行号、列号都放入 float 类型二维表,取行列参与寻址时应转回整型;正式结构体通常给行列单独使用整数成员,避免隐式精度与类型问题。
按行建立链表时,每个非零结点保存列号、值和行后继链接;行号可由所属行表隐含。十字链表同时组织行表与列表,每个非零结点保存行号、列号、值,以及 right、down 两个链接域。
right 连接同行的下一个非零元素,down 连接同列的下一个非零元素。行、列头指针分别给出入口;插入或删除非零元素时,两条方向的链都要维护,不能只改行链。总表头可保存行数、列数、非零元素总数等信息。
稀疏矩阵转置:交换行列后为何还要重排
扫描表达式:遇左括号入栈;遇右括号,先判栈是否非空,再检查栈顶是否为相同类型的左括号,匹配才弹出。右括号到来时栈空、括号类型不一致,或扫描结束后栈仍非空,都属于不匹配。
从左向右扫描后缀表达式,操作数入栈;遇二元运算符,先弹出右操作数 b,再弹出左操作数 a,计算 a 运算符 b,将结果压回。减法和除法尤其不能颠倒顺序。扫描结束应恰好剩一个结果,栈下溢或剩余多个值都提示表达式不合法。
递归把当前问题转成同类、规模更小的子问题,必须有终止条件。调用栈保存返回地址、参数和局部状态;递归写法简洁,但时间和空间代价取决于具体递推关系,不能笼统认为所有递归都低效。
树的层序遍历、图的广度优先遍历使用队列,把后续层结点按发现顺序等待处理。设备缓冲、请求排队等系统场景也利用队列协调速度差异;具体是否严格先进先出仍由调度策略决定。
括号匹配与栈顶的变化
串由零个或多个字符组成;长度为 0 的串是空串,一个或多个空格构成的串是非空的空格串。子串由主串中连续字符组成,其位置用第一个字符在主串中的位序表示。
串相等要求长度相同且对应字符全相同。比较按字符编码逐个进行,首个不同字符决定大小;如果一方为另一方前缀,则较短者较小。不能只比较长度,也不能把字符数组首地址相等当作串值相等。
| 表示 | 特点与长度 |
|---|---|
| 定长顺序表示 | 为串分配固定容量,另存 length 或约定结束标记。超过容量可能截断,但截断是接口策略,不能误认为自动保留完整串。 |
| 堆分配顺序表示 | 运行时按所需长度申请连续空间,保存字符指针和长度,使用完后释放。连接与复制时须申请足够空间。 |
| 块链表示 | 一个结点可保存一个或多个字符,结点之间链接。每块字符越少,链接开销比例越大;最后一块可能不满。 |
赋值、复制、求长、判空、比较、连接、取子串、定位、清空和销毁都是串操作。字符数组不能用普通数组赋值符直接整体复制,但语言提供的字符串类型可能重载赋值,必须区分抽象串和具体 C 数组。
取子串前检查起点和长度是否越界;下标从 0 或位序从 1 开始会改变边界公式。若存储格式要求末尾 \0,连接两个串时除有效字符总数外还要为结束标记留空间,且 \0 不计入串长。
从主串的一个候选起点开始,逐个比较模式串字符;全部匹配则返回该起点。失败时让主串候选起点后移一位,模式串回到开头,继续尝试。主串长 n、模式长 m 时,最多有 n−m+1 个候选起点,最坏比较量为 O((n−m+1)m),通常简写为 O(nm)。
若 i、j 都采用从 1 开始的下标,当前失配时已匹配 j−1 个字符,原候选起点为 i−j+1;下一候选起点为 i−j+2,因此回退可写 i=i−j+2、j=1。若模式串已全部匹配,起点为 i−m。
模式串某一前缀的最长相等真前缀与真后缀长度表示已匹配信息可以保留多少。KMP 失配后主串指针 i 不回退,只让模式指针按预处理表跳转,避免重新比较已知相同的片段。
使用 1 起始 next 数组时,next[1]=0;j≥2 时,next[j] 等于模式串前 j−1 个字符的最长相等真前后缀长度加 1。失配令 j=next[j];当 j=0 时,统一推进 i、j,相当于主串移到下一字符并重新从模式开头开始。预处理 O(m),完整匹配 O(n),总计 O(n+m)。
对 ABABA,每个前缀的最长相等真前后缀长度依次为 0、0、1、2、3。它描述的是各前缀的长度;1 起始 next[j] 使用的是 j 前面的已匹配部分。0 起始失败函数又有另一套下标约定,不能直接把数值平移后套进代码。
例如已匹配 q 个字符,能保留的前后缀长度为 ℓ,则模式串相对主串后移 q−ℓ 位。先明确 q、ℓ 的定义,再计算移动量,避免把 next 值本身当成后移位数。
本代码假定模式串非空,S、P 的有效字符均从下标 1 开始,next 已按同一规则生成;0 为失败返回值。条件 j==0 或 k==0 必须放在字符访问之前,利用短路求值避免访问无效的第 0 个字符。
void getNext(const char P[], int m, int next[]) {
int j=1, k=0; next[1]=0;
while (j<m) {
if (k==0 || P[j]==P[k]) { ++j; ++k; next[j]=k; }
else k=next[k];
}
}
int kmp(const char S[], int n, const char P[], int m, const int next[]) {
int i=1, j=1;
while (i<=n && j<=m) {
if (j==0 || S[i]==P[j]) { ++i; ++j; }
else j=next[j];
}
return j>m ? i-m : 0;
}例:主串abababaca,模式ababaca
在 1 起始约定下,若模式字符 P[j] 与 P[next[j]] 不同,可令 nextval[j]=next[j];若相同,则令 nextval[j]=nextval[next[j]],跳到更前的位置。nextval[1]=0。优化只是减少必然失败的重复比较,不改变匹配结果与 O(n+m) 的渐近复杂度。
第 5 个字符 b 的 next 值为 2,而第 2 个字符也是 b,继续比较仍会失配,因此继承 nextval[2]=1。第 7 个字符 a 与第 1 个字符 a 相同,故 nextval[7]=0。
| j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| P[j] | a | b | a | a | b | c | a | c |
| next[j] | 0 | 1 | 1 | 2 | 2 | 3 | 1 | 2 |
| nextval[j] | 0 | 1 | 0 | 2 | 1 | 3 | 0 | 2 |
nextval逐项推导:相同字符为什么要继续跳
非空树由一个根和若干互不相交的子树组成,定义具有递归性。结点的度是孩子数,度为 0 的结点是叶子,度大于 0 的是分支结点;树的度是结点度的最大值。孩子、双亲、兄弟、祖先与子孙分别描述局部和跨层关系。
根通常位于第 1 层,树高是最大层数。若改用根深度为 0 的约定,所有高度公式相应少 1。路径是沿相邻结点构成的序列,路径长度按边数计;森林是若干互不相交的树的集合。有序树的孩子有确定次序,交换孩子可能得到不同树。
n 个结点的非空树有 n−1 条边,也等于所有结点孩子数之和,所以 n=1+Σd(v)。注意这里求和的是出度/孩子数;若按无向图的度计数,则度和为 2(n−1)。
m 叉树指每个结点最多 m 个孩子;“度恰为 m 的树”还要求至少有一个结点具有 m 个孩子,因此二者不是同一限制。m≥2、根为第 1 层时,第 i 层最多 mⁱ⁻¹ 个结点,高度 h 的最大结点数为 (mʰ−1)/(m−1)。
m≥2、n≥1,求含 n 个结点的 m 叉树最小高度;来自 n≤(mʰ−1)/(m−1)。
二叉树可以为空;非空时由根、左子树和右子树组成。每个结点最多两个孩子,度只可能为 0、1、2。即使只有一个孩子,也必须区分左孩子还是右孩子。
度为 2 的有序树至少存在一个度为 2 的结点,并不等同于二叉树;二叉树可以只有一个结点甚至为空。
| 名称 | 定义与性质 |
|---|---|
| 满二叉树 | 按本资料采用的完美二叉树定义:高度 h、每层达到最大结点数,共 2ʰ−1 个结点;叶子全在第 h 层。 |
| 完全二叉树 | 与同高度满二叉树按层从左到右编号的前 n 个位置一致;最后一层从左连续排列,其上各层均满。 |
| 二叉排序树 | 所有左子树关键字小于根,右子树关键字大于根,左右子树也满足此规则;重复关键字另按题设。 |
| 平衡二叉树(AVL) | 每个结点左右子树高度差绝对值不超过 1,同时满足二叉排序树次序。 |
完全二叉树中度为 1 的结点至多一个,且只有左孩子;编号大于 ⌊n/2⌋ 的结点均为叶子。遇到第一个不完整的分支结点后,后续层序位置都不能再有孩子。奇数 n 时无度为 1 的结点,偶数 n 时恰有一个。
设度为 0、1、2 的结点数分别为 n₀、n₁、n₂。由 n=n₀+n₁+n₂ 和 n−1=n₁+2n₂ 相减,得到 n₀=n₂+1,不要求完全或平衡。
根为第 1 层时,第 i 层至多 2ⁱ⁻¹ 个结点,高度 h 的二叉树至多 2ʰ−1 个结点;含 n 个结点的最小高度为 ⌈log₂(n+1)⌉,最大高度为 n。
| 对象 | 公式与存在条件 |
|---|---|
| 双亲 | i>1 时为 ⌊i/2⌋;偶数 i 为左孩子,奇数 i>1 为右孩子。 |
| 左孩子 | 编号 2i,只有 2i≤n 时存在。 |
| 右孩子 | 编号 2i+1,只有 2i+1≤n 时存在。 |
| 结点层次 | ⌊log₂i⌋+1。 |
| 树高与叶子数 | h=⌊log₂n⌋+1=⌈log₂(n+1)⌉;叶子数为 ⌈n/2⌉。 |
二叉链表每个结点有左右两个孩子链接,共 2n 个链接域;非空孩子链接等于 n−1 条边,因此空链接域有 n+1 个。这一结论用于解释线索化可利用的空域,而不是说树有 n+1 个叶子。
完全二叉树编号:双亲、孩子与叶子区间
顺序存储通常按照完全二叉树编号把结点放入数组。完全树紧凑,父子位置可直接计算;一般树需为空缺位置保留编号,深度 h 的右单支树可能用到编号 2ʰ−1,空间浪费明显。
二叉链表用 data、lchild、rchild 描述结点,适合不规则形态;若常需找双亲,可增设 parent 成为三叉链表。没有双亲指针时,不能仅凭任意结点的孩子链接 O(1) 找到其双亲。
| 遍历 | 递归次序 | 根出现的位置 |
|---|---|---|
| 先序 | 根→左子树→右子树 | 子树序列的第一项。 |
| 中序 | 左子树→根→右子树 | 左右子树序列之间。 |
| 后序 | 左子树→右子树→根 | 子树序列的最后一项。 |
把 visit 放到左递归前得到先序,放到右递归后得到后序。三种遍历访问每个结点一次,时间 O(n),递归辅助空间为 O(h),最坏单支树 O(n)。层次遍历用队列:根非空则入队,反复出队访问,再依次将非空左、右孩子入队,直至队空。
void inorder(Node *p) {
if (p == NULL) return;
inorder(p->left);
visit(p);
inorder(p->right);
}一棵树逐步走完中序遍历
先序可先把根压栈,循环弹出并访问,再依次压右孩子、左孩子;由于栈后进先出,左孩子会先访问。中序则反复把当前结点及其左链压栈,遇空后弹栈访问,再转向弹出结点的右子树。
例如根 A、左孩子 B、右孩子 C,B 的左右孩子为 D、E:先序 A B D E C,中序 D B E A C,后序 D E B C A。必须按同一棵树检查三种序列。
后序要在左右孩子都完成后才访问根。可用一个栈与 lastVisited 记录上次访问结点:沿左链入栈,查看栈顶;若其右孩子未访问则转入右子树,否则访问并弹出。也可先得到“根→右→左”的逆后序,再逆转序列,但需额外保存结果。
每个分支结点都交换,不能只交换根的两个孩子而保留子树内部不变。代码访问 n 个结点,时间 O(n),递归栈空间 O(h)。
void mirror(Node *p) {
if (!p) return;
mirror(p->left);
mirror(p->right);
Node *tmp = p->left;
p->left = p->right;
p->right = tmp;
}非递归中序:显式栈究竟保存什么
先序+中序:先序首项是根,在中序中找根,左侧是左子树、右侧是右子树;按左右结点数切分先序后递归。后序+中序同理,只是根取后序末项。层序+中序也可唯一确定:层序中在当前结点集合里最先出现的结点是当前根,再按中序左右范围分别筛出子树层序。
单独先序、单独后序或先序+后序一般不能唯一确定二叉树,例如一个孩子时无法判断是左孩子还是右孩子。若关键字重复,还需额外标识或条件,不能直接套用唯一性结论。
先序 D A E F B C H G I,中序 E A F D H C B G I。先找根 D:中序分为左侧 E A F、右侧 H C B G I;左子树先序 A E F,所以根为 A、左右孩子 E 和 F。右子树先序 B C H G I,所以根为 B,左侧 H C 由 C 作根、H 为左孩子;右侧 G I 由 G 作根、I 为右孩子。
得到的后序应为 E F A H C I G B D,可用来反向检查,而不是任意挑选序列中的相邻字符作为父子。
先序加中序:左右子树的范围怎样一步步划分
普通二叉链表中无左孩子时,可令 lchild 指向遍历前驱;无右孩子时,可令 rchild 指向遍历后继。常用 ltag=0 表示左孩子、ltag=1 表示前驱线索,rtag 类似。必须先看标志再追指针,否则可能把线索当子树并陷入循环。
线索化要明确先序、中序或后序;同一棵树的前驱后继会随遍历次序改变。可增加头结点统一边界:头的左链接指向根,右链接指向最后访问结点,并让首结点前驱和末结点后继指向头。具体标志设定仍需按题设。
若 rtag=1,右链接就是中序后继;若 rtag=0,则转入右子树,沿着“有真实左孩子”的链接走到最左结点。中序前驱对称处理:有左线索直接取,无则到左子树最右结点。
根的后序后继为空;若当前结点是双亲的右孩子,或是左孩子且双亲无右子树,后继为双亲;若它是左孩子且双亲有右子树,后继为右子树的后序首结点。仅靠普通后序线索不一定能直接取得双亲,因此后序遍历的实现不能简单照搬中序。
中序线索:真实孩子与前驱后继怎样区分
| 表示法 | 保存内容 | 优势与限制 |
|---|---|---|
| 双亲表示 | 数组中保存 data 和 parent 下标,根可用 −1 标识。 | 已知结点找双亲 O(1);找全部孩子往往扫描数组。 |
| 孩子表示 | 每个结点对应一个孩子链表,头表保存结点信息和首孩子入口。 | 找孩子直接,叶子的孩子链为空;找双亲通常需扫描。 |
| 孩子兄弟表示 | 每个结点保存 firstchild 和 nextsibling。 | 两个指针即可表示任意度树,方便树与二叉树转换;找双亲仍可额外增设 parent。 |
孩子兄弟表示中,左链接不是“普通树唯一的左孩子”,而是第一个孩子;右链接是同一层的下一个兄弟。单棵树根没有兄弟,转换后的二叉树根因此无右孩子。必须保留原树兄弟次序,否则转换结果会改变。
| 原结构的遍历 | 对应二叉树的遍历 |
|---|---|
| 树的先根遍历:根先于依次各子树 | 先序遍历。 |
| 树的后根遍历:依次各子树后访问根 | 中序遍历。 |
| 森林的先序遍历 | 先序遍历。 |
| 森林的中序遍历(逐树后根次序) | 中序遍历。 |
普通树层次遍历按层从左到右,用队列保存待访问结点;转换后的二叉树层次结构已经改变,因此不能把二叉树的层次遍历序列直接当作原树层次序列。
孩子兄弟转换:为什么右链接连的是兄弟
叶子带权路径长度是叶权值乘从根到叶经过的边数;树的 WPL 为全部叶子的这一乘积之和。给定正权叶子集合,使 WPL 最小的二叉树称哈夫曼树。叶子权值是原始数据,新产生内部结点权值等于孩子权值之和。
把每个叶子看成独立树,反复取根权最小的两棵树作为新根的左右子树,新根权为两者之和,再放回候选集合,直到只剩一棵。n 个叶子(n≥2)产生 n−1 个内部结点,总结点数 2n−1,没有度为 1 的结点。
同权时选择不同或交换左右可能产生不同形态和编码,但最优 WPL 相同。把“较小权放左边”等作为画图约定即可,不是哈夫曼最优性的必要条件。
权值 2、3、7、8:先 2+3=5,再 5+7=12,最后 8+12=20。WPL 等于所有合并权之和 5+12+20=37;也可按叶深度算 2×3+3×3+7×2+8×1=37。两种计算可互相复核。
哈夫曼构造:最小两棵树反复合并
沿根到叶路径分别以 0、1 标记左右边,叶子路径串就是其编码。由于叶子不会成为其他叶子的祖先,一个完整编码不会是另一编码的前缀,因此可即时识别边界。哈夫曼编码在给定权重和二元前缀码条件下使加权平均码长最小,码字本身通常不唯一。
定长编码每个符号使用相同位数;变长编码可让高频符号码字更短。不能把“不同长度”与“可唯一译码”直接画等号,前缀性是便于唯一译码的重要结构条件。
m 叉版本每次合并 m 个最小权根。满 m 叉树叶子数 L 与内部结点数 I 满足 L=(m−1)I+1,所以原始叶子数 n 不满足 (n−1) 能被 m−1 整除时,先补最少数量的 0 权虚拟叶子。
补齐后 I=(L−1)/(m−1),也等于 ⌈(n−1)/(m−1)⌉。虚拟叶子不代表实际符号,编码时只输出原始符号。
每个元素起初独立为一个集合。双亲数组 parent 保存森林:非根项存父结点下标,根项可用负数标识;根的负值也可编码集合规模。Find(x) 沿双亲链返回所属集合的根,两个元素根相同表示同属一个集合。
合并前先分别查根,仅当两个根不同才连接,否则不应再改变结构。把一个集合根接到另一个集合根是常见实现;按规模或按秩合并,可避免无谓增加高度。不能直接把任意非根元素的 parent 改向另一集合,否则可能只移动了原集合的一部分。
这里根的负值表示负的集合大小,故数值更小的根代表更大的集合。路径压缩把查找途中结点直接接到根,不改变集合划分。并查集可用于动态连通性、Kruskal 选边时的环检测等。
int Find(int x) {
if (parent[x] < 0) return x;
return parent[x] = Find(parent[x]);
}
void Union(int a, int b) {
int ra = Find(a), rb = Find(b);
if (ra == rb) return;
if (parent[ra] > parent[rb]) { int t=ra; ra=rb; rb=t; }
parent[ra] += parent[rb];
parent[rb] = ra;
}假设原父指针链A→B→C→R,R为根。递归路径压缩的find(A)在返回时把沿途A、B、C直接指向R。
集合划分和代表根不因此改变;压缩后的图仍是一棵树。按秩合并中的秩在压缩后一般不等于当前精确高度。
图记为 G=(V,E),顶点集 V 与边集 E 分别描述对象及其联系。本资料采用顶点集非空、边集可以为空的约定。无向边 (u,v) 无方向,有向弧 ⟨u,v⟩ 从弧尾 u 指向弧头 v;简单图不含自环与重边。
无向图的度为与顶点关联的边数,度和为 2|E|;有向图中每条弧为弧尾贡献一次出度、弧头贡献一次入度,因此入度和=出度和=|E|。n 个顶点的简单无向完全图有 n(n−1)/2 条边,有向完全图有 n(n−1) 条弧。
路径长度按边数计;带权路径长度按经过边的权值之和计。简单路径不重复顶点;简单回路只允许起点与终点相同,其余顶点不重复。
无向图任意两点有路径则连通;连通分量为极大连通子图,不能再添加同图中的顶点仍保持为同一连通块。有向图强连通要求每对顶点双向可达,强连通分量是极大强连通子图。一次从某点可达全部顶点,不足以说明有向图强连通。
连通无向图的生成树包含全部 n 个顶点和 n−1 条边;删任意树边都会断开,补任意非树边都会出现环,因此是极小连通生成子图。非连通无向图各连通分量生成树的集合是生成森林。
边数少于 n−1 的无向图必不连通,但边数达到 n−1 不保证连通。有向树还需满足根入度 0、其他结点入度 1,并从根可达全体且无环;只列入度条件不能排除另有环状分量。
判断邻接、列出邻居、插删顶点、增删边以及读写边权是基本操作。FirstNeighbor、NextNeighbor 负责依次枚举一个顶点的邻接点;返回 −1 等标记表示枚举结束。删除顶点时也要删除所有关联边。
邻接矩阵用一维表保存顶点信息,用 |V|×|V| 矩阵保存邻接关系。无权简单图可用 1 表示有边、0 表示无边;带权图可用边权表示有边、∞ 表示无边,对角线通常另设为 0。若允许权为 0 的边,就不能把所有 0 都当作无边。
无向图矩阵对称,可以压缩保存一侧;有向图第 i 行反映出边、第 i 列反映入边。对无权图数 1 可求度,对带权图应按“边是否存在”计数而非简单数非零项,且按题设排除对角线。
邻接判定与访问指定边权为 O(1),枚举一个顶点全部邻居需扫描一行 O(|V|),空间为 O(|V|²),适合稠密图。边数统计通常需扫描矩阵,无向图还要避免重复计数。
对普通 0/1 邻接矩阵 A,(Aᵏ)ᵢⱼ 表示从 i 到 j 长度恰为 k 的游走数,允许重复顶点和边;不能一概称为简单路径条数。
无向简单图邻接矩阵对称,每条无向边在邻接表中对应两个邻接项。矩阵占Θ(V²),邻接表占Θ(V+E)。
使用邻接矩阵遍历全部邻边每个顶点要扫描一整行,完整BFS/DFS通常Θ(V²);邻接表通常Θ(V+E)。
邻接表由顶点表与各顶点的边链组成。顶点项保存数据和第一条边入口,边项保存邻接顶点下标、下一条边链接以及可选权值。无向图每条边通常出现在两个顶点的链中,共 2|E| 个边结点;有向图按出边存储时每条弧出现一次。
空间为 O(|V|+|E|),适合稀疏图。无向图数顶点链长得到度;有向出边表数链长得到出度,求入度通常扫描全部表。邻居次序可因头插、尾插等实现不同而不同。
弧结点同时保存 tailvex、headvex 两端下标,tlink 连接同弧尾的下一条弧,hlink 连接同弧头的下一条弧。顶点项含 firstout 与 firstin 两个入口,从而都能直接枚举出弧与入弧;每条弧只对应一个弧结点。
边结点保存 ivex、jvex、ilink、jlink 和可选的 mark、info。ilink、jlink 分别接同一端点的下一条关联边,顶点 firstedge 给出入口。每条无向边只有一个结点,通过两个端点的链共同使用,适合需要按边标记或删除的处理。
从未访问顶点 v 开始,先访问并置 visited[v],再逐个检查邻接顶点,对每个未访问邻居递归 DFS。当前结点所有邻居都已处理时返回上一层。visited 避免在环中重复访问。
只调用一次 DFS,覆盖从起点可达的部分。遍历全部无向图应外层扫描所有顶点,遇未访问者重新启动 DFS;启动次数等于连通分量数。遍历中首次发现顶点的边形成深度优先生成树,各分量合起来是生成森林。
邻接矩阵需要逐行扫描,遍历全部图时间 O(|V|²);邻接表每个顶点和各边项被检查常数次,时间 O(|V|+|E|)。访问数组及最坏递归栈为 O(|V|)。
起点和邻居访问次序可能改变 DFS 序列与生成树。固定顶点编号、邻接矩阵和扫描顺序后实现输出可以确定,但“图使用矩阵存储”本身不规定唯一起点或扫描顺序。
DFS:深入与回退时递归栈怎样变化
访问起点并置 visited 后入队;循环取队头,依次发现未访问邻居,立即标记并入队。若延迟到出队才标记,同一个顶点可能由不同前驱多次入队。遍历全部非连通图同样需要外层扫描未访问起点。
BFS 首次发现 v 时可记录 dist[v]=dist[u]+1、parent[v]=u。在无权图或所有边权相同的图中,得到从起点到 v 的最少边数;不可达顶点距离保持 ∞。对任意非等权图,BFS 不能代替带权最短路算法。
完整 BFS 用邻接表为 O(|V|+|E|),用邻接矩阵为 O(|V|²);队列与标记最坏占 O(|V|)。首次发现边形成 BFS 生成树,但同层顶点的访问次序及最短路前驱可能不唯一。
无向连通图从任一点都能一次覆盖全图;有向图从某点覆盖全图只证明它能到达其他点,不证明其他点能返回它,不能据启动次数直接统计强连通分量。
例:BFS发现时入队并标记,避免重复入队
在带权连通无向图中,生成树总边权最小者为最小生成树。它有 |V|−1 条边,最小权值总和固定,边集合可能不唯一。所有边权互不相同是 MST 唯一的充分条件,不是必要条件;原图若已是一棵树,生成树就是它本身。
从任一顶点加入集合 S 开始,每轮在一端在 S、另一端不在 S的边中选最小边,加入该边及新顶点。随后更新每个未选顶点连接 S 的最小边权及其前驱。直到所有顶点加入;若无法找到跨集合的有限边,则原图不连通。
普通数组实现每轮扫描未选顶点并更新连接代价,共 O(|V|²),适合稠密图;此代价对应具体实现。稀疏图用邻接表和优先队列可有不同复杂度,不能把 O(|V|²) 当作所有实现必然相同。
Prim 的 key[v] 是 v 连接当前生成树的一条最轻边;Dijkstra 的 dist[v] 是源点到 v 的整条路径长度。二者都逐步扩展集合,但更新公式和优化目标不同。
从A做Prim:先AB(1),再BC(2),再CD(3),总权6。Kruskal按边权1、2、3、4、5排序,前三条已连接全部顶点。
这里所有边权不同,最小生成树唯一。两种算法都解决带权无向连通图的最小生成树问题;不是从起点到各点的最短路径。
开始时每个顶点是独立分量,将所有边按权值非降序排序。依次检查边 (u,v):Find(u)≠Find(v) 时选入并合并集合;若根相同,加入会形成环,跳过。已选 |V|−1 条边后可结束。
边排序通常 O(|E|log|E|),并查集操作在优化后相对较小,因此总体常记为 O(|E|log|E|)。适合边较少的稀疏图。若遍历完仍不足 |V|−1 条边,则只得到最小生成森林。
三角形边 AB=1、BC=2、AC=4:Kruskal 先取 AB,再取 BC,AC 因两端已连通而跳过,总权 3。Prim 从 A 开始也先取 AB,再从跨集合边 BC=2 与 AC=4 中取 BC。两者构建过程不同,但都满足每步安全选边原则。
Kruskal:为什么要跳过一条较轻的边
令 dist[s]=0,其余为 ∞,重复选出未确定顶点中 dist 最小者 u,把它固定,再对其出边 (u,v) 松弛:若 dist[u]+w(u,v)<dist[v],更新 dist[v] 和前驱。已确定的距离在非负权条件下不会再被更短路径推翻。
数组扫描选最小值的普通实现时间 O(|V|²);在此实现下,即便用邻接表,选点步骤仍可能主导。优先队列实现可改善稀疏图时间,需与具体数据结构对应。负边可能使已固定距离后来变小,标准 Dijkstra 不适用。
从顶点 1 出发,资料图的初始可达距离为 d(2)=10、d(5)=5。先固定 5,经过 5 将 d(2) 改为 8、d(4) 改为 7、d(3) 暂为 14;再固定 4,把 d(3) 改为 13;固定 2 后,把 d(3) 改为 9。最终固定顺序可为 1、5、4、2、3。
最终路径依次为 1→5(5),1→5→4(7),1→5→2(8),1→5→2→3(9)。记录 predecessor 并从终点反向追到源点,再逆序,即可恢复路径。
例:从A到C,逐轮确定最短路径
初始化 dist[i][i]=0,有边取对应权值,无边为 ∞。第 k 轮允许顶点 k 作为中间点,更新 dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j])。k 必须处于最外层,表示本轮扩展的中间顶点集合。
同时维护中转点或前驱/后继矩阵,可以恢复具体路径。三重循环时间 O(|V|³),距离和路径矩阵空间 O(|V|²)。相加前应检查两段是否可达,避免用有限整数模拟 ∞ 时溢出。
Floyd 可以处理负权边,但通常最短路问题要求不存在可达的负权回路;负环会使受影响路径长度没有有限下界。运行后 dist[i][i]<0 可用于识别相关负环。无向图一条负边按往返游走解释便会引出负环问题。
Floyd:距离矩阵为何要把k放在最外层
有向无环图称 DAG。AOV 网把活动作为顶点,弧 u→v 表示 u 必须先于 v 完成。拓扑序要求每条有向边的起点都先于终点,每个顶点恰好出现一次;存在有向环时无法输出完整拓扑序。
先计算所有顶点入度,把入度为 0 的顶点放入队列或栈。每取出一个顶点就输出,并对其所有出边终点入度减 1,减为 0 后加入候选集合。输出数不足 |V| 时,图中存在环。邻接表实现时间 O(|V|+|E|)。
同时有多个零入度顶点时,选择不同通常产生不同拓扑序。对当前 DAG 若每一步都只有一个可选零入度顶点,则拓扑序唯一。不要将拓扑序列中的相邻项都理解成原图存在直接边。
拓扑排序:删除边怎样让入度变为0
AOE 网用顶点表示事件,用有向边表示活动,边权为活动持续时间。事件发生后,其出边活动才可开始;所有入边活动完成后,该事件才可发生。通常设置唯一源点和汇点,多源多汇可引入虚拟事件统一处理。
源点到汇点的最长带权路径称关键路径,长度是满足依赖的最短工期。关键活动时间余量为 0;关键路径可能不止一条。
ve 表示事件最早发生时间;vl 表示不延长总工期的最迟发生时间。活动最早开始取弧尾 ve,最迟开始取弧头 vl 再减自身持续时间。
源点 ve=0,按拓扑序向前计算。
汇点 vl=ve(汇点),按逆拓扑序向后计算。
d=0 的活动为关键活动;事件时间与活动开始时间不要混用。
活动为 v₁→v₂(3)、v₁→v₃(2)、v₂→v₅(3)、v₂→v₄(2)、v₃→v₄(4)、v₄→v₆(2)、v₃→v₆(3)、v₅→v₆(1)。正推得到 ve=[0,3,2,6,6,8],逆推得到 vl=[0,4,2,6,7,8]。
按上述八条边顺序,活动余量为 [1,0,1,1,0,0,3,1],关键路径为 v₁→v₃→v₄→v₆,总工期 2+4+2=8。正推 v₄ 时须等 v₂ 和 v₃ 两条入活动都完成,故取 max(3+2,2+4)=6。
存在多条关键路径时,只缩短其中一条路径的独占活动,其他关键路径可能仍决定原工期。缩短共同关键活动或同时处理所有关键路径后才可能缩短总工期;继续缩短还可能使原非关键路径转为新的关键路径。
两条路径工期分别2+4=6、3+2=5,因此最短完成工期6,关键路径源→A→汇。
活动B→汇的最早开始e=ve(B)=3,最迟开始l=vl(汇)−2=4,余量1。事件ve/vl与活动e/l不能混用。
查找是在集合中寻找满足条件的数据元素。主关键字可唯一标识记录,次关键字不一定唯一;讨论允许重复键时应明确规则。静态查找表主要做查找,动态查找表还频繁插入与删除,因此适合的数据结构可能不同。
成功与失败的平均查找长度分别计算。判定树中成功查找的比较次数通常等于目标结点层次;失败结点是空区间或空链接,不应自动额外计作一次关键字比较,须按题目约定计数。
pᵢ 为查找第 i 项的概率,cᵢ 为相应关键字比较次数;概率和为 1。
顺序查找从表一端依次比较关键字,匹配即成功,到达边界仍未匹配则失败。顺序表用下标推进,链表用 next 推进,两种存储均适用。
哨兵实现可把待查关键字临时存入保留单元,例如下标 0,从表尾向前比较,直到相等;返回 0 表示未在有效元素中找到。哨兵减少循环中的边界判断,并不减少渐近阶,且保留单元不能与有效记录冲突。
n 个有效记录等概率查找成功时,比较次数为 1、2、…、n 的平均值,即 (n+1)/2。普通实现失败通常比较 n 个有效关键字;若把与哨兵的比较也计入,则失败为 n+1,计数口径必须统一。
有序表顺序查找可提前失败:升序扫描首次遇到大于 key 的值即可停止。若查找概率不等且不要求关键字有序,可把高概率记录排在更靠近查找起点的位置,降低成功 ASL。
每次比较中间元素后,排除中间元素及不可能包含目标的一半;low>high 表示区间为空。若 mid 取下整或上整不同,判定树形态可能改变,但最坏时间仍 O(log n)。避免 low+high 溢出可用 low+(high−low)/2。
int binarySearch(const int a[], int n, int key) {
int low = 0, high = n - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a[mid] == key) return mid;
if (a[mid] < key) low = mid + 1;
else high = mid - 1;
}
return -1;
}n 个有效键对应 n 个内部成功结点,空区间对应 n+1 个外部失败位置。成功比较次数为根到目标的结点数,失败比较次数为到外部结点父亲的关键字比较数。普通折半判定树高度为 ⌊log₂n⌋+1(n≥1),不应笼统称为完全二叉树。
等概率成功 ASL 应按各层结点数加权求和,近似 O(log n)。仅在 n=2ʰ−1、各层全满等条件下,可直接使用按满树求和的特定公式。
折半查找:区间怎样一半一半缩小
将记录分成若干块,前块最大关键字小于后块的所有关键字。索引项保存块内最大关键字和块首地址,索引表按关键字有序。先在索引表找到目标可能所属块,再在块内顺序查找;块内无需排序。
若均匀分为 b 块,每块 s 个元素、n=bs,索引和块内都采用顺序查找且成功等概率,则 ASL=(b+1)/2+(s+1)/2=(s²+2s+n)/(2s)。当 s 约为 √n、b 也约为 √n 时,该表达式最小,约为 √n+1。
索引也可折半查找,则总 ASL=索引定位平均代价+块内顺序扫描平均代价。log₂(b+1) 的上取整只是索引最坏比较层数的一种表达,不能不加条件当作准确平均值。
这里 m 表示一个结点最多有 m 个孩子,最多 m−1 个关键字。结点内关键字严格递增,k 个关键字分隔出 k+1 个子树区间;每棵子树内全部关键字落在相邻分隔键规定的范围。
非空根至少有 1 个关键字,若非终端则至少 2 个孩子;除根以外,每个实际结点至少有 t−1 个关键字,其中 t=⌈m/2⌉,非终端结点至少 t 个孩子。所有外部失败结点处于同层,它们不存实际关键字;最下层实际结点则是终端结点。
最满时 n≤mʰ−1;最稀疏时根至少两个孩子,之后每层至少扩展 t 倍,可得 n≥2tʰ⁻¹−1。磁盘模型下访问结点的次数与树高相关,结点内部查键通常在已读入内存的数据块中完成。
非空 m 阶 B 树,h 不含外部失败结点层,n 为关键字总数,m≥3。
进入一个结点后,先在该结点的有序关键字中查找;相等则成功,否则选择包含目标区间的孩子指针,继续下降,到外部空位置则失败。插入时把新关键字加到查找失败所对应的终端结点中。
插入后关键字数不超过 m−1 则完成;达到 m 时,对结点分裂,取约定中间关键字上升到双亲,左、右部分形成两个孩子。双亲也超满时继续向上,根分裂则新增根,树高增加 1。
5 阶 B 树最多 4 个关键字。若终端结点已有 [1,2,6,7],插入 11 后为 [1,2,6,7,11],中间键 6 上升,左右结点为 [1,2] 与 [7,11];若原结点就是根,则新根为 [6]。两个孩子各有 2 个关键字,满足 ⌈5/2⌉−1=2 的下限。
后续插入先按新根定位目标区间;一次插入可能沿路径触发多级分裂,但不会任意重排整棵树的其他分支。
3阶B树的一个结点最多2个关键字。图中是向原叶结点[10,20]插入30后的临时溢出状态。
把中间关键字20上提,10和30分别留在左右结点;若原结点为根,新建根,高度增加1。非根分裂时上提可能继续导致父结点溢出。
若删除内部结点关键字,可以用中序前驱或后继替换,再在对应终端结点删除那个替代键。随后检查实际删除位置的关键字数。不能直接把带有子树的键及其整棵子树一并删掉。
令 t=⌈m/2⌉。删除后若仍至少 t−1 个关键字,无需调整;若只剩 t−2 个,先检查相邻兄弟能否借。兄弟若至少有 t 个关键字,就通过双亲分隔键旋转:双亲键下降填空,兄弟边界键上升到双亲,相应子树指针一并转移。
若相邻兄弟都只有 t−1 个关键字,则将欠缺结点、双亲分隔键与一个兄弟合并,双亲关键字数减 1。双亲若也欠缺,继续向上借位或合并。
根不受普通结点下限约束;若根关键字减少为 0 且只有一个实际孩子,删除空根,让该孩子成为新根,树高减少 1。空树与只有根的边界应单独处理。不同阶数下“预先合并”与“删除后修复”的容量条件不同,不能不检查结点上限就直接拼接。
B树删除:经父结点向右兄弟借位
B树删除:兄弟不能借时合并与根收缩
| 性质 | B 树 | B+ 树 |
|---|---|---|
| k 个关键字的子树数 | 通常为 k+1。 | 内部索引关键字数与子树数相等。 |
| 实际数据位置 | 各实际结点可保存记录信息。 | 全部记录/记录地址在叶结点,内部仅保存子树边界键及指针。 |
| 索引键是否重复出现在叶层 | 各结点实际关键字通常不重复。 | 内部索引键也在叶层数据中出现。 |
| 查找停止位置 | 可能在任意实际层成功。 | 从根查找均下降至叶层。 |
| 顺序访问 | 通常靠树遍历。 | 叶结点按关键字顺序链接,便于范围与顺序扫描。 |
可同时保存根指针和最小叶结点指针,分别支持多路定位和从最小键开始的顺序访问。资料以内部分支的最大关键字作为索引边界;其他实现也可能使用最小键分隔,计算时必须沿用题设。
内部根至少两个孩子,其他内部结点孩子数下限通常为 ⌈m/2⌉,上限为 m;叶层的容量定义要与题目中“阶”的约定一致,不能直接照搬 B 树的 m−1 个关键字上限。
本图沿用正文资料的子树最大键约定:内部键20、40、60分别对应三棵子树,关键字数与孩子数相等。查找35先选最大键40对应的中间子树。
所有记录入口在叶子,内部索引键20、40、60也在叶子出现;叶子顺序链接方便扫描区间。内部命中索引键仍要下降到叶子;其他实现可能采用最小键分隔,作题须跟随题设。
B+树范围查询:定位一次,再沿叶链继续
散列函数 H(key) 把关键字映射为表中初始地址,两个不同关键字映射到同一地址称冲突,它们互为同义词。散列表的性能与散列函数分布、冲突处理方法及装填因子共同相关。装填因子 α=n/m,是记录数与表长之比。
| 方法 | 构造与适用条件 |
|---|---|
| 直接定址 | H(key)=key 或 a·key+b;在目标范围内保持单射时可无冲突,适合连续且范围不大的键,稀疏范围可能浪费空间。 |
| 除留余数 | H(key)=key mod p,p≤表长,常选合适质数;函数只给初始位置,冲突后探查可能使用整个表长。 |
| 平方取中 | 取 key² 的中间若干位,使地址尽可能综合原键各位信息,具体均匀性依赖键分布。 |
| 数字分析 | 在已知关键字集合中选分布较均匀的数位组合地址;键分布改变后可能需要重新选择。 |
成功 ASL 对各已有关键字从初始地址到实际位置的比较次数取平均;失败 ASL 按可能的初始地址与相应概率求探查到可判定不存在的代价。α 越大通常冲突和探查越多,但不能说 ASL 只由 n 决定,也不能忽略冲突策略。
开放定址删除一般不能直接改成“从未使用的空位”,否则可能截断其他键的探查链。可设置“已删除”标记,查找继续经过它,插入时按规则复用。
链地址法在每个散列桶保存同义词链入口,冲突键接入相应链表;查找在桶内比较,删除可改链并释放结点。装填因子可以大于 1,与开放定址必须保留空位的限制不同。
线性探查:为什么不能把删除位置直接清空
在不重复键约定下,左子树所有键小于根、右子树所有键大于根,左右子树继续满足同一性质;中序遍历得到递增序列。查找时 key 小于当前键走左,大于走右,相等成功,走到空链接失败。
插入落在失败空链接位置,新增结点起初为叶子。按输入次序从空树逐个插入即可建树;插入序列不同,树形可能不同,排序输入可能形成单支树。重复键的保留与插入方向应另按题设。
| 待删结点 | 处理 |
|---|---|
| 叶子 | 把双亲相应链接置空并释放。 |
| 只有一个孩子 | 让唯一孩子接到原结点双亲下,维护根删除的特殊入口。 |
| 有两个孩子 | 用中序前驱(左子树最右结点)或后继(右子树最左结点)替代,再删除该前驱/后继,转成前两类。 |
查找沿一条根到叶路径,代价 O(h)。高度保持对数时约 O(log n),退化单支时 O(n);平均复杂度须说明随机输入等假设。与有序数组相比,BST 插删无需整体移动元素,但未平衡的 BST 不享有折半查找那样始终对数的最坏界。
BST删除:用后继替换后还要删除哪个结点
AVL 是满足二叉排序性质的平衡树,每个结点平衡因子 BF=左高−右高,只允许 −1、0、1。插入后从新结点向根回溯,首先遇到 |BF|>1 的祖先是最小不平衡子树根 A。
| 类型 | 路径特征 | 调整 |
|---|---|---|
| LL | 插入到 A 左孩子的左子树。 | 对 A 右旋一次。 |
| RR | 插入到 A 右孩子的右子树。 | 对 A 左旋一次。 |
| LR | 插入到 A 左孩子的右子树。 | 先对左孩子左旋,再对 A 右旋。 |
| RL | 插入到 A 右孩子的左子树。 | 先对右孩子右旋,再对 A 左旋。 |
插入 7 后,15 的左孩子 3 的右侧增长,发生 LR,局部根调整为 7;再插入 10、9 时,在 15 处发生 LL 右旋;插入 8 后,在 7 处发生 RL,最终根为 9,左侧为 7(孩子 3、8),右侧为 10(右孩子 15)。
删除可能使多级祖先失衡,因此需沿路径继续检查;删除叶子后重插不一定恢复原结构,删除分支后重插也不一定改变最终结构。高度 h 的 AVL 最少结点数 N(h)=N(h−1)+N(h−2)+1,取 N(0)=0、N(1)=1,得到高度 O(log n),查找最坏 O(log n)。
例:依次插入30、20、10,处理LL失衡
图以插入后沿路径找到的最低失衡祖先为根,LL/RR/LR/RL描述其重侧及下一段方向。
旋转保持中序序列不变。删除后的平衡因子和修复传播有额外情况,不能机械套用插入时“只修复一次”的结论。
黑高通常按从当前结点向下到 NIL 的路径所包含黑结点数计,是否计当前结点需按教材约定统一。由于红结点不能连续,任意向下路径的最长长度不超过最短长度的两倍;有 n 个实际结点时,树高至多 2log₂(n+1)。
一次左旋把右孩子提升为局部根,原根成为其左孩子,并把中间子树接回原根右侧;右旋完全对称。旋转保持中序次序,颜色修复保持黑高。不能将“局部旋转次数有界”误说成整个插删只需 O(1):查找位置和向上调整仍为 O(log n)。
新结点若为根,染黑后结束;父为黑时无需修复;父为红则违反“不连续红”,需检查叔结点与祖父。以下以父是祖父左孩子为例,右侧情况完全镜像。
| 条件 | 处理与后续 |
|---|---|
| 叔为红 | 父、叔染黑,祖父染红,把祖父当作当前结点继续向上检查;最终根仍染黑。 |
| 叔为黑,当前是父右孩子(折线 LR) | 先对父左旋,把结构变为直线 LL,再按下一行处理。 |
| 叔为黑,当前是父左孩子(直线 LL) | 父染黑、祖父染红,对祖父右旋,消除当前局部的连续红冲突。 |
叔链接为空时,把叔当作黑色 NIL,不能省略该分支。插入修复不仅移动结点,也可能只重着色并把冲突上移。
红黑树插入:红叔叔时为何先变色
红黑树插入:黑叔叔与LR双旋
待删结点有两个实际孩子时,用中序后继或前驱的键替代,实际移除该替代结点;它至多有一个实际孩子。移除红叶不会改变路径黑高。黑结点若只有一个实际孩子,该孩子必为红,提升该孩子并染黑即可;实际移除黑结点而替代位置仍为黑(可能为 NIL)时,需要补偿黑高。
| 兄弟 w 的状态 | 修复动作 |
|---|---|
| w 为红 | w 染黑、父染红,对父左旋,改写成黑兄弟情形并重新取兄弟。 |
| w 黑,两个孩子都黑 | w 染红,把父视为新的欠黑位置继续向上;若父原红,可将父染黑结束。 |
| w 黑,近侄(w 左)红、远侄(w 右)黑 | 近侄染黑、w 染红,对 w 右旋,转成远侄红情形。 |
| w 黑,远侄(w 右)红 | w 继承父颜色,父与远侄染黑,对父左旋,修复完成。 |
x 是右孩子时,将左、右及近侄、远侄对应交换。欠黑只是分析修复时的状态,不是普通结点多出第三种永久颜色;根处可以吸收黑高减少。处理 NIL 时要依靠保存的父链接或哨兵,不能解引用空指针。
红黑树删除:黑兄弟、红远侄时怎样旋转
红黑树删除:兄弟两侄全黑为何要向上修复
排序按关键字重排记录。若相等关键字记录在排序后仍保持输入时的相对次序,算法稳定;例如两个 49 分别标为 49₁、49₂,排序后仍需 49₁ 在 49₂ 前。仅有一个样例未打乱相等项,不能证明整个算法稳定。
全部记录能在内存处理称内部排序;大文件需反复读写外存,称外部排序。内部通常关注比较、移动和辅助空间,外部还必须重点计算块 I/O。
| 类别 | 主要动作 | 代表算法 |
|---|---|---|
| 插入 | 把当前记录插到已排好序的部分。 | 直接插入、折半插入、希尔。 |
| 交换 | 通过交换使记录靠近或到达最终位置。 | 冒泡、快速。 |
| 选择 | 每轮选出当前最小或最大记录。 | 简单选择、堆。 |
| 归并 | 合并两个或多个有序段。 | 二路归并、外部多路归并。 |
| 分配收集 | 按可分解关键字的各位分组。 | 基数排序。 |
处理 a[i] 前,a[0…i−1] 已有序,后段仍待处理。先保存当前记录,向右搬移所有大于它的前缀元素,再填入空位。只移动严格大于的项,遇相等就停止,保证稳定。
void insertionSort(int a[], int n) {
for (int i=1; i<n; ++i) {
int x=a[i], j=i-1;
while (j>=0 && a[j]>x) {
a[j+1]=a[j];
--j;
}
a[j+1]=x;
}
}依次插入后,有序前缀变化为:[38,49];[38,49,65];[38,49,65,97];[38,49,65,76,97];[13,38,49,65,76,97];[13,27,38,49,65,76,97];[13,27,38,49,49,65,76,97]。最后一个 49 插在原有 49 后,不跨越相等记录。
已有序时,每轮只需常数次比较而不移动,最好 O(n);逆序时第 i 次插入需移动 i 个记录,总量 n(n−1)/2,最坏与平均 O(n²)。只使用常数临时变量,辅助空间 O(1)。适合规模较小或基本有序的序列。
直接插入排序:有序区逐轮扩大
处理第 i 个记录时,对前 i−1 个有序记录折半查找插入位置,然后把插入位置后的记录整体后移一位,再写入新记录。为了稳定,相等时应把新记录放在已有相等记录之后。
闭区间实现可令 low=0、high=i−1;若 x<a[mid] 则 high=mid−1,否则 low=mid+1,结束时 low 是插入位置。相等归右正是保持稳定的关键。
比较次数总计 O(nlog n),与初始有序程度相对不敏感,但精确比较次数仍可能随定位路径不同而差一个常数;移动次数与直接插入同阶。通常最好整体为 O(nlog n),平均、最坏仍为 O(n²),空间 O(1)。若增加“已在末尾”快速判断等优化,最好时间又可能变化,必须对应具体代码。
从左向右比较相邻元素,前者大于后者就交换,一轮把最大值推到未排序段末尾;下一轮缩小范围。也可反向扫描把最小值向前移动,但必须沿用同一方向分析每轮结果。
设置本轮是否交换的标志,若一轮无交换则整体已有序,可提前结束。相等不交换时稳定;最好 O(n),平均和最坏 O(n²),空间 O(1)。无提前终止标志的实现,最好比较次数也可能仍为 O(n²)。
在未排序段扫描出最小元素下标,与该段首元素交换,下一轮从后一个位置继续。关键字比较总数为 n(n−1)/2,与初始次序无关,最好、平均、最坏均 O(n²);交换次数至多 n−1,空间 O(1)。
远距离交换可能把相等记录的顺序打乱,因此简单选择通常不稳定。例如 [2₁,2₂,1] 第一轮把 1 与 2₁ 交换后成为 [1,2₂,2₁]。当记录移动代价很大、规模又不大时,较少交换可能有意义。
对增量 gap,把下标相差 gap 的记录分成一组,在每组内做插入排序;再逐步缩小 gap,最后 gap=1 对全体做直接插入。前面的大步调整减少远距离逆序,最后一轮利用基本有序性。
各增量应满足最终覆盖到 1,合适增量序列影响性能;“所有增量必须互质”不是一般定义要求,不能当作判断希尔排序是否合法的必要条件。
跨组跳跃可能越过相等记录,所以希尔排序不稳定,辅助空间通常 O(1)。复杂度随增量序列而变,不能统一断言平均必为 O(nlog n)。简单折半增量最坏可达 O(n²),某些精心设计序列有更好界;分析具体题目应使用给定增量和实现。
希尔排序:增量4、2、1怎样改变数组
保存枢轴后,右侧先找小值填左空位,左侧再找大值填右空位,直到两指针相遇,最后放回枢轴。每次划分 O(n),递归只处理枢轴两侧,不再把枢轴放回子问题。不同划分方案的等号和指针推进不同,代码应成套使用。
int partition(int a[], int low, int high) {
int pivot=a[low];
while (low<high) {
while (low<high && a[high]>=pivot) --high;
a[low]=a[high];
while (low<high && a[low]<=pivot) ++low;
a[high]=a[low];
}
a[low]=pivot;
return low;
}
void quickSort(int a[], int low, int high) {
if (low>=high) return;
int p=partition(a,low,high);
quickSort(a,low,p-1);
quickSort(a,p+1,high);
}划分近乎均衡时递归深度 O(log n),总时间 O(nlog n);每次只排定一个端点时退化为 O(n²),栈空间最坏 O(n)。平均情况下时间 O(nlog n),栈空间通常 O(log n),算法不稳定。
以首元素为枢轴的朴素实现遇有序序列容易退化;随机枢轴、三数取中等可降低特定输入风险。因此“不适用于有序序列”应限定为对应的朴素策略,不能套用到所有快排实现。
快速排序:完整的一次划分
大根堆每个父结点不小于孩子,根是全堆最大;小根堆反之。堆不要求左右子树相互有序,也不等于二叉排序树。用 1 起始数组时,i 的孩子为 2i、2i+1,最后一个非叶结点为 ⌊n/2⌋。
大根堆向下筛选先挑较大孩子,再与当前保存值比较;小根堆则选较小孩子。child≤n 判断有没有孩子,child<n 判断有没有右兄弟,不可混用。由最后一个非叶结点倒序建堆,保证处理父时其孩子子树已经是堆。
单次筛选最坏 O(log n),但建堆各层需要下沉的高度不同,累计为 O(n),不能把 n 个结点都机械乘上 log n。
void siftDown(int a[], int root, int n) {
int x=a[root];
for (int child=2*root; child<=n; child=2*root) {
if (child<n && a[child]<a[child+1]) ++child;
if (x>=a[child]) break;
a[root]=a[child];
root=child;
}
a[root]=x;
}
void buildMaxHeap(int a[], int n) {
for (int i=n/2; i>=1; --i) siftDown(a,i,n);
}对 [23,17,72,60,25,8,68,71,52],按 60、72、17、23 的根位置依次筛选:60 与较小孩子 52 交换;72 与较小孩子 8 交换;17 已小于 52 与 25;23 先与 8 交换,再与 68、72 比较停下。最终可得 [8,17,23,52,25,72,68,71,60]。
线性建堆:为何从最后一个非叶结点往前调整
升序排序先建大根堆,把根与当前末元素交换,最大值落到最终位置;有效堆长减 1,再从根向下筛选,重复直到剩一个元素。降序可采用小根堆对应处理。
堆排序各常见情况下为 O(nlog n),常数辅助变量使空间为 O(1)。长距离交换与筛选可能颠倒相等记录,因此不稳定;与快排不同,它有 O(nlog n) 的最坏保证。
插入先把新记录放在完全二叉树末尾位置,保持形状,再与双亲比较向上筛选。删除堆顶时,用最后一个结点填根位置,移除末位置,再向下筛选。
若删除任意内部位置,末元素填入后可能需向上,也可能需向下,应先与双亲比较决定;不能把所有任意结点删除都只写成向下调整。每次插入或删除通常 O(log n)。
最大堆取出堆顶与向下筛选
将待排序列看成长度 1 的有序段,两两归并成长度 2、4、8…的有序段,直到整表有序。递归实现先排左半,再排右半,最后合并;迭代实现按段长逐轮扩大。
合并时把两段复制到辅助空间,用 i、j 分别指向两段首项,比较后取较小者写回;一段用尽后,另一段剩余项可以直接整体追加。遇相等键时先取左段记录,才保持原有相对次序。
一轮合并处理全体 n 个记录,为 O(n);迭代二路归并约有 ⌈log₂n⌉ 轮,总时间各常见情况 O(nlog n)。普通数组归并需要 O(n) 辅助数组,递归还有 O(log n) 调用栈,整体额外空间仍为 O(n)。
链表归并可通过改链接实现,不必复制所有记录,因此空间和移动代价不能不区分存储方式。归并适合稳定排序和外部排序,但普通数组实现不属于 O(1) 额外空间的原地排序。
有序归并:两个相等的2应该先取谁
把一个逻辑关键字分成 d 位,每位有 r 种取值。MSD 从最高位分组,再在各组内处理次高位;LSD 从最低位开始,逐轮稳定地按更高位分配、收集。LSD 每轮必须保持同桶记录相对顺序,否则会破坏已完成的低位次序。
使用 r 个队列或桶,扫描记录把它按当前位放入相应桶尾,然后按位值从小到大顺次收集各桶。每轮扫描 n 个记录并访问 r 个桶,时间 O(n+r),共 d 轮,为 O(d(n+r)),与初始排列通常无关。
空间取决于表示:链式队列可利用原记录链接并配 r 组首尾指针,额外桶指针 O(r);数组分配一般还要 O(n+r) 辅助空间。不能把 d 轮各自可复用的桶空间再乘 d,也不能忽略数组实现实际保存记录的位置。
数字、定长编码等可直接分位的关键字适合;负数、可变长串及混合字符必须先约定编码、位值顺序和补齐规则。算法稳定性依赖每一轮分配收集稳定。
LSD基数排序:个位排完为何还不算有序
文件大于可用内存时,先分批读入、在内存排序并写出,得到若干初始归并段,再进行外部多路归并。一次最多把 k 个有序段归为一个,每轮段数由 r 变为 ⌈r/k⌉。
提高 k 或减少初始段数 r 可减少趟数。k 路归并至少要给 k 个输入段准备输入缓冲,还需要输出缓冲;若只有总共 M 个缓冲块,常见单缓冲模型要求 k≤M−1。实际双缓冲会增加需求。
增大 k 会增加缓冲空间并改变内存选最小值的成本,不能只看趟数。逐次线性比较 k 个段首每输出一条需 O(k) 比较,败者树可降到 O(log k)。
r 个初始归并段做平衡 k 路归并需要的合并趟数;r=1 时无需合并。
若文件共 B 个磁盘块,每轮完整读取并写回一次;初始段生成另计约 2B。
外部排序:8个初始段用3路归并需几趟
将工作区填满后,在当前可输出记录中取最小值写入当前有序段,再读入下一记录填空。新值若不小于刚输出值,可继续参与当前段;若更小,标记为下一段冻结记录。
当前活动集合耗尽时,本段结束,解除冻结开始下一段,直到输入与工作区都处理完。初始段可能长于内存可容纳记录数,且各段长度可以不同;随机输入下常见平均长度约两倍工作区大小,但具体输入并不保证这一长度。
工作区最多 3 条,初始为 4、6、8,先输出 4;若补入 3,因为 3<4,冻结 3;再输出 6,补入 7,7 可留在本段;随后输出 7、8 等。冻结的 3 不应为了继续取全局最小而插入当前段,否则会破坏当前段升序。
置换选择:为什么有的较小记录必须冻结
当初始段长度不同时,不同合并顺序造成记录被反复读写的次数不同。把段长作为叶权,每次选 k 个最短段合并,构造 k 叉哈夫曼式最佳归并树;必要时补 0 权虚拟叶,使叶数满足满 k 叉树条件。
每个原始段被合并的次数等于其叶深度,因此总读写量为 2×WPL(权重单位为记录或块时,结果单位也应一致)。这优化的是加权 I/O 总量,不等同于单纯让所有叶子高度相同。
k 个叶子保存 k 个归并段当前段首记录;内部结点记录比较中的失败者,让胜者继续向上。最终总胜者的位置通常另设冠军单元保存,不能把所有内部结点都说成保存胜者。
输出最小记录后,只替换其所属段的叶子,沿该叶到根的路径与已保存对手比较,更新约 O(log k) 层。段耗尽时可用 ∞ 哨兵避免继续输出它。树本身需要 O(k) 状态,另外有输入输出缓冲,不是整体 O(1) 空间。
普通逐个插入式建树可 O(klog k),专门自底向上构造可 O(k);应按实际初始化方法计数。败者树减少内部比较次数,归并路数是否能扩大仍受内存缓冲约束。
败者树:内部存输家,为什么只重赛一条路径
| 算法 | 最好 | 平均 | 最坏 | 典型额外空间 | 稳定性 |
|---|---|---|---|---|---|
| 直接插入 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
| 折半插入 | O(nlog n) | O(n²) | O(n²) | O(1) | 相等插在后方时稳定 |
| 希尔 | 依增量序列 | 依增量序列 | 依增量序列,简单方案可 O(n²) | O(1) | 不稳定 |
| 冒泡(可提前结束) | O(n) | O(n²) | O(n²) | O(1) | 相等不交换时稳定 |
| 简单选择 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
| 快速 | O(nlog n) | O(nlog n) | O(n²) | 平均 O(log n)、最坏 O(n) | 不稳定 |
| 堆 | O(nlog n) | O(nlog n) | O(nlog n) | O(1) | 不稳定 |
| 二路归并(普通数组) | O(nlog n) | O(nlog n) | O(nlog n) | O(n) | 稳定 |
| LSD 基数 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | 链式桶 O(r),数组通常 O(n+r) | 稳定分配时稳定 |
简单选择的比较次数不依赖初始序列;折半插入和标准归并的比较量相对稳定,但精确次数仍可能取决于比较结果与区间边界。冒泡是否提前结束、快排划分层数、堆筛选的实际比较数会受输入影响;标准堆排序主循环次数仍固定为 n−1。
交换类(冒泡、快速)及选择类(简单选择、堆)通常每轮或每次划分至少确认一个最终位置;插入中已排序前缀的记录仍可能被后续插入后移,不能把“当前有序”误认为“最终位置不变”。
对 n 个互异关键字,仅基于比较的排序要区分 n! 种输入次序,决策树最坏高度至少 log₂(n!),所以最坏比较下界为 Ω(nlog n)。基数排序利用键结构,不受这个纯比较模型下界直接限制。
计算机系统由硬件和软件共同组成。硬件包含 CPU、存储器和外部设备;软件包括程序及其相关数据、文档。硬件提供执行环境,软件决定完成的任务。
运算器与控制器组成 CPU;CPU 与主存组成主机。辅助存储器及其他主机外部设备属于外设。辅存中的程序与数据通常先调入主存,再由 CPU 处理;不能把辅存容量当成主存容量。
| 部件 | 保存内容 | 位宽关联 |
|---|---|---|
| MAR | 待访问存储单元的地址 | 若按编址单位寻址,n 位地址可区分 2ⁿ 个单元 |
| MDR | 将读出或写入存储器的数据 | 通常对应存储字长 |
| PC | 下一条待取指令地址 | 应能覆盖指令地址空间 |
| IR | 当前指令 | 应能容纳机器相应的指令信息 |
取指时,把 PC 中的地址送 MAR,由主存取出的内容经 MDR 送 IR;指令的操作码交 CU 译码。取操作数时,按照指令地址信息形成有效地址,再由主存经 MDR 送执行部件。
在寄存器传送语句中,(PC)+1→PC 的括号不能随意省去;这里增加的是 PC 内容,且增量应按实际指令长度和编址方式确定,不能机械认为所有机器都加 1。
读主存:MAR传地址,MDR接内容
系统软件管理资源并为其他软件提供服务,如操作系统、语言处理系统、数据库管理系统和服务程序;应用软件针对计算、设计、统计处理等具体任务。
同一逻辑功能可以由软件或硬件实现,称为软硬件在逻辑功能上等效。硬件往往执行效率高,软件往往易修改、扩展;“等效”不表示成本、速度、结构完全相同。
| 阶段 | 处理内容 | 常见产物 |
|---|---|---|
| 预处理 | 处理宏定义、头文件及条件编译等指令 | .i |
| 编译 | 把高级语言转换为汇编形式 | .s |
| 汇编 | 转换成机器指令组成的可重定位目标文件 | .o |
| 链接 | 合并目标文件与所需库,完成符号解析和重定位 | 可执行目标文件 |
机器语言是硬件直接识别的指令编码;汇编语言以助记符表示机器指令,需要汇编程序转换。高级语言更接近问题表达,需要编译或解释等处理。编译通常先产生目标程序;解释方式通常在执行期间逐条或逐段翻译、执行,不能把两者简单等同。
| 指标 | 含义 | 注意 |
|---|---|---|
| 机器字长 | CPU 一次整数运算通常能够处理的二进制位数 | 通常与整数 ALU 和通用寄存器宽度相关 |
| 指令字长 | 一条指令编码的位数 | 可能等于、短于或长于机器字长 |
| 存储字长 | 一个存储字包含的位数 | 通常与 MDR 位宽相联系 |
| 数据通路带宽 | 相应路径一次并行传送的数据位数 | 看题目指定的是内部通路还是外部数据总线 |
主存容量等于可编址单元数×每单元位数。按字节编址时,地址数对应字节数;按字编址时,再乘每字字节数。不要把地址线条数直接当作存储容量的位数。
当指令字长等于存储字长且一次存储访问能取出该指令时,一次访问即可完成指令读取;整个取指阶段还包含地址传送、控制等过程。机器周期和时钟周期关系取决于具体实现,须服从题设。
响应时间从请求发出到结果返回,可能包含 CPU 执行、I/O、排队等等待。吞吐量是单位时间完成的任务或请求数量,受 CPU、存储系统、I/O 及并发程度共同影响;提高吞吐量不必然等比例缩短单个任务的响应时间。
Ninst 为执行的指令条数,CPI 为该程序的平均每条指令时钟周期数,f 为主频。
以各类指令在动态执行指令中的比例加权,不能只把各类 CPI 直接求平均。
MIPS 表示每秒执行的百万条指令,MIPS=f/(CPI×10⁶)。不同指令集、不同程序的单条指令工作量不同,不能仅用 MIPS 比较所有计算机。MFLOPS 等浮点指标计的是浮点操作数,应分清一次指令是否含多个浮点操作。主频更高也不自动代表程序执行更快。
平均CPI:先按指令数加权,再换成执行时间
基数 r 表示一个数位可使用的不同数码个数;第 i 位的位权为 rⁱ。一个数的真值由各位数码与位权相乘后求和得到。二进制便于由双稳态器件表示,并与逻辑真、假对应,编码和运算电路较简单。
任意有限位二进制小数都能写成有限十进制小数;有限十进制小数未必能由有限位二进制准确表示。因此机器中的有限位小数是离散的,转换和运算可能需要舍入。
0.625转二进制:乘2后取出的整数放哪里
原码用符号位和数值位直接表达符号与绝对值;反码中负数由对应正数逐位取反形成;补码中负数可由相同位宽的正数按位取反再加 1得到。正数的原、反、补码形式相同,不能把“取反加一”无条件用于正数。
常见模型用补码表示定点整数,用原码形式表示浮点数尾数,用移码表示阶码;具体浮点标准还包含隐含位、特殊指数等约定。
| 编码 | n 位整数范围 | 零 |
|---|---|---|
| 原码、反码 | −(2ⁿ⁻¹−1) 至 +(2ⁿ⁻¹−1) | 各有正零与负零 |
| 补码 | −2ⁿ⁻¹ 至 2ⁿ⁻¹−1 | 唯一的全 0 |
| 偏置 2ⁿ⁻¹ 的移码 | −2ⁿ⁻¹ 至 2ⁿ⁻¹−1 | 100…0 |
在同一位宽且偏置为 2ⁿ⁻¹ 时,移码可由补码仅翻转最高位得到。移码按无符号编码比较即可保持真值次序。IEEE 754 单精度指数偏置是 127,不能照搬“8 位移码偏置 128”;移码的具体偏置须以定义为准。
4bit的−3:原码、反码、补码如何对应
| 对象 | 左移补入 | 右移补入 |
|---|---|---|
| 正数原码、反码、补码 | 0 | 0 |
| 负数原码 | 0 | 0 |
| 负数反码 | 1 | 1 |
| 负数补码 | 0 | 1 |
逻辑移位把操作数当作无符号位串,左右移均补 0;高低位移出的部分被舍弃。循环移位把移出的一端重新送回另一端,可分为带进位标志 CF 与不带 CF 两种。循环移位适合字节交换、位循环处理,但不等价于普通乘除。
算术左移可能超出表示范围:正数移出有效的 1、负数原码移出 1、负数反码或补码破坏必要的符号扩展位,都可能导致错误。右移丢失低位一般影响精度;补码算术右移相当于除以 2 后向负无穷方向取整,遇负奇数时与向 0 截断不同。
同一位串右移:逻辑、算术与循环为何不同
半加器接收两个本位输入,给出本位和与进位;全加器还接收来自低位的进位,因此是三输入、两输出。全加器可由半加器与附加逻辑组合实现。
三个输入中 1 的个数为奇数时,本位和为 1。
至少两个输入为 1 时产生进位。
串行加法器用一个全加器逐位处理,操作数由移位寄存器从低位依次送入,并用触发器保存上一步进位;器件少、成本低,但速度慢。并行加法器使用多个全加器同时处理各位,本位和仍可能等待进位,故“并行加法器”不等于“并行进位”。
串行进位链逐级传递进位;先行进位用进位产生函数 Gᵢ=AᵢBᵢ和进位传递函数 Pᵢ=Aᵢ⊕Bᵢ展开关系,使高位进位不必逐级等待。
ALU 根据控制信号完成算术、逻辑和移位等操作。组合逻辑电路的输出只取决于当前输入,本身不记忆状态;时序逻辑电路含存储元件,输出还与原状态有关。一个触发器保存一位,多位暂存器由多个存储元件组成。正逻辑与负逻辑对高低电平的 0、1 定义相反,同一物理电路在两种约定下可对应不同逻辑函数。
本位产生进位或允许低位进位传递。
把前一级进位展开后,C₂ 可直接由各位输入及初始进位形成,减少逐级等待。
图中采用从0开始编号:Cᵢ输入第i位,Cᵢ₊₁从第i位输出。四个本位输入可以同时到达,但高位和仍可能等待低位进位。图上的加号表示逻辑或,乘积表示逻辑与。
这是用一个加法器反复工作的移位相加模型。M保留被乘数,A保留高半部分积,Q最初装乘数并逐渐成为低半积,C暂存加法进位。每轮先看当前Q0,必要时相加,再联合右移;相加产生的进位不能提前丢弃。
例:3位无符号数3×5,M=011,初始C:A:Q=0:000:101。下表每一行给出完成联合右移后的状态。
| 轮次 | 本轮Q0与动作 | 右移后 C:A:Q |
|---|---|---|
| 初始 | A=0,Q=101 | 0:000:101 |
| 1 | Q0=1,000+011=011 | 0:001:110 |
| 2 | Q0=0,不加 | 0:000:111 |
| 3 | Q0=1,000+011=011 | 0:001:111 |
| 结果 | A:Q=001111₂ | 15=3×5 |
此图限定无符号n位被除数、非零n位除数、n位商。A使用n+1位保留试减的符号,M零扩展到同宽。每轮A:Q先联合左移,再试减;若试减为负,就加M恢复并写商位0,否则保留试减值并写商位1。
例:13÷3,n=4,初始A=00000、Q=1101、M=00011。表中试减按整数真值列出,便于看出何时需要恢复。
| 轮次 | 左移后的A | A−M与处理 | 轮末A / Q |
|---|---|---|---|
| 1 | 1 | −2:恢复,商位0 | 00001 / 1010 |
| 2 | 3 | 0:保留,商位1 | 00000 / 0101 |
| 3 | 0 | −3:恢复,商位0 | 00000 / 1010 |
| 4 | 1 | −2:恢复,商位0 | 00001 / 0100 |
| 核对 | 商4,余数1 | 13=3×4+1 | 0≤1<3 |
逐位看进位:0111 + 0011
寄存器逐轮演示:5×3的无符号乘法
长除法逐位试减:13÷3=4余1
Booth补码乘法:3×(−3)的寄存器变化
补码加法连符号位一起相加,超出固定位宽的进位舍去;减法转为加被减数的相反数补码。舍弃最高进位不等于发生有符号溢出。
固定 n 位补码按模 2ⁿ 运算;最小负数的相反数不在同位宽正数范围内,须注意边界。
补码定点小数的范围包含 −1 而不包含 +1,因此结果≥1 为正溢出,结果<−1 为负溢出。n 位乘 n 位用完整 2n 位保存结果可容纳乘积;若只保留低 n 位,无符号乘积需高 n 位全 0,有符号乘积需被舍弃的高位全部是保留结果符号位的正确扩展,等价于乘积前 n+1 位全 0 或全 1。
Sub同时连接每一位B输入前的异或门以及最低位进位。Sub=1时,B按位取反且最低进位为1,完成补码减法;Sub=0时B不变且最低进位为0。
例:4位运算0101−0011,先得~B=1100,再算0101+1100+1=1 0010,保留0010,即2。最外层进位被舍弃,这一次没有有符号溢出。
补码最小负数:按位取反加1为何仍是自己
| 标志 | 为 1 的含义 | 用于判断 |
|---|---|---|
| ZF | 运算结果各位全 0 | 是否相等、是否为零 |
| SF | 运算结果最高位为 1 | 结果符号;需结合 OF 判断有符号比较 |
| OF | 有符号结果超出范围 | 有符号溢出 |
| CF | 加法有最高进位,或减法需要借位(本模型) | 无符号溢出、无符号比较 |
SF 直接取结果最高位,ZF 由全部结果位是否为 0 得到,OF 可用最高两级进位异或得到。对采用“减法借位置 1”约定的加减运算器,CF=Sub⊕Cout:加法时取最高进位,减法时取其反。
OF 与 SF 的有符号解释不能直接用于无符号大小比较;CF 不负责判断有符号运算是否超出范围。具体 ISA 对 CF、借位的定义可能不同,判断题设电路和指令时先确认约定。
同一加法器:CF和OF为何可能不相同
在题目采用补码整数模型时,相同位宽的有符号与无符号转换通常保留低 n 位位串,改变解释。最高位为 1 的位串,解释为无符号数时比其有符号真值大 2ⁿ。例如 8 位 11111111,无符号为 255,补码为 −1。
1111扩成8位:符号扩展与零扩展
浮点数写成 N=M×rᴱ,r 为约定的基数,M 为尾数,E 为阶码真值。阶码决定小数点位置和可表示范围,尾数决定有效数字精度,尾数符号决定整个数的正负。一般教材模型的阶码和尾数编码须看题设,不能与 IEEE 编码混用。
| 格式 | 符号位 | 指数位 / 偏置 | 尾数字段 / 规格化有效位 |
|---|---|---|---|
| binary32(通常为 float) | 1 | 8 / 127 | 23 / 24 |
| binary64(通常为 double) | 1 | 11 / 1023 | 52 / 53 |
指数编码既非全 0 又非全 1 时,前导 1 隐含,不实际存入尾数字段。
指数编码全 0 且尾数字段非 0 时为非规格化数,前导位是 0。
1.5的S=0,E=127(01111111),F为100…0,十六进制编码0x3FC00000。
隐含的首位1只适用于规格化数;E=0时指数采用1−127,尾数为0.F。E=255且F=0表示无穷,F非0表示NaN。
基数为 2、原码小数尾数模型中,非零规格化尾数满足 1/2≤|M|<1,即第一数值位为 1。补码小数尾数的规格化特征是符号位与第一数值位不同,负数的边界与原码不同;−1 可作为补码规格化尾数,而 −1/2 的补码不是规格化形式。IEEE 的 1.f 是另一种等价表达约定,不要混写小数点位置。
浮点加法:1.5 + 0.25如何对阶
舍入到最近偶数:正好一半时看哪一位
同总位宽下,浮点数把部分位分给阶码,通常能覆盖比定点数更大的数量级,但有效数字位较少。规格化后才判断浮点阶码是否越界;固定点结果则直接与其固定表示范围比较。
| 转换 | 主要检查 | 在常见 32 位 int、IEEE float/double 模型下 |
|---|---|---|
| int→float | 有效二进制位能否装入 24 位有效尾数 | 可能丢失低位精度,但范围足够 |
| int→double | 有效二进制位能否装入 53 位有效尾数 | 全部 32 位 int 可精确表示 |
| float→double | 更宽精度和范围 | 可精确保持原 float 值 |
| float/double→int | 是否有小数、是否越界 | 小数部分向 0 截断;越界不能简单当作安全回绕 |
| double→float | 范围与有效位同时检查 | 可能舍入,也可能上溢或下溢 |
long、long double 的宽度和格式与平台有关;固定长度的例子应理解为题设数据模型。判断整数是否能精确转浮点,应检查实际有效位及尾随零,而不是仅按类型最大位数认定每次转换都丢失。
单精度float:为什么2²⁴+1可能丢掉那一个1
在信息位之外加入依赖于信息内容的冗余位,使合法码字符合特定关系。两个码字不同的位置数为汉明距离;整个编码的最小码距决定最坏情况下的检错纠错能力。
保证检测 t 位错误所需的最小码距。
保证纠正 t 位错误所需的最小码距。
一位奇偶校验使整个码字的 1 的数量满足奇数或偶数约定;能发现奇数个位翻转,不能保证发现偶数个位翻转,也不能定位错误。海明码把校验位置于 1、2、4、8…等位置,按位号二进制展开决定各校验组。
k 个信息位配 r 个校验位,要区分 k+r 个单比特错误位置及无错状态。
采用偶校验,位号从右到左为 1~10。k=6,取最小 r=4,使 2⁴≥6+4+1。把 P₁、P₂、P₃、P₄ 放在 1、2、4、8 号位;按高位到低位把 D₆…D₁=101101 放在 10、9、7、6、5、3 号位。
海明(7,4)码:校验组与综合征怎样定位错位
信息串 1011001,生成位串 11001(G(x)=x⁴+x³+1)。先构造 10110010000,模 2 除以 11001 得到余数 1010,发送码字为 10110011010。接收时对完整码字作相同除法,可检查余数是否为 0。
生成多项式选得适当可保证检测长度不超过 r 的突发错误;更长错误存在漏检可能,概率分析还依赖错误模型与生成多项式。不要把“余数某个值”无条件解释为某一位翻转,也不要宣称 CRC 能发现所有错误。
生成多项式最高次数r=3,所以补3个0,余数也按3bit写为001。1101000 ÷1011的模2余数为001。
发送码字为原数据拼余数1101001;接收端用同一除数得到000可通过检验,但余数为0并不能保证绝对没有任何未检出的差错。
设阶符 2 位、阶码数值部分 3 位,尾符 2 位、尾数数值部分 9 位,均按题设双符号位补码模型。x=−5/256=2⁻⁵×(−0.101000000),y=59/1024=2⁻⁴×0.111011000。
初始阶码 [Eₓ]补=11011,[Eᵧ]补=11100;尾数 [Mₓ]补=11.011000000,[Mᵧ]补=00.111011000。
尾数临时溢出后右规只是恢复规格化,不应立即判整个结果溢出;对负尾数右移应补符号位,不能按无符号右移补 0。
| 分类维度 | 类别 | 识别点 |
|---|---|---|
| 作用层次 | Cache、主存、辅存 | 越靠近 CPU 通常越快、越贵、容量越小 |
| 介质 | 半导体、磁表面、光存储等 | 物理存储方式不同 |
| 访问方式 | 随机、顺序、直接存取 | 随机时间近似与位置无关;顺序需依次经过;直接存取兼有定位与顺序访问 |
| 保持信息 | 易失性、非易失性 | 是否需要持续供电维持 |
| 读出影响 | 破坏性、非破坏性 | 读后是否必须恢复原信息 |
CPU 可访问寄存器、Cache 和主存;辅存内容通常先调入主存。Cache—主存之间以块交换,主要由硬件管理,对一般程序员透明;主存—辅存之间的调入调出由操作系统和硬件协同完成。
局部性使少量快速存储器可以保存近期常用内容,形成接近高速存储器的访问性能和接近低层存储器的容量、成本。
常用教学层次把高层内容看作低层部分数据的副本,但写回 Cache 的脏块可能暂时比主存更新,包含性也依具体体系而定。判断命中和一致性时,应结合写策略,而不把“每一时刻各层完全相同”当作普遍结论。
| 类别 | 含义 | 保持和修改方式 |
|---|---|---|
| RAM | 随机读写存储器 | 通常易失;SRAM 和 DRAM 是常见形式 |
| MROM | 掩膜只读存储器 | 制造时确定内容 |
| PROM | 可编程只读存储器 | 用户可一次性编程 |
| EPROM | 可擦除可编程只读存储器 | 典型以紫外线整片擦除后重编程 |
| EEPROM | 电可擦除可编程只读存储器 | 采用电方式擦写,粒度以器件为准 |
| Flash | 闪存 | 电擦写、非易失,典型按块擦除 |
半导体存储芯片一般含存储矩阵、译码驱动电路、读写电路和控制逻辑。地址线通常单向输入;数据线读出与写入时方向不同;片选 CS/CE、读写 WE、输出使能 OE 等控制线须看高低电平有效标记。单译码直接选取存储行,双译码用行列共同定位单元。
存取时间是从启动一次存储操作到该操作完成的时间;存取周期是连续两次独立存储操作启动之间必须间隔的最小时间,可能包含恢复时间,通常不小于存取时间。不能把它们当作同一个参数。
读周期中要在地址和片选有效后等待数据稳定,并满足保持时间。写周期除地址有效外,还应使片选与写允许满足规定的脉冲宽度,数据也要满足建立、保持时间。只有电平极性与时序均满足要求,才能保证读写正确;不要只看地址是否相同就判断数据已可用。
| 比较项 | SRAM | DRAM |
|---|---|---|
| 存储信息 | 双稳态电路 | 电容电荷 |
| 刷新 | 不需周期刷新 | 需要周期刷新 |
| 读出 | 通常非破坏性 | 需读后再生 |
| 速度、成本 | 速度快、位成本高 | 速度较慢、位成本低 |
| 集成度与典型用途 | 密度较低,常用于 Cache | 密度较高,常用于主存 |
| 断电保持 | 易失 | 易失 |
| 地址传送 | 一般直接送地址 | 常行列地址复用,分时送入 |
再生针对一次破坏性读出后恢复所读内容;刷新是为防止电荷泄漏而按计划定期恢复整行信息。刷新周期指同一行必须再次刷新的最大间隔,不是一次读写周期,也不是刷新一行所需时间。
若存储周期为 0.5 μs,要求 2 ms 内刷新 16 行,每行刷新占一个周期,则集中刷新耗时 16×0.5=8 μs。2 ms 中共 4000 个存储周期,死时间率为 16/4000=0.4%。这里 16 是行数,不能误用总存储单元数;全部芯片若并行按同一行号刷新,也不能再乘芯片数量。
DRAM刷新:周期、行数与死区怎样联系
Flash 从 EEPROM 技术发展而来,是可电擦写的非易失存储器,断电仍可保持信息。典型 NAND Flash 的读取、编程以页组织,擦除以更大的块组织;不同类型闪存访问粒度不同,不能统称“一律按块读取”。
SSD 由闪存芯片和控制器等组成,闪存转换层 FTL 把主机逻辑地址映射到闪存物理位置,承担地址映射、垃圾回收、磨损均衡等工作。无机械运动使随机访问延迟、噪声、抗震性和能耗等优于传统磁盘。
Flash 写入前通常需要擦除,且擦除不能无限次进行。FTL 尽量把擦除分散到不同块,以避免某些块过早磨损;逻辑覆盖可能触发搬移、回收等操作,实际内部写入量可大于主机写入量。SSD 不需要磁盘的寻道和旋转等待,但仍有控制器、闪存读写和回收的延迟。
闪存更新:为何不能直接在原页覆盖所有新数据
双端口 RAM 提供两套独立端口,访问不同单元,或同时读取同一单元,一般可并行进行;同时写同一单元、或一读一写同一单元存在冲突,应按仲裁与忙信号规则延后一个端口。
单体多字存储器用更宽的单元和总线一次读出多个连续字,提高单次传输量;这些字需位于同一宽存储单元,不能把任意离散地址同时读取。
| 组织 | 连续地址分布 | 访问特征 |
|---|---|---|
| 高位交叉 | 同一个体内连续,高位区分体号 | 方便扩充;顺序读取同一区域时通常仍受单体周期限制 |
| 低位交叉 | 相邻字分到相邻体,低位区分体号 | 可轮流启动各体,以重叠周期提高连续访问带宽 |
m 个体、体存储周期 T、相邻启动间隔 τ;再次访问同一体前需满足其恢复时间。
题设允许 n 次连续访问按间隔 τ 流水启动、首字延迟取 T 时使用;实际首字读出时间以题设为准。
低2位选择存储体,连续字可按启动间隔τ轮流启动。若存储周期T≤4τ,理想连续访问可维持每τ一个字的启动节拍。
传送n个字的常见理想模型为T+(n−1)τ;同一体冲突、总线占用、未满足轮流启动条件都会改变结果。
低位交叉:连续字与跨4字访问为何不同
依次核对地址线、数据线、读写控制、片选译码和芯片规格。系统程序区可用 ROM,读写数据区使用 RAM,具体区域按题设分配。片选还要与 CPU 的访存请求信号协调,避免非存储访问误选芯片。
片选选择芯片或芯片组,字选选择已选芯片内的具体单元。线选法简单但地址利用与别名问题需检查;译码片选可用 n 位产生最多 2ⁿ 路选择信号。总芯片数可由总位容量比求得,但接线仍须分别判断字数和位数。
A9…A0接每片芯片内部地址,共1024个字位置;A11、A10参与片选,扩展成4组。
数据位并联使字宽从8bit扩为16bit;总容量4K×16bit=8KiB。此图按16bit字编址说明,若CPU按字节编址还要处理字节选择和地址接法。
一个记录面对应一个磁头,盘面分为同心磁道,各磁道进一步划分扇区。不同记录面相同半径的磁道合成柱面。教学模型以柱面号、盘面号、扇区号定位扇区;扇区是基本读写单位,操作系统逻辑块可能由若干扇区组成。
各面同样组织时,容量为盘面数×每面磁道数×每道扇区数×每扇区字节数。
平均旋转等待通常取一圈时间的一半;实际题设还可能另计控制器开销。
若有 4 个驱动器,每个盘组 256 个柱面、16 个记录面,每道 16 个扇区,则驱动器号、柱面号、盘面号、扇区号分别需 2、8、4、4 位,共 18 位。要确认“每盘面磁道数”和“每磁道扇区数”,不能因资料口述简略而把两者互换。
磁盘几何地址:柱面、磁头、扇区怎样对应线性块号
RAID 把多个物理盘组织成一个逻辑存储体,利用条带化并行传输,利用镜像或校验等冗余方式提高可靠性。并非每一级 RAID 都同时具有性能提升和容错能力。
| 级别 | 组织方式 | 关键区别 |
|---|---|---|
| RAID 0 | 条带化,无镜像或校验 | 可并行但无容错能力 |
| RAID 1 | 镜像 | 典型双副本可用容量约为总容量一半 |
| RAID 2 | 以海明码组织纠错冗余 | 采用位级纠错思想 |
| RAID 3 | 字节或细粒度条带、专用校验盘 | 适合连续大块并行传输的教学模型 |
| RAID 4 | 块级条带、专用校验盘 | 校验盘可能成为写瓶颈 |
| RAID 5 | 块级条带、分布式校验 | 没有独立固定校验盘;典型可容忍一个盘故障 |
RAID校验:一块盘丢失后怎样用异或恢复
Cache 是主存部分活跃内容的高速副本,容量远小于主存,不能增加程序可使用的逻辑存储容量。时间局部性指近期使用的信息可能再次使用;空间局部性指其相邻地址的信息可能很快被访问,常见于循环、顺序指令和连续数组。
Cache 与主存按相同大小的块交换,Cache 块又称行;CPU 访问的对象可为字节、字或更宽操作数,不应把 CPU—Cache 访问粒度无条件固定为一个机器字。命中检查同时涉及标记与有效位。
命中率为命中次数占全部相关访问次数的比例。
Pmiss 定义为命中检查之外的额外失效开销时使用。
若 Tmiss,total 已包含一次失效的全部访问过程,不能再重复加命中检查时间。
Cache 容量、块大小、相联度、替换算法、访问序列和程序局部性都会影响命中率。块变大可利用空间局部性,但会减少行数并增加填充代价,命中率并非一定单调提高。多级 Cache 的局部命中率和全局命中率也要区分。
平均访存时间:失效代价是否已经包含查Cache
| 方式 | 允许的位置 | 地址字段与替换 |
|---|---|---|
| 直接映射 | 主存块号 mod Cache 总行数,唯一固定行 | 标记 / 行号 / 块内偏移;无替换对象选择 |
| 全相联 | 任意 Cache 行 | 标记为完整主存块号 / 块内偏移;满时选择替换行 |
| R 路组相联 | 主存块号 mod 组数指定组,组内任意 R 行 | 标记 / 组号 / 块内偏移;组满时在组内替换 |
C 是数据区容量,B 是块字节数,R 是相联路数;按字节编址且各量为 2 的幂时便于按位划分。
a 位地址分成 t 位标记、s 位组号、b 位块内字节偏移。
例:访问地址 0x12345678,随后访问同一块
块内偏移由块大小决定;直接映射的行号位数为log₂L,组相联的组号位数为log₂S,全相联没有组/行索引位。
同一组内比较候选标记并按替换策略选行;全相联灵活但比较开销大。容量计算要说明是否含标记、有效位和脏位。
2路组相联:别组有空行仍要替换吗
直接映射中目标行固定,发生冲突就覆盖该行,不需要选择替换算法。组相联只在指定组内选择,全相联可在全部行中选择;有无效空行时通常优先使用空行。
| 算法 | 选择依据 | 使用时注意 |
|---|---|---|
| FIFO | 最早进入 Cache 的块 | 命中不改变进入顺序 |
| LRU | 最长时间没有访问的块 | 命中也更新最近使用次序 |
| LFU | 一段统计范围内访问次数最少的块 | 次数和老化规则须看题设 |
| 随机 | 随机选取候选行 | 硬件简单,不依赖精确访问历史 |
可把组内各行按新近程度排队:命中的行移到最近端,其余相对次序不变;新块装入最近端,满时淘汰最久端。若用计数器表达次序,应只调整受影响的次序值,不能每次把所有行无条件加 1 而导致相同次序。进行访问序列模拟时,把组号、命中/失效和每组的使用顺序分列记录。
| 情形 | 策略 | 动作 |
|---|---|---|
| 写命中 | 全写 / 写直达 | 同时更新 Cache 和下级存储 |
| 写命中 | 写回 | 仅改 Cache,置脏位;脏行被替换时写回 |
| 写失效 | 写分配 | 把相应块调入 Cache,再修改 |
| 写失效 | 非写分配 | 直接写下级,不为此分配 Cache 行 |
写回常与写分配组合,以利用随后对同块的访问;全写常与非写分配组合。它们是常见搭配而非硬性规定,多级 Cache 也不能仅凭层级推断唯一策略。
全写增加下级写流量,可用写缓冲隐藏部分等待;写回需记录脏位,替换脏块时增加写回代价。全写的写命中时间不一定机械等于完整主存周期,写缓冲与总线协议会影响 CPU 实际等待时间。
有效位说明该行是否包含可使用的标记数据;脏位说明当前 Cache 内容是否比下级更新。脏位为 0 不代表无效,有效位为 0 也不能凭残留标记判定命中。计算写失效代价时分别计调入、写修改、必要的旧脏块写回。
写回Cache:脏块为什么要先写回
虚拟地址是程序使用的逻辑地址,物理地址指向实际主存单元。虚拟存储依靠局部性,在主存与辅存间按需调入内容,以时间开销换取较大的逻辑地址空间,并提供隔离、保护等能力;“具有主存速度和辅存容量”是目标近似,不是每次访问都没有额外代价。
虚地址划为虚页号与页内偏移,物理地址划为页框号与相同页内偏移;页大小等于页框大小。页表给出虚页与页框的映射,常包含存在/有效位、脏位、访问位、权限和外存定位信息。页表由操作系统维护,基址寄存器指出其内存位置;不能理解为整张页表都直接塞在 PCB 中。
按字节编址时必须乘页表项字节数,不能把页号直接当作字节位移。
P 为页大小;以 2 的幂分块时等价于页框号与页内偏移拼接。
存在位为 1 且权限允许时可形成物理地址;存在位为 0 引起缺页异常,由操作系统调入页面后重试。分页便于管理固定大小单元,但进程最后一页通常存在内部碎片,页本身未必对应程序逻辑模块。
程序按代码、数据等逻辑模块划分为长度可变的段,虚地址由段号、段内位移组成。段表项记录基址、长度和保护等信息;先检查段号与位移是否合法,再由段基址加位移得到物理地址。段便于按逻辑单位共享、保护与独立管理,但长度不一可能产生外部碎片。
程序先按逻辑分段,每段再划分固定大小的页,主存仍按页框分配。地址由段号、段内页号、页内偏移组成:段号定位段表项得到该段页表基址,段内页号定位页表项得到页框号,再与页内偏移组成物理地址。
查表地址均须考虑表项大小;在未命中地址转换缓存的简化模型中,段页式需要查段表和页表,再访问数据。逻辑段长度不必恰好等于整页倍数,物理分配占用整页所留下的尾部才是内部碎片。
| 方式 | 划分依据 | 主要碎片 |
|---|---|---|
| 分页 | 系统规定的固定大小 | 内部碎片 |
| 分段 | 程序逻辑,长度可变 | 外部碎片 |
| 段页式 | 逻辑分段、段内分页 | 末页内部碎片;避免按整段连续分配的外部碎片问题 |
| 部件 | 保存内容 | 未命中后的动作 |
|---|---|---|
| TLB | 近期页表映射及权限信息 | 查页表;可能仅补充 TLB,也可能发现缺页 |
| 页表存在位 | 页面当前是否驻留主存及映射 | 页面不在内存时由操作系统处理缺页 |
| Cache | 物理主存块中的数据或指令副本 | 一般由硬件从下级存储调入 |
在采用物理 Cache 的简化串行模型中,先由虚地址查 TLB;TLB 未命中则遍历页表,驻留时补充 TLB,不驻留时处理缺页。获得物理地址后再查 Cache,失效再访问主存并调块。真实机器可并行查询部分字段,不能把该逻辑流程当作所有硬件的固定时序。
TLB 未命中不等于缺页;Cache 未命中也不等于缺页。在正常有效映射下,TLB 命中说明已有可使用的驻留映射,Cache 命中说明所需数据有高速副本;Cache 命中不能反推 TLB 必命中。
计算访问次数时明确是否含取指、页表多级遍历、TLB 查询时间、Cache 查询时间和缺页处理开销。最好情况下 TLB 与 Cache 都命中,无须访问主存;但不能说整个访存完全没有任何硬件访问。关联存储器按内容比较,适用于快表等结构,不应与“随机按地址访问”混淆。
例:一次TLB未命中与下一次命中
图采用物理地址访问Cache的简化模型;TLB命中只说明地址映射较快得到,不能推断数据Cache也命中。
TLB未命中但页表项驻留有效,可以查表后继续,不一定发生缺页。真实处理器可能并行访问或使用VIPT等设计,计时要看题设。
一条指令规定机器完成的基本功能,指令系统是全部机器指令的集合。操作码指定操作性质,地址字段可给出操作数、操作数地址或地址形成方法;结果位置和下一条指令地址也可能由约定隐含给出。指令字长描述编码占用空间,与机器字长、存储字长分别讨论。
| 地址数 | 典型形式 | 隐含信息 |
|---|---|---|
| 零地址 | 仅操作码;或栈式运算 | 操作数可能隐含在栈顶和次栈顶 |
| 一地址 | 对 A 单目操作;或 ACC 与 A 运算 | ACC 可隐含为另一源和目的 |
| 二地址 | 两个源之一兼作目的 | 源目的顺序由指令语法规定 |
| 三地址 | 两个源、一个独立目的 | 下一条地址通常由 PC 顺序形成 |
| 四地址 | 两个源、结果、下一条地址 | 显式给出后继指令地址 |
定长 n 位操作码最多区分 2ⁿ 种编码。扩展操作码在地址字段较少时利用腾出的位扩展操作码,要求短操作码不能成为长操作码的前缀;若上一层保留 m 个扩展入口,下一级增加 n 位就可产生 m×2ⁿ 种编码。
指令可按数据传送、算术逻辑、移位、程序控制和 I/O 等分类。访存次数取决于指令长度、操作数位置和间接层数;例如单次取指且三地址均在主存的运算,需取指一次、读两个源、写一个结果共四次,不能仅凭“三地址”无条件断定所有机器都四次访存。
扩展操作码:短操作码留下的前缀怎样继续使用
顺序寻址由当前指令后继位置形成下一条地址;跳跃寻址由转移指令改变 PC。若按字节编址,32 位定长指令通常顺序 PC+4;按字编址且一字一条指令则 PC+1。变长指令须先识别实际长度,不能固定增加机器字长。
相对地址通常 EA=(PC)+有符号位移 A,但 PC 是当前指令地址还是已递增的后继地址,要以 ISA 与题设为准。位移可能以字节为单位,也可能先左移若干位表示按指令或字对齐的距离。相对寻址利于程序在一定范围内浮动,常用于条件分支。
计算分支目标时依次明确:基准 PC、位移位宽与补码真值、位移单位、目标地址位宽。例如位移 −10 若以字节计就减 10;若以 4 字节指令计则减 40,不能只看到 −10 就直接代入。
PC相对寻址:位移到底加当前PC还是下一PC
| 方式 | 操作数或 EA | 执行阶段额外主存读取(简化模型) |
|---|---|---|
| 立即 | 操作数就在指令字段 A 中 | 0 |
| 直接 | EA=A,操作数 M[A] | 1 |
| 一次间接 | EA=M[A],操作数 M[EA] | 2 |
| 寄存器 | 操作数 Rᵢ | 0 |
| 寄存器间接 | EA=(Rᵢ),操作数 M[EA] | 1 |
| 隐含 | 由操作码或约定寄存器指定 | 取决于隐含位置 |
立即数不需再读取操作数,但其范围受字段长度限制;直接寻址简单,范围受地址字段限制;间接寻址可用存储字中的完整地址扩大范围并支持指针、子程序等,但增加访存。寄存器寻址编码短、速度快,但寄存器数量有限;寄存器间接便于指针与循环。
表中的次数不含取指,也不含结果写回、多级间接或跨界访问;若题目问总访存,应把这些加回。寄存器寻址“地址字段是寄存器编号”,不能把 Rᵢ 中内容再误当成内存地址。
间接寻址:地址字段不是最终操作数
同一个地址字段100:立即、直接和间接寻址
基址寻址 EA=(BR)+A,常以程序或区域基地址加偏移,便于重定位;变址寻址 EA=A+(IX),常把 A 作为数组起点、IX 作为随循环变化的偏移。二者硬件都是相加,但用途和寄存器更新约定不同。教材的操作系统基址模型与通用寄存器基址模式应分开理解,不能绝对断言任何基址寄存器用户都不能改。
先间接再变址是 EA=M[A]+(IX),先变址再间接是 EA=M[A+(IX)]。括号改变访问哪一个内存单元,应画出每一步取值,不能因加法形式相似就交换次序。
堆栈按后进先出组织,可由寄存器构成硬栈,也可用内存区域形成软栈。栈顶由 SP 指示;向低地址增长还是向高地址增长,以及 SP 指向当前顶元素还是下一空位置,都以题设为准。
在向低地址增长、先减后存的栈中,入栈先减 SP 再写,出栈先读再加 SP;增减量等于一个栈元素占用的编址单元数。例如按字节编址、元素 16 位,则每次为 2,而不是无条件 1。
基址与变址:公式相似,谁固定谁变化
在常见 32 位字、按字节编址的自然对齐模型中,半字地址为 2 的倍数,字地址为 4 的倍数。结构体成员前和结尾可能加入填充,以满足各字段及整个结构的对齐要求。对齐可避免一个数据横跨多个存储字,提高访问效率,但会浪费部分空间;具体对齐和非对齐支持由 ABI、ISA 与硬件决定。
| 地址 | 0x4000 | 0x4001 | 0x4002 | 0x4003 |
|---|---|---|---|---|
| 大端存 0x12345678 | 0x12 | 0x34 | 0x56 | 0x78 |
| 小端存 0x12345678 | 0x78 | 0x56 | 0x34 | 0x12 |
大端把最高有效字节放在最低地址,小端把最低有效字节放在最低地址。只有一个字节时不存在字节端序差别。端序不改变一个字节内部二进制位的书写顺序,也不改变数值本身。
同一个32位数的大端与小端布局
| 维度 | CISC 典型设计 | RISC 典型设计 |
|---|---|---|
| 指令与寻址 | 种类多、功能复杂、格式可能变长 | 常用简单指令为主,格式较规整 |
| 访存 | 多类指令可直接操作内存 | 主要由 Load/Store 访存,运算在寄存器间完成 |
| 寄存器 | 典型早期设计较少 | 通常提供较多通用寄存器 |
| 控制实现 | 常用微程序便于复杂指令 | 常用硬布线,便于流水实现 |
| 编译优化 | 复杂功能不一定都频繁使用 | 依赖编译器组合简单指令完成复杂工作 |
RISC 常见的“多数指令一周期”指设计目标或理想吞吐特征,不代表一条指令从取指到完成只经历一个流水段。现代 CISC/RISC 都可能采用流水、乱序和内部微操作,不能只靠名称判定实际微结构。
EAX、EBX、ECX、EDX 为 32 位通用寄存器,AX 等表示其低 16 位,AH/AL 等表示相应高、低 8 位;ESI/EDI 常用于索引,EBP/ESP 常用于栈帧与栈顶。mov dst,src 复制源到目的,普通 mov 不能两端同时为内存。
| 指令 | 基本含义 | 结果 |
|---|---|---|
| add / sub | 目的加源 / 目的减源 | 写回目的 |
| inc / dec | 操作数加 1 / 减 1 | 原位置写回;标志影响以 ISA 为准 |
| and / or / xor | 按位与、或、异或 | 写回目的 |
| not / neg | 逐位取反 / 取相反数 | 两者不是同一操作 |
| shl / shr | 逻辑左移 / 逻辑右移 | 空位补 0;有符号右移另看 sar |
| imul | 有符号乘法 | 两操作数或三操作数形式均写目的 |
| idiv | 有符号除法 | 32 位形式以 EDX:EAX 为被除数,商 EAX、余数 EDX |
乘法是否截断、哪些标志有效由具体 imul 形式定义;OF/CF 由 CPU 执行指令时产生,并非编译器在运行时直接设置。置溢出标志不等于必然自动调用异常处理程序。除法需检查除数和商范围,不能把高位寄存器残留值忽略。
cmp dst,src 按减法 dst−src 更新标志而不保存差;test 按位与更新相关标志但不保存与结果。jmp 无条件改变指令指针,条件跳转依据状态字选择后继地址。
| 解释 | 小于 | 等于 | 大于 |
|---|---|---|---|
| 无符号 a−b | CF=1 | ZF=1 | CF=0 且 ZF=0 |
| 有符号 a−b | SF≠OF | ZF=1 | SF=OF 且 ZF=0 |
选择结构先比较,再按条件跳向某分支,另一个分支可通过无条件跳转避开。循环可在入口先检查退出条件,主体末尾更新变量并再次判断是否回跳;do-while 则通常先执行主体再判断。计数循环可使用专门指令或计数寄存器自减配分支,关键是检查时点和执行次数。
CMP同一位串:无符号大于与有符号小于可以同时成立吗
以常见 32 位 x86 约定为例,push 先使 ESP 减 4 再写入,pop 先读出再使 ESP 加 4。call 保存调用之后下一条指令的地址并跳转,ret 从栈恢复该返回地址;不能把返回地址写成 call 指令自身的起始位置。
; 示意:具体参数传递与清栈责任由调用约定规定
push ebp
mov ebp, esp
sub esp, local_size
; 使用参数与局部变量,必要时保存寄存器
mov esp, ebp
pop ebp
ret示例为常见32位x86、向低地址增长、使用push ebp; mov ebp,esp建立帧的约定,不代表所有ABI都这样传参。
call保存其后下一条指令的返回地址;递归每次建立独立栈帧。使用帧指针省略优化时不应照搬图中的EBP布局。
CPU 负责指令控制、操作控制、时间控制、数据加工及中断异常处理。逻辑上可分控制单元、运算单元和内部暂存/寄存器等部分;指令的程序顺序语义必须保持,但实际微结构可以重叠甚至乱序执行,不能由教学描述推出所有指令物理执行都完全串行。
| 类别 | 典型对象 | 意义 |
|---|---|---|
| 程序可见的体系结构状态 | 通用寄存器、PC、部分状态字 | 程序或特权程序通过指令约定访问 |
| 内部实现寄存器 | IR、MAR、MDR、暂存 Y/Z 等 | 通常对应用程序员透明 |
| 状态字 | 条件码、控制状态等 | 不同位的访问权限和更新方式不同 |
三态门使一个输出可处于高阻态,避免共享总线多个源同时驱动;多路选择器根据选择信号从多路输入选一路输出;译码器把输入编码转换为相应选通信号;加法器可专用于 PC 递增或地址计算,ALU 承担更一般运算。单总线 ALU 往往配 Y、Z 暂存器,以满足两个输入同时有效并保存组合逻辑输出。
寄存器位宽、地址宽度和实际实现地址空间须分别讨论。64 位寄存器不自动意味着实现全部 2⁶⁴ 字节主存,也不能靠基址寻址凭空突破物理地址线可表示的空间。
指令周期是取出并完成一条指令的过程;机器周期是实现中用于组织基本阶段的时间单位;时钟周期是主时钟的一次周期。一个指令可含若干机器周期,一个机器周期可含若干时钟周期;具体对应数量并不固定,一个节拍可完成多个互不冲突的微操作。
| 阶段 | 主要目的 | 典型动作 |
|---|---|---|
| 取指 | 把当前指令送 IR | PC→MAR,存储读,MDR→IR,形成后继 PC |
| 间址(需要时) | 取得间接地址中的有效地址 | 指令地址字段→MAR,存储读,MDR 保存有效地址 |
| 执行 | 完成该条指令规定功能 | 随指令而异,可运算、访存、转移或控制 |
| 中断响应(满足条件时) | 保存断点并转入服务程序 | 保存 PC/状态,形成入口,改变处理状态 |
CLA 可把累加器清零,不需执行阶段读主存;ADD A 需读 A 指定操作数后与累加器相加;STA A 应把累加器内容写到指定地址;JMP A 把目标地址送 PC;NOP 不改变程序规定的数据状态,但仍经历取指等过程。若是间接存储,须先取得真实目标地址,不能把直接存储的动作当成间接寻址。
执行阶段不一定访存,间址也可能来自寄存器而不是主存;中断保存现场可用栈或专用寄存器,取决于体系。响应通常在指令边界检查,但异常、流水线与精确中断还需按具体模型判断,不能笼统称“四个阶段必然各访问一次主存”。
取指微操作:PC如何把下一条指令送入IR
数据通路是信息在寄存器、运算器、存储接口等部件之间流动的路径。单总线结构简单但共享冲突明显;多总线结构能同时传送更多数据;专用通路按常见数据流设置连接,性能高但硬件量较大。控制信号决定哪个源输出、哪个目的接收以及 ALU 做何种操作。
PC→MAR 可使 PCout 与 MARin 同时有效;取指还须发存储读命令,等数据进入 MDR 后,再使 MDRout 与 IRin 有效。不能在存储数据尚未到达前就把 MDR 中旧值当新指令。
第 1 拍:R1out, Yin ; R1 经总线存入 Y
第 2 拍:R2out, ALU=ADD, Zin ; Y 与总线的 R2 相加,结果锁存 Z
第 3 拍:Zout, R3in ; Z 经总线写回 R3图中寄存器组R1/R2/R3为简化画法,Y和Z分别保存第一个输入和运算结果。箭头表示可用传送方向,不代表所有源能同时驱动总线;控制器在各拍只使相应输出门有效。
硬布线控制器用组合逻辑及状态存储元件形成控制信号。输入包括指令译码结果、当前执行阶段、节拍和条件标志等;同一指令在不同阶段需要不同微操作,条件指令还受当前状态影响。
一般先列各指令的微操作序列,安排可同时完成的相容微操作,再形成各控制信号的逻辑表达式。时钟与节拍组织要给数据传播和锁存留足时间,机器周期通常按基本操作要求安排,不能仅凭指令数推算。
硬布线响应快,适合规整指令与高性能实现;指令和控制流程复杂时,逻辑设计、修改与扩充较困难。RISC 常采用硬布线是典型关联,不是硬性等价关系。
微命令是控制部件发出的基本控制信号,微操作是相应硬件执行的动作;微指令把一个节拍允许发出的微命令及后继地址控制信息组织成一条控制字;有序微指令序列形成微程序,用以实现机器指令。微指令周期是取出并执行一条微指令的时间。
控制存储器 CM 位于控制器内部,保存微程序;主存保存用户程序与数据。相容微操作可在资源和依赖允许时同拍执行,相斥微操作不能同时发出。不能把机器指令操作码当成微指令完整编码,也不能认为一条机器指令恰好只对应一条微指令。
固定微程序通常保存在只读或固化控制存储中;可修改控制存储支持动态微程序设计。毫微程序设计进一步把微指令分解,用第二级控制存储实现,属于用存储层次折中控制字长度与实现复杂度的思路。
机器指令与微指令:两个存储器各保存什么
微程序控制器包含控制存储器 CM、微指令寄存器 CMDR、微地址寄存器 CMAR、微地址形成部件和转移判别逻辑。取指微程序通常可共享,再按机器指令操作码转入相应执行微程序;“n 条机器指令必至少 n+1 个独立微程序”依教材把共享取指和执行段分开计数的约定,不是不可共享执行片段的普遍定理。
| 编码 | 组织 | 取舍 |
|---|---|---|
| 直接编码 | 一位直接控制一个微命令 | 译码少、并行性好,微指令长 |
| 字段直接编码 | 互斥微命令编入同字段,不同字段可并行 | 字长缩短,但需译码;通常保留一种编码表示无操作 |
| 字段间接编码 | 某字段的解释受另一字段影响 | 进一步压缩,控制更复杂、并行度受限 |
| 混合编码 | 部分直接、部分字段编码 | 按高频控制和资源关系折中 |
后继地址可由下地址字段直接给出,按机器操作码映射得到入口,或由 CMAR/微程序计数器加 1 顺序产生;分支时再依据条件选择转移地址。多路分支还可结合测试字段和状态,在若干候选地址间选择。
水平型可同条微指令定义多种并行操作,微指令较长、微程序较短;垂直型以微操作码表达较少操作,微指令较短、微程序较长。混合型在紧凑编码上保留部分并行性。设计需同时权衡控制字宽度、控存容量、微程序长度、执行速度和修改便利性。
字段编码:9个控制选择为何可以用6bit表示
| 类别 | 发生与返回特点 | 示例 |
|---|---|---|
| 故障 Fault | 常在指令完成前检测,处理后可重试该指令 | 缺页、部分非法操作或除错情形 |
| 自陷 Trap | 通常在指令完成后报告,返回后继指令 | 断点、显式陷入 |
| 终止 Abort | 严重故障,通常无法可靠恢复原执行 | 部分硬件错误 |
CPU 内部按指令执行状态检测异常;外部设备通过中断请求等机制通知 CPU。外部中断分可屏蔽和不可屏蔽,是否响应还受当前优先级、使能及处理状态约束。中断与异常都需转入相应处理入口,但不同类别保存的 PC 含义可能不同,不能把所有异常都说成返回当前指令。
硬件通常完成保存必要断点与状态、改变处理状态并形成入口等响应动作;服务程序再保存所需通用寄存器、处理事件、恢复状态并返回。是否需要调度其他进程由操作系统及事件决定,响应中断本身并不必然切换进程。
故障与陷阱:返回到原指令还是下一位置
将一个任务拆成多个有先后关系的子任务,用不同功能部件重叠执行不同任务的子阶段。典型五级为 IF 取指、ID 译码读寄存器、EX 运算或地址计算、MEM 数据访问、WB 寄存器写回。每级之间的流水寄存器保存数据和后续仍需使用的控制信息。
| 指令 | EX | MEM | WB |
|---|---|---|---|
| 算术运算 | ALU 运算 | 通常无数据访存动作 | 写运算结果 |
| Load | 基址加位移得有效地址 | 读取内存/数据 Cache | 写读得数据 |
| Store | 计算地址,并传递待存数据 | 写内存/数据 Cache | 通常不写寄存器 |
| 分支/跳转 | 比较或计算目标(具体阶段依题设) | 可能完成 PC 更新或为空 | 一般为空 |
规整的指令长度和寄存器字段位置、Load/Store 设计以及对齐访问,有利于流水段安排,但不是流水线能存在的唯一条件。各段时间应尽量均衡,否则最慢段限制时钟或形成等待;连续任务才能摊薄装入、排空成本。
流水不保证单条指令延迟缩短,额外锁存器甚至可能增加延迟。并发是任务在一段时间内交错推进,并行强调时间上真正重叠;指令级并行主要由硬件开发,任务/进程级并行还依赖软件组织。
五段流水线:三条指令怎样重叠执行
| 类别 | 原因 | 常见处理 |
|---|---|---|
| 结构冒险 | 同一时刻争用同一不可并行资源 | 等待、复制资源、分离指令与数据存储 |
| 数据冒险 | 后续读写与前序结果存在时序依赖 | 停顿、旁路、编译调度,复杂流水可重命名 |
| 控制冒险 | 下一条正确指令地址尚未确定 | 等待、预测、提前判定、错误路径冲刷 |
RAW(写后读)要求消费者获得前面生产者的新值;WAR(读后写)要求后写不能破坏前面尚未读取的旧值;WAW(写后写)要求最终值来自程序顺序靠后的写。经典顺序五级、固定阶段读写的模型主要面对 RAW,WAR/WAW 是否可能出现需看是否乱序、多周期和读写时点。
旁路把执行阶段已经产生的结果直接送给后续使用者,无须等寄存器写回;Load 的数据通常到 MEM 后才可用,因此即使有旁路,也可能有 Load-use 停顿。不能固定认为“只检查后面三条”适用于所有流水线。
分支在第几段确定方向、何时更新 PC、是否先预测继续取指,决定需要停顿或冲刷多少拍。停 2 或 3 拍仅适用于相应的特定分支完成阶段;未发生跳转也可能产生已经等待的控制代价,不能笼统说不跳转就永远没有控制冒险。
load-use冒险:有转发也可能需要停顿
相邻ALU指令:转发如何消除RAW等待
各段均为 τ、无冒险停顿且每拍启动一个任务。
实际吞吐率计有限任务的装入排空开销;最大吞吐率是任务充分多时的极限。
非流水执行时间取 nkτ 时;理想加速比极限为 k,效率等于有效工作面积占全部段时面积。
五级等时流水执行 8 条指令,无停顿需 (5+8−1)=12 拍;若依赖和分支共引入 3 个未重叠停顿拍,则需 15 拍。计算加速比应使用同一批指令的非流水时间,而不是单条指令时间除以整批流水时间。
段时间不等时,时钟需至少覆盖最慢组合逻辑段及锁存开销;不能直接把全部阶段时长相加作为每拍。若题设允许多个任务同拍发射、不同功能单元异步完成,须按实际资源画图,不能继续无条件使用单发射公式。
| 技术 | 并行方式 | 主要组织者 |
|---|---|---|
| 超标量 | 配置多个执行部件,同一时钟可发射多条独立指令 | 硬件动态选择、调度;也受编译安排影响 |
| 超流水 | 把原阶段进一步细分,提高流水时钟、缩短启动间隔 | 硬件分段,依赖与分支代价也随之变化 |
| VLIW 超长指令字 | 一条长指令含多个可并行操作字段 | 编译器静态挖掘并安排并行操作 |
超标量可以有顺序或乱序实现,不能定义为“一定不能调整指令顺序”;超流水也不意味着每个旧时钟周期都能无条件完成多条依赖指令。并行度仍受数据依赖、资源数和控制流限制,多部件并不自动带来线性加速。
| 类别 | 指令流 / 数据流 | 特点 |
|---|---|---|
| SISD | 单 / 单 | 单一指令流处理标量数据,内部仍可流水 |
| SIMD | 单 / 多 | 同一控制操作作用于多组数据 |
| MISD | 多 / 单 | 常规通用体系中少见,不宜绝对说逻辑上不存在 |
| MIMD | 多 / 多 | 多个处理器独立执行不同指令流、处理不同数据 |
SIMD 适合对数组各元素实施相同运算,可把多对数据并行送 ALU;若不同数据走不同控制分支,利用率会下降。向量处理器可直接处理向量,将数据送入向量寄存器后流水完成批量操作,适用于数值计算等规则工作负载。
MIMD 可采用消息传递的多计算机,每节点拥有独立地址空间,通过通信交换数据;也可采用共享存储多处理器,多个处理器通过共同物理地址空间中的变量通信。
为多个线程保留各自寄存器等体系结构状态,可降低软件保存恢复开销。细粒度多线程频繁轮换线程,通常每拍选择不同线程;粗粒度多线程在长延迟阻塞时切换,流水清空与重装代价更明显;SMT 可在同一拍从不同线程发射多条指令,共享执行部件。
多核在同一芯片集成多个处理核心,各核可独立执行线程,Cache 可私有或共享;硬件线程与核不能按数量直接画等号,也不必假定所有现代核心均对称。
共享存储体系使用单一物理地址空间,但各进程仍可有独立虚地址空间。UMA 中各处理器访问各存储位置的时间大致相同;NUMA 中延迟取决于处理器与数据所在节点的关系,可分无一致性 Cache 或具有一致性 Cache 的组织。
共享变量可能被多个处理器同时访问,需锁、原子操作或其他同步方式防止竞争。Cache 一致性解决多个副本的可见性,不自动保证复合操作原子性,也不代替程序同步。
总线是多个部件共享的信息传输介质,同一共享数据通路同一时刻通常只允许一个源驱动,但可有多个接收者。机械特性规定尺寸与引脚;电气特性规定电平和方向;功能特性规定各线用途;时间特性规定信号先后与保持关系。
| 分类 | 对象 | 要点 |
|---|---|---|
| 片内总线 | CPU 内寄存器、ALU 等 | 实现内部数据传送 |
| 系统总线 | CPU、主存、I/O 接口等 | 包括地址、数据和控制信号 |
| 通信总线 | 系统间或系统与其他设备 | 适合相应通信距离和协议 |
| 串行 / 并行 | 按位 / 多位同时传输 | 串行信号少;并行需考虑偏斜、串扰和距离 |
单总线结构连接简单、易扩充,但部件争用大,难以同时开展多次传输。双总线可用存储器总线和 I/O 总线经通道等连接,把低速外设流量分离;三总线教学模型再增加 DMA 等专用通路。
不同总线段可能并行,但若都争用同一个单端口主存,仍需仲裁。总线条数不能直接证明整个系统每时刻只能用一种,也不能由“共享地址线”推出必然内存映射 I/O;编址方式和控制信号还须单独看。
W 为每次数据传输的有效位数,ftransfer 为每秒有效传输次数,带宽单位 B/s。
只有题设每个时钟进行 q 次有效传输时可这样换算;协议空拍、地址和等待还会降低实际带宽。
总线周期是完成一次规定总线操作所需时间,可能包含多个时钟周期;总线时钟周期是同步节拍,两者不一定相同。数据宽度指同时传输的数据位数,信号总线数还包含地址与控制线。地址/数据复用在不同时段共用同一组引脚,减少引脚数量,但需分时传送和锁存。
申请分配阶段提出请求并仲裁;寻址阶段由主模块给出从模块地址和读写等命令;传输阶段完成源到目的的数据交换;结束阶段撤除信号、释放总线。读事务、写事务、突发事务可能有不同细节,计算时不能把整个事务周期都当成在发送有效数据。
突发总线传送:地址阶段不能每个字重复算
| 方式 | 协调机制 | 适用与代价 |
|---|---|---|
| 同步 | 统一时钟规定传送时刻 | 控制较简,适于距离短、速度相近;慢设备可能需等待 |
| 异步 | 请求—回答握手,无统一公共时钟作为完成标准 | 适应速度差异,控制和握手开销较大 |
| 半同步 | 统一时钟基础上结合等待信号 | 按设备响应插入等待周期 |
| 分离事务 | 请求和返回数据阶段分离,中间释放总线 | 利用从模块准备时间服务其他请求 |
不互锁方式下,请求和回答撤销不都等待对方确认;半互锁中,请求端等待回答后撤请求,但回答端可按约定延时撤销回答;全互锁中,发请求、发回答、撤请求、撤回答依次由对方状态确认。全互锁完成关系明确,但仍须考虑信号传播及控制实现。
异步不是完全没有任何局部时钟,而是不依赖双方统一公共时钟规定数据完成时刻。半同步的等待一般按时钟边界延长事务,不应随意解释为任意改变系统时钟频率。
四相异步握手:REQ和ACK不是同一条信号
I/O 接口在主机与外设之间完成设备选择、时序联络、数据缓冲、信号格式转换和命令状态传送。常含数据缓冲寄存器、状态寄存器、控制寄存器、地址译码及控制逻辑;接口连接系统总线的一侧与连接设备的一侧信号并不相同。
数据端口用于数据读写,状态端口报告设备状态,控制端口接受命令;具体读写副作用和是否可回读由设备规范决定。统一编址(内存映射 I/O)把端口放入存储地址空间,用普通访存指令访问;独立编址提供独立 I/O 地址空间,使用专门 I/O 指令。
| 方式 | 优点 | 代价 |
|---|---|---|
| 统一编址 | 指令使用较灵活,不需另设一套 I/O 指令 | 占用部分物理地址空间,需设备映射约定 |
| 独立编址 | I/O 与内存地址分离,专用指令清晰 | 需识别 I/O 与存储访问,指令灵活性可能较少 |
按传送方式可分串行接口与并行接口;按 CPU 控制方式可分程序查询、中断和 DMA 接口;按功能选择是否可配置又分可编程与不可编程。并行/串行描述数据传送,查询/中断/DMA 描述组织控制,两者不能互相替代分类。
程序查询反复读设备状态,准备好后由 CPU 搬数据,简单但忙等会占用执行资源;中断方式让 CPU 在设备准备期间继续执行其他指令,设备就绪再请求服务,数据搬运仍由 CPU 执行。阻塞式系统调用与硬件忙轮询不是同一概念,不能断言 scanf 必然以 while 循环持续占 CPU。
设备连续输入速率为 R 字节/秒、每次可服务缓冲量为 B 字节,需及时取走;具体安全裕量看题设。
服务时间取查询或中断的实际 CPU 开销;若已用指令数表示,先除主频/CPI 换成秒。
中断服务的时间通常应包含硬件响应与服务程序执行,以及题设要求的现场保存恢复;不能只计读取一个字的指令。缓冲区填满的间隔 B/R 要与可及时开始并完成取数的时点比较,防止新数据覆盖旧数据。双缓冲、批量中断等组织会改变每次可用容量和服务次数,不能总按一字一中断。
满足请求、允许与优先级条件后,CPU 通过硬件响应动作保存必要断点和状态、确定入口并改变中断状态;所谓中断隐指令是一系列硬件操作,不是程序中可见的一条普通指令。随后服务程序保存需使用的寄存器和屏蔽字,处理设备,恢复现场并执行中断返回。
服务入口可由软件按状态查询确定,也可由中断类型号索引向量表得到。向量表项的大小、是否包含额外状态和入口格式依体系规定,不能把中断号直接当字节地址。
响应优先级决定多个请求同时到达时先受理哪个;处理优先级决定服务过程中哪些请求能够打断当前服务,可由屏蔽策略调整。支持嵌套需在安全保存现场后开放相应更高优先级请求;退出前也需保证恢复动作不被破坏。
屏蔽字每一位对应一个中断源,0/1 表示屏蔽还是允许必须按题设。若按“1 表示屏蔽”且顺序为严格全序,自身和所有较低优先级应屏蔽,较高者允许;不能脱离具体矩阵只数 1 的数量推断所有优先级。
不可屏蔽请求、异常、DMA 总线请求和设备中断属不同机制,其响应关系由处理器与系统设计确定。DMA 传输请求通常不是“DMA 中断”,不要把 DMA 争总线与块完成后的中断通知混为同一事件。
中断嵌套:高优先级处理完返回哪里
DMA 在外设接口与主存之间组织直接数据传送,数据无须逐字经 CPU 通用寄存器。控制器通常保存主存地址、传送计数、方向/控制状态和暂存数据,并具有总线请求与握手逻辑;每传一个单位后相应更新地址和计数。主存中需准备合适缓冲区。
| 方式 | 动作 | 影响 |
|---|---|---|
| 停止 CPU 访存 / 成组传送 | 取得总线后连续传一块,再归还 | 吞吐较高,CPU 访存长时间受阻 |
| 周期挪用 | 请求时在安全边界借用一个或数个存储周期 | CPU 可穿插执行,发生争用时让出相应周期 |
| DMA 与 CPU 交替访存 | 预先安排交错时隙 | 需 CPU 周期与存储器时序允许,不能凭空同时访问单端口主存 |
CPU 与设备可以重叠工作,但若共享单总线或单端口主存,真正的存储访问仍可能互斥。周期挪用不是不耗 CPU 时间,而是减少 CPU 直接搬运的指令开销,同时占用部分可用总线周期。
DMA整段传送:CPU何时参与
操作系统是管理和控制计算机硬件与软件资源、组织计算机工作并为用户提供服务的系统软件。它是硬件之上的基础软件,使裸机成为便于使用的扩充机器。
方便性强调用户使用;有效性强调资源利用率与吞吐量;可扩充性强调适应功能和硬件发展;开放性强调与其他系统协同。系统软件还包括语言处理系统等,不能把全部系统软件等同于操作系统。
| 管理对象 | 主要工作 | 理解线索 |
|---|---|---|
| 处理机 | 进程控制、同步、通信、调度、死锁处理 | 决定谁运行、如何合作与竞争 |
| 存储器 | 分配与回收、地址映射、保护与共享、逻辑扩充 | 让程序装得进、访问对、互不破坏 |
| 文件 | 存储空间、目录、读写和访问保护 | 按名存取,定位数据并限制非法访问 |
| 设备 | 设备分配、驱动、缓冲、I/O处理、虚拟设备 | 协调CPU与外设,屏蔽硬件细节 |
管理资源是内部职责,提供接口是让用户和程序使用这些职责;同一个文件读请求会同时涉及文件管理、存储管理和设备管理,四类功能并非互相隔绝。
并发指多个事件在同一时间间隔内推进,单核上通常表现为交替执行;并行指同一时刻实际同时执行,需相应硬件支持。多核既可并行,也仍存在进程之间的并发。
共享是多个并发进程共同使用系统资源。互斥共享要求某时段内只有一个进程访问临界资源;同时访问允许多个进程在宏观时间段内共同使用,例如多个文件访问请求,底层仍可能交替服务。并发和共享相互依存:并发产生共享需求,有序共享又支持并发。
虚拟把一个物理实体表现为若干逻辑对应物。时分复用利用不同时段,例如把一个处理器虚拟成多个逻辑处理器;空分复用利用不同空间,例如虚拟存储等技术对存储空间的组织。
异步指进程受资源竞争和调度影响,以不可预知的速度走走停停。异步不等于随机得不到正确结果;正确的同步互斥机制应保证结果满足程序要求。
| 类型 | 工作方式 | 优势与局限 |
|---|---|---|
| 手工操作 | 用户独占机器,人工装入和启动 | 人机速度矛盾突出,CPU大量等待 |
| 单道批处理 | 监控程序自动依次处理作业,内存一次一项 | 减少人工干预,但I/O时CPU仍可能空闲 |
| 多道批处理 | 多项作业驻留内存,共享资源并交替执行 | 提高利用率、吞吐量;缺乏交互,周转可能较长 |
| 分时系统 | 以较短时间片轮流服务多个终端用户 | 多路性、独立性、及时性和交互性 |
| 实时系统 | 在规定期限内响应并完成事件处理 | 及时性与可靠性优先;硬实时期限严格,软实时容忍有限违约 |
多道批处理的宏观并发与单处理器上的微观交替是一组描述;当系统有多个处理器时,可以有真正并行。分时强调交互响应,实时强调期限保证;不能将“运行很快”直接等同于实时。
网络操作系统强调联网计算机之间的通信和资源共享;分布式操作系统进一步追求把分散资源组织成统一、透明的整体。个人计算机操作系统是按使用场景分类,与是否支持多道、分时等特性并不互斥。
命令接口供用户直接操作:联机命令接口适合交互,一次命令得到反馈;脱机命令接口把一组命令预先写好,由系统依次执行。图形界面也是面向用户的交互方式。
程序接口由系统调用组成。应用程序通过系统调用请求进程控制、文件与设备操作、内存管理、通信等内核服务,内核统一检查和管理资源。
典型过程:应用准备参数与调用号 → 执行陷入/系统调用指令 → 硬件转入内核入口并保存必要现场 → 内核检查参数与权限、分派服务 → 完成后恢复现场并返回用户态。参数准备通常发生在用户态,真正的内核服务在内核态执行。
普通函数调用通常不改变CPU特权级;系统调用涉及受控的特权级转换。库函数是语言或运行库提供的接口:一部分在用户态即可完成,一部分封装一个或多个系统调用。不能把“库函数”和“系统调用”看作同义词。
格式化输出函数可能先在用户缓冲区中处理数据,缓冲区需要提交时才调用内核写服务。因此一次库函数调用不必恰好对应一次系统调用;内核能在入口处统一检查文件描述符、缓冲地址和访问权限。
CPU通过程序状态寄存器中的模式位等硬件机制区分运行权限,具体位值由体系结构规定。内核态可以执行特权指令和非特权指令;用户态只能执行允许的非特权指令。
修改关键控制寄存器、执行某些I/O控制或开关中断等操作属于典型特权操作。访管/陷入指令必须允许用户程序执行,否则用户程序便无法请求内核服务;服务请求本身不赋予用户任意内核权限。
时钟中断可更新系统时间、记录进程已用CPU时间和剩余时间片,并为超时、定时唤醒及调度提供机会。一次时钟中断不等于必然发生一次进程切换;当前进程仍可被继续选中。
系统调用:进入内核态是否等于切换到另一进程
外部中断通常来自CPU外部设备,与当前指令不直接对应,例如I/O完成和时钟中断;异常在执行当前指令时产生,例如缺页、越界、非法指令、除法错误以及受控陷入。
陷阱是程序有意触发的受控事件,如系统调用;故障可能在修复后重新执行原指令,如缺页;终止类异常表示难以恢复的严重错误。具体异常归类、返回地址和处理方式依体系结构而定,不应把“除零一定属于终止类”当通用结论。
单重中断处理期间不响应其他可屏蔽中断;多重中断允许更高优先级中断嵌套。中断响应优先级决定同时请求先响应谁,中断处理优先级由屏蔽等规则决定能否打断当前服务。
中断处理程序通常称中断服务例程。进入服务例程不等于创建新进程;中断栈/内核栈上的控制流与被调度进程的状态需要分别分析。
中断返回:处理结束不必切换进程
内核保留与硬件紧密相关、执行频繁和需特权保护的核心功能,如中断、时钟和原语;进程、内存、文件和设备管理在不同体系结构中可安排在不同层次。
大内核将较多管理功能放在内核态,内部调用高效但耦合和故障影响范围较大;微内核只保留基本机制,将较多服务作为用户态服务器,通过消息通信合作,便于扩展和隔离,但通信与模式转换有开销。
分层结构使上层使用下层服务、接口清晰;层次过多会增加穿越成本,合理划分并不容易。模块化通过明确接口组织功能,允许相对独立扩展,但模块边界和依赖仍需设计。
开机时固件完成基本检查并寻找引导设备,装入引导程序,再把内核装入内存;内核建立基本数据结构、初始化设备与内存管理,启动后续系统进程,最后提供用户环境。引导、内核初始化和普通用户进程执行属于不同阶段。
内聚性衡量模块内部各部分联系的紧密程度,耦合度衡量模块之间相互依赖的程度;高内聚、低耦合有利于模块独立。划分过小会使接口和跨模块调用过多,过大会增加内部复杂度,因此不是模块越多越好。
可加载模块允许按需要向内核增加驱动或文件系统等功能,不必每次重新编译整个内核;模块仍运行在内核权限时,其错误仍可能影响系统,模块化不等于天然的用户态故障隔离。
机制说明怎样完成基本操作,如切换线程、传递消息或建立地址映射;策略决定在什么条件下对谁执行这些操作,如选择哪个线程、给多少页框。微内核可把基础机制留在内核,把更多策略交给服务器,从而便于更换策略。
微内核的低级存储管理提供基本映射和保护,较高层虚拟存储策略可在外部服务器;中断入口完成前期处理并转换为消息交给相关服务。收益包括扩展、移植、维护和分布式通信组织,代价包括频繁消息和模式转换。
用户态服务器故障通常比内核故障更易隔离,但其他服务若依赖它仍可能受影响;不能把“不会影响任何其他服务”作为无条件保证。分层也不应与模块化对立:一个系统可以同时采用分层、模块接口和可加载组件。
程序是描述计算步骤的静态指令和数据集合;进程是程序的一次执行,是资源分配和保护的基本单位(引入线程后调度单位可转为线程)。同一程序可以形成多个进程,进程运行时也可更换所执行的程序映像。
进程实体由程序段、数据段和进程控制块PCB组成。PCB保存标识、处理器现场、状态、调度与资源信息,是系统感知和管理进程存在的依据。进程创建时建立PCB,撤销时回收。
| 字段类别 | 典型内容 | 何时使用 |
|---|---|---|
| 标识与关系 | PID、用户标识、父子关系及外部名称 | 定位进程、授权及管理进程家族 |
| 处理器状态 | PC、PSW、通用寄存器、栈指针等 | 暂停与恢复运行现场 |
| 调度信息 | 当前状态、优先级、已用CPU时间、等待事件 | 决定是否可运行、由谁先运行 |
| 资源与控制 | 地址空间指针、已分配/所需资源、打开文件信息、通信队列与链接指针 | 访问资源、同步通信及队列组织 |
程序计数器指出将继续执行的位置,程序状态字记录条件码、模式和中断等状态;用户栈/内核栈指针用于恢复相应调用现场,不是栈中全部数据。PCB保存页表等结构的指针,并不表示把程序代码、全部页表和文件数据都直接存入PCB。
运行现场暂停后要保存到内核管理的上下文结构,恢复时再装回寄存器;具体字段可在PCB、TCB或关联内核栈中分布,教材以PCB概括是抽象模型。
运行态表示正在CPU上执行;就绪态表示除CPU外所需条件已满足;阻塞态表示等待某事件或资源,即使有空闲CPU也不能继续。创建态完成必要建立工作,终止态做退出清理。
| 转换 | 触发原因 | 关键区别 |
|---|---|---|
| 就绪 → 运行 | 调度程序分配CPU | 不是I/O完成直接进入运行 |
| 运行 → 就绪 | 时间片用完、被更高优先级进程抢占 | 执行条件仍满足,仅失去CPU |
| 运行 → 阻塞 | 等待I/O、信号量、消息等 | 主动等待某事件,通常由执行相应内核操作引起 |
| 阻塞 → 就绪 | 等待事件到达或资源可用 | 唤醒后仍要参加调度 |
挂起关注进程是否暂时被调出内存或被停止参与正常调度;阻塞关注是否等待事件。引入挂起后可区分就绪挂起、阻塞挂起。阻塞事件完成后,阻塞挂起进程通常先变为就绪挂起,是否调入还要由中级调度决定。
PCB可按状态链接成队列,或由索引表组织。状态是进程属性,队列是系统组织方式,不能把某种数据结构当作状态定义。
运行、就绪、阻塞:转换由什么事件触发
阻塞时保存运行现场,修改PCB状态并挂入对应等待队列,然后调度其他就绪进程。唤醒时把等待条件已经满足的进程从等待队列摘下,改为就绪并入就绪队列。二者按同一事件配对,不能出现状态已改但队列未改的半完成状态。
原语要求执行期间不被与之冲突的控制流破坏,体现原子性。单处理器上可借助关中断保护短小内核临界区;多处理器上仅关闭本CPU中断不能阻止另一CPU访问共享结构,仍需原子指令或锁。
进程切换需保存旧进程现场并装入新进程现场,可能切换地址空间等;用户态和内核态转换可以发生在同一进程内部。系统调用进入内核后若没有阻塞或抢占,返回的仍可为原进程。
进程的程序映像通常包含代码、已初始化与未初始化数据、堆和栈,以及操作系统记录的相关信息。代码区可按实现只读共享;堆用于动态分配,栈保存调用帧、局部数据和返回信息,二者并非PCB。
父子进程是不同进程:具有不同标识与独立控制信息;哪些资源继承、共享或复制由创建语义决定。使用写时复制时可暂时共享物理页,写入时才复制,逻辑地址空间仍相互独立。
fork创建子进程,父子从规定返回位置分别继续,返回值用于区分角色;不能只按源代码行数计执行次数,要画派生树。exec以新程序映像替换当前进程映像,不等于创建新进程。父进程可通过等待接口回收退出状态;子进程退出与父进程收取状态是不同事件。
每个成功执行到fork的进程都会派生一个子进程。若两次fork都无条件执行且均成功,进程数从1变2再变4;若fork在条件、循环或短路表达式中,必须逐条标出各进程是否走到调用点。
图以常见布局帮助记忆,堆、栈的增长方向是典型约定,不能据此认定所有平台地址布局一致。多个线程常共享进程代码、数据和堆,但各线程有自己的调用栈;PCB是内核中的进程控制信息,不等于用户空间某一数据段。
代码区对应指令,静态数据随进程映像生存;栈中的自动局部变量随调用帧建立和撤销,动态分配对象的生存期由分配与释放决定。虚拟地址相邻也不要求对应物理页框相邻。
以采用写时复制的私有匿名页为例,fork后父子可通过不同页表映射同一个物理页;写保护异常由内核识别并处理。需要分离时分配新页并复制内容,随后恢复写入。实际系统还可能在页面已独占时直接恢复可写权限。
这与共享内存映射不同:显式共享映射的写入可以按其共享语义被其他进程看到。exec替换当前进程映像,通常保持当前PID,不能在图上再数成一次fork。
写时复制:fork之后谁真正复制物理页
| 方式 | 信息怎样交换 | 同步与限制 |
|---|---|---|
| 共享存储 | 把同一物理存储区域映射给通信进程 | 数据量大、拷贝少;访问一致性需额外同步 |
| 消息传递 | 发送/接收带格式的消息 | 可直接指明目标,也可经信箱/端口间接传递;有缓冲与阻塞语义 |
| 管道 | 通过内核维护的缓冲区传递字节流 | 教材匿名管道常为半双工;读空、写满时的行为与接口规则相关 |
教材模型中匿名管道一般用于有亲缘关系的进程;双向通信通常需两条单向管道。命名管道扩展了通信对象范围,不能把匿名管道条件套给一切管道实现。
读空管道且仍有写者时,阻塞读会等待数据;全部写端关闭后,可读到文件结束。无读者仍写入会触发相应错误或信号。写满与一次读写是否原子,应按管道容量及操作系统约定分析,不能把“缓冲区有数据”理解为所有数据必须一次读完。
管道:读者为什么等不到EOF
信号是向进程通知某事件的机制,可以由异常、终端输入、定时器或其他进程通过系统接口产生。产生后可处于未决状态;若被屏蔽则暂不递送,解除屏蔽后再按规则处理。异步通知并不表示处理函数可以任意执行非安全操作。
收到信号后,进程按处置方式执行默认动作、忽略或调用自定义处理函数。处理函数完成后通常可恢复原执行流,但若默认动作或处理程序终止进程,就不会继续原代码。
| 信号 | 典型来源/含义 | 边界 |
|---|---|---|
| SIGINT | 终端中断,如Ctrl+C | 可按接口规则捕捉、忽略或默认终止 |
| SIGILL | 非法指令 | 不等同于一般外设中断 |
| SIGFPE | 算术异常,例如整数除零等 | 名称含浮点不表示仅来自浮点运算 |
| SIGSEGV | 无效内存访问 | 与访问保护相关 |
| SIGALRM | 定时器到期 | 默认动作与自定义处置要区分 |
| SIGCHLD | 子进程状态变化 | 通知父进程;状态回收仍需相应接口 |
| SIGKILL / SIGSTOP | 强制终止 / 停止 | 不能被捕捉、忽略或屏蔽 |
| SIGABRT | abort请求异常终止 | 标准名称为SIGABRT |
普通信号适合简单控制与事件通知,不承担大量数据传输;实时信号等扩展可附带有限信息。共享内存、管道和消息队列承担数据交换,信号量用于同步互斥;管道和消息传递也可通过阻塞规则起到同步效果,不能绝对说“毫无同步能力”。
引入线程后,线程是CPU调度和执行的基本单位,进程仍是资源分配的基本单位。同一进程的线程共享地址空间、打开文件等进程资源,但各有线程标识、寄存器现场、程序计数器和栈。共享堆并不意味着各线程不需要同步。
进程间切换通常需要切换地址空间等更多内容,同进程线程切换通常较轻;不同进程的内核线程仍可能涉及进程地址空间切换。
| 方式/模型 | 调度可见性 | 优点与限制 |
|---|---|---|
| 用户级线程 | 内核只看到进程,由用户线程库调度 | 切换开销小;某线程阻塞的系统调用可能阻塞整个进程,通常不能直接在多核并行 |
| 内核级线程 | 内核看到并调度各线程 | 一线程阻塞不必阻塞同进程其他线程,可并行;管理切换开销较大 |
| 多对一 | 多个用户线程映射一个内核线程 | 用户态管理方便,受单一内核执行实体限制 |
| 一对一 | 每个用户线程对应内核线程 | 并发和并行能力好,内核资源消耗较多 |
| 多对多 | 多个用户线程映射到一组内核线程 | 兼顾灵活性与并行,映射和调度较复杂 |
线程栈和寄存器是逻辑私有现场,但同进程线程在缺乏保护的共享地址空间中可能通过指针访问其他线程存储区;“私有”不是进程级隔离保护的同义词。线程终止也不应无条件等同整个进程终止。
线程共享进程资源,但每个执行流需要自己的寄存器现场和栈。某线程的栈在共享地址空间中,其他线程技术上可能访问其地址,不能把“独有使用”误解为天然硬件隔离。
用户级线程是否由内核直接调度,取决于线程映射模型;多对一模型下一个阻塞系统调用可能阻塞整个进程。
TCB可记录线程标识、运行状态、优先级、程序计数器、状态寄存器和通用寄存器等现场,以及栈和局部存储管理信息。创建函数通常指定入口与参数,并可配置栈大小、优先级等,成功后返回线程标识。
初始线程可创建后续线程,同进程线程共享全局变量和地址空间;每个线程保留自己的调用栈,用来保存返回地址、调用关系和局部数据。切换线程至少需要保存/恢复寄存器和栈指针。
线程退出后,退出状态等资源在可连接模型中可能保留,供其他线程join收取;分离线程按规则自动回收。具体接口并非所有系统都相同,已经终止的线程不能通过一次普通函数调用“复活”,要继续工作通常需创建新线程或采用挂起/唤醒模型。
不要把线程退出与进程退出混淆;也不要把“栈属于该线程的执行现场”理解成硬件保证同进程其他线程绝不能通过指针碰到它。
| 层次 | 迁移方向 | 对象和频率 |
|---|---|---|
| 高级调度/作业调度 | 后备队列 → 内存就绪环境 | 选择接纳作业,发生相对较少 |
| 中级调度/内存调度 | 挂起状态 ↔ 相应驻留状态 | 改善内存利用和多道程序度 |
| 低级调度/进程调度 | 就绪 → 运行 | 从就绪实体中分配CPU,发生频繁 |
当前进程完成、阻塞,或发生允许抢占的时间片到期与更高优先级进程就绪等事件时,可能需要调度。处理中断的关键区间、持有相关内核锁或处于不可抢占区间时,通常需延迟到安全点再调度。
非抢占式让运行实体继续,直到完成或主动放弃;抢占式允许满足规则的更紧迫实体夺取CPU。调度只决定选谁,分派/切换还包括现场保存、恢复和控制权移交。
三级调度:从外存作业到就绪队列再到CPU
若作业0时到达,CPU总需求4,完成于时刻10,周转时间为10、带权周转时间为2.5。若期间阻塞3且其余时间除运行外均在就绪队列,等待时间为10−4−3=3。若首次执行始于2,首次CPU响应时间为2。
多个CPU或CPU与设备可并行时,不能把所有资源忙碌时间相加当总历时;同一时刻结束、到达、唤醒和时间片用完的入队顺序若影响答案,需要明确采用的题设规则。
周转、等待与响应:同一张时间轴怎样分别计算
| 算法 | 选择规则 | 特点与代价 |
|---|---|---|
| 先来先服务FCFS | 按到达就绪/后备队列的先后选择 | 实现简单,利于长作业;短作业可能被长作业拖延 |
| 短作业/短进程优先SJF/SPF | 就绪者中服务时间短者优先 | 可降低典型平均等待,需估计执行时间;长作业可能饥饿 |
| 最短剩余时间优先SRTN | 剩余CPU时间最短者优先,可抢占 | 新短任务到达可能打断长任务,要重新比较剩余量 |
| 高响应比优先HRRN | 比较(等待+服务)/服务,较大者优先 | 非抢占典型;兼顾等待增长与短作业优势 |
每次出现调度点时,只在已到达且就绪的实体中比较。SJF比较本次预测服务时间,SRTN比较剩余时间;HRRN用当前时刻更新等待时间,已在运行者通常不因新作业到达立即被抢占。
高响应比中的“响应比”是调度优先值,不是“响应时间”。服务时间相同可按先来先服务打破平局;平局处理若题目另有规定则服从题设。
最短剩余时间优先:新短作业到达时怎样抢占
优先级调度可为抢占式或非抢占式;静态优先级建立后基本不变,动态优先级可依据等待、执行行为等调整。合理提高久等任务的优先级有助于防饥饿;具体优先数值大或小代表更高由题设决定。
时间片轮转RR按就绪队列循环服务,未结束且不阻塞者片末入队尾;阻塞者离开就绪队列,唤醒后按规则重新入队。片过大会近似FCFS、响应变差,过小会增大切换开销。
记录队列级别、队首次序、剩余CPU量和剩余时间片;推进到最近的到达、完成、阻塞或片末事件;按规则更新队列后再选择下一运行者。把设备结束带来的唤醒也加入事件表,避免只盯CPU队列。
时间片轮转:完整队列与甘特图
没有普通就绪进程时可运行最低优先级的闲逛/空闲任务,它不等待普通资源来完成用户工作,负责在安全状态下等待中断、降低能耗或检查调度条件。有新工作就绪后,调度程序选择它们替代idle。
教材把闲逛进程描述为不阻塞、最低优先级,是为了保证随时有可运行实体;具体低功耗等待指令和实现结构不必都是所谓“0地址指令”。
多对一用户线程模型中,内核先调度进程,再由进程内线程库决定哪个用户线程运行;用户线程之间切换可仅保存少量现场,不涉及系统调用。内核线程模型中,内核直接选择可运行内核线程,可能来自同一或不同进程。
同进程内核线程切换未必改变页表映像,不同进程切换才通常需要地址空间切换;缓存和TLB是否刷新取决于架构与标记机制。不能笼统认为“每次内核线程切换必使全部缓存失效”。
若n个同类进程共享处理机,理想情况下各得到约1/n的CPU份额。系统跟踪进程已获得的CPU时间,再与按存活时间及共享对象数计算的应得时间比较,优先补偿实际/应得比率较小的进程。
例如A、B、C比率分别0.5、0.8、1.2,A相对欠账最多,可先运行A直至其比率追上相邻候选。动态进程集合、权重等情况需按实际算法重新计算应得份额。
如果用户甲有4个进程,乙有1个进程,给每进程相同时间会使用户分别得到80%与20%;这实现的是进程公平,不是用户公平。用户公平先确定用户份额,再在各自进程中分配。
等长片下,甲的A/B/C/D与乙的E按“A E B E C E D E”循环,两个用户各占一半;按“A B E C D E”循环,甲占2/3、乙占1/3。此类序列仅是体现比例的一种构造,不是唯一实现。
硬实时任务要求在期限内完成,软实时任务可容忍有限超期。非抢占式任务需等待当前任务释放CPU;抢占式调度可在时钟点或事件触发后更快响应,但仍受不可抢占临界区约束。
EDF选择绝对截止时间最早的已就绪任务,既可有抢占实现也可有非抢占实现。最低松弛度优先LLF比较“截止时间−当前时间−剩余执行时间”,松弛度越小越紧迫;0表示不能再等待,负值表示在当前条件下已无法按期完成。
A周期20ms、每次需10ms,B周期50ms、每次需25ms,截止时间等于周期末且均在0释放:0–10执行A₁;10–20执行B₁;20–30执行A₂;30–45继续B₁(40时新A₃期限60晚于B₁期限50);45–55执行A₃;55后处理已在50释放的B₂。
固定让A永远优先时,B₁在40时仍剩5ms,却被A₃占用40–50,导致B₁错过50期限;固定让B永远优先时,A₁会错过20期限。由此说明固定优先规则可能无法满足这一任务集,而EDF可根据期限变化选择。
只比较已经释放且就绪的任务;剩余运行量随执行减少,等待任务的松弛度随时间下降。若当前任务连续运行,其“当前时间增加”与“剩余量减少”抵消,松弛度在这一阶段保持不变。多个任务松弛度相等时应明确平局规则,连续时间LLF可能频繁切换。
例如deadline=200ms、当前时刻50ms、剩余100ms,松弛度为50ms;不能不写当前时刻就把“200−100”固定为任务全程的松弛度。
EDF与最低松弛度:同一时刻为何会选不同任务
紧密耦合系统通常通过共享存储等高速互连合作;松散耦合系统的各结点具有更独立的存储器、I/O和操作系统,通过通信线路交换信息。通信代价取决于具体系统,不把某一组纳秒、微秒数当永久标准。
对称多处理SMP中处理器地位通常相等,各自可执行调度和系统代码;非对称多处理由主处理器承担较集中控制,其他处理器完成分派工作。非对称并不必然意味着CPU指令集或硬件型号不同,关键是控制职责的非对称。
| 方案 | 工作方式 | 权衡 |
|---|---|---|
| 公共就绪队列 | 空闲CPU从同一队列取任务 | 负载自然较均衡,但共享队列访问需同步,可能形成争用 |
| 私有就绪队列 | 各CPU维护本地候选 | 本地操作高效、亲和性较好,但可能忙闲不均 |
| 静态/绑定式分配 | 任务较固定地留在指定CPU | 迁移少,需考虑负载不均 |
| 动态分配/迁移 | 按运行需求在处理器间重新分配 | 改善均衡,增加迁移和缓存工作集重建代价 |
推迁移由负载检测机制把任务从繁忙处理器移出;拉迁移由空闲处理器主动获取任务。软亲和性表示尽量继续在原处理器运行,硬亲和性通过限制集合约束可运行CPU。
迁移后的新CPU可能缺少已热的缓存和TLB工作集,需重新建立局部性;不应绝对理解为旧CPU全部缓存必然被硬件标无效。负载均衡和亲和性需要权衡,共享调度队列也仍有NUMA、锁和缓存局部性等问题。
| 方式 | 基本做法 | 主要权衡 |
|---|---|---|
| 自调度 | 空闲处理器从公共就绪队列选线程/进程 | 沿用FCFS、优先级等规则,利用率高;队列竞争和迁移开销可能突出 |
| 成组调度 | 把一组合作线程同时分配到一组CPU | 减少相互等待与上下文切换;可能为凑齐执行组而空闲部分CPU |
| 动态处理器分配 | 按作业并行度和空闲情况增减分配CPU | 适应线程数变化,提高利用率,但决策与重分配更复杂 |
4台处理器上,应用A有4线程、B有1线程。若让两个应用各占整组处理器一半时段,B运行时只有1台忙、3台空闲;若按总5线程分配时间,A可占4/5、B占1/5,可减少这一模型下的闲置。是否允许B的空闲CPU再分配,取决于调度模型。
有空闲处理器时分配给请求者;新作业若需获得初始处理器,可从已占多个CPU的作业中回收,体现新作业优先的特定策略;无法满足的请求进入等待。CPU释放时扫描请求队列,按规定优先级和先来先服务分配。这里是补充资料描述的一种动态策略,不应推成所有系统都必须“新作业绝对优先”。
同步是合作进程为完成共同任务而建立的执行次序关系,例如产品放入后才能取走;互斥是竞争同一临界资源导致的间接制约,例如同一时刻只能有一个执行者修改共享队列。
临界资源是一次只允许一个执行者使用的资源;临界区是访问该资源的代码段。进入区负责检查与申请,退出区负责释放,剩余区完成无关工作。不同临界资源可以分别保护,不必把所有并发操作串成一把大锁。
执行用户临界区时仍可被抢占或发生中断;只要锁状态保持正确,别的竞争者就不能进入同一临界资源。内核临界区能否被抢占取决于内核模型和锁规则,不等于系统中任何临界区都“一律不能调度”。
| 方案 | 进入逻辑 | 问题 |
|---|---|---|
| 单标志轮流 | turn指定轮到谁,退出时转交权限 | 强制交替;对方不再进入时,本方也可能被挡住,违反空闲让进 |
| 双标志先检查 | 先看对方意愿,再设置自己意愿 | 两个进程都先看到对方false,可能同时进入 |
| 双标志后检查 | 先设置自己意愿,再看对方 | 双方同时置true后都等待,不能保证进展 |
本进程先表明意愿,再把优先权让给对方;若双方都想进入,最后一次写turn使其中一个等待。该教材算法在对共享读写原子、可见且按预期顺序执行的模型下,满足互斥、进展和有限等待,但采用自旋,不满足让权等待。实际语言与处理器重排序环境需内存模型支持。
bool flag[2] = {false, false};
int turn = 0;
// 进程 i,另一进程 j=1-i
while (true) {
flag[i] = true;
turn = j;
while (flag[j] && turn == j) { }
// 临界区
flag[i] = false;
// 剩余区
}Peterson:同时想进入时turn让谁先走
单处理器内核可在短小关键区间关可屏蔽中断,防止相关中断与调度打断;用户程序不能任意执行特权开关中断,多处理器上关闭本地中断也挡不住其他CPU。关中断过久会损害中断响应。
TestAndSet原子地返回旧值并写入true;Swap/XCHG原子交换锁值与局部值。原子性由硬件与内存系统保证,不能把普通的“读后再写”两条操作当成等价。
bool TestAndSet(bool *lock) { // 作为一个原子操作
bool old = *lock;
*lock = true;
return old;
}
while (TestAndSet(&lock)) { } // 自旋等待
// 临界区
lock = false; // 释放锁自旋锁失败后反复检查,未释放CPU;临界区很短、持锁者可在另一CPU继续运行时,可避免睡眠与唤醒开销。单CPU上持锁者不运行时,等待者自旋难以等到释放。
阻塞锁把等待者挂起、释放CPU,解锁时唤醒;适合等待较长的情况。互斥锁不必然等于自旋锁,具体acquire/release实现决定是否忙等。简单原子取锁不自动保证公平,等待者仍可能饥饿。
信号量初始化后,通过P/wait与V/signal原语操作。整数型的传统等待循环会忙等;记录型增加等待队列,资源不足时阻塞,实现让权等待。资源计数初值r表示可用r个单位;同步事件初值常为0,互斥初值通常为1。
P先减1,结果小于0才阻塞;V先加1,结果不大于0说明仍有已等待者需要获得这次释放。按此定义,value>0表示可立即分配单位数,value<0时其绝对值表示等待者数量。0既不等于“队列一定有等待者”,也不能跳过P直接访问资源。
struct semaphore { int value; queue waiters; };
P(S) { // 整个更新与排队动作必须原子完成
S.value--;
if (S.value < 0) {
把当前进程加入 S.waiters;
block();
}
}
V(S) {
S.value++;
if (S.value <= 0) {
从 S.waiters 取一个等待进程;
wakeup(该进程);
}
}初值1,A执行P后为0并进入;B执行P后为−1并阻塞;A执行V后为0,需唤醒B。若把V的条件误写成<0,则这一步无法唤醒B。实现使用非负计数配合另一个队列规则时,不能生搬这里的负数语义。
记录型信号量:负数与等待队列同时变化
互斥:为共享临界资源设置mutex=1,进入前P(mutex),退出后V(mutex)。不同资源应按需要分别设置锁;共享计数器的读改写也属于需要保护的临界操作。
同步:为“事件A完成后事件B才能执行”设置s=0,在A后V(s)、B前P(s)。V发生在P之前时,信号量保留许可,不会像未记录的通知那样天然丢失。
前驱图:可对每条前驱边设置初值0的信号量。一个结点必须在所有入边相应P成功后开始,完成后对所有出边执行V;分叉要分别通知各后继,汇合要等待所有必要前驱。
AB与AC的两次通知不能只用一次V替代,否则B、C只有一个能获得许可。D前两个P只是在等待独立前驱,可按规则排列;若同时持有互斥锁再等待其他进程发信号,则需检查能否产生循环等待。
semaphore ab=0, ac=0, bd=0, cd=0;
A: 执行A; V(ab); V(ac);
B: P(ab); 执行B; V(bd);
C: P(ac); 执行C; V(cd);
D: P(bd); P(cd); 执行D;前驱图PV:A完成后同时允许B和C推进
有n个缓冲单元,初始全空:empty=n表示空位,full=0表示产品;mutex=1保护共享缓冲结构。生产者必须先取得空位才放入,消费者先取得产品才取走。生产产品和使用产品通常放在锁外,缩短临界区。
semaphore empty=n, full=0, mutex=1;
producer() {
while (true) {
item = produce();
P(empty);
P(mutex);
insert(item);
V(mutex);
V(full);
}
}
consumer() {
while (true) {
P(full);
P(mutex);
item = remove();
V(mutex);
V(empty);
consume(item);
}
}若生产者先P(mutex)再P(empty),缓冲已满时会持有mutex等待空位,消费者又需mutex才能取走产品,形成死锁;消费者先互斥后等待full同理。故此模型中等待资源的P在前,访问缓冲结构的P在后。
两个相邻V不因等待而直接阻塞,在不改变资源正确计数和共享结构可见性的前提下常可交换,但把通知放在释放锁前可能使被唤醒者再次等待锁。多个full表示多个可消费单位,不要求一次消费全部,也不要求全部缓冲区读完后才允许再次写入。
例:容量2,一个产品从放入到取出
一个只能放一个水果的盘子,父亲放苹果、母亲放橘子、女儿取苹果、儿子取橘子。盘子变空由任一消费者产生,放水果由任一生产者执行,所以共用plate=1;apple=orange=0分别通知对应消费者。
semaphore plate=1, apple=0, orange=0, mutex=1;
father: while(true) { 准备苹果; P(plate); P(mutex); 放苹果; V(mutex); V(apple); }
mother: while(true) { 准备橘子; P(plate); P(mutex); 放橘子; V(mutex); V(orange); }
daughter:while(true) { P(apple); P(mutex); 取苹果; V(mutex); V(plate); 吃苹果; }
son: while(true) { P(orange); P(mutex); 取橘子; V(mutex); V(plate); 吃橘子; }若严格保证单槽且每次只放/取一个,plate及类别信号量已约束同一时刻只有一方访问,mutex可能可以省略。容量增加、操作拆分或共享管理信息改变后,不能照搬省锁结论。
建模顺序是列参与者 → 找临界资源与事件 → 确定初值 → 按每方动作放置P/V → 在空、满和竞争同时到达的边界检查。两个生产者不需要各自一份“盘子空”信号,否则可能重复占用同一个空盘。
允许多个读者同时读,任一写者工作时不允许其他读者和写者访问。readCount表示正在访问的读者数,用rmutex保护计数更新,用rw保护读者组和写者之间的排他关系。第一个读者申请rw,最后一个读者释放rw。
int readCount=0;
semaphore rmutex=1, rw=1;
reader() {
P(rmutex);
if (readCount==0) P(rw);
readCount++;
V(rmutex);
读共享文件;
P(rmutex);
readCount--;
if (readCount==0) V(rw);
V(rmutex);
}
writer() { P(rw); 写共享文件; V(rw); }不能先在锁外判断readCount==0再进锁更新,否则两个读者都可能把自己当第一个,或退出时错误释放rw。若不断有新读者加入当前读者组,readCount始终不为0,等待写者可能饥饿;互斥正确并不自动意味着公平。
读者优先:只有第一位读者锁住写者
int readCount=0;
semaphore gate=1, rmutex=1, rw=1;
reader() {
P(gate);
P(rmutex);
if (readCount==0) P(rw);
readCount++;
V(rmutex);
V(gate);
读共享文件;
P(rmutex);
readCount--;
if (readCount==0) V(rw);
V(rmutex);
}
writer() {
P(gate);
P(rw);
写共享文件;
V(rw);
V(gate);
}写者一旦取得gate,就不再允许后到读者加入,已有读者继续执行并在最后离开时释放rw;写者获得rw后写入,再开放gate。gate不应一直包围普通读操作,否则所有读者会被不必要地串行化。
此方案通常称读写公平闸门或教材中的写优先改进。严格无饥饿仍依赖信号量排队公平性;不能只因增加一把锁便声称任何调度下都满足有限等待。
读写入口闸门:怎样阻止新读者越过等待写者
5名哲学家各需左右两支筷子才能进餐。若所有人都先取同一侧再等另一侧,可能各占一支并循环等待。每支筷子设初值1的信号量只保证单支互斥,并不消除多资源死锁。
take只保护取齐动作,不包住思考与整个进餐阶段;释放筷子不需要take,因此持有take等待另一支筷子者不会阻止现有食者释放。无死锁与无饥饿仍须区分。
semaphore chopstick[5]={1,1,1,1,1};
semaphore take=1;
philosopher(i) {
while(true) {
思考;
P(take);
P(chopstick[i]);
P(chopstick[(i+1)%5]);
V(take);
进餐;
V(chopstick[i]);
V(chopstick[(i+1)%5]);
}
}哲学家进餐:循环等待怎样被统一顺序打破
三个吸烟者分别长期持有烟草、纸、胶水的一种;供应者每次放另外两种材料。只有持有缺少第三种材料的人能取走组合。设offer[3]=0,分别通知匹配吸烟者,done=1表示允许供应下一轮。
一次只允许一组材料在桌上,供应者等待完成反馈后才进入下一轮,给出隐含的单槽互斥。若允许多组材料并存,需要重新定义数量和临界区保护,不能继续把这一省锁版本当作通用解法。
semaphore offer[3]={0,0,0}, done=1;
provider() {
while(true) {
P(done);
k = 选择本轮缺少的材料对应吸烟者;
放置另外两种材料;
V(offer[k]);
}
}
smoker(k) {
while(true) {
P(offer[k]);
取材料并卷烟、吸烟;
V(done);
}
}死锁指一组进程因资源或事件依赖而互相等待,若无外界干预,这组进程无法继续;饥饿是某进程长期得不到需要的服务,其他进程仍可推进;死循环是控制流一直重复,既可能是逻辑错误,也可能是有意循环。
教材资源模型下死锁参与者通常阻塞;自旋锁场景也可能表现为忙等死锁,因此不要把“死锁必然所有线程都处于阻塞队列”当作一切实现的定义。系统并不一定所有进程都死锁。
死锁意味着存在循环等待,但某类资源有多个实例时,仅看图中有环不一定足以认定死锁;每类资源单实例的相应模型中,有环才可成为充分判据。错误P/V顺序同样可通过持锁等待形成死锁。
| 破坏条件 | 做法 | 适用性与代价 |
|---|---|---|
| 互斥 | 可共享化、假脱机等把独占访问转成排队服务 | 不是所有资源都能共享化;物理设备服务仍可串行 |
| 不可剥夺 | 请求失败时释放已持资源,或在可恢复资源上强行剥夺 | 需保存恢复状态,可能工作回退、开销与饥饿 |
| 请求和保持 | 运行前一次申请所需全部资源,满足后才运行 | 简单但资源可能长期闲置,需预知需求 |
| 循环等待 | 统一给资源编号,按递增顺序申请,同类按规则一次申请 | 不易扩充,可能限制编程并造成资源闲置 |
一次性分配消除的是边持有边继续申请的机会,不是简单“使用后早点释放”;有序分配必须覆盖所有相关资源和代码路径,不能一处按升序另一处逆序。预防改变请求规则,避免则在每次请求时分析是否仍安全。
若存在一个进程序列,使每个进程按声明的最大剩余需求都能从当前可用资源及前面完成者释放资源中得到满足,则状态安全。序列可能不唯一;安全意味着在假设成立并按合适策略分配时可避免死锁。
不安全不等于已经死锁;它表示无法保证所有声明最大需求都能完成。实际进程可能少于最大需求就结束,因此不安全状态也可能继续运行。
Available[m]是可用向量;Max[n][m]是最大需求;Allocation是当前已分配;Need=Max−Allocation;Request是本次请求。各向量比较均按分量进行。
请求处理:①若Request>Need,超过声明需拒绝/报错;②若Request>Available,资源不足需等待;③否则试减Available、增加Allocation、减少Need;④执行安全性检验,安全才正式分配,不安全则回滚并等待。
模拟完成时把原已分配Allocation加回,而不是把Max直接加回。Need由Work提供只是模拟临时借出后归还,净增加的是该进程原先占有的Allocation。
Work = Available;
Finish[i] = false;
while (存在 i 使 !Finish[i] 且 Need[i] <= Work) {
Work = Work + Allocation[i];
Finish[i] = true;
将 i 追加到安全序列;
}
若所有 Finish[i] 都为 true,则安全;否则不安全。银行家安全性检查:Work怎样增加
银行家算法:资源够给,为何仍拒绝请求
圆点表示进程,资源类结点可含多个实例;进程→资源是请求边,资源实例→进程是分配边。找当前请求可由空闲资源满足的进程,假设它完成并撤去相关边、归还已持资源,重复简化。
在所用教材资源请求模型下,若图能完全简化,当前不死锁;最终无法被简化的相关进程构成需要处理的死锁/受阻集合。检测按当前请求判断,不是银行家按最大剩余Need保证未来。
预防:破坏必要条件;避免:拒绝使系统进入不安全状态的申请;检测与解除:允许发生后发现并恢复。三者分别改变资源使用规则、动态分配判断和事后处置,不能混为同一种“银行家算法”。
P1持有R2等R1,P2持有R1等R2,单实例且不可抢占时形成相互等待环。请求边从进程指向资源,分配边反向。
当各资源类型都只有一个实例时,环是死锁的充分必要条件;有多个实例时,存在环只是必要条件,仍须检查是否有进程可以完成。
编译把源程序生成目标模块,模块中包含机器指令、相对地址及待解决符号;链接把目标模块和所需库连接起来,解决外部符号与模块间地址关系;装入把可执行映像安排到内存并完成相应地址绑定。
静态链接在运行前组合模块;装入时动态链接边装入边处理所需模块;运行时动态链接在执行到相关调用时再处理模块。动态链接便于库更新和共享,但具体查找、重定位及版本规则由实现决定。
| 方式 | 地址绑定时机 | 移动与限制 |
|---|---|---|
| 绝对装入 | 编译/汇编等阶段已确定物理地址 | 预先知道装入位置,位置改变需重新处理 |
| 可重定位装入/静态重定位 | 装入时一次把相对地址修改为物理地址 | 装入后通常不能任意移动,需重新重定位 |
| 动态运行时装入/动态重定位 | 真正访问时由硬件进行地址变换 | 可支持运行中移动、非连续存放与按需装入等机制 |
逻辑/虚拟地址是程序所用地址,物理地址是内存实际访问位置;二者偶然数值相同不代表概念相同。按字节编址时每个地址对应1B,按字编址时对应一个机器字,计算页大小和偏移位数前必须统一单位。
编译/汇编生成目标代码;链接把目标模块和所需库组合并处理符号引用,静态与动态链接的发生时点不同。
装入把程序映像准备到内存或虚拟地址空间。运行时地址变换由MMU等机制完成,不是每执行一条指令都重新链接整个程序。
上下限寄存器可规定允许的物理地址范围;基址加限长模型先检查0≤逻辑地址<长度,再计算物理地址=基址+逻辑地址。限长表示单元数量还是最大合法偏移以题设为准,不能漏掉等号边界。
这些保护寄存器由内核通过受控方式设置,用户程序不能任意改写来绕过隔离。分页、分段中还可通过表项的存在位、读写执行权限等完成更细的保护。
可重入/纯代码在运行期间不修改自身,便于多个进程共享同一代码副本,各进程的可写数据及现场另行保存。共享只读代码较容易,但共享内存也可以是可写的,可写共享需要访问权限和同步保护,不能把“只有只读区能共享”当作普遍定义。
| 方式 | 基本组织 | 主要特点 |
|---|---|---|
| 单一连续 | 系统区外一次只装一个用户程序 | 简单,利用率和并发受限,用户区未使用部分可能成为内部浪费 |
| 固定分区 | 预先分成固定大小或不等大小分区,各放一作业 | 支持多道,实现简单;分区内部可能浪费,过大作业放不进 |
| 动态分区 | 按到达进程需求临时划分,分区大小数目变化 | 利用更灵活,但反复申请回收产生外部碎片 |
内部碎片在已分配范围内暂未使用,不能直接给其他请求者;外部碎片是分散空闲区,合计够大却没有足够连续空间。动态分区按精确需求分配的理想模型无内部碎片,真实实现的对齐或最小分配量仍可能引入内部浪费。
紧凑把已占分区移动到一起,合并空闲空间以缓解外部碎片,需可重定位支持和数据移动开销;不能当成完全没有成本的“把空闲块数字相加”。
| 算法 | 空闲区组织与搜索 | 典型影响 |
|---|---|---|
| 首次适应FF | 按地址递增,每次从低地址找首个够大的区 | 保留高地址大块,但低地址小碎片增加查找 |
| 邻近适应NF | 按地址循环,从上次结束处继续 | 减少重复低址扫描,也可能更快打散高址大块 |
| 最佳适应BF | 选满足请求且最小的空闲区,常按容量递增 | 尽量不占大块,但容易留下大量小碎片 |
| 最坏/最大适应WF | 选最大的空闲区,常按容量递减 | 剩余块较大,但大块可能迅速被消耗 |
回收区前后都不空闲:新增一项;仅前邻空闲:并入前区;仅后邻空闲:调整后区起点并增大长度;前后均空闲:三段合并并删除多余表项。合并依据物理地址相邻,不依据它们在按大小排序链表中是否相邻。
按地址排列空闲区100、500、200、300、600KB,请求212KB:FF取500,BF取300,WF取600;NF需再知道上次搜索位置。完成分割后要更新起点和剩余长度;零大小剩余区应删除。算法不存在脱离请求分布永远最优的保证。
假设按地址排列的空闲分区大小依次20、50、30KiB,申请18KiB:首次与最佳均选20,最坏选50。
邻近适应从上次结束位置继续循环搜索,缺少上次指针就不能唯一判定。回收时须检查地址上相邻的空闲分区并合并,不能仅按分区表排列相邻就合并。
物理内存分成等大的页框/页帧/物理块,进程逻辑空间分成同样大小的页。各页可以落在不相邻页框中,页内连续;基本分页模型通常一次装入所需全部页,请求分页则允许部分驻留。
页表项按虚页号记录对应页框及权限等信息。连续数组组织时页号可由表项位置隐含,不必逐项额外保存;页表通常作为独立内存结构存在,PCB保存其地址等管理信息,不是把整张页表塞进PCB。
页大小为2^k个编址单元时,低k位是偏移,其余为页号。页号≥页表长度属于越界;页表项有效但页不在内存才是缺页,两者不是同一情况。
A为逻辑地址,L为页大小,p为页号,d为偏移,b为查得的页框号。地址与L需使用同一编址单位。
按字节编址,页4KB,逻辑地址0x12345:页号0x12,偏移0x345。若该页对应页框0x2A,则物理地址0x2A345。页内偏移保持不变,不能把页号原样当作物理块号。
物理内存容量M、页大小L时,可用页框数为M/L,需要至少⌈log₂(M/L)⌉位表示页框号;完整页表项还可能含状态、访问权限等,实际表项大小按题设给出的位数或对齐规定,不能只算页框号便直接认定表项长度。
页表基址与长度等信息在进程调入CPU时加载到相应寄存器。硬件拆出页号/偏移 → 检查页号范围 → 用页表基址+页号×表项长度定位表项 → 检查状态与权限 → 取页框号组合物理地址 → 访问目标数据。
不带TLB且页表驻内存、没有缓存等额外条件的单级页表模型:读表一次、取目标一次,共两次主存访问。分页地址在用户看来可用一个线性逻辑地址表示,是一维地址;拆成页号与偏移是硬件解释,不意味着用户必须显式给出两个逻辑维度。
页表容量:虚拟页数与物理页框号位数分别算
TLB是用于缓存近期页表映射的高速结构。命中且权限有效时可直接得到页框;未命中则需查内存页表,成功后把翻译装入TLB。TLB未命中不等于缺页,页面完全可能已经在物理内存。
切换地址空间时需避免误用旧翻译:无地址空间标记的模型可能刷新TLB;有ASID等标记可保留不同空间映射。不能绝对说一切进程切换必然把全部TLB清空。
若TLB与页表并行查询、命中时取消慢表访问,路径时间需分别按硬件重叠关系计算,不能直接套顺序公式。缺页处理时间往往远高于纳秒级内存访问,给出缺页率时应额外加权。
单级页表、TLB顺序查询,命中率h、TLB查询ε、一次主存t;忽略缺页、Cache及并行重叠。
ε=10ns、t=100ns、h=0.9时,命中路径110ns,未命中路径210ns,加权为120ns。若错误把未命中只算一次主存,会漏掉页表读取。
单级页表可能很大并要求较大连续空间,而且未使用的虚页也占用表项。多级页表让各级指向下级表,未覆盖到的地址区可暂不建立相应低级表;表项的管理空间与逐级查表开销需要权衡。
拆分顺序为顶级索引、各级低级索引和页内偏移;页目录不是直接把所有虚页映射到数据页框,它的表项先指向下一层页表。
无TLB且各级表都在主存,N级页表访问一个数据通常需N次读表加1次取数据。若已有某级缓存、TLB命中、表页缺页或其他条件,另按题设分析。
页大小L=2^k、表项E为2的幂且每级表至多一页时,完整低级表可承担q位索引;v为虚址位数,顶级剩余位可较少。
40位字节地址、页4KB、表项4B:偏移12位,每页可放1024项,低级索引每级10位;页号共28位,需3级,可拆8+10+10+12。无TLB命中时通常读三次表,再访问一次数据。
每个4KiB页表页可装4096÷4=1024个表项,所以一层索引占10位。两级各10位加12位页内偏移恰好为32位;一级表项给出下一层表的位置,最终表项给出目标页框号。不同地址位宽、页大小和表项大小必须重新拆分,不能直接套用10/10/12。
二级页表查找:10位目录、10位页号、12位偏移
分段按代码、数据等逻辑意义划分,段长可变;逻辑地址由段号与段内位移组成,各段内连续、段间可离散。段表项保存基址、长度和相关权限,地址转换先查段号是否越界,再检查偏移是否小于该段长度,最后相加得到物理地址。
段便于按逻辑单位共享、保护和增长,但可变长段的物理安置容易出现外部碎片;分页通常没有外部碎片、最后一页可能有内部碎片,用户通常不感知页的分界。
先把程序分段,再将各段分页;逻辑地址分为段号、段内页号、页内偏移。段表项指向本段页表并记录页表长度;检查段号 → 查段表 → 检查页号 → 查本段页表 → 取页框号与偏移组合。无TLB的典型单层段表加单层页表模型共三次访存。
段页式用户地址仍按段及段内位置表达,段内位置再由系统拆为页和偏移。TLB可用段号与页号等信息作为查询关键字,命中时直接得到目标页框。
页大小固定,所以基本分页页表项通常不需为每页记录长度;段长可变,段表必须记录边界。基本分页与基本分段都可引入TLB,不能以“是否有快表”作为两者的本质区别。
分段按逻辑模块划分,段长可变;分页按固定大小划分。段内越界先判断,不能直接拿基址加偏移而跳过保护。
段页式把段内地址继续分为页号和偏移,每段通过自己的页表映射到物理页框。所需访存次数取决于TLB及各级表缓存等条件。
分段保护:先判断段内偏移,再做基址相加
时间局部性:近期访问的指令或数据很可能再次访问,循环是典型来源;空间局部性:访问某单元后,邻近单元也可能很快被访问,顺序指令和数组遍历体现这一规律。二者使只保留活跃部分也能有效推进成为可能。
传统全部装入模式具有一次性、运行期驻留等限制;虚拟存储通过请求调入与置换,从逻辑上提供比当前物理内存更大的地址空间。其多次性允许分批装入,对换性允许运行时换入换出,虚拟性体现逻辑扩展。
实现可基于请求分页、请求分段或请求段页式,需地址变换、缺失异常处理、外存后备和置换管理协同。虚拟容量受虚拟地址位数、体系结构与后备存储等约束,不是无限大;物理内存仍限制同时活跃数据的容纳量。
虚拟内存不意味着CPU直接把磁盘当内存访问;需要的页/段先调入物理内存,指令再完成原访存。
| 字段 | 作用 | 用途 |
|---|---|---|
| 存在/有效状态 | 页面当前是否驻内存或映射是否有效 | 页不存在与地址非法需结合体系结构语义区分 |
| 访问字段A | 近期是否访问、次数或时间信息 | 为置换提供依据 |
| 修改位M | 调入后是否被写过 | 决定换出是否需保留新内容 |
| 外存位置 | 页后备数据位置 | 用于请求调页 |
有效TLB翻译指向驻留页时,不应再发生“该页不在内存”的缺页;TLB miss后查到驻留页仅是翻译未缓存。置换必须使旧翻译失效,避免TLB还指向已被重用的页框。
一条指令可访问指令页、源操作数页和目标页等多个位置,因此可能在重启后又缺另一页。缺页不是等待下一条指令才处理的普通外设中断,重启语义应保证指令最终效果正确。
缺页处理:从异常到重新执行
缺页置换:脏页为什么多一次磁盘写回
请求段表除基址、段长外,可含存取方式、访问字段、修改位、存在位、增补/增长标记和外存始址。访问合法但不驻留的段时,先调入该段并更新段表,再完成地址转换。
段保护包括段号及段内偏移的越界检查、读写执行等存取控制,也可使用环保护等体系结构机制。是否动态增长及增长后如何重定位,由管理策略决定。
系统可设共享段表记录段长、物理起点、存在状态、后备位置和共享计数;各进程在自己的段表中建立映射,所以同一物理共享段在不同进程中可以有不同段号。
首次共享者申请时建立物理段和共享表项,计数置1;后续映射成功计数增加。进程取消共享时撤其映射并减少计数;只有没有任何使用者且满足其他回收条件时才可释放共享物理区,不能因某一进程退出便删掉大家仍使用的数据。
驻留集是为进程实际保留的物理页框集合,大小可能小于完整虚拟地址空间。固定分配使配额运行期间基本不变;可变分配允许按缺页等情况增减。局部置换只在本进程驻留集中选受害页;全局置换可影响其他进程或使用全局空闲池。
| 组合 | 缺页时处理 | 权衡 |
|---|---|---|
| 固定分配+局部置换 | 在固定驻留集内换页 | 隔离较好,但初始配额难定 |
| 可变分配+全局置换 | 可取空闲框,也可换其他进程的可换页 | 适应性强,进程间相互影响 |
| 可变分配+局部置换 | 缺页时局部替换,同时另按策略调整配额 | 按缺页频率增减框数,控制复杂度更高 |
典型教材不采用“固定配额且任意全局夺取”作为独立稳定组合,因为夺取会改变其他进程驻留集。具体系统可有更复杂配额约束,做题按给出的固定/可变和局部/全局定义判断。
请求调页在确实缺页时调入,信息明确但发生等待;预调页根据预测提前装入可能需要的页,减少连续缺页但有预测失败和多余I/O风险。首次装入可选预调,运行期也可按实现预取,不把两者机械限定为只能发生在某一阶段。
后备可来自原文件或交换区。已有完整未修改文件副本的干净页可重新从文件读入;匿名或修改后需要保留的数据通常要有适当交换/后备位置。对换区是否连续、更快等属于教材模型或具体布局,不是磁盘分区名称本身赋予的性能。
工作集W(t,Δ)是最近一个窗口内实际引用过的页面集合;驻留集是当前实际留在物理内存的集合。二者可不同,工作集大小也随执行阶段和窗口变化。
分给进程的页框不足以容纳活跃工作集时,刚换出的页很快又要调回,形成频繁调页/换出的抖动。CPU利用率下降若盲目增加多道程序度,会进一步挤压内存,恶化抖动;可降低多道度、增配框数、按工作集或缺页率控制。
工作集窗口:最近4次访问会留下哪些页
| 算法 | 淘汰规则 | 特点 |
|---|---|---|
| OPT | 未来最久才使用或不再使用的页 | 给定引用串下缺页最少的比较基准,运行前通常不知道未来 |
| FIFO | 最早进入内存的页 | 实现简单,命中通常不改变入队顺序;可能Belady异常 |
| LRU | 过去最久没有访问的页 | 依赖局部性,每次引用都影响最近次序;准确实现需记录访问 |
先判断命中;命中不产生换入,但LRU必须更新最近次序,FIFO不因命中把该页移到队尾。缺页而有空框时直接装入,不应计为替换;缺页数包括初次装入,置换次数不一定等于缺页次数。
FIFO可能随着页框数增加反而增加缺页,即Belady异常。OPT、LRU具有栈性质,不产生这一异常;“只有FIFO可能异常”只宜用于限定比较的常见算法,其他不具栈性质的策略也可能出现。
引用1、2、1、3,两个空页框:前三次后FIFO进入顺序仍为1、2,LRU次序则2最旧、1最新;访问3时FIFO换1而LRU换2。若把命中后刷新顺序套到FIFO,实际上已经改成另一算法。
例:3个页框,访问1、2、3、1、4、2
FIFO的Belady异常:3框9次,4框为什么10次
把驻留页组织成循环队列,设置扫描指针。页面被访问后A置1;需要受害者时,从指针处检查:A=0可选,A=1先清0并继续。若本轮全为1,先清完一圈,再次遇到0时选中;正常无并发重新置位的教材模型下至多两圈可找到。
命中时设置访问位,不随意移动扫描指针;换入新页后访问位和指针下一位置依题设初始化规则,必须在模拟表里注明。
同时考虑(A,M):(0,0)近期未访问且干净,优先级最高;(0,1)近期未访问但脏;(1,0)近期访问但干净;(1,1)近期访问且脏。目标是在局部性与写回代价间折中。
常见多轮实现先找(0,0),未找到再找(0,1)并在扫描中清访问位,必要时重复。教材也可能采用不同清位轮次,不能只按四类排序便忽略指针起点和扫描时清位动作。脏页换出写回不意味着每次写访问都立刻写盘。
Clock扫描:清访问位与选择牺牲页
伙伴系统按2的幂组织空闲块。请求大小向上取足够大的2^k块;若本级无空闲块,则向更大级寻找并逐级二分,分出一半供继续分配,另一半进入对应空闲链。
回收时只与同阶、相邻且来自同一父块二分的空闲伙伴合并,然后继续尝试更高阶。两个同样大且地址相邻的块不一定是伙伴;在对齐的二进制模型中可用块起址异或块大小寻找伙伴。
请求20KB、最小单位1KB时,向上取32KB,产生12KB内部浪费;若32KB链为空而64KB有块,就拆成两个32KB。释放一块32KB后,只有另一伙伴也空闲才能合成64KB,不能跨父块随意拼接。
| 类型 | 典型处理 | 原因 |
|---|---|---|
| 受保护/锁定、关键内核页等 | 不可随意回收 | 不能破坏内核执行或硬件正在使用的数据 |
| 干净文件页 | 可丢弃物理副本,未来由文件重读 | 后备已包含相同内容 |
| 脏文件页 | 先写回适当后备,再释放 | 需保留未保存修改 |
| 匿名页 | 若可换出则写入交换后备 | 堆栈等没有原文件可直接重读 |
| 可回收缓存/空闲slab等 | 按相应缓存回收规则释放 | 减少可重建元数据占用 |
页框回收的目标是获得可再分配的物理框;页面置换策略决定哪些虚拟页适合失去驻留副本,两者高度相关,不能硬划成互不相干的两套操作。已空闲页本来无需再“回收”;干净页丢弃虽可免写回,但未来再访问仍可能增加缺页与I/O成本。
伙伴系统:13KiB为何要分配16KiB
文件是一组具有意义的信息,支持长期保存、共享和按名访问。属性可包括名称、系统标识、类型、大小、拥有者、位置、创建/修改/访问时间和保护信息;同目录下通常不允许同名,不同目录可有同名文件。
FCB保存管理文件所需的信息,包含基本属性、存取控制和使用信息。传统FCB目录中一个目录项对应一份文件控制信息;采用索引结点后,把名称以外的大部分元数据集中到inode,目录项保存名称和inode标识。
磁盘inode可记录拥有者、类型、权限、长度、地址索引、链接数和时间等;具体直接/间接地址项数量由文件系统格式规定,不能把某教材的13项当作所有inode固定长度。
内存inode还可记录引用计数、修改或锁状态、设备标识以及缓存链表/散列表指针。把目录项缩小后,一个磁盘块能容纳更多文件名,路径检索时可减少目录I/O;真正需要文件元数据时再读取相应inode。
目录项与inode:文件名是否必须存进inode本身
| 操作 | 主要工作 | 关键区别 |
|---|---|---|
| 创建 | 分配元数据/必要空间并建立目录项 | 不等于已写入大量用户数据 |
| 打开 | 解析路径、检查权限,建立打开状态,返回描述符/句柄 | 后续通常按描述符操作,不必每次重新按名检索 |
| 读/写 | 检查访问方式与缓冲区,按位置传送数据并更新位置 | 文件位置指针与数据缓存是不同对象 |
| 定位 | 改变当前打开上下文的文件偏移 | 本身不必传送文件数据 |
| 关闭 | 释放当前描述符及相应打开引用 | 一般不删除目录项和磁盘数据 |
| 删除/解除链接 | 移去名称引用,按链接和打开情况决定最终回收 | 实际空间回收可能延后 |
一个打开实例常使用同一个偏移供读和写共同推进;不同独立打开实例可有各自偏移。I/O并发是否允许、是否原子以及缓存刷新由接口规定,不能从“一个读写指针”推出全系统永远不能同时读写。
close后文件仍可存在并被再次open;unlink删除一个文件名后,已有打开引用在常见系统中仍可能继续使用文件,等最后引用消失后再回收数据。
删除文件名后:为什么已打开的进程还能读
进程打开文件表把文件描述符映射到系统维护的打开实例;系统打开实例记录访问模式、当前位置及对文件对象的引用;inode/FCB缓存提供文件元数据和数据定位。教材有时把系统打开表与内存FCB简化为一层,分析时应先声明所用模型。
同一文件对象不等于只有一个打开实例。独立open常建立独立偏移,dup或fork继承可让多个描述符引用同一打开实例并共享偏移。
A对文件独立open两次得到fd1、fd2:二者可指向同一inode但拥有不同打开实例,fd1读10字节后fd2仍可从自己的原偏移读。若fd2由dup(fd1)得到,它们共享打开实例,fd1读后会推动fd2看到的共同偏移。
目录硬链接数反映多少目录项指向文件对象;打开实例引用数反映多少描述符等引用同一打开实例;内存inode引用/打开计数反映内核对象被使用情况。删一个名称、关一个描述符和最终回收inode/数据块不是同一个时刻,也不应只用一个count混算。
dup与再次open:文件偏移到底是否共享
| 方式 | 原理 | 优缺点和边界 |
|---|---|---|
| 口令保护 | 访问时提交约定凭据 | 实现简单但共享口令难区分用户;安全实现不应明文保存口令 |
| 加密保护 | 用密钥变换文件内容,授权方解密 | 提高保密性,增加密钥管理与编解码开销 |
| 访问控制 | 按主体、对象及操作权限决定是否放行 | 可用访问矩阵、ACL、拥有者/组/其他用户权限等组织 |
路径访问要通过各级目录的搜索/执行等权限检查,再检查目标文件所请求操作的权限。不能一概说“不能列目录就不能访问其任何已知名字的文件”,因为列目录和穿越目录在具体系统中可为不同权限。
加密并不自动解决删除、篡改、共享管理和密钥泄露;ACL也不保证磁盘被离线读取时内容仍保密,两者可共同使用。
| 结构 | 组织与查找 | 增删和空间 |
|---|---|---|
| 无结构/流式 | 看作字节序列,记录边界由应用解释 | 灵活,不是“没有任何内部含义” |
| 顺序记录 | 串结构不按关键字,顺序结构按关键字;定长记录可计算位置 | 适于顺序处理,插删维护可能代价大 |
| 索引文件 | 每记录有索引项,表项定长并指向记录 | 支持变长记录定位,索引占空间 |
| 索引顺序文件 | 记录分组,每组一个索引项;组间关键字有序 | 先定位分组再组内查找,兼顾索引空间与检索速度 |
定长记录的偏移可由记录号×记录长度得到;变长顺序记录通常不能仅凭记录号直接算偏移,需扫描长度或辅助索引。按关键字排序且记录定位方便时可用折半等方法;链式物理组织与逻辑顺序组织不能不加区别地套同一查找复杂度。
索引表本身是定长项组织,若表太大可分组或分级。索引顺序的组内关键字可不完全有序,但组间边界要支持按关键字确定候选组。
FCB记录起始块号与块数。逻辑块i合法时,物理块=起始块+i,块内偏移不变;需检查0≤i<文件块数。连续区域有利于顺序和随机访问、减少机械寻道。
文件增长若相邻空间已被占用,可能要移动或增加其他扩展机制;反复增删产生外部碎片,预留过多空间又浪费。长度不一定必须永久不变,但传统单连续区域模型难以灵活扩展。
每个数据块中保存后继块指针,目录保存首块,常附尾块便于追加。文件块可离散,增长容易且无外部碎片;末块仍可能有内部浪费,指针也占数据容量。
定位第i个逻辑块需沿链依次读前面的块,不支持高效的直接随机定位。指针损坏可断链;不能把“离散无外部碎片”说成任何空间浪费都没有。
要读逻辑块3(从0计),若仅首块号在内存且没有缓存,需先读块0、1、2获取后继,再读块3,共4次数据块读取;若FCB、首块或部分链已缓存,次数按题设减少。
同一文件的3个块:连续放置与链接放置
显式链接把各物理块的后继指针集中到文件分配表FAT,目录项保存文件首块号,FAT[当前物理块]给下一物理块。表还可编码空闲、坏块和链末等状态。
教材常假设FAT读入内存,此时沿链的地址查询不用每跳读一次磁盘,但仍要在表中逐项走链,不等于像数组那样O(1)直接算出任意逻辑块。若表本身不全在内存,还需考虑缓存和表页I/O。
若卷上有N个可编号簇,仅表达编号至少需⌈log₂N⌉位;实际表项还需预留特殊状态编码并按格式分配字节数。FAT空间约为表项数×每项长度,多副本和保留项由题设另计。
卷大小/簇大小决定簇数,而不是文件大小决定整张FAT长度。查某文件占多少簇,可沿其链统计;不要把整个卷FAT条目数误当作每个文件都拥有的索引项数。
FAT文件读取:逻辑第2块不一定是磁盘第2块
索引分配在文件的索引块中按逻辑块顺序保存物理数据块号,FCB指向顶级索引。数据块可离散,随机定位比链式便捷,但索引也占盘块,小文件可能浪费索引块。
索引块链接方案允许索引表扩展,但找后面的索引块仍需沿链;多级索引通过顶级表索引下级表;混合索引同时提供直接地址和一级、二级等间接入口,让小文件用较少I/O,大文件仍可扩展。
访问直接块不需读索引块;一级间接需读1个索引块,二级需沿路径读2个,再读目标数据。FCB、inode或索引已缓存会减少I/O,读整个文件的总索引块数不能用“每块最大级数”机械相乘。
B为块字节数,a为地址项字节数,d个直接项,另有一个一级、一个二级和一个三级间接入口;仅计数据容量,地址格式按题设。
若块1KB、地址4B,则每索引块256项。10个直接项后,逻辑块0–9直接;10–265经一级间接;266起进入二级间接。先减去较低级覆盖量,再用商和余数确定二级路径,最后保留文件字节的块内偏移。
间接索引:逻辑块号怎样变成磁盘块
树形目录层次清楚、方便分类和访问控制,不同目录可重名;绝对路径从根开始,相对路径从当前工作目录开始。检索逐级进行,中间目录是否已缓存会影响磁盘访问次数。
无环图目录允许多个目录项指向同一共享对象,支持共享而不复制数据。用引用计数可辅助回收;设计必须避免引入环或另配环检测/垃圾回收机制。
搜索找到文件目录项;创建插入新条目;删除移除相应名称;显示枚举目录内容;重命名/移动改变名称或父目录关系;修改属性更新相关元数据。目录本身也是由文件系统组织的对象,不是仅存在于屏幕上的文件夹。
目录项缓存和inode缓存可以减少路径查找I/O,但缓存命中与是否已经open是两件事。计算路径代价时先列根目录、各级目录块和目标inode哪些已在内存,再逐级计数。
路径解析:每一级目录究竟帮忙查了什么
| 属性 | 硬链接 | 符号/软链接 |
|---|---|---|
| 指向对象 | 目录项直接引用同一文件对象/inode | 链接自身是文件,内容通常是目标路径 |
| 对象数 | 多个名称共享同一数据和元数据对象 | 链接文件与目标是两个对象 |
| 删除一个名称 | 减少链接数,其他硬链接仍可使用 | 删除目标后可留下悬空符号链接 |
| 跨文件系统 | 通常受同一文件系统限制 | 目标路径可跨文件系统 |
| 目录链接 | 普通用户创建常受限制,避免环 | 可指向目录,但解析要防循环 |
硬链接计数统计名称引用,不是“有几个用户打开”。用户删除原文件名不表示有权立即删除其他硬链接指向的数据;系统还需考虑最后硬链接和打开引用。
符号链接不会因为目标不存在而自动消失,访问可失败而链接仍在。共享写会被其他共享访问者看到;复制后修改自己的副本通常不改变其他副本。
硬链接和软链接:名字指向哪里
文件卷可由物理分区等承载,也可通过卷管理组合多个设备。持久布局包括引导/卷描述信息、空闲空间管理信息、目录与inode等元数据,以及文件数据区域;具体不必严格固定为两块连续“目录区+数据区”。
运行时内核维护目录缓存、inode/文件对象缓存、系统打开实例和进程描述符表等。缓存是持久内容的内存副本或运行状态,关机后不能把它当作唯一长期记录。
空闲表记录各连续空闲区的起点和长度,适于连续分配;申请可用首次、最佳等规则,回收按前后相邻情况合并。空闲盘块链逐块链接,分配摘下足够块,回收重新链接;空闲盘区链按连续区链接,批量申请较高效,并在回收时尽可能合并相邻区。
位示图每块一位,0空闲/1占用是本资料约定,题设相反则反转。申请找足够的空闲位,换算块号后置占用;回收按块号定位位并清空。存N块需要⌈N/8⌉字节位图,若按整字存储还需按字长向上取整。
b为从0编号的块,i为从0编号的机器字,j为从0编号的位,w为每字位数;若编号从1开始,应先减1换算再加回。
空闲块位图:分配与释放到底修改哪几位
按共享属性分独占与共享;按数据交换单位分字符设备和块设备;按用途分人机交互、存储、通信等;按速率分高速、中速、低速是相对分类。典型磁盘块设备支持块定位,典型终端字符设备按流交换,不能由“字符设备”推出所有实现都必须物理独占。
设备控制器连接主机总线和外设,含主机接口、设备接口和I/O逻辑,可接收译码命令、交换数据、报告状态、识别地址、缓冲和检测错误。一个控制器可以连接多个设备,设备编号与控制器端口地址不是同一个概念。
I/O端口通常指可被CPU访问的接口寄存器:数据寄存器缓冲数据,状态寄存器报告准备/错误,控制寄存器接受命令。多个端口及控制逻辑组成接口;不能只因CPU访问了状态端口便认为数据已经传完。
独立编址使用单独I/O端口空间及专用指令;内存映射I/O把寄存器放入统一地址空间,可用相应访存指令访问,但访问权限、缓存属性和顺序仍由系统控制。端口映射区不能同时当作普通内存使用,具体地址未必都在最高端。
| 方式 | CPU参与 | 数据路径与特点 |
|---|---|---|
| 程序直接控制/轮询 | 发命令后反复检查状态,按单位搬数据 | CPU开销大,典型模型中与设备传送串行 |
| 中断驱动 | 启动后可做其他工作,设备就绪后中断服务 | 数据常经CPU寄存器,减少忙等但有频繁中断开销 |
| DMA | CPU设置起址、方向、计数等,控制器搬一批,完成后通知 | 设备与内存直接交换,数据不逐字经过CPU寄存器 |
| 通道 | CPU启动通道程序,通道执行一系列I/O控制 | 更强I/O处理实体,可组织多设备操作,硬件与管理更复杂 |
命令/状态寄存器记录控制与状态,内存地址寄存器记录下一传送地址,数据计数器记录剩余字/字节数,数据寄存器可临时缓冲数据。每次传送相应更新地址和计数,结束后向CPU报告。
CPU和DMA共享总线/主存时会有总线仲裁与访存竞争。DMA请求可在适当总线周期边界处理,不等于普通中断响应只能在指令边界进入服务例程。DMA完成可发中断,所以“DMA不用CPU逐字搬”并不等于“DMA完全不使用中断”。
CPU发I/O后,发起进程是否阻塞取决于接口语义;它阻塞时CPU可执行别的进程。不要把“同一进程等待I/O”和“整个CPU与所有设备不能并行”混为一谈。现代控制器能力也可能超出教材简化的一控制器一设备模型。
| 层次 | 工作 |
|---|---|
| 用户层I/O软件 | 库函数、格式转换、用户接口,通过系统调用请求内核 |
| 设备独立性软件 | 统一命名、保护、分配回收、缓冲、通用差错处理及逻辑块接口 |
| 设备驱动程序 | 将抽象请求翻译为具体控制器命令,检查设备状态、设置寄存器 |
| 中断处理程序 | 对设备中断保存必要现场、识别状态、完成处理并唤醒等待者 |
| 控制器与物理设备 | 执行数据传送和硬件操作 |
应用使用逻辑设备名或统一接口,不依赖某个物理设备的硬件细节;系统映射到可用物理设备和相应驱动,便于更换设备与I/O重定向。设备独立不表示驱动与硬件无关,而是硬件相关细节被封装在下层。
驱动与内核之间应有规范接口,新设备通过相应驱动接入,避免每来一台新设备都改全部上层程序。某些简单硬件差错由低层处理,较通用的资源、缓冲和命名由较高层处理,具体界限仍依操作系统实现。
图按概念层次展示,不表示每个请求都必须同步进入“中断处理程序”再访问硬件。驱动启动设备,设备完成后产生中断,由处理程序进入完成路径。
设备独立层使上层不必掌握每种控制器的寄存器细节;具体缓冲、调度和错误处理可以跨层实现。
字符接口以字节/字符流为中心,常见get/put或read/write;块接口把设备抽象为按逻辑块编号的序列,将上层块请求映射到底层操作,屏蔽几何结构;网络接口常以socket建立通信端点并发送接收数据。
文件系统可在块设备接口之上提供按名文件访问,因此“对文件read”与“直接对盘块read”不能直接视为同一层。设备典型使用中断或DMA是性能选择,不是由接口名字唯一决定。
阻塞I/O在请求暂时不能完成时使调用线程等待,事件就绪后被唤醒;非阻塞I/O立即返回已完成量或暂不可完成状态,应用可稍后重试或借助事件通知。非阻塞不表示必然返回错误,也不等于完整的异步I/O。
阻塞是调用者状态,中断/DMA是底层传送控制;可同时存在阻塞read和DMA传送。多个线程时,一个阻塞系统调用是否拖住其他线程还取决于用户/内核线程映射。
阻塞与非阻塞read:没数据时返回路径有什么不同
磁盘高速缓存通常在主存保留磁盘块数据,减少反复磁盘I/O,不是CPU与主存之间的硬件Cache;可划固定区域,也可与空闲内存、页缓存等统一管理。
缓冲用于协调CPU与设备速度、数据粒度,减少中断频率、放宽响应时限并增加重叠。既可在硬件设置小缓冲,也可在主存设置缓冲区。
缓冲区被用户提取后,设备可输入下一块,同时CPU处理上一块,所以T和C可重叠;搬运M仍是独立约束。此式是连续多块处理的平均稳态周期,不应不加首尾启动与排空时间就当作任意n块总时间。
设备填满一块T,内核缓冲搬到用户区M,用户处理C;采用教材一次一块且搬运阶段不可与相应填充重叠的模型。
T=8ms、M=1ms、C=3ms时,稳态单块周期9ms。若只处理第一块,从设备开始到CPU处理完需要8+1+3=12ms;把单块端到端时间和连续流水稳态周期分开,才能正确计算有限批次。
单缓冲:输入、复制、处理哪些阶段能重叠
两区交替空/满,设备填一块时CPU可处理另一块。忽略M时常近似max(T,C),但题目明确给M且不可忽略时不能擅自删去。双向同时传输还需明确发送与接收缓冲配置,不是任意多一个缓冲就自动支持全双工。
教材双缓冲流水模型:设备在一个区填充时,CPU从另一区搬运并处理;忽略额外争用,具体有限n块仍需画时间线。
把多个等大缓冲区首尾链接,输入指针in指向下一个可填空区,输出指针out指向下一个可取满区;消费后释放,填满后发布。in==out的空/满歧义可用计数、空出一格或标志解决,采用哪种需要明确。
有多个满区时消费者可以逐区取出,生产者也可继续填其他空区;不要求所有满区一并读完。缓冲区状态和指针更新需互斥或满足相应单生产者单消费者的原子条件。
按状态组织空缓冲队列、输入满队列、输出满队列。输入进程从空队列取区,收满后挂输入队列;计算进程从输入队列取数据,使用后归空。计算进程要输出时取空区填充并挂输出队列,输出进程取出送设备后归空。四种工作缓冲角色可以由公用池中的不同区承担。
双缓冲:输入与处理怎样交叠
| 结构 | 范围 | 主要信息 |
|---|---|---|
| 系统设备表SDT | 全系统设备列表 | 设备类型、标识、DCT指针、驱动入口 |
| 设备控制表DCT | 每台物理设备 | 类型、状态、标识、控制器指针、等待队列、重试信息 |
| 控制器控制表COCT | 每控制器 | 控制器状态、通道指针、等待队列 |
| 通道控制表CHCT | 每通道 | 状态、连接控制器信息、等待队列 |
按逻辑映射得到物理设备后,查SDT定位DCT;设备忙则进入设备等待队列,空闲且分配安全才占用;再沿指针申请控制器,最后申请通道。相应层忙就挂到对应队列,三者满足后才能启动所需传送。
实际系统可能异步预约或合并这些步骤,教材分层链的意义是说明共享控制器、共享通道也会造成等待;不能因为有两台不同设备就断定其I/O一定完全并行。
设备分配链:拿到设备后为何还可能等待控制器
静态分配在作业执行前一次获得所需设备、控制器和通道,按模型直到结束再释放,破坏请求和保持可避免这一类设备死锁,但利用率低。动态分配在运行中按需请求、用后释放,提高利用率,却需正确处理持有等待与安全性。
独占设备可静态或动态分配,共享设备通常按请求动态安排;先请求先分配或按优先级分配可决定等待次序,不能仅靠队列顺序就代替死锁避免。
教材安全分配方式让进程在发I/O后等待、不再带着已有设备继续请求其他设备,以限制多设备占有;不安全方式允许继续运行和提出更多设备请求,并发度可能更高但有死锁风险。这里“安全”依赖所限定的持有/请求模型,不是“任何阻塞I/O天然不会死锁”。
逻辑设备表LUT记录逻辑名→物理名→驱动入口。全系统一张表时同名逻辑设备可能冲突,按用户/进程设置表可允许不同用户各自使用相同逻辑名。应用请求逻辑设备后,系统按分配结果建立映射,后续据此调用驱动,方便I/O重定向与设备替换。
SPOOLing以程序模拟早期外围控制机的脱机I/O,将慢速设备操作通过高速磁盘暂存和后台进程组织起来。磁盘上设置输入井、输出井,内存中设置输入/输出缓冲区,并由输入进程、输出进程负责搬运。
输入路径可为输入设备→内存缓冲→输入井,用户需要时从井读入;输出先由用户数据写入输出井,后台在输出设备可用时经输出缓冲送出。井在外存,缓冲区在内存,不能颠倒。
多个进程提交打印请求,各自在输出井拥有待打印文件与队列项,感觉拥有一个逻辑打印机;后台逐个取出交给物理打印机。打印机本体没有变成能同时打印多份页面,而是通过排队与磁盘空间把独占设备虚拟成可共享服务。
用户提交速度不再直接受慢速外设逐字符处理速度束缚,CPU与设备可更好重叠,并实现虚拟设备。并非提高物理打印头的固有速度;后台服务、磁盘空间和队列管理也占资源。
用户进程把作业写入磁盘队列后可继续运行;输出进程再串行驱动物理打印机,形成逻辑上的多用户共享。
SPOOLing依赖外存中的输入/输出井和管理程序;它不等于只在主存里增加一个普通缓冲区,也不是把一台物理设备变成真正同时工作的多台硬件。
机械盘由盘片、盘面、磁道、扇区和磁头等组成;同半径磁道形成柱面。传统等扇区教材模型中内圈记录密度更高,现代盘区记录可改变每轨扇区数,做题按给定几何结构。
低级格式化建立物理扇区的标识、数据与校验等结构;分区规定逻辑卷的起点和范围;高级/逻辑格式化建立文件系统的空闲信息、目录等数据结构。现代分区布局不一定都是传统MBR四项模型,术语与题设一致即可。
文件系统可把若干相邻扇区组成一个簇/分配块,按簇分配文件数据;非空文件长度s、簇大小c且无稀疏等特殊结构时,占⌈s/c⌉簇,末簇有内部浪费。
空文件不必占一个数据簇,仍可能占目录/inode等元数据;稀疏文件也不按逻辑长度全部分配数据块。遇到“0字节也一律占一簇”要先确认是否是题设简化。
| 算法 | 下一步怎么选 | 边界与代价 |
|---|---|---|
| FCFS | 按请求到达顺序 | 公平简单,可能往返很多 |
| SSTF | 选当前距离最近请求 | 局部移动较短,但远端请求可能饥饿 |
| SCAN | 当前方向服务,走到磁盘边界再反向 | 通常无无限拖延,需计到端点的移动 |
| C-SCAN | 只单向服务,到端点后回另一端 | 回程不服务,回程距离是否计入必须看题设 |
| LOOK | 当前方向无待请求即转向 | 不必走到物理端点 |
| C-LOOK | 单向服务,至最远请求后跳到另一端最远请求 | 跳转不服务,仍可能有实际移动代价 |
把所有请求按磁道排序,标起点与方向;按算法写服务序列,并补SCAN/C-SCAN所需物理端点;相邻位置差取绝对值相加;平均寻道长度=总移动/请求数。仅比较访问序列不够,遗漏“无请求端点”会把SCAN误算成LOOK。
若所有请求已给定,可静态排序;若运行中继续到达,必须按到达时间更新候选。SSTF的饥饿来自近处持续新请求,不能只凭有限静态列表断言某请求永远无法处理。
SSTF磁盘调度:每次重新找最近请求
寻道是磁头移动到目标磁道,可按启动时间+每跨道时间×距离估计;旋转等待使目标扇区来到磁头下;传输是在扇区经过时搬数据。磁盘调度主要优化寻道及请求次序,并不改变硬件固有转速。
另有控制器、排队或命令开销时按题设加上;不能把这些默认包含在转速公式里。
r以转/秒,N为每轨字节数,b为连续传送字节数,采用给定几何与连续传输模型。
7200转/分=120转/秒,一圈约8.333ms,平均半圈约4.167ms。若每轨64KB,连续传4KB占1/16圈,传输约0.521ms;再加题设寻道和其他开销。若已知当前扇区或请求位置,应算实际旋转角度,不直接套平均半圈。
磁盘访问时间:7200转每分钟怎样换成平均半圈
SSD由闪存芯片及控制逻辑/闪存翻译层FTL等组成,FTL把逻辑块地址映射到物理闪存页。闪存按页读写、按更大的擦除块擦除;已编程页不能像DRAM那样直接原位任意改写,需遵守先擦除与编程限制。
更新数据通常写到空闲已擦除页并修改映射,旧页失效;垃圾回收把仍有效页复制到其他位置,再整块擦除。复制和擦除使实际闪存写入量可能大于主机请求量,即写放大。
无活动机械部件,随机访问通常比机械盘快,抗震、噪声和功耗有优势;但随机写、回收、可用空闲块和工作负载仍影响延迟,不能说所有访问时间都完全相同。
动态磨损均衡优先在较少擦写的空闲块写入新数据;静态磨损均衡还迁移长期不变的冷数据,让磨损较低的块有机会承担新写入、较旧块保存冷数据,尽量均衡擦除次数并延长寿命。
SSD不需要机械磁头寻道,因此不能照搬磁道距离最短就是最快的判断。操作系统仍需I/O排队、请求合并、公平与延迟管理,但其优化目标和机械SCAN模型不完全相同。
计算机网络由互联的、自治的计算机组成:互联要求能够交换信息;自治意味着每台计算机仍能独立完成自身功能。网络可抽象为结点和连接结点的链路。共享硬件、软件和数据资源需要各系统遵守共同的协议。
“主机之间通信”是简写,实际交换数据的是不同主机上的进程。不要把网络应用限定为机器本身的通信。
| 划分依据 | 组成与职责 |
|---|---|
| 工作方式 | 边缘部分由主机及其应用组成,供用户通信与共享资源;核心部分由网络及连接网络的路由器组成,向边缘提供连通和分组交换。 |
| 组成部分 | 硬件包括主机、链路、交换设备、网卡等;软件实现网络服务和使用工具;协议约定交换数据的规则。 |
| 功能组成 | 通信子网提供数据传输、交换和控制;资源子网提供可共享的计算、存储、数据与软件资源。 |
| 分类依据 | 类别 | 辨析 |
|---|---|---|
| 地理范围 | PAN、LAN、MAN、WAN | 个人网范围最小;局域网覆盖局部场所;城域网通常覆盖城市;广域网连接更大区域。距离是典型尺度,不能替代技术与管理边界。 |
| 使用者 | 公用网、专用网 | 公用网对符合条件的公众提供服务;专用网面向特定单位或业务。 |
| 传输技术 | 广播式、点对点式 | 广播式共享通信信道;点对点式每条链路连接一对结点,可经中间结点转发。 |
| 传输介质/交换 | 有线、无线;电路、报文、分组交换 | 不要将交换技术和拓扑形状混为同一分类。 |
| 拓扑 | 优点 | 局限 |
|---|---|---|
| 总线形 | 布线较省,增减结点相对方便 | 共享介质,重负载性能下降,总线故障影响较大。 |
| 星形 | 便于集中管理和定位故障 | 依赖中心设备,布线和中心设备有成本。 |
| 环形 | 结点按环连接,可按规定次序传递访问权 | 链路或结点故障可能影响环的工作,需保护措施。 |
| 网状形 | 路径冗余多,可靠性较高 | 线路成本和路由控制复杂度较高。 |
传统共享式局域网常用广播技术;现代交换式以太网的主机到交换机链路通常为点对点、全双工链路。局域网不等于永远只有一条共享总线。
| 术语 | 含义 | 常用单位 |
|---|---|---|
| 速率 | 单位时间传送的数据量;标称速率并不保证实际应用达到 | bit/s、b/s |
| 带宽 | 频域表示频带宽度;网络时域语境表示信道可提供的最高数据率 | Hz;或 bit/s |
| 吞吐量 | 单位时间实际通过信道、接口或网络的数据量 | bit/s、B/s、帧/s |
端到端路径经过多个接口和链路时,持续吞吐量受其中最慢部分限制,还受竞争、协议首部、丢包与处理能力影响。不能把各段带宽相加,也不能把标称带宽直接视为有效载荷速率。
各段串联传输且没有其他瓶颈时,上限由最小速率决定。
网络速率的 k、M、G 通常按十进制;1 B=8 bit。传输 1 MB 数据不能直接用“1÷100 Mb/s”,应先统一成 bit,再按题设确定 MB 是十进制还是二进制。
| 类型 | 决定因素 | 容易混淆的地方 |
|---|---|---|
| 发送时延 | 数据长度 L 与发送速率 R | 从首比特发出到末比特发出所需时间,与传播距离无直接关系。 |
| 传播时延 | 介质长度 d 与传播速度 v | 首比特沿链路传播的时间,不由数据帧长度决定。 |
| 处理时延 | 检错、解析首部、查表等处理 | 题设可忽略,不应自行加入固定数值。 |
| 排队时延 | 队列长度、竞争与业务负载 | 随机变化,拥塞时会明显增大。 |
总时延按实际经过的链路和结点,将发送、传播、处理、排队时延相加。
单向时延带宽积表示传播管道中可容纳的比特数。
RTT 是往返时间,不能不加区分地用单程传播时延替代。具体是否包含确认发送、处理和排队时间,以题设 RTT 定义为准。高速长距离链路即使发送一个小帧很快,传播等待仍可能很长。
信道利用率衡量信道被使用的时间比例;网络利用率可看作各信道利用情况的综合。接近饱和时排队时延急剧增加。简单排队模型中可写 D=D₀/(1−U),但该式有模型前提,不能作为所有实际网络的普遍精确公式。
L用bit、R用bit/s,传播距离d与速率v须使用相容单位;传播时延与报文长度无直接关系。
存储转发多跳时还要算每跳发送、传播、排队与处理;分组可流水重叠,不能把一整个报文的发送时间对所有分组和链路重复累加。
协议是水平关系:不同系统的对等实体遵守的规则。协议包含语法、语义和同步:分别说明信息格式、动作及响应含义、事件时序。
服务是垂直关系:某层借助自身和下层的功能,向直接上层提供能力;服务通过接口和服务访问点使用。接口描述相邻层怎样交互,协议描述对等层怎样合作。
上层交下来的 PDU 到本层成为 SDU;本层加入协议控制信息 PCI 后构成本层 PDU,再交给下层。发送方向下封装,接收方向上拆封;链路层通常还有帧尾,不能把所有层都画成只有首部。
这里的“+”表示组成关系,不一定是简单连续首部拼接。
应用内容相当于信件正文,进程端点信息相当于部门收件信息,主机寻址相当于外层地址。每个层次处理自己负责的控制信息,不需要理解所有上层内容。类比只帮助理解封装,不能据此推导网络层必然可靠。
发送到接收:数据怎样逐层封装
| 维度 | 一种服务 | 另一种服务 |
|---|---|---|
| 连接 | 面向连接:先建立逻辑连接,传送数据后释放 | 无连接:无需预建连接,各数据单元独立提交。 |
| 应答 | 有应答:接收方给出相应确认 | 无应答:本层不自动提供接收确认。 |
| 可靠性 | 可靠:利用检错、重传、排序等机制满足交付约定 | 不可靠:尽力交付,不保证正确、完整或按序到达。 |
面向连接并不自动等于可靠,无连接也并不表示应用不能实现可靠性。TCP 以不可靠 IP 服务为基础实现可靠字节流;UDP 提供无连接数据报服务,应用可以在 UDP 之上增加自己的确认机制。
请求由服务用户交给服务提供者;指示由提供者通知用户发生事件;响应是用户对指示的回答;证实是提供者对先前请求的确认。应沿着“谁向谁发出”理解,不能仅凭中文词义猜方向。
| OSI 七层 | 五层教学模型 | 主要数据单位与任务 |
|---|---|---|
| 应用、表示、会话 | 应用层 | 应用报文;应用规则,数据表示转换、加密/压缩和会话管理在 OSI 中另分层。 |
| 运输层 | 运输层 | TCP 报文段、UDP 用户数据报;进程到进程通信、复用分用。 |
| 网络层 | 网络层 | 分组/IP 数据报;逻辑寻址、路由与转发。 |
| 数据链路层 | 数据链路层 | 帧;成帧、差错检测、介质访问,部分协议含可靠传输。 |
| 物理层 | 物理层 | 比特;接口特性、编码、比特同步与信号传输。 |
TCP/IP 四层由应用层、运输层、网际层、网络接口层组成。网络接口层对应教学模型的数据链路层和物理层;应用层覆盖 OSI 的应用、表示与会话层功能。两种体系均分层并支持异构互联,但分层边界、服务设计和历史形成方式并不相同。
参考模型描述“本层可能承担什么”,不等于每个具体协议都实现全部功能。IP 不提供端到端可靠交付和流量控制;以太网 FCS 检错也不等于链路层必然重传。主机通常实现完整协议栈,路由器主要工作到网络层,交换机主要工作到链路层。
数据是承载信息的符号序列;信号是数据的电气或电磁表现。模拟信号的相关参数连续变化,数字信号取离散状态。码元是在一个固定时间间隔内用来表示某种状态的基本波形,一个码元可以携带一个或多个比特。
信源产生数据,发送器编码或调制后送入信道;接收器恢复可处理的数据,再交给信宿。信道强调某方向的信息传输,一条通信线路可含发送与接收信道。
M 是等概率离散状态数;码元率 R_s 单位 Baud,比特率 R_b 单位 bit/s。二进制码元时二者数值相同,多进制时不同。
单工只能一个方向传送;半双工可双向但不能同时;全双工可同时双向传送。通信方式与链路速率、物理线数没有简单的一一对应。同步传输按共同节拍组织,异步传输通常通过字符起止位实现字符边界同步。
奈奎斯特公式:理想无噪声、带宽受限信道;W 以 Hz 为单位,M 是码元可区分状态数。
香农公式:有高斯白噪声信道的理论容量;S/N 必须是功率比的线性值。
给出 dB 时,先做 S/N=10^(dB/10),不能直接把 dB 数值代入容量式。
奈氏上限说明不能无限提高码元率;增大 M 可提高每码元携带的信息,但状态越密越难区分。香农容量同时受带宽和信噪比约束;只要考虑噪声,增加状态数也不能突破容量上限。若题目同时给出 M、W 和 S/N,应分别计算约束并取较小上限。
若 W=3 kHz、M=4、S/N=1000,则奈氏上限为 2×3000×2=12000 bit/s;香农上限为 3000×log₂1001,约 29.9 kbit/s。受两种条件同时约束时,不能超过 12 kbit/s。
码元与噪声:两条上限怎样同时检查
| 编码 | 规则示例 | 特点 |
|---|---|---|
| 不归零 NRZ | 用两种持续电平表示 0/1 | 连续相同比特可能长时间无跳变,不便提取时钟。 |
| 归零 RZ | 码元内部回到零电平 | 提供一定跳变,所需带宽与波形约定有关。 |
| 反向不归零 NRZI | 讲义约定 0 触发翻转、1 不翻转 | 应明确题设规则和初始电平,不能反套另一种约定。 |
| 曼彻斯特 | 每个比特中间必有一次跳变;跳变方向区分 0/1 | 自同步,通常一个数据比特对应两个半位信号单元。 |
| 差分曼彻斯特 | 位中间跳变用于同步;位开始是否跳变表示数据 | 绝对电平不重要,首位仍须按题设初始状态读图。 |
曼彻斯特“向上为 0”或“向下为 0”存在不同约定,讲义示例使用位中心向上表示 0、向下表示 1。解题必须先读题图和编码约定;不能仅凭一个边沿就断定数据。
ASK 用幅度表示离散状态;FSK 用频率;PSK 用相位;QAM 联合改变幅度和相位。若调制可稳定区分 M 种状态,每码元可表示 log₂M bit;提高阶数会提高识别精度与信噪比要求。
两种编码每个码元位中间都有跳变,接收方可据此恢复定时。曼彻斯特看位中间跳变方向;差分曼彻斯特看位边界是否发生跳变。
图给出一种常见约定,不同标准或题设可能把0和1的定义反过来。不要只记某个跳变图而不读编码规则。
曼彻斯特波形:把1010画成电平跳变
数字数据可经数字编码成为数字信号,也可经调制成为模拟信号;模拟数据可经 PCM 成为数字信号,也可经模拟调制搬到适合传输的频带。基带信号直接承载原始信息;带通信号是调制后搬移到某一通带的信号。
若采样频率为 8 kHz,每次量化编码 8 bit,则一路 PCM 速率为 64 kbit/s。这里的“两倍”约束采样频率,不是把最终码率直接设置为模拟最高频率的两倍。
PCM:采样率、量化位数与输出码率怎样联系
| 技术 | 过程 | 优点与代价 |
|---|---|---|
| 电路交换 | 建立连接→传送→释放;通信期间预留端到端资源 | 建立后时延稳定、顺序自然保持;建立有开销,空闲时资源仍被占用,不适合突发业务。 |
| 报文交换 | 每一跳完整接收整个报文后再转发 | 无需预建电路,可动态利用线路;报文大时结点缓存需求和等待时间大。 |
| 分组交换 | 把报文拆成带控制信息的小分组,逐个存储转发 | 后继分组的接收可与前驱分组的转发重叠,缓存更易管理;新增首部开销,可能乱序、丢失、重复。 |
分组交换不一定都是无连接:它还包括虚电路方式。存储转发要求接收当前分组的全部比特后才能转发该分组,而不是等整个原始文件都到齐。分组大小不必在一切网络中完全固定。
同速率、无排队、n 个等长分组经过 k 条链路时的流水时间;每组长度 L 含实际发送首部,单链路传播时延为 τ。不同速率或变长末包须画时间线。
最后一个分组到达时,发送端已连续发送 n 个分组,后续 k−1 段各补一个分组发送时间,再加各段传播。若题设给的是路由器数量 r,链路数量通常是 r+1,应先画路径再代式。
分组存储转发:3包通过2条链路为什么只用4ms
每个分组携带独立转发所需的地址信息,中间结点逐个查转发表。不同分组可以走不同路径,因而到达时延和顺序不一定相同;发生故障后可调整路径。数据报网络不要求交换结点为每次通信预建一条连接状态。
通信前通过呼叫请求与应答建立逻辑路径,中间结点建立虚电路表;传送阶段依据虚电路标识转发;结束后释放相应状态。虚电路号通常是逐链路有效,沿途可转换。
同一物理链路可同时承载多个虚电路,因此“虚电路”不能理解成电路交换那样独占的物理通路。固定路径有利于按序传送,但具体可靠性仍取决于协议机制。
| 项目 | 数据报 | 虚电路 |
|---|---|---|
| 建立连接 | 无需事先建立 | 需要建立与释放 |
| 分组控制信息 | 通常带完整目的地址等信息 | 传送阶段主要带虚电路标识 |
| 路径与结点状态 | 各包独立选择;无需为每个连接保留虚电路表 | 同一虚电路走已选路径;结点维护连接状态 |
| 故障影响 | 可利用其他路径继续转发 | 路径故障可能要求重新建立虚电路 |
| 介质 | 构造与特点 | 注意 |
|---|---|---|
| 双绞线 | 绝缘铜线成对绞合以减轻串扰;屏蔽型另加屏蔽层 | 衰减与频率、长度等有关;模拟信号可用放大器,数字信号用中继器整形再生。 |
| 同轴电缆 | 内导体、绝缘、外屏蔽与保护层;抗干扰较强 | 传统 50 Ω 基带与 75 Ω 宽带用途属于典型分类,不要仅由外观判断速率。 |
| 光纤 | 纤芯与包层形成光波导;容量大、损耗小、抗电磁干扰 | 多模与单模的传播模式、色散不同;光纤不是中空管。 |
无线电波、微波、红外与激光采用自由空间传输。低频无线电通常较易绕射、覆盖范围广;微波和光通信方向性强,常要求视距条件。地球同步卫星作为微波中继覆盖广,但距离造成较长传播时延;短波可利用电离层传播,质量受环境影响。
| 特性 | 回答的问题 |
|---|---|
| 机械 | 连接器的形状、尺寸、引脚与排列是什么? |
| 电气 | 各线路的电压、电流和信号范围是什么? |
| 功能 | 某条线或某电平代表什么意义? |
| 过程 | 各信号的动作顺序和时序怎样协调? |
中继器恢复数字信号的电平、波形与时序后转发,不只是同时放大信号和噪声。它工作在物理层,不解析 MAC 地址,不检查 FCS,不执行路由选择。连接两侧须满足相容的物理层与速率约定。
集线器可视为多端口中继器:物理外观是星形,逻辑上仍共享介质。收到信号会向其他端口转发,连接主机共享一个冲突域;半双工共享以太网使用 CSMA/CD,同一时刻不能让多个站成功占用同一共享信道。
旧式 10 Mb/s 以太网的 5-4-3 规则限定中继段数与可挂主机段数,属于特定模型,不能推广到现代交换式以太网。
通信中的同步关注收发时钟和数据边界,不能直接套用程序设计中“调用是否等待”的同步/异步定义。数字基带传输直接在介质上传数字波形;频带传输经过调制;宽带复用可让多个子信道同时承载不同信号。
链路是两个相邻结点间的物理通路;数据链路还包括控制数据传输的协议和设备能力。链路层在物理层之上把比特流组织成帧,识别边界,检测差错,并按协议提供介质访问、流量控制或链路管理。
无确认无连接服务不预建连接、不逐帧确认,如常见以太网;有确认无连接服务可在不预建连接的情况下确认帧;有确认面向连接服务先建立逻辑链路再有序可靠传送。是否允许流水发送由窗口机制决定,面向连接不等于只能逐帧停等。
任意上层数据都应能被承载;如果数据内容恰好与帧定界符相同,就需要转义、填充或特殊编码,避免误判为边界。MTU 限制帧载荷能装的最大网络层分组,而不是把所有首尾字段一并计入。
| 方法 | 发送/接收规则 | 优缺点 |
|---|---|---|
| 字符计数 | 帧头给出帧内字符数量,按协议约定是否含计数字段本身 | 计数字段出错可能导致连续失去帧边界。 |
| 字节填充 | 用标志字节定界;数据中的标志和转义字符用转义序列替换,接收端逆变换 | 适合面向字节的链路,字符集与转义规则要明确。 |
| 零比特填充 | 规定区域每连续五个 1 后插入一个 0,接收端去掉该 0 | 配合 01111110 标志,处理连续比特序列,不按字符重置。 |
| 违规编码 | 使用正常数据编码中不会出现的信号组合定界 | 需要底层编码本身有冗余状态,不适用于所有编码。 |
异步 PPP 中 0x7E 变为 0x7D 0x5E,0x7D 变为 0x7D 0x5D;对需转义的控制字符加入转义并作规定变换。接收方遇到转义符后恢复原字节,不能再把恢复出的 0x7E 当成新帧标志。
发送时扫描帧内容,连续读到五个 1 后立刻添 0;填入的 0 不属于原数据。接收时遇到对应的五个 1 后删去填充 0。遇到数据中的原始 0 会打断连续 1 的计数;标志本身不进行同样的填充。
零比特填充:第五个1之后为什么插0
比特差错是 0/1 翻转;更高层次还可能出现丢失、重复和乱序。误码率 BER 是错误比特数与总传输比特数之比。ARQ 通过差错检测、确认和重传恢复;FEC 通过冗余编码在接收端直接纠错,能力受码距约束。
仅丢弃出错帧可减少向上交付的错误数据,但没有恢复丢失、抑制重复和按序交付等机制,仍不是可靠传输。讲义所说“无差错接收”是协议层面的理想化表述;CRC 存在漏检可能,不能宣称接受的帧绝对无比特错误。
CRC模2长除:1101000除以1011的每次异或
发送窗口包含已发未确认以及允许发送但尚未发送的序号;可用窗口只是其中未发部分。发送一帧后,可用额度减少,但固定宽度滑动窗口并不因此整体变窄。接收窗口表示当前可以接受的序号范围,是否缓存乱序帧由协议决定。
发送缓存需保留未确认副本以备重传,收到有效确认后才能释放;接收缓存可保存按序但应用未读数据,以及协议允许缓存的乱序数据。
累计确认表示某序号之前的连续数据均已正确收到;确认号究竟指“最后收到”还是“下一期望”,要看协议定义。捎带确认是把确认字段放进反向数据帧,与是否累计确认是两个不同维度。
| 协议 | 发送窗口 | 接收窗口 | 重传范围 |
|---|---|---|---|
| 停等 | 1 | 1 | 当前未确认帧 |
| GBN | 可大于 1 | 1 | 从最早未确认帧起的所有已发未确认帧 |
| SR | 可大于 1 | 可大于 1 | 单独出错或超时的帧 |
序号循环:窗口太大会把旧帧当成新帧吗
发送方发送一帧后保留副本并启动定时器,收到正确确认才发送下一帧。接收端检查差错:错误帧丢弃;未按时获得有效确认时,发送端重发。即使物理信道无误码,接收端处理速度仍可能比发送端慢,停等也承担流量约束。
数据帧损坏或丢失:接收端未交付该帧,发送方超时后重传。确认损坏或丢失:接收端已交付,发送方仍会重传;接收端应通过序号识别重复帧,丢弃重复数据但再次确认,不能再交给上层一次。
交替比特协议用 0、1 两个序号即可区分当前帧和最近重复帧。定时器需要足以容纳正常往返,过短会引起不必要重传。
此处 RTT 表示双向传播与题设纳入的其他等待,T_D 是数据帧发送时延、T_A 是确认发送时延;若题设 RTT 已包含某项,不可重复计入。
停止等待:ACK丢失后如何避免重复交付
仅正确且正好按序到达的帧才被接受并交付;其余帧丢弃,并重复确认最近按序接收的信息。不缓存后续失序帧,因此丢失一个早期帧可能导致后续已到达帧也需要重传。
m 位序号共有 2^m 个取值;留出一个序号,避免确认全部丢失后旧帧被误判为新一轮帧。
已发 0~7,收到对 0、2、3 的累计确认后,0~3 都已确认;超时需重发 4、5、6、7,共 4 帧。若题设 ACK 表示下一期望序号,必须先换成相同语义再计算。
例:帧1丢失后,GBN从最早未确认帧重传
正确且处于接收窗口内的帧,无论是否按序都单独确认;失序帧先缓存,待左边界缺口补齐后,将连续数据按序交付并移动窗口。对仍可能由确认丢失引起的旧重复帧,应按协议范围再次发送 ACK,避免发送方一直超时。
一般滑动窗口约束;常见 SR 取相同发送、接收窗口。
等窗口时各不能超过序号空间一半,避免新窗口与旧帧序号重叠。
序号 0~7 若窗口宽 5,接收完 0~4 后新窗口含 5、6、7、0、1。若 0~4 的确认全部丢失,发送端重发旧 0,接收端会误认作新 0。因此等窗口最多为 4。
选择重传SR:乱序缓存后一次交付
W 为最大发送帧数;忽略出错、处理与排队,并明确 RTT 的组成。
为避免连续发送中途停顿,发送窗口需覆盖从首帧发出到确认可用的整个时间。
用“窗口帧数×每帧大小”估算允许的在途数据时,应分清大小是否包括首部以及确认是否累计。求出的理想窗口还须受 GBN/SR 序号范围、实际缓存和接收能力约束,不能只满足带宽时延积就结束。
若填满管道需要 W=9,GBN 要满足 2^m−1≥9,至少 m=4;等窗口 SR 要满足 2^(m−1)≥9,至少 m=5。SR 重传更精细,但相同序号位数允许的等窗口通常更小。
流水窗口下界:为什么本例至少要连续发19帧
| 方式 | 划分资源 | 特征 |
|---|---|---|
| FDM | 不同频带 | 用户同时占不同频带,通常需要保护间隔;带宽此时是 Hz。 |
| TDM | 周期性时隙 | 各用户在不同时隙使用同一频带;没有数据的固定时隙可能空闲。 |
| STDM | 按需分配时隙 | 仅为有数据的终端安排资源,需携带标识及控制信息。 |
| WDM | 不同光波长 | 光纤中的频分复用;不同波长光可同时传输。 |
| CDM/CDMA | 不同码片序列 | 同时间同频带,通过码序列区分用户。 |
把码片 0/1 映射到 −1/+1,各站码片向量相互正交且与自身归一化内积为 1。发送比特 1 用本码片,发送 0 用其反码,不发用零向量。接收叠加信号后与某站码片归一化求内积:结果 +1、−1、0 分别代表该站发 1、发 0、未发。
理想同步且功率条件满足的正交码模型;不要把复用后的和直接当成二进制字符串。
CDMA:同一收到的码片序列怎样恢复两路比特
纯 ALOHA 有数据就发,未获得成功确认则随机等待后重传;一帧发送前后各一个帧时长内的其他发送都可能造成重叠。时隙 ALOHA 将时间同步划为等长时隙,只能在时隙开始发送,使冲突危险期缩短。理想泊松模型下最大吞吐量分别为 1/(2e) 与 1/e,约 0.184、0.368。
| 策略 | 空闲时 | 忙时 |
|---|---|---|
| 1-坚持 | 立即发送 | 持续监听,空闲即发送;多个等待站可能同时发。 |
| 非坚持 | 立即发送 | 随机等待后再监听;减少碰撞但可能浪费空闲时间。 |
| p-坚持 | 时隙系统中以概率 p 发送,以 1−p 推到下时隙 | 等待忙期结束后继续按策略竞争。 |
载波监听只描述本地此刻看到的信道状态,远端刚发出的信号可能还未到达。因此 CSMA 降低碰撞概率,但不会自动消除碰撞。随机接入与固定时分、频分复用的资源安排不同。
发送前监听:忙则按 1-坚持方式继续监听;空闲则发。发送过程中继续检测碰撞;若发生碰撞,停止正常发送,发送强化碰撞信号,再随机退避后重新竞争。强化信号用于使其他站明确碰撞,不是“通知网络拥塞”的拥塞控制报文。
τ 是总线两端单程最大传播时延;发送端必须保持发送直到最坏碰撞信号返回。
经典以太网 MAC 帧最短 64 B,从目的 MAC 到 FCS 计,不含前导同步字段;不足载荷用填充补齐。100 Mb/s 等模型中的最小帧长和网络跨度须结合标准及题设。全双工交换链路不使用 CSMA/CD,不再依靠争用期来限制链路跨度。
CSMA/CD时空图:争用期为何是2τ
设第 n 次重传采用 k=min(n,10),从 0~2^k−1 中等概率选择整数 r,退避 r 个基本退避单位。经典以太网基本单位为争用期 2τ;达到规定的尝试上限仍失败则停止并报告。
题设可能把“重传次数”与“总尝试次数”分别计数,先确定 n 从哪里开始。
第一次重传 k=1,r∈{0,1};第二次 k=2,r∈{0,1,2,3}。所以可以随机选择零等待,不是第 n 次一定等待 2^n 个时隙。
令 a=τ/T_D;在理想无碰撞、单站连续占用模型下,一帧占用 T_D+τ,极限有效利用率为 1/(1+a)。真实共享网络还会受竞争和碰撞开销影响。缩短介质、适当增大帧载荷可减小 a;但不能为提高效率忽略 MTU 或无限增大帧。
二进制指数退避:扩大的是随机范围,不是固定等待
典型 MAC 地址长 48 bit,以 6 个字节表示;网卡在主机与网络间完成串并转换、缓存、编码解码、帧封装/拆封及介质访问等,涉及物理层和链路层。硬件可完成大量收发工作,但软件驱动、DMA 和中断等仍与主机协作。
MAC 地址既可以由厂商分配,也存在本地管理和软件修改;“任何网卡地址永远不能改变”不是一般结论。接收时可按单播、多播、广播或混杂模式筛选。
| 字段 | 长度 | 说明 |
|---|---|---|
| 目的/源地址 | 各 6 B | 链路上的目的与源 MAC。 |
| 类型 | 2 B | 标识上层协议,如 IPv4、ARP。 |
| 数据与填充 | 46~1500 B | 普通以太网的载荷范围;不足最小值需填充。 |
| FCS | 4 B | CRC-32,覆盖目的地址到数据/填充末尾。 |
| 前同步码+帧开始定界符 | 7 B+1 B | 物理同步使用,通常不计入 64~1518 B MAC 帧长。 |
以太网不对每帧编号和确认,FCS 出错帧通常直接丢弃;上层若需要可靠性自行恢复。10BASE-T 中 10 表示典型数据率 10 Mb/s,BASE 表示基带,T 表示双绞线;并非所有现代 BASE 以太网都采用曼彻斯特编码。
以太网最短帧:64B中哪些字段占了多少字节
站点监听信道,满足规定的 DIFS 空闲时间并按退避规则竞争;退避计数仅在信道空闲的时隙递减,信道变忙时冻结,重新空闲并等待规定间隔后恢复。倒计时到零才发送。接收端成功收到单播数据后,通常在 SIFS 后发 ACK;未获确认则按重试规则处理。
SIFS 最短,用于 ACK、CTS 等同一次交换中的快速响应;PIFS 位于 SIFS 与 DIFS 之间,服务特定协调方式;DIFS 用于普通分布式竞争。因为等待更短,响应帧可以先于新竞争数据获得介质。
听不到对方的两个发送站仍可能在共同接收端发生碰撞。可选 RTS/CTS 在发送数据前预约介质,其他站通过持续时间字段设置 NAV,进行虚拟载波监听。RTS/CTS 增加开销,不能消除所有碰撞;不能因用了 CA 就认定“永远无碰撞”。
普通竞争访问典型先等待DIFS并随机退避,之后可以直接发DATA,也可采用RTS/CTS保护。CTS、ACK等响应采用更短的SIFS。
RTS/CTS能缓解隐藏站问题,但不是所有802.11数据帧发送前都必须有RTS/CTS;无线无法简单等同共享以太网的边发边检测碰撞。
数据帧传送上层数据;控制帧辅助介质访问和确认,如 RTS、CTS、ACK;管理帧用于发现、加入、认证、关联、离开和漫游等。基本数据帧包含 MAC 首部、帧主体与 FCS,首部长度及字段随模式而异,不能把一个示例长度强行用于所有扩展格式。
| To DS | From DS | 地址1 | 地址2 | 地址3 | 地址4 |
|---|---|---|---|---|---|
| 0 | 0 | 目的/接收站 | 源/发送站 | BSSID | 通常不用 |
| 1 | 0 | 接收 AP(BSSID) | 发送/源站 | 最终目的站 | 不用 |
| 0 | 1 | 接收/目的站 | 发送 AP(BSSID) | 原始源站 | 不用 |
| 1 | 1 | 当前接收端 | 当前发送端 | 最终目的站 | 原始源站 |
A→AP:地址1=AP,地址2=A,地址3=B;AP→B:地址1=B,地址2=AP,地址3=A。若 AP 桥接到有线以太网,转成以太网帧后源/目的仍分别是原始端系统,不把 AP 的 MAC 无条件塞成源地址。
A经AP发送给B:无线帧里的三个地址怎样变化
先用源 MAC 与入端口更新转发表,条目会老化。再查目的 MAC:若目的端口已知且与入端口不同,定向转发;若目的仍在入端口侧,过滤;目的未知则在同 VLAN 的其他端口泛洪。广播帧在同广播域扩散,多播按设备支持的规则处理。
交换机是多端口网桥,工作到链路层,按 MAC 转发;集线器在物理层重复信号。交换机端口划分冲突域,但默认同 VLAN 仍属一个广播域。全双工端口没有 CSMA/CD 意义上的碰撞;不能把“每端口独立冲突域”误解为全双工链路仍在运行碰撞检测。
直通方式读取目的地址后尽早转发,时延低但可能转发错误帧;存储转发方式完整缓存并检查帧,再转发。交换环路会导致广播风暴和 MAC 表抖动,可用生成树协议保持无环逻辑拓扑。
交换机自学习:从泛洪到定向转发
VLAN 将交换网络划成逻辑广播域,可跨多台交换机,与主机的地理位置不必一致。同 VLAN 可通过二层交换通信;不同 VLAN 即使接在同一交换机,也通常要经路由器或三层交换功能互通。
标签插在源 MAC 与原类型字段之间,共 4 B;TPID 常为 0x8100,其后包括优先级、丢弃资格和 12 bit VID 等控制信息。VID 的 0 和 4095 有保留用途,不能直接宣称 4096 个值全部可作普通 VLAN。普通带标签 MAC 帧最大长度由 1518 B 增为 1522 B(按该基本格式计)。
终端接入口通常收发无标签帧,交换机内部依据端口归属确定 VLAN;承载多个 VLAN 的交换机间干道通常保留标签。帧到达目标终端的接入口前按配置去标签。是否存在本征 VLAN 无标签传送应看具体端口配置,不能说所有交换机间帧都必有标签。
VLAN广播:交换机连在一起为何不一定都收到
PPP 是面向字节的点到点链路协议:封装规则把多种网络层分组放到串行链路;LCP 建立、配置、测试和终止链路;一组 NCP 分别协商不同网络层协议参数。PPP 支持差错检测、透明传输、链路状态检测、MTU 与网络层地址等配置。
| 字段 | 典型值/长度 | 作用 |
|---|---|---|
| Flag | 0x7E,1 B | 标记帧边界,连续空标志不构成有效数据帧。 |
| Address/Control | 0xFF / 0x03,各 1 B | 基本格式中的固定字段,不用于普通以太网式的源/目的 MAC 寻址。 |
| Protocol | 通常 2 B | 0x0021 为 IPv4,0xC021 为 LCP,0x8021 为 IPCP。 |
| 信息 | 按协商 MTU,默认典型最大 1500 B | 无需以太网式的 46 B 最小载荷。 |
| FCS 与结束 Flag | 基本格式 FCS 通常 2 B | 检错后错误帧丢弃;可存在协商扩展,按题设格式计。 |
PPP 本身不提供逐帧可靠重传,不使用 CSMA/CD,没有以太网争用期规定的最短帧长。异步链路采用字节转义,面向比特的同步链路可采用零比特填充。点到点只有唯一对端,网络层下一跳已经由出接口确定时,无需 ARP 查找该 PPP 对端的 MAC 地址。
PPP字节填充:7E与7D分别怎样转义
LCP、认证报文、NCP 和用户 IP 数据都可由 PPP 承载,但 Protocol 字段不同。某个网络层尚未配置完成,不应仅因物理线路通了就认定它的数据已经可以正常传输。
不同网络可采用不同介质、编码、帧格式和接入协议;通过统一的网络层编址与转发机制形成逻辑互联网络,使主机不必理解沿途每种链路细节。物理中继器和二层网桥通常只是扩大同一网络,网络层互联依靠路由器等设备。
| 项目 | 路由选择 | 分组转发 |
|---|---|---|
| 问题 | 到目标应选哪条路径? | 当前分组从哪个端口发出? |
| 范围 | 由路由算法及协议维护网络可达性与路径 | 当前路由器依据转发表逐包处理 |
| 主要状态 | 路由表侧重路径与度量的维护 | 转发表侧重快速匹配、下一跳与出接口 |
输入端口恢复比特、拆帧并提取 IP 分组;交换结构将输入分组送到相应输出端口;输出端口排队并封装新的链路帧。交换结构可基于存储器、总线或互联网络。路由协议报文要交控制处理逻辑,普通过境数据主要沿转发路径处理。
没有 NAT、隧道等特殊处理时,源/目的 IP 通常端到端保持,TTL 每过一个路由器减少,IPv4 首部校验和相应更新。MAC 地址逐链路重新封装,二层交换机在同一链路网络转发时通常不改原帧源/目的 MAC。
传统路由器通常同时承担路由计算和逐包转发。SDN 将控制逻辑与数据转发分离:控制器掌握网络状态,按策略生成或下发转发规则,交换/路由设备按规则高速处理数据。网络应用通过可编程接口提出策略需求。
| 接口 | 连接对象 | 作用 |
|---|---|---|
| 北向 | 控制器与网络应用 | 给应用提供网络抽象、状态和策略编程能力。 |
| 南向 | 控制器与转发设备 | 下发规则、读取状态;OpenFlow 是一种方案,不是唯一方案。 |
| 东西向 | 多个控制器之间 | 协调状态和控制,增强扩展性及可靠性。 |
全局视角利于统一优化、自动配置与开放接口,设备转发和软件策略可独立演进;代价是控制器负载、状态一致性、故障与安全防护需要专门设计。逻辑集中不意味着必须用一台物理服务器,也不意味着每一个分组都先送控制器计算路径。
流量控制保护接收端,协调发送速率与接收缓存/应用读取能力;拥塞控制保护网络,避免对链路、交换设备、缓存和处理资源的总需求过大。拥塞可能导致排队时间增加、丢包和重传,严重时增加负载反而使有效吞吐下降。
开环方法在设计和准入阶段预防拥塞,如资源预留、流量整形和调度策略;闭环方法监测拥塞,把反馈传给可采取行动的结点,再调节运行。两者强调是否利用运行反馈,并不是“开环系统永远不能修改任何配置”。
增加缓存可暂缓溢出却可能加大时延;增加链路速率也可能把瓶颈推到后续处理器或链路。丢包是拥塞信号之一,也可能来自误码等原因,不能把“所有丢包都等于拥塞”作为普遍结论。
| 字段 | 位数 | 解释 |
|---|---|---|
| 版本/IHL | 4 / 4 | IPv4 版本为 4;IHL 按 4 B 计,常见 5 即 20 B,最大 15 即 60 B。 |
| 区分服务 | 8 | 用于服务分类及相关标记,具体位义按协议约定。 |
| 总长度 | 16 | 整个 IPv4 数据报的字节数,含首部,最大 65535 B。 |
| 标识 | 16 | 分片重组关联所需信息;同一原数据报各片保持同一标识。 |
| 标志/片偏移 | 3 / 13 | DF 决定是否允许分片;MF 标记是否还有后续片;偏移以 8 B 计。 |
| TTL/协议 | 8 / 8 | TTL 逐路由器递减;协议标识上层载荷,如 ICMP=1、TCP=6、UDP=17。 |
| 首部校验和 | 16 | 只检验 IPv4 首部,不覆盖整个载荷。 |
| 源/目的地址 | 各 32 | 分别占 4 B,标记发送和接收接口地址。 |
选项使首部长度可变,填充保证首部是 4 B 整数倍。路由器修改 TTL 后需更新首部校验和;分片时各片也各有自己的 IP 首部。IP 提供尽力而为的数据报服务,不为每个过境数据报进行端到端确认或重传。
链路层 MTU 限制可装入帧载荷的 IP 数据报总长。例如普通以太网 MTU 为 1500 B,包含 IP 首部但不包含以太网 MAC 首尾。IPv4 总长超过 MTU 时,若允许分片则分片;DF=1 时不能直接强行切开。
原总长 4000 B、首部 20 B,则数据 3980 B。每个前片最多装 1480 B,故分为 1480、1480、1020 B 三段数据,各片总长为 1500、1500、1040 B;片偏移分别为 0、185、370;MF 分别 1、1、0,标识均为原值 777。
某个中间片再次被分片时,子片偏移在原片偏移基础上增加;若原片 MF=1,最后一个子片也仍应保留 MF=1。存在选项时,各片首部长度可能受复制规则影响,不能未看题设就一律按 20 B。
例:4000B IPv4数据报通过MTU1500链路
IPv4再次分片:最后子片的MF为何仍可能是1
IPv4 为 32 bit,通常点分十进制表示;通过网络前缀与主机部分表达层次。一个路由器通常每个参与三层转发的接口各有地址;同一主机可因多网卡或配置拥有多个地址。地址唯一性的范围受公有地址、私有地址及本地网络配置影响。
| 类别 | 前导位/首字节范围 | 传统默认前缀 |
|---|---|---|
| A | 0;通常可用传统网络号 1~126 | /8,主机部分 24 bit |
| B | 10;128~191 | /16,主机部分 16 bit |
| C | 110;192~223 | /24,主机部分 8 bit |
| D | 1110;224~239 | IPv4 多播,不按普通主机网络号划分 |
| E | 1111;240~255 | 历史保留用途,另有 255.255.255.255 特殊广播地址 |
现行前缀长度由 /n 或掩码决定,看到首字节为 172 就直接断言网络一定 /16 会出错。早期资料中“B 类或 C 类最小网络号不能分配”等说法不应当作现代 CIDR 规划规则。普通含 h 位主机号的广播子网常有 2^h−2 个主机地址,但 /31 点到点和 /32 主机路由等另有约定。
| 地址 | 主要含义 | 使用限制 |
|---|---|---|
| 主机部分全 0 | 普通子网的网络地址 | 通常不分配给普通主机接口。 |
| 主机部分全 1 | 该子网定向广播地址 | 作为目的使用;路由器是否转发定向广播受安全配置限制。 |
| 255.255.255.255 | 本地受限广播 | 不由路由器转发到其他网络。 |
| 0.0.0.0 | 未指定/初始化中的本机地址语境 | DHCP 初始请求可用作源;0.0.0.0/0 在路由表中表示默认路由,语境不同。 |
| 127.0.0.0/8 | 环回地址块,常见 127.0.0.1 | 交给本机协议栈,不在外部链路传送。 |
私有 IPv4 地址块为 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16。不同私网可重复使用,公共互联网通常不为其提供全局可达路由。企业内部路由器可以正常转发私有地址;“所有路由器都不转发私有 IP”是错误的绝对化表述。
网络号全 0、主机号为特定值的“本网络某主机”写法属于历史地址语义,现代联网一般使用明确前缀和完整接口地址。规划地址时不能把历史分类限制与现行掩码规则混用。
NAT 在私网与外网边界改写 IP 地址,并维护转换关系;NAPT/PAT 进一步使用传输层端口区分多个内部会话,使多个内部端点可以共享一个公有 IPv4 地址。狭义一对一地址 NAT 与多对一端口转换要区分。
内部主机发出数据报时,边界设备将源私有地址以及必要时的源端口改成外部映射;回包的目的地址/端口对应映射表,再改回内部端点转发。改写 IP 和传输层伪首部相关字段后,校验和也须相应更新。
10.0.0.2:5000 与 10.0.0.3:5000 可分别映射到 203.0.113.5:40001、203.0.113.5:40002。回包依据外部端口找到正确内部主机。端口号相同并不冲突,因为内部 IP 不同;外部映射仍须能区分会话。
NAT 缓解 IPv4 地址不足,但破坏端到端地址透明性;外部主动发起连接通常需要预先配置映射或其他机制。NAT 不等同于完整安全防火墙,也不是所有私有网互联都必须经过 NAT。
示例两个内网主机访问同一外部服务,NAPT把源IP和源端口改写为公网地址与不同端口。203.0.113.1使用文档示例地址。
返回报文到达不同公网端口后,按转换状态还原内网目的IP与端口;相关校验和也要更新。具体实现可以把远端元组纳入映射/过滤规则。
NAPT返回路径:外网端口怎样找到内网进程
掩码中的连续 1 对应前缀,0 对应主机位。IP 与掩码逐位 AND 得网络地址;把主机位全置 1 得普通子网广播地址。传统子网划分从原主机部分借位,子网数量增多、每个子网主机容量相应减少。
普通广播子网 /p 的常见计算;/31、/32 等特殊用途不按此普通主机扣减式处理。
218.75.230.0/24 使用 255.255.255.128 后,前缀为 /25。第一子网网络 218.75.230.0、广播 .127、普通主机 .1~.126;第二子网网络 .128、广播 .255、普通主机 .129~.254。两个子网都可使用,不再按古早规则把全 0、全 1 子网整体排除。
两主机用同一掩码进行 AND,结果相同才属于同一对应子网;若各自掩码不同,双方对“是否同网”的判断可能不同。是否直连还取决于接口与链路配置,不能仅比较前三个十进制字节。
网络前缀统一为192.168.1,图中省略了前三段。每个/26地址块大小2^(32−26)=64,起点必须按64对齐。
在常规IPv4子网中,每块扣除网络地址和广播地址后可分配62个主机地址;/31点到点和/32等特殊用途不能套同一个减2规则。
子网计算:192.168.10.130/26怎样确定网络
CIDR 用网络前缀替代固定 A/B/C 类边界,以“地址/前缀长度”表示地址块。/p 对应 2^(32−p) 个地址;前缀越长,块越小、路由越具体。路由表项目至少表达目的前缀与下一跳/出接口。
/20 保留前 20 bit,掩码 255.255.240.0;第三字节步长 16,35 落在 32~47,故块范围为 128.14.32.0~128.14.47.255,共 4096 个地址,相当于 16 个 /24 块。
仅当网络前缀相同部分连续、地址对齐、转发策略和下一跳相容时,才能用较短共同前缀概括。若聚合包含原来没有的地址,应明确它是覆盖性汇总,可能需要更具体的丢弃路由,不能称为精确无额外覆盖的合并。
172.1.4.0/25、172.1.4.128/25、172.1.5.0/25、172.1.6.0/25、172.1.7.0/25 的最短覆盖前缀是 172.1.4.0/22,但它还包含未列出的其他半个 /24。转发匹配多条路由时选择前缀最长的一条,默认路由 /0 仅在无更具体匹配时使用。
最长前缀匹配:同一地址命中多条怎么办
静态路由配错下一跳可形成环路;聚合路由和默认路由互相指向时,也可能让发往不存在子网的数据报来回循环。TTL 最终使它停止,但 TTL 只是限制危害,不会自动修复路由表。可在适当汇总边界配置更具体的黑洞/丢弃路由,防止无效目的沿默认路由返回。
一些教材把直连、主机、网络、默认分开列步骤;采用 CIDR 时应以最长前缀匹配统一理解,避免“查到第一条匹配就转发”。同前缀多路径如何选择还取决于协议优先级、度量与负载均衡配置。
ARP 在 IPv4 以太网等链路上,把同一链路的下一跳 IP 解析为 MAC。先查本机 ARP 缓存;命中则直接封帧;未命中时发送本地广播请求,目标通常用单播应答,并更新缓存。缓存随时间老化,以适应地址和设备变化。
| 发送者与目的位置 | ARP 解析对象 |
|---|---|
| 主机→同网主机 | 最终目的主机接口 IP 对应的 MAC。 |
| 主机→异网主机 | 路由选出的本地网关接口 IP 对应的 MAC。 |
| 路由器→直连目的主机 | 目的主机接口的 MAC。 |
| 路由器→仍需其他路由器 | 下一跳路由器在本出接口链路上的 MAC。 |
DNS 把域名等名字解析成资源记录,常用于得到 IP;ARP 把本链路 IPv4 下一跳映射为硬件地址。两者既不是同一层次的查询,也不是互相替代。ARP 请求不由普通路由器跨网广播,PPP 等点到点链路则不必先运行 ARP。
| 步骤 | 方向 | 主要信息 |
|---|---|---|
| Discover | 客户→广播 | 尚无可用 IPv4 时,源 0.0.0.0、目的 255.255.255.255,寻找服务器。 |
| Offer | 服务器→客户 | 提出可租用地址、掩码、网关、DNS 等配置,具体投递方式受客户状态和广播标志影响。 |
| Request | 客户→服务器/广播 | 客户表明选择的服务器与租约;初始选择阶段常广播,通知其他提供者。 |
| ACK | 服务器→客户 | 确认租约,客户完成必要地址冲突检查后使用。 |
DHCP 使用 UDP,服务器端口 67、客户端端口 68。广播不能自行穿过路由器,跨子网获取配置依赖 DHCP 中继等机制;不同设备角色收到同一广播后是否交给应用,由端口与进程监听情况决定。
讲义用全广播简化初始过程;不能据此断言所有 DHCP 报文、续租报文都广播。服务器可探测候选地址,客户端也可通过 ARP 检查冲突;租约不是永久静态分配,更新与释放有各自状态。
DHCP初次获取地址:DORA四步
ICMP 报文装入 IP 数据报,用于报告无法交付、超时、参数问题、重定向等网络事件,或进行回送请求/回答。它不是 TCP/UDP 的应用协议,也不为 IP 增加逐包可靠交付。源点抑制、地址掩码查询等属于已废弃或历史类型,不宜作为现代拥塞控制的正常机制。
Ping 常用 ICMP Echo Request/Reply 判断可达性与 RTT;traceroute/tracert 通过逐渐增大的 TTL 触发沿途 Time Exceeded,再依据最终端点返回判断到达。探测报文可以是 UDP、ICMP 或 TCP,工具版本不同,不能一律说都用 UDP。
Traceroute:TTL=1和TTL=2分别停在哪里
| 字段 | 位数 | 作用 |
|---|---|---|
| 版本/通信类别/流标签 | 4 / 8 / 20 | 版本为 6,分类业务并标识数据流。 |
| 有效载荷长度 | 16 | 基本首部以外的字节数,包括扩展首部;通常最大 65535 B,超大载荷另有扩展机制。 |
| 下一个首部 | 8 | 指明后续扩展首部或上层协议。 |
| 跳数限制 | 8 | 经路由器转发递减,为 0 时停止转发。 |
| 源/目的地址 | 各 128 | 各 16 B;基本首部合计 40 B。 |
基本首部取消首部校验和与 IHL,把可选功能移到扩展首部;不再在基本首部直接放 IPv4 那样的分片字段。IPv6 路由器不分片,包太大时丢弃并报告 Packet Too Big;源端可用分片扩展首部进行分片,不能说 IPv6 完全不支持任何分片。
IPv6 有单播、多播和任播,没有 IPv4 式广播;任播交付到组内按路由意义较合适的一个结点。双协议栈同时运行 IPv4/IPv6;隧道把一种协议分组作为另一种协议载荷,经过不原生支持它的网络,再在出口解封装。
128 bit 分成 8 组,每组 16 bit 用十六进制表示。组内前导零可省略,例如 003a→3a;连续全零组可用 :: 压缩,但同一地址最多使用一次,以便唯一恢复组数。不能把一组中间或末尾的零任意删去。
| 地址/前缀 | 含义 |
|---|---|
| :: | 未指定地址,不能作为普通目的地址分配。 |
| ::1 | 本机环回。 |
| fe80::/10 | 链路本地单播;作用域为相应链路。 |
| ff00::/8 | 多播地址。 |
| 全球单播 | 可用于全球路由的单播地址,实际分配需遵循对应地址规划。 |
2001:db8::12:0:1 已显式写出 5 组,因此 :: 表示 3 组零。地址恢复为 2001:0db8:0000:0000:0000:0012:0000:0001。链路本地地址在多接口主机上还常需要作用域标识来确定具体链路。
IPv6压缩:::到底替代了几组0
静态路由由管理员配置,开销小、行为可控,但故障或拓扑变化后通常需人工维护。动态路由协议让路由器交换信息并更新路径,适应性较强,代价是报文、计算、收敛和配置复杂度。
| 项目 | 距离向量 | 链路状态 |
|---|---|---|
| 交换信息 | 到各目的的距离估计与下一跳相关信息 | 邻接关系和各本地链路的状态/代价 |
| 传播范围 | 通常给相邻路由器 | 按协议在区域内洪泛 |
| 计算依据 | 依赖邻居对目的的估计 | 建立拓扑数据库后本地运行最短路径算法 |
| 典型 | RIP | OSPF |
自治系统 AS 是统一技术管理和路由策略下的网络集合。域内 IGP 解决 AS 内部路由,如 RIP、OSPF;域间协议处理 AS 之间的可达性与政策,如 BGP。链路状态数据库同步不代表“一步就立即收敛”,实际还受传播、计算和定时器影响。
RIP 通常只与相邻路由器交换自己的路由信息,按周期并可触发更新。以跳数作距离,讲义模型直连网络为 1,最大可达距离 15,16 表示不可达;RIP 以 UDP 承载,标准服务端口 520。
R1 到 N1 的链路断开后,若 R2 仍通告“可经 R1 到达 N1”,R1 可能误以为可改经 R2,随后双方距离反复增加直到 16。水平分割、毒性逆转、触发更新等可减轻问题,但不能宣称单个简单措施消除所有复杂环路。
| 原条目 | 从 C 得到的候选 | 处理 |
|---|---|---|
| N2:距离2,下一跳C | N2:距离5,下一跳C | 同一下一跳的最新信息,更新为5。 |
| 原来没有 N3 | N3:距离9,下一跳C | 加入新网络。 |
| N6:距离8,下一跳F | N6:距离5,下一跳C | 新路径更短,改经C。 |
| N8:距离4,下一跳E | N8:距离4,下一跳C | 成本相同,可按实现保留等价路径。 |
| N9:距离4,下一跳F | N9:距离6,下一跳C | 候选更差且不同下一跳,一般不改。 |
首部 4 B;普通路由条目每条 20 B,含地址族、路由标记、网络地址、掩码、下一跳和度量等。传统基本报文至多 25 个普通条目,因此 RIP 载荷最大 4+25×20=504 B;加 UDP 首部 8 B 为 512 B,再按题设加 IP 首部。认证条目等会占用对应空间,不能在带额外功能时机械塞满 25 条普通路由。
RIP更新:原下一跳报坏消息时也必须改
每台路由器发现邻居并描述自身链路状态,生成 LSA;LSA 装入链路状态更新报文,按区域洪泛。区域内数据库达到一致后,各路由器从自己出发运行 Dijkstra/SPF,生成自己的路由表。因此数据库一致并不要求各路由器的下一跳表完全一样。
| 类型 | 作用 |
|---|---|
| Hello | 发现、维护邻居及可达关系。 |
| 数据库描述 DD | 交换链路状态数据库摘要。 |
| 链路状态请求 LSR | 请求缺失或较新状态。 |
| 链路状态更新 LSU | 携带一个或多个 LSA。 |
| 链路状态确认 LSAck | 确认接收的链路状态信息。 |
OSPF 按链路代价选路,可支持等价多路径。划分区域可限制拓扑与状态洪泛规模,主干区域负责区域间连接。状态变化触发更新,同时协议也有刷新和维护报文,不能说“拓扑不变就绝不发任何 OSPF 包”。OSPF 直接由 IP 承载(协议号 89),不使用 TCP/UDP 端口。
OSPF链路变化:共享拓扑后各自算路由
| 协议 | 范围/算法 | 交换内容与承载 |
|---|---|---|
| RIP | 域内、距离向量,以跳数衡量 | 邻居间通告距离向量;UDP。 |
| OSPF | 域内、链路状态,按代价 SPF | 区域内洪泛本地链路状态;直接用 IP。 |
| BGP | 域间、路径向量,重视政策 | 对等体交换网络前缀可达性及路径属性;TCP 179。 |
大型互联网若让每个路由器详细维护所有内部拓扑,状态和更新开销会非常大。AS 与区域把管理、策略和计算范围分层;BGP 选择的是符合策略的可行路径,不保证从物理时延、带宽或 AS 数量看都是最优。
BGP 发言人先建立 TCP 连接,再交换 OPEN 建立会话。初次交换可达性信息后,主要通告增量变化和撤销,而不是周期性把全部路由表广播。路径属性可包含经过的 AS 序列,若发现自己的 AS 已在路径中可据此拒绝该回路路由。
| 报文 | 作用 |
|---|---|
| OPEN | 协商版本、AS、保持时间、BGP 标识和能力等,初始化会话。 |
| UPDATE | 通告可达前缀与路径属性,或撤销失效路由。 |
| KEEPALIVE | 维持邻居会话,不承载完整路由表。 |
| NOTIFICATION | 报告检测到的差错并按协议终止会话。 |
通用首部 19 B,含 16 B Marker、2 B Length、1 B Type;基本 KEEPALIVE 仅含该首部。传统基本消息长度上限 4096 B,现代扩展可协商更大上限。OPEN 中历史 2 B AS 字段配合能力可支持 4 字节 AS,不能把所有现代自治系统号都限定为 16 bit。UPDATE 含撤销路由长度与内容、路径属性长度与内容、NLRI 等。
BGP 对等体依据 TCP 会话通信,不应简单理解成永远必须接在同一根物理链路。跨 AS 与 AS 内 BGP 有不同使用场景;发言人还需和域内路由可达性协作,避免出现“知道外部前缀却到不了下一跳”。
BGP路径向量:看到自己的AS号为什么要警惕环路
源发送一份组播数据,网络在必要分叉处复制,交给加入该组的接收者,降低发送端重复发送与公共路径负载。主机可以加入多个组,也可以离开;组成员身份和位置可能动态改变。IPv4 组地址位于 224.0.0.0/4,其中有保留和特殊范围,并非任意地址都供普通应用自由使用。
IGMP 让主机向本地组播路由器表达组成员关系;路由器间的组播路由协议负责把组流量沿适当分发树传递。IGMP 不负责计算整个互联网的组播树。组播通常基于无连接数据报,不能把 TCP 一对一连接直接当成原生一对多组播。
本地硬件多播与跨路由器 IP 组播是不同范围。为避免响应风暴,IPv4 对多播等情形的 ICMP 差错响应有抑制规则,但不能从中推出所有平台对组播 Echo 都有统一的“永不响应”行为。
以太网 IPv4 多播 MAC 的固定前缀是 01-00-5E,其后 24 bit 的最高位固定为 0,剩余 23 bit 取 IPv4 多播地址最低 23 bit。结果范围为 01-00-5E-00-00-00~01-00-5E-7F-FF-FF。
IPv4 组地址中前 4 bit 为 1110,余下 28 bit 可区分组;只映射其中 23 bit,所以有 5 bit 未映射,理论上 32 个不同 IP 组可对应同一 MAC。
第二字节 215 的二进制为 11010111,去掉最高位后为 1010111,即十六进制 57;后两字节 145、230 分别为 91、E6,因此映射为 01-00-5E-57-91-E6。
226.0.9.26 与 225.128.9.26 的低 23 bit 相同,均映射到 01-00-5E-00-09-1A。网卡可先按 MAC 接收候选帧,但 IP 层仍要检查目的组是否真是本机加入的组;硬件过滤通过不等于应用必定收到。
IPv4多播映射:不同组地址为何会得到同一个MAC
移动结点拥有相对稳定的归属地址;本地/归属代理在归属网络维护其当前位置映射;转交地址指出移动结点当前可到达的位置;外部代理可在访问网络提供辅助,也存在结点自行获得转交地址的方式。
DHCP 动态分配地址不自动保证主机换网后原有连接继续使用原端点。移动 IP 通过位置绑定和隧道维持归属地址语义;“不改变 IP”主要指对会话可见的归属地址,不是说所有封装层的外层 IP 都永远不变。
运输层向应用提供端到端进程通信,屏蔽下层经过的链路和路由细节。发送端把多个应用的数据复用到运输协议;接收端依据协议与端点信息分用。运输层端口是软件接口,不能与交换机网口等物理接口混为一谈。
| 范围 | 传统名称 | 说明 |
|---|---|---|
| 0~1023 | 系统/熟知端口 | 常见标准服务使用,具体可用性受系统权限规则约束。 |
| 1024~49151 | 用户/登记端口 | 可登记用于服务,不能据此说所有实际使用都必须先登记才可收发。 |
| 49152~65535 | 动态/私有端口 | 常用作临时端口,实际操作系统临时范围可配置。 |
端口为 16 bit,数字只在本机及相应运输协议语境中有意义;不同主机可用同一端口,TCP 和 UDP 也可分别使用同号端口。讲义将 Socket 简写为“IP:端口”;一个 TCP 连接通常由源 IP、源端口、目的 IP、目的端口四元组标识,在多协议比较时还需指明 TCP/UDP。一个进程可以拥有多个套接字,不能把进程、端口和连接一一等同。
| 项目 | UDP | TCP |
|---|---|---|
| 建立连接 | 不预先握手 | 先建立连接,再传送并正常释放 |
| 数据形态 | 面向报文,保留应用提交的数据报边界 | 面向字节流,不保留应用每次写入的边界 |
| 可靠与顺序 | 本身不提供确认、重传和排序保证 | 通过序号、确认、重传等实现可靠有序字节流 |
| 流量/拥塞 | UDP 首部本身不提供这两种控制 | 提供接收流量控制与拥塞控制 |
| 首部 | 固定 8 B | 基本 20 B,可含选项,传统最大 60 B |
| 通信关系 | 可支持单播、多播等数据报场景 | 每连接两个端点,全双工一对一 |
UDP 的协议开销和启动时延通常较小,但“UDP 不拥塞控制所以网络拥塞不影响效率”是错误推论;拥塞仍会导致丢包和时延,应用应采取适当控制。TCP 可靠性是运输服务的约定,不表示任何故障下连接都永不失败。
应用连续两次写入 100 B,TCP 对端可能一次读到 200 B,也可能多次读到若干部分;应自行规定消息长度或分隔。UDP 的一次交付对应一个数据报,数据报过大可能在 IP 层分片,过小又会使首部比例过高。
两次发送AB与CD:TCP和UDP接收边界怎样不同
| 字段 | 长度 | 含义 |
|---|---|---|
| 源端口 | 2 B | 便于回送,某些 IPv4 场景不需时可置零。 |
| 目的端口 | 2 B | 接收端用于分用。 |
| 长度 | 2 B | UDP 首部加数据的字节数,最小为 8。 |
| 校验和 | 2 B | UDP 与相应 IP 伪首部的反码校验。 |
IPv4 UDP 中校验和字段全 0 表示未使用该校验;若计算结果恰好为全 0,发送时编码为全 1,因此 UDP 校验和值可以是 0xFFFF。IPv6 中 UDP 校验和原则上必需,少数隧道等特殊例外不能推广成一般可选。
UDP校验:伪首部只参与计算,是否真的传在UDP前面
| 字段 | 宽度 | 解释 |
|---|---|---|
| 源/目的端口 | 各16 bit | 标识通信端点。 |
| 序号 seq | 32 bit | 本段首数据字节序号,按模 2^32 运算。 |
| 确认号 ack | 32 bit | ACK 有效时,表示希望收到的下一个连续字节序号。 |
| 数据偏移 | 4 bit | 以4 B计首部长度;最小20 B,传统最大60 B。 |
| 窗口 | 16 bit | 向对方通告接收窗口;窗口扩大选项可扩展解释范围。 |
| 校验和 | 16 bit | 覆盖伪首部、TCP首部和数据,必需。 |
| 紧急指针/选项 | 16 bit/可变 | 紧急语义由 URG 指示;选项可含 MSS、窗口扩大、SACK、时间戳等。 |
ACK 指示确认号有效;SYN 同步初始序号;FIN 表示发送方向数据结束;RST 重置连接;PSH 请求尽快向应用交付;URG 指示紧急指针有效。不是每个建立或释放报文都同时令 SYN、ACK、FIN 为 1。紧急数据仍在 TCP 字节流中,不是无序号的带外独立通道。
seq=301,数据100 B,对应301~400,下段新数据从401开始。累计 ack=701 表示已连续收到到700,不能据此断言701之后的乱序字节完全没到。接收窗口1000则通告可接受701~1700,计算右边界须注意包含端点。
seq=1000表示本段首个数据字节的序号;600字节使用1000~1599,不能把末字节写成1600。
ack=1600表示1600之前的字节都已连续收到。纯ACK不占序号,SYN和FIN各占一个序号;没有数据的段也仍有seq字段。
| 步骤 | 报文 | 主要状态变化 |
|---|---|---|
| 1 | A→B:SYN=1,seq=x | A进入 SYN-SENT;B预先处于 LISTEN。 |
| 2 | B→A:SYN=1、ACK=1,seq=y,ack=x+1 | B进入 SYN-RECEIVED。 |
| 3 | A→B:ACK=1,seq=x+1,ack=y+1,可携带数据 | A进入 ESTABLISHED;B收到有效确认后进入 ESTABLISHED。 |
建立连接不仅要知道对方存在,还要让双方确认两方向初始序号与收发能力,并建立相应连接状态。第三次确认可避免旧的连接请求使服务器错误建立无效连接。两次就简单认定双方都建立成功会留下半开或状态不一致问题,实际 TCP 还靠重传、定时器和重置等机制处理故障。
A 的 SYN seq=11220,则 B 对它的有效确认是 ack=11221(未回绕时);B 自己的 seq=y 独立选择,不必是11221。纯 ACK 不消耗序号;SYN 即使没有应用数据仍消耗1个序号。
常见模型前两次握手不携带应用数据、第三次可带;TCP 也有特定扩展允许不同数据携带行为。计算题应使用题设的标准模型,不将扩展机制擅自加进去。
三次握手:seq、ack与状态逐条对照
| 报文 | 内容 | 状态 |
|---|---|---|
| A→B FIN | FIN=1、seq=u,通常ACK=1 | A:FIN-WAIT-1 |
| B→A ACK | ack=u+1,seq=v | B:CLOSE-WAIT;A收到后:FIN-WAIT-2 |
| B→A FIN | B应用完成发送后,FIN=1、seq=w,ack=u+1 | B:LAST-ACK |
| A→B ACK | ack=w+1,seq=u+1(A之后无新数据时) | A:TIME-WAIT;B收到确认后:CLOSED |
A 发 FIN 后不能再在该方向发送新应用数据,但仍可接收 B 尚未完成的数据。B 的最终 FIN 序号 w 可以不同于先前确认的序号 v,因为半关闭期间 B 还能发送。FIN 本身消耗一个序号,纯 ACK 不消耗。
让最后确认丢失后对方重传的 FIN 仍能得到确认;同时使旧连接残留报文有机会在网络中消失,减少新连接混入旧报文的风险。TIME-WAIT 方若收到重传 FIN,会按规则重发 ACK 并重启等待。
ACK 与 FIN 可以合并,同时关闭也有不同状态路径;“四次挥手”是典型独立关闭过程。CLOSE-WAIT 表示本机已经收到对端 FIN、等待本地应用关闭;TIME-WAIT 则是另一种等待,不能混为同一故障原因。
四次挥手:半关闭、序号和2MSL
TCP 用校验检测损坏,字节序号和累计确认识别缺口,发送缓存保存未确认副本,重传恢复丢失,并抑制重复、按序向应用提交。接收端收到乱序数据可缓存,但累计确认仍停留在第一个缺失字节,除非另有 SACK 等附加信息。
测量报文发送到相关确认到达的 RTT,平滑估计往返时间及其波动,RTO 需留出适当余量。重传后的确认可能无法判定对应哪次发送,估计时须避免采样歧义;超时通常采用退避。RTO 不能固定等于最近一次 RTT,也不能只看平均值而完全忽略波动。
M1到达后,M2丢失;随后M3、M4、M5分别乱序到达,接收端都重复确认缺口之前的连续数据。发送端收到三个重复 ACK后可提前重传M2,无需等RTO到期。重传定时器仍保留,并未被“快重传”取代。
TCP 的 ack 值是下一期望字节,图中“确认M1”通常是教材对段的简写。要计“三个重复”需在原确认之后再收到三次相同确认,不能把原来的那次当成三个重复中的一个。
快速重传:第3个重复ACK究竟数到哪里
接收端根据缓存空余和应用读取情况,在 TCP 窗口字段中通告 rwnd;发送方据此限制未确认数据。接收端的流量控制是端到端的,与相邻结点的链路层流控范围不同。
rwnd保护接收端,cwnd保护网络;单位必须统一成字节或 MSS 数。
FlightSize是已经发送但尚未确认的在途数据;实际还受应用有无待发数据等约束。
ack=201、rwnd=300,允许范围为201~500;若201~300已经发出但未确认,可新增发送301~500共200 B,而不是再发300 B新数据。收到确认后左边界移动,已确认副本才能从发送缓存释放。
MSS 是一个 TCP 报文段数据部分的最大长度,不含 TCP/IP 首部;MTU 是链路可承载的 IP 分组总长度上限。典型无选项 IPv4/以太网中 MSS 可取1500−20−20=1460 B,但实际选项、隧道和路径条件须另算。TCP 可因 MSS、应用推送或定时策略形成不同长度报文段。
例:发送600字节,再收到累计确认
B 通告 rwnd=400,A可发1~400。A发送1~100、101~200、201~300,第三段丢失;A又收到 ack=201、rwnd=300,于是窗口覆盖201~500。201~300仍是未确认旧数据,A还能发301~400、401~500。
A窗口中201~500都已发送且未全部确认时,不能继续发送501及之后的新数据。若201~300超时,A重传该旧段;B已缓存301~500,缺口补齐后可累计确认 ack=501,并通告rwnd=100。此后A可发501~600。
B确认到ack=601并通告rwnd=0,表示序号600及之前的连续数据已收到,普通新数据发送额度为零。A已确认的数据可以移出缓存,但这不意味着A可无视零窗口继续发送后续普通数据。
上面只展示接收流量控制,假设拥塞窗口与其他条件不构成更小限制;若题设还给 cwnd,应取两种限制较小者。序号是字节序号,不是“第几个报文段”。
B通告零窗口后,应用读走数据腾出空间,B发非零窗口更新,但更新丢失。A仍以为不能发送,等B通知;B以为已通知,等A数据。若没有额外机制,这种等待可持续。
A收到零窗口信息后,按持续计时器机制发送零窗口探测,促使B回复当前窗口。若仍为零则继续按机制探测;若已非零,A可恢复发送。探测报文是处理窗口僵局的特殊控制行为,不能据此说零窗口时普通新数据可随意发送。
重传计时器解决未确认数据的可靠交付;持续计时器解决零窗口更新丢失;保活计时器探测长时间空闲连接的对端状态;TIME-WAIT 的2MSL等待处理关闭与旧报文。中文都叫“计时器”,但启动条件和目的不同。
零窗口探测:丢失的窗口更新怎样恢复
发送端用 cwnd 控制网络在途负载,用 ssthresh 分隔慢开始与拥塞避免。教材常以1 MSS起始并假设每段均被立即确认,此时慢开始大约每RTT翻倍;真实实现的初始窗口和ACK行为可不同。
| 阶段/事件 | 典型教材规则 | 辨析 |
|---|---|---|
| 慢开始 | 每个对新数据的确认增加一定窗口,理想轮次1、2、4、8… | “慢”指从较小窗口开始,增长本身是指数式。 |
| 拥塞避免 | 约每个RTT增加1 MSS | 线性增长,不是每收到一个ACK都再加整整1 MSS。 |
| 超时 | 降低门限,cwnd回小初始值,重新慢开始 | 门限常按故障前在途量一半并设下限;题目可简化为原cwnd一半。 |
| 三重复ACK | 快速重传,并按算法快恢复 | 不应与RTO超时后直接回1的路径混同。 |
理想慢开始序列1、2、4、8、16;达到门限后按17、18…线性增长。若24时超时,题设按窗口一半取新门限12,cwnd回1,再1、2、4、8,达到/跨至门限附近时切换规则。具体临界轮次须看题设“达到门限”的更新约定,不能机械多翻一次至16后才处理。
拥塞窗口:到门限后为什么不再翻倍
连续重复ACK说明仍有数据穿过网络,与完全无反馈的超时相比,可用较温和的窗口缩减后继续。教材简化常设新门限为原窗口一半、cwnd降到新门限,再进入拥塞避免。标准Reno恢复期间会临时把cwnd置为ssthresh+3 MSS,并随额外重复ACK调整;收到确认新数据的ACK后退出恢复。
TCP拥塞控制存在多种算法。计算题若明确Tahoe/Reno、是否快恢复、初始窗口、门限及延迟确认,应严格使用给定模型;没有这些信息时,不能对所有TCP实现给出唯一的逐轮曲线。
Reno快恢复:门限、临时膨胀与退出
服务器进程等待请求并提供服务,客户进程主动请求;服务器可并发处理多个客户,通常具有相对稳定的服务入口。建立通信后双方都可以传数据,客户先发起连接不等于服务器以后只能被动应答单个字节。
结点可同时作为服务请求者和资源提供者,直接共享内容或任务。新增结点也增加可用资源,因此有扩展优势;但结点在线状态、资源发现、信任和一致性管理更复杂。P2P也可能使用中心化的索引、登录或发现辅助,不能一概说任何P2P系统都完全没有服务器。
集中式管理便于权限和一致性控制,但中心性能/可用性可能成为瓶颈;P2P减轻单点负载、利用分散资源,代价是动态管理。Web、FTP、邮件多采用C/S交互,文件分发可用P2P;一个系统也可混合使用。
域名由点分隔的标号组成,从右到左由高到低;最高层是根,其下包括国家/地区顶级域、通用顶级域及基础设施域等。域名本身通常不区分字母大小写。域是命名层次,区是实际由某套权威服务器负责的数据范围,两者不总相同。
| 角色 | 主要职责 |
|---|---|
| 根服务器 | 提供顶级域等转介信息。 |
| 顶级域服务器 | 给出对应下级权威服务器等信息。 |
| 权威服务器 | 负责其权威区的资源记录,给出权威回答或相应转介。 |
| 本地递归解析器 | 代表客户查询、缓存结果,不属于客户域名必经的某个固定层级。 |
DNS不仅查IPv4地址;A对应IPv4,AAAA对应IPv6,MX用于邮件交换,NS标记权威服务器,CNAME表示别名等。主/辅助权威服务器通过区域传送等机制保持数据,提供冗余;不能把“每台主机所有信息都登记在本地递归服务器”当成规则。DNS通常使用53端口,既可用UDP也可用TCP。
应用先查本机缓存、配置等可用信息;需网络查询时,主机通常向本地递归解析器提出递归请求。解析器若无可用缓存,可向根、顶级域、权威服务器依次作迭代查询:每次拿到下一步线索或最终结果,再返回给客户。
递归要求被询问者负责继续查询并给出最终回答或错误;迭代允许被询问者只给出下一步应找谁。不是因为“查询目标更远”就自动变成递归,关键在该次请求的责任分工。
缓存减少时延和查询流量,但记录会变化,因此按TTL等有效期使用;命中后无需再逐级访问根和权威。直接使用IP、hosts或已有缓存都可能避免本次DNS网络交互。若计算访问网页总时延,必须先明确哪些记录已缓存、查询是否串行及DNS使用的RTT。
DNS得到服务器IP后,若目标不在本地链路,仍需路由选择及可能的网关ARP;DNS不会顺带给出沿途每跳MAC。反过来,ARP缓存命中也不能替代域名到IP的解析。
DNS:本地服务器代客户端逐级询问
FTP以C/S方式提供文件传输、目录与权限管理,可支持匿名资源访问。控制连接由客户连到服务器TCP21,承载命令/应答,通常跨越整个会话;数据连接另行建立,传送文件或目录内容,传送结束可关闭。不能把命令和文件内容都塞入同一条“FTP21连接”模型。
| 方式 | 协商 | 数据TCP连接发起方 |
|---|---|---|
| 主动PORT | 客户向服务器告知自己监听的数据地址/端口 | 服务器典型从TCP20连到客户指定端口。 |
| 被动PASV | 服务器给客户一个监听的数据地址/端口 | 客户连到服务器所提供的端口;该端口通常不是20。 |
“主动/被动”描述数据连接由哪一侧发起,不描述文件是上传还是下载。两种模式的数据TCP连接都可以按应用请求传送相应方向的文件。NAT和防火墙对回连可能有限制,被动模式因此常更易穿过客户侧网络边界。
讲义一处将被动模式写成使用TCP20;应结合前后流程理解为:主动模式典型使用服务器源端口20,被动模式使用协商端口。
FTP主动与被动:到底哪一方主动建立数据TCP
用户代理用于撰写、显示和管理邮件;邮件服务器排队、转发、存储并报告结果。发送服务器A向接收服务器B传邮件时,A是SMTP客户、B是SMTP服务器;方向反过来角色也会互换,不能把客户与某台终端硬件永久绑定。
浏览器与Web邮件站点之间通常使用HTTP/HTTPS,站点后端与邮件系统之间仍可用SMTP/IMAP等。教材服务器间SMTP常用TCP25;客户提交与加密服务可能用其他标准端口。不能把“用户发送邮件”和“SMTP服务器间投递”一律混成同一条TCP25连接。
发件客户端通常通过SMTP提交到发送服务器,服务器之间通过SMTP投递。收件人用POP3或IMAP访问其邮箱;网页邮箱的浏览器到Web服务通常使用HTTPS。
图中POP3/IMAP箭头表示客户端发起访问请求,邮件数据响应方向相反。POP3是否删除服务器副本取决于客户端会话命令和保留设置;IMAP支持服务器端状态同步。
邮件传输有信封和内容两层:SMTP的MAIL FROM、RCPT TO等信封信息用于投递;内容含首部与主体,两者用空行分隔。To、From、Subject等是内容首部,不能假定它们与实际信封收件人永远完全一致,例如密送。
传统SMTP的基础文本传输模型不直接适合任意二进制、多语言和多媒体内容。MIME定义内容类型、传输编码、多部分结构等,常见首部有MIME-Version、Content-Type、Content-Transfer-Encoding、Content-ID、Content-Description;它使编码后的内容可以通过邮件系统传送,并不替代SMTP投递。
TCP连接建立后进行SMTP问候与会话协商;MAIL FROM声明信封发件人,RCPT TO逐个指定收件人,DATA阶段传内容;服务器返回相应状态码。完成后QUIT并收到结束应答,再关闭连接。提前检查收件人可避免在明确拒收的情况下发送整封大邮件。
SMTP可以经中继服务器传递,不应把讲义“直接连接目标”示例误解为绝无中间服务器。成功接收邮件表示服务器接受后续处理责任,不等于收件人已经打开或阅读。
POP3使用C/S方式,教材常见TCP110,客户请求下载邮箱邮件。可配置下载后保留服务器副本,也可在协议删除和会话提交后删除;不能说所有POP3收件都必然立即删除服务器文件。
IMAP通常把邮箱状态保留在服务器,支持文件夹、标记、搜索与部分内容获取,便于多个设备协同访问;教材常见TCP143,加密服务有不同端口。只取首部或所需部分有利于低带宽访问。
SMTP是发送/转送;POP3与IMAP是访问已存储邮件。邮件服务器可以同时运行这些服务。应用界面中的“发件箱”“已发送”“删除”是产品行为,还需结合客户端和服务端同步策略,不能仅凭协议名字确定全部实现细节。
URL标识可访问的资源位置与方法;HTTP定义客户与服务器的请求/响应交互;HTML描述文档结构并提供超链接。WWW是运行在互联网之上的应用体系,不是互联网本身的同义词。
典型结构为 scheme://host:port/path,可带查询与片段。scheme与主机名通常大小写不敏感,但路径、查询等可能区分大小写;不能把“整个URL不区分大小写”当普遍规则。省略端口时依方案采用默认值,如传统HTTP80、HTTPS443。
教材HTTP/1.x通常以TCP为基础;HTTPS还涉及TLS;HTTP/3基于QUIC/UDP。做题先按明确的协议模型,不把不同版本的RTT开销混算,也不把“所有现代HTTP永远TCP”说成无例外事实。
| 部分 | 请求 | 响应 |
|---|---|---|
| 开始行 | 方法、请求目标、版本,如 GET /index.html HTTP/1.1 | 版本、状态码、原因短语,如 HTTP/1.1 200 OK |
| 首部 | Host、Accept、Cookie等控制信息 | Content-Type、Content-Length、Set-Cookie等元数据 |
| 空行与主体 | 空行分开首部与可选主体 | 主体可承载资源,也可能依状态/方法没有主体 |
HTTP请求语义本身不要求服务器记住同一客户此前每次请求的历史。服务器仍可使用数据库、会话标识、Cookie等保存业务状态;同一客户第二次请求也可能因资源改变或鉴权不同而得到不同响应,“无状态”不意味着每次响应必须字节相同。
服务器响应用Set-Cookie下发标识及属性;浏览器按域、路径、有效期和安全属性保存;后续符合条件的请求用Cookie首部带回,服务器据此关联会话数据。Cookie不等于整份用户活动数据库,往往只是关联索引;HTTP/1.x的文本格式也不能推广到HTTP/2、HTTP/3的所有线格式。
| 模型 | 请求方式 | 典型代价 |
|---|---|---|
| 非持久 | 每个对象单独建立连接 | 若无DNS/TLS、忽略请求发送时延,一个对象约2RTT+对象发送时间。 |
| 持久非流水线 | 复用同一连接,前一响应到达后再发下一请求 | 初始建立一次,后续每个串行请求仍需往返等待。 |
| 持久流水线 | 同一连接可连续发多个请求而不等前一响应 | 可减少等待,但HTTP/1.1响应顺序等仍可能形成队头等待。 |
2RTT模型通常把TCP建立约1RTT、请求到首响应约1RTT合计;若第三次握手可捎带HTTP请求,不应再额外给它一个完整RTT。对象发送、DNS、TLS、排队、并行连接、慢开始和缓存命中是否计入,都须按题设。
若先下载基础HTML才知道N个内嵌对象地址,不能把所有对象都从t=0并发请求。先完成HTML阶段,再依据是否持久、是否流水、并行连接上限安排后续对象。总时间取各并行支路完成的最大值,而不是不加区分地把所有时间相加。
HTTP非持久连接:一个对象的2RTT
HTTP持久连接:三个小对象怎样数请求RTT
2026-11-06 更新