第41题(数据结构·图)

题目

已知有向图G采用邻接矩阵存储,类型定义如下:

typedef struct {
    int numVertices, numEdges;      // 图的顶点数和有向边数
    char VerticesList[MAXV];        // 顶点表
    int Edge[MAXV][MAXV];           // 邻接矩阵
} MGraph;

将图中出度大于入度的顶点称为K顶点。请设计算法 int printVertices(MGraph G):对给定任意非空有向图G,输出G中所有K顶点,并返回K顶点的个数。

要求:

  1. 给出算法的设计思想。
  2. 根据算法思想,写出C/C++描述,并注释。

答案

算法设计思想: 遍历邻接矩阵,同时统计每个顶点的出度和入度;再遍历一次顶点表,输出所有出度大于入度的顶点,并计数返回。

C/C++代码:

int printVertices(MGraph G) {
    int n = G.numVertices;
    int outDeg[MAXV] = {0};   // 出度数组
    int inDeg[MAXV]  = {0};   // 入度数组
    int count = 0;            // K顶点个数

    // ① 一次双重扫描,同时统计出度和入度
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (G.Edge[i][j] != 0) {   // 存在弧 i → j
                outDeg[i]++;           // i 的出度加1
                inDeg[j]++;            // j 的入度加1
            }
        }
    }

    // ② 遍历顶点表,输出K顶点并计数
    for (int i = 0; i < n; i++) {
        if (outDeg[i] > inDeg[i]) {
            printf("%c ", G.VerticesList[i]);
            count++;
        }
    }

    return count;
}

复杂度: 时间O(n²),空间O(n)。

详细解析

关键理解: 邻接矩阵中,第i行非零元素的个数就是顶点i的出度,第i列非零元素的个数就是顶点i的入度。一次双重循环即可同时统计出度和入度,无需分别扫行和扫列。

示例验证: 以题目给出的图为例,弧为 a→b、a→d、b→d、b→c、c→d。

顶点出度入度出 > 入?
a20✓ K顶点
b21✓ K顶点
c11✗
d03✗

输出结果:a b,返回值为2。

常见失分点: 输出时误打印下标而非顶点名;未同时统计入度和出度导致重复扫描;返回值逻辑错误。

涉及知识点详解

知识点1:图的邻接矩阵存储

邻接矩阵是用一个二维数组 Edge[i][j] 表示顶点之间相邻关系的存储结构。

  • 有向图: Edge[i][j] = 1 表示存在弧 i→j;Edge[i][j] = 0 表示不存在。
  • 无向图: 邻接矩阵是对称矩阵,Edge[i][j] = Edge[j][i]。
  • 带权图: Edge[i][j] 存储权值,不存在边时用∞或0表示。

邻接矩阵的性质:

性质说明
空间复杂度O(n²),适合稠密图
判断两点是否相邻O(1)
求某顶点所有邻接点O(n)
第i行非零元素个数有向图中为顶点i的出度
第i列非零元素个数有向图中为顶点i的入度
无向图第i行非零元素个数顶点i的度

知识点2:顶点的度(出度与入度)

  • 无向图: 顶点v的度 = 与该顶点相关联的边数,记为TD(v)。所有顶点度之和 = 2×边数。
  • 有向图:
    • 出度OD(v): 以v为起点的弧的数目。
    • 入度ID(v): 以v为终点的弧的数目。
    • 度TD(v) = OD(v) + ID(v)。所有顶点出度之和 = 所有顶点入度之和 = 弧数。

第42题(数据结构·外部排序)

题目

对含有n(n>0)个记录的文件进行外部排序,采用置换-选择排序生成初始归并段时需要使用一个工作区,工作区中能保存m个记录。

(1) 若文件中有19个记录,关键字依次为 51, 94, 37, 92, 14, 63, 15, 99, 48, 56, 23, 60, 31, 17, 43, 8, 90, 166, 100,当m=4时,可以生成几个初始归并段?各是什么?

(2) 对任意的m (n≫m>0),生成的第一个初始归并段的长度最大值和最小值分别是多少?

答案

(1) 共生成 3个初始归并段:

归并段关键字序列长度
第1段14, 37, 51, 63, 94, 996
第2段15, 23, 31, 48, 56, 60, 90, 1668
第3段8, 17, 43, 1004

(2) 首段长度最大值 = n,最小值 = m。

详细解析

置换-选择排序原理

置换-选择排序的核心思想是利用内存工作区(通常实现为最小堆)不断选择当前可输出的最小记录,同时从输入文件读入新记录进行"置换",从而生成比工作区容量更长的有序归并段。

关键规则: 每次从堆顶输出最小元素后,读入一个新记录替换该位置。若新记录的关键字 ≥ 当前归并段已输出的最大关键字,则它属于当前归并段;若 < 当前归并段已输出的最大关键字,则它不能加入当前归并段,需标记为下一归并段的记录,但仍暂存在工作区中。当工作区中所有记录都被标记为"下一归并段"时,当前归并段结束。

第(1)问逐步模拟

初始工作区读入前4个记录,构建最小堆。

初始状态: 工作区:14, 37, 51, 94;待读入:63, 15, 99, 48, 56, 23, 60, 31, 17, 43, 8, 90, 166, 100

生成归并段1:

步骤输出max读入工作区状态判断
114146337, 51, 94, 6363 ≥ 14,可加入
237371551, 94, 63, 1515 < 37,标记为下一段
351519994, 63, 15(标记), 9999 ≥ 51,可加入
463634894, 99, 15(标记), 4848 < 63,标记为下一段
594945699, 15, 48, 5656 < 94,标记为下一段
699992315, 48, 56, 2323 < 99,标记为下一段

工作区中所有元素均被标记,归并段1结束 = {14, 37, 51, 63, 94, 99}(长度6)。

生成归并段2: 工作区重置为 15, 23, 48, 56;待读入:60, 31, 17, 43, 8, 90, 166, 100

步骤输出max读入判断
115156060 ≥ 15,可加入
223233131 ≥ 23,可加入
331311717 < 31,标记为下一段
448484343 < 48,标记为下一段
5565688 < 56,标记为下一段
660609090 ≥ 60,可加入
79090166166 ≥ 90,可加入
8166166100100 < 166,标记为下一段

归并段2 = {15, 23, 31, 48, 56, 60, 90, 166}(长度8)。

归并段3: 工作区剩余 8, 17, 43, 100,依次输出。归并段3 = {8, 17, 43, 100}(长度4)。

第(2)问理论边界

  • 最大值 = n: 当输入文件的关键字序列已经有序时,每次读入的新记录都 ≥ 当前归并段的最大关键字,所有n个记录都进入同一个归并段。
  • 最小值 = m: 当输入文件的关键字序列严格递减时,每次读入的新记录都小于当前已输出的最大关键字,初始工作区的m个记录输出完毕后立即触发归并段切换。

涉及知识点详解

知识点1:外部排序

外部排序是指待排序的记录数量很大,无法一次性全部装入内存,需要在内存和外存之间多次交换数据才能完成的排序。

外部排序的两个阶段:

  1. 生成初始归并段: 将文件分成若干段,每段装入内存排序后写回外存,形成有序的初始归并段。
  2. 多路归并: 将多个初始归并段逐步合并,最终得到全局有序文件。

优化方向:

  • 增加归并路数k → 减少归并趟数 → 减少I/O次数(但k过大时内部归并开销增加)。
  • 减小初始归并段数量 → 使用置换-选择排序。

知识点2:置换-选择排序

置换-选择排序是一种生成初始归并段的算法,其核心优势是生成的初始归并段长度可以超过工作区容量。

算法步骤:

  1. 从输入文件读入m个记录到工作区,建最小堆。
  2. 输出堆顶最小记录,记为MIN。
  3. 从输入文件读入下一个记录R:
    • 若R ≥ MIN,则R可加入当前归并段,替换堆顶后调整堆。
    • 若R < MIN,则R不能加入当前归并段,标记为"下一段",暂存工作区。
  4. 重复步骤2~3,直到工作区中所有记录都被标记为"下一段"。
  5. 当前归并段结束,清除标记,重新建堆,开始下一段。

平均归并段长度: 约为工作区容量的2倍(当输入随机排列时)。

时间复杂度: O(n log m),空间复杂度O(m)。

第43题(计算机组成原理·虚拟存储与Cache)

题目

计算机M字长32位,按字节编址,采用请求调页的虚拟存储管理方式,虚拟地址32位,页面大小4KB。数据Cache采用4路组相联映射,主存块大小32B,Cache数据区大小8KB。C语言程序段包含一个二维数组 int a[24][64],数组首地址为 0042 2000H,数组按行优先存储,每个int占4B。执行前数组a不在主存中。

for (i = 0; i < 24; i++)
    for (j = 0; j < 64; j++)
        sum += a[i][j];

(1) 数组a分为几个页面存储?访问数组a会发生几次缺页异常?页故障地址各是什么?

(2) 该程序段对数组a的访问是否具有时间局部性?

(3) 虚拟地址中哪几位用作块内地址?哪几位用作Cache组号?a[1][0] 的虚拟地址是什么?对应的Cache组号是多少?

(4) 数组a占用多少个主存块?在按行、按列两种循环顺序下,数组a的Cache命中率分别是多少?

答案

问题答案
(1) 页面数2个页面
(1) 缺页次数2次
(1) 页故障地址0042 2000H、0042 3000H
(2) 时间局部性不具备,每个元素仅访问一次
(3) 块内地址位A₄~A₀
(3) Cache组号位A₁₀~A₅
(3) a[1][0] 虚拟地址0042 2100H
(3) a[1][0] Cache组号8
(4) 主存块数192块
(4) 按行命中率87.5%
(4) 按列命中率87.5%

详细解析

第(1)问:页面分布与缺页

数组a共 (24 \times 64 = 1536) 个元素,每个int占4B,总大小 = (1536 \times 4 = 6144\text{B} = 6\text{KB})。

页面大小4KB,6KB需要 (\lceil 6/4 \rceil = 2) 个页面。首地址 0042 2000H 页内偏移为0,恰好是页面起始地址。第一页存放前4KB,第二页存放剩余2KB。

执行前数组a不在主存,因此发生2次缺页异常:

  • 第一次:访问数组起始位置,页故障地址 = 0042 2000H
  • 第二次:跨越到第二页,页故障地址 = 0042 3000H(第一页起始地址 + 4KB)

第(2)问:时间局部性

不具备时间局部性。 时间局部性是指一个内存位置被访问后,在不久的将来可能会被再次访问。本程序段中,每个数组元素 a[i][j] 在整个循环过程中仅被访问一次,不存在重复访问同一元素的情况。

第(3)问:地址划分与组号

① 块内地址: 主存块大小32B = (2^5)B,按字节编址,块内地址需要低5位,即 A₄~A₀。

② Cache组号: Cache数据区8KB,4路组相联,主存块32B:

[
\text{组数} = \frac{8\text{KB}}{32\text{B} \times 4} = \frac{8192}{128} = 64 \text{ 组}
]

64组需要6位组号。块内地址占低5位,组号占接下来的6位,即 A₁₀~A₅。

③ a[1][0] 虚拟地址: 按行优先存储,a[1][0] 前面有 (1 \times 64 = 64) 个元素,偏移量 = (64 \times 4 = 256\text{B} = 100\text{H})。

[
\text{虚拟地址} = 0042\ 2000\text{H} + 100\text{H} = \mathbf{0042\ 2100\text{H}}
]

④ Cache组号: 将 0042 2100H 分解,组号位 A₁₀~A₅ = 01000(二进制)= 8。

第(4)问:主存块数与命中率

① 主存块数: (6144 / 32 = 192) 块。

② 按行访问命中率: 一个主存块32B可存放 (32/4 = 8) 个int元素。按行访问时元素连续存放,每访问一个主存块中的第一个元素时产生一次Cache缺失,随后该块内剩余7个元素均命中。

[
\text{命中率} = \frac{7}{8} = \mathbf{87.5%}
]

③ 按列访问命中率: Cache数据区8KB可容纳256个主存块((8\text{KB}/32\text{B}=256)),而数组a仅占192个主存块,整个数组可以全部装入Cache。一旦数组被完全调入,按列访问时每个元素仍只访问一次,但每次访问时其所在主存块已在Cache中,因此命中率同样为87.5%。

关键点: Cache容量(8KB)大于数组总大小(6KB),不存在因容量不足导致的块替换冲突。

涉及知识点详解

知识点1:虚拟存储与请求调页

虚拟存储是将主存和辅存统一管理,为用户提供一个远大于实际主存容量的虚拟地址空间。

请求调页: 程序运行时只将当前需要的页面调入主存,访问到不在主存的页面时触发缺页异常,由操作系统将所需页面从辅存调入主存。

虚拟地址划分:

  • 虚拟地址 = 虚页号 + 页内偏移
  • 页内偏移位数 = (\log_2(\text{页面大小}))
  • 虚页号位数 = 虚拟地址位数 - 页内偏移位数

缺页异常处理: CPU检测到缺页 → 保存现场 → 操作系统查找页面在辅存的位置 → 调入主存 → 更新页表 → 重新执行指令。

知识点2:Cache映射方式

映射方式特点地址划分冲突情况
直接映射每个主存块只能映射到唯一Cache行标记+行号+块内地址冲突最严重
全相联主存块可映射到任意Cache行标记+块内地址无冲突,但比较器复杂
组相联主存块映射到唯一组,组内任意行标记+组号+块内地址折中方案

组相联映射的地址划分:

[
\text{主存地址} = \text{标记} + \text{组号} + \text{块内地址}
]

  • 块内地址位数 = (\log_2(\text{块大小}))
  • 组号位数 = (\log_2(\text{组数}))
  • 组数 = (\frac{\text{Cache数据区大小}}{\text{块大小} \times \text{路数}})
  • 标记位数 = 主存地址位数 - 组号位数 - 块内地址位数

知识点3:局部性原理

  • 时间局部性: 刚被访问过的数据很可能在不久的将来被再次访问(如循环变量)。
  • 空间局部性: 刚被访问过的数据其相邻数据很可能在不久的将来被访问(如数组的顺序访问)。

知识点4:Cache命中率

[
\text{命中率} = \frac{\text{命中次数}}{\text{总访问次数}}
]

影响命中率的因素:

  • 程序访问的局部性越好,命中率越高。
  • Cache容量越大,命中率越高。
  • 块大小要适中:过大会减少块数,增加冲突;过小则无法充分利用空间局部性。
  • 组相联路数越大,冲突越少,命中率越高。
  • 替换算法(LRU、FIFO、随机)影响命中率。

第44题(计算机组成原理·指令系统)

题目

题43中C程序段在计算机M上的部分机器级代码如下:

1  00401072  C7 45 F8 00 00 00 00   mov [ebp-8], 0
2  00401079  EB 09                  jmp 00401084h
3  0040107B  8B 55 F8               mov eax, [ebp-8]
...
7  00401088  7D 32                  jge 004010bch
8  0040108A  C7 45 FC 00 00 00 00   mov [ebp-4], 0
...
19 004010AE  C7 84 82 00 20 42 00 0A 00 00 00   mov [ecx+edx*4+00422000h], 0Ah
20 ...

(1) 第20条指令的虚拟地址是多少?

(2) 第2条jmp和第7条jge都是跳转指令,操作码分别是EBH和7DH,跳转地址分别为00401084H、004010BCH,这两条指令都采用什么寻址方式?给出第2条指令jmp的跳转目标地址计算过程。

(3) 第19条mov指令的功能是"a[i][j]←10",其中ecx和edx为寄存器名,00422000H是数组a的首地址,指令中源操作数采用什么寻址方式?已知edx中存放的是变量j,ecx中存放的是什么?根据该指令的机器码判断计算机M采用的是大端还是小端方式。

(4) 第一次执行第19条指令时,取指令过程中是否会发生缺页异常?为什么?

答案

小问答案
(1)第20条指令虚拟地址 = 004010B9H
(2)均采用相对寻址;jmp目标地址 = 00401079H + 2H + 09H = 00401084H
(3)源操作数:立即寻址;ecx中存放 i×256;小端方式
(4)不会发生缺页异常,因为第1条和第19条指令位于同一页面(虚页号均为00401H)

详细解析

第(1)问:第20条指令地址

第19条指令地址为004010AEH,机器指令为 C7 84 82 00 20 42 00 0A 00 00 00,共11个字节。按字节编址:

[
004010AEH + 11 = \mathbf{004010B9H}
]

第(2)问:跳转指令寻址方式

寻址方式: 两条指令都采用相对寻址。

第2条jmp目标地址计算:

  • 指令地址:00401079H
  • 机器指令:EB 09H,EBH是操作码,09H是偏移量
  • 指令长度:2字节
  • 下一条指令地址:00401079H + 2H = 0040107BH
  • 目标地址 = 下一条指令地址 + 偏移量 = 0040107BH + 09H = 00401084H

第(3)问:寻址方式与大小端

源操作数寻址方式: 立即寻址。源操作数是立即数0Ah(十进制10),直接出现在指令中。

ecx内容: 数组 int a[24][64],每行64个int,每行占 (64 \times 4 = 256) 字节。目的操作数地址为 ecx + edx*4 + 00422000h,edx存放j,edx*4为元素内偏移。ecx对应第i行的起始偏移,即 i × 256。

大小端判断: 第19条指令机器码中立即数0Ah的编码为 0A 00 00 00,低位字节0A存放在低地址处,符合小端序规则。

第(4)问:缺页异常判断

第19条指令地址004010AEH,第1条指令地址00401072H。32位虚拟地址、4KB页面下:

  • 第1条指令虚页号:00401072H >> 12 = 00401H
  • 第19条指令虚页号:004010AEH >> 12 = 00401H

两者虚页号相同,位于同一页面。程序开始执行时第1条指令已被取指执行,说明该页面已调入主存,因此第一次执行第19条指令取指时不会发生缺页异常。

涉及知识点详解

知识点1:指令格式与指令长度

机器指令通常由操作码和地址码两部分组成。指令长度可以是固定长度或可变长度。

x86指令特点: 变长指令,长度115字节。操作码占13字节,后跟ModR/M字节、SIB字节、位移量、立即数等。

计算下一条指令地址:

[
\text{下一条指令地址} = \text{当前指令地址} + \text{当前指令长度}
]

知识点2:寻址方式

寻址方式含义示例
立即寻址操作数直接出现在指令中mov eax, 100
寄存器寻址操作数在寄存器中mov eax, ebx
直接寻址指令中给出操作数的内存地址mov eax, [1000H]
间接寻址指令中给出操作数地址的地址mov eax, [[1000H]]
寄存器间接寻址寄存器中存放操作数地址mov eax, [ebx]
相对寻址目标地址 = PC + 偏移量jmp 09H
基址寻址基址寄存器 + 偏移量mov eax, [ebx+8]
变址寻址变址寄存器 + 偏移量mov eax, [esi*4+100H]
基址变址寻址基址寄存器 + 变址寄存器×比例 + 偏移量mov [ecx+edx*4+00422000h], 0Ah

相对寻址的目标地址计算:

[
\text{目标地址} = \text{下一条指令地址} + \text{偏移量}
]

其中,下一条指令地址 = 当前指令地址 + 指令长度。偏移量是有符号数,可为正(向前跳转)或负(向后跳转)。

知识点3:大端与小端

方式规则示例:0x12345678
大端高位字节存放在低地址12 34 56 78
小端低位字节存放在低地址78 56 34 12

判断方法: 观察多字节数据在内存中的存储顺序。若低位字节在低地址,则为小端;若高位字节在低地址,则为大端。

知识点4:虚拟地址到页号的转换

32位虚拟地址、4KB页面:

[
\text{虚页号} = \text{虚拟地址} \div 4096 = \text{虚拟地址的高20位}
]

即虚页号 = 虚拟地址 >> 12。

同一页面的判断: 若两个虚拟地址的高20位相同,则它们位于同一页面,共享同一个页表项和物理页面。

第45题(操作系统·进程同步与互斥)

题目

现要求学生使用swap指令和布尔型变量lock实现临界区互斥。lock为线程间共享的变量:值为TRUE时线程不能进入临界区,值为FALSE时线程能够进入临界区。某同学编写的伪代码如下:

bool lock = FALSE;
// ── 进入区 ──
bool key = TRUE;
if (key == TRUE)
    swap key, lock;         // 原子交换 key 和 lock 的值
// ── 临界区 ──
临界区;
// ── 退出区 ──
lock = TRUE;

交换两个变量值的函数 newSwap() 代码如下:

void newSwap(bool *a, bool *b) {
    bool temp = *a;
    *a = *b;
    *b = temp;
}

(1) 伪代码中哪些语句存在错误?将其改为正确的语句(不增加语句条数)。

(2) 函数 newSwap() 是否可以用函数调用语句 newSwap(&key, &lock) 代替指令 swap key, lock 以实现临界区的互斥?为什么?

答案

第(1)问

错误一:进入区使用了if而非while。

bool key = TRUE;
while (key == TRUE)    // if 改为 while
    swap key, lock;

错误二:退出区将lock设置为TRUE(应为FALSE)。

lock = FALSE;          // TRUE 改为 FALSE

修正后的完整代码:

bool lock = FALSE;
bool key = TRUE;
while (key == TRUE)
    swap key, lock;
// ── 临界区 ──
临界区;
// ── 退出区 ──
lock = FALSE;

第(2)问

不可以。 swap指令是原子操作,由硬件保证其执行过程不可被打断。而 newSwap() 函数由多条普通C语句组成,不是原子操作。在多线程并发环境下,newSwap() 的执行过程中可能发生线程切换,导致交换操作出现竞态条件,无法保证互斥的正确性。

详细解析

第(1)问:错误分析

错误一分析:

if语句只进行一次条件判断。如果lock为TRUE(临界区已被占用),多个线程可能同时执行到此处,都通过判断并执行swap操作,导致多个线程同时进入临界区。

改为 while 后,线程会持续检查key的值。执行 swap key, lock 后,key变为FALSE,lock变为TRUE,key不再为TRUE,线程退出循环,成功进入临界区。如果lock已为TRUE,swap后key仍为TRUE,线程继续忙等待。

错误二分析:

题目规定lock值为TRUE时线程不能进入临界区,值为FALSE时能够进入。线程退出临界区后应释放锁,即让lock变为FALSE。原代码将lock设为TRUE,会导致后续所有线程都无法进入临界区,造成死锁。

第(2)问:原子性分析

newSwap(&key, &lock) 的执行包含三步:

  1. temp = *a(读key)
  2. *a = *b(将lock写入key)
  3. *b = temp(将原key写入lock)

如果线程A执行到第2步后发生线程切换,线程B也可能同时进入临界区,导致两个线程都认为自己获得了锁,互斥失效。只有硬件提供的原子swap指令才能保证整个交换过程一气呵成、不被中断。

涉及知识点详解

知识点1:临界区与互斥

临界区: 进程中访问临界资源的那段代码。

互斥: 同一时刻只允许一个进程进入临界区。

临界区互斥的四条原则:

  1. 空闲让进: 临界区空闲时,应允许一个进程进入。
  2. 忙则等待: 已有进程在临界区时,其他进程必须等待。
  3. 有限等待: 进程应在有限时间内进入临界区,避免饥饿。
  4. 让权等待: 进程不能进入临界区时,应释放CPU(但swap方案不满足此条)。

知识点2:原子操作与硬件指令

原子操作: 不可被中断的一个或一系列操作。硬件提供以下原子指令:

指令功能
TestAndSet原子地测试并设置标志
Swap原子地交换两个变量的值
CompareAndSwap (CAS)原子地比较并交换

swap指令实现互斥: 每个进程维护一个局部变量key,共享变量lock。进入区不断swap key和lock,直到key变为FALSE(表示获得了锁)。退出区将lock置为FALSE。

特点: 不满足"让权等待"原则,未获得锁的进程会忙等待(自旋),浪费CPU时间。适用于多处理器系统或临界区很短的情况。

知识点3:忙等待与自旋锁

忙等待: 进程在等待进入临界区时,持续执行循环检测,不释放CPU。

自旋锁: 基于忙等待的锁机制,适用于:

  • 多处理器系统(一个CPU自旋时,其他CPU可继续工作)
  • 临界区执行时间很短的情况

缺点: 单处理器系统中,自旋会浪费CPU时间;长时间等待会导致优先级反转问题。

第46题(操作系统·I/O管理与系统调用)

题目

进程P通过执行系统调用从键盘接收一个字符的输入,已知此过程中与进程P相关的操作包括:

  • ① 将进程P插入就绪队列
  • ② 将进程P插入阻塞队列
  • ③ 将字符从键盘控制器读入系统缓冲区
  • ④ 启动键盘中断处理程序
  • ⑤ 进程P从系统调用返回
  • ⑥ 用户在键盘上输入字符

(1) 按照正确的操作顺序,操作①的前一个和后一个操作分别是哪个?操作⑥的后一个操作是哪个?

(2) 在哪个操作之后CPU一定从进程P切换到其他进程?在哪个操作之后CPU调度程序才能选择进程P执行?

(3) 完成哪个操作的代码属于键盘驱动程序?

(4) 键盘中断处理程序执行时,进程P处于什么状态?CPU处于内核态还是用户态?

答案

第(1)问

  • 操作①的前一个操作是 ③(将字符从键盘控制器读入系统缓冲区)
  • 操作①的后一个操作是 ⑤(进程P从系统调用返回)
  • 操作⑥的后一个操作是 ④(启动键盘中断处理程序)

完整正确顺序:② → ⑥ → ④ → ③ → ① → ⑤

第(2)问

  • CPU一定从进程P切换到其他进程:操作②(将进程P插入阻塞队列)之后
  • CPU调度程序才能选择进程P执行:操作①(将进程P插入就绪队列)之后

第(3)问

操作③(将字符从键盘控制器读入系统缓冲区) 的代码属于键盘驱动程序。

第(4)问

  • 进程P处于 阻塞态
  • CPU处于 内核态

详细解析

第(1)问:操作顺序

  1. ② 将进程P插入阻塞队列: 进程P发起系统调用请求从键盘输入字符,但键盘尚无数据可读,操作系统将P设为阻塞态,插入阻塞队列,等待I/O完成。

  2. ⑥ 用户在键盘上输入字符: 用户在物理键盘上按键,产生输入事件。

  3. ④ 启动键盘中断处理程序: 键盘控制器检测到按键,向CPU发送中断信号,CPU响应中断,启动键盘中断处理程序。

  4. ③ 将字符从键盘控制器读入系统缓冲区: 键盘中断处理程序执行期间,将字符从硬件控制器读入系统缓冲区。

  5. ① 将进程P插入就绪队列: 字符读取完成后,操作系统唤醒等待该I/O的进程P,将其从阻塞队列移至就绪队列。

  6. ⑤ 进程P从系统调用返回: 进程P被调度执行后,从系统调用返回用户态,继续执行后续指令。

第(2)问:CPU切换与调度时机

操作②之后CPU一定切换: 进程P因等待键盘输入而被插入阻塞队列后,P无法继续执行,CPU必然进行上下文切换,转去执行就绪队列中的其他进程。

操作①之后调度程序才能选择P: 只有当进程P被移入就绪队列后,它才具备被CPU调度程序选中的条件。操作③时P仍处于阻塞态,调度程序无法选择它。

第(3)问:驱动程序的职责

设备驱动程序是操作系统内核中直接与硬件设备交互的模块。将字符从键盘控制器读入系统缓冲区,正是驱动程序的核心职责——完成硬件到内核缓冲区的数据传输。操作⑥是用户在物理键盘上的按键动作,属于硬件事件,不是驱动代码。

第(4)问:进程状态与CPU模式

键盘中断处理程序是内核代码,执行时CPU自然处于内核态。此时进程P正在等待I/O完成,已被插入阻塞队列,因此处于阻塞态。

涉及知识点详解

知识点1:系统调用

系统调用是操作系统提供给应用程序的接口,使应用程序能够请求操作系统内核的服务。

系统调用的执行过程:

  1. 用户程序执行系统调用指令(如 int 0x80 或 syscall),CPU从用户态切换到内核态。
  2. 内核根据系统调用号执行相应的服务例程。
  3. 服务完成后,CPU从内核态返回用户态,用户程序继续执行。

常见系统调用类型: 进程控制、文件操作、设备管理、信息维护、通信。

知识点2:进程状态转换

状态含义
就绪态进程已获得除CPU外的所有资源,等待调度
运行态进程正在CPU上执行
阻塞态进程等待某个事件(如I/O完成),即使CPU空闲也无法执行
创建态进程正在被创建
终止态进程正在被终止

状态转换:

  • 就绪 → 运行:被调度程序选中
  • 运行 → 就绪:时间片用完或被更高优先级进程抢占
  • 运行 → 阻塞:请求I/O或等待事件
  • 阻塞 → 就绪:等待的事件发生(如I/O完成)
  • 运行 → 终止:进程结束

知识点3:中断与中断处理

中断: CPU在执行程序过程中,遇到某些紧急事件需要暂停当前程序,转去执行处理该事件的程序,处理完毕后再返回原程序继续执行。

中断处理过程:

  1. 中断请求:设备向CPU发送中断信号。
  2. 中断响应:CPU在当前指令执行完毕后,检查中断信号。
  3. 保护现场:保存当前程序的PC、寄存器等。
  4. 中断处理:执行中断服务程序。
  5. 恢复现场:恢复被中断程序的上下文。
  6. 中断返回:返回原程序继续执行。

中断处理程序运行在内核态。

知识点4:设备驱动程序

设备驱动程序是操作系统内核中负责控制硬件设备的模块,是硬件与操作系统之间的接口。

主要功能:

  • 初始化设备
  • 向设备发送命令
  • 从设备读取数据
  • 处理设备中断
  • 管理设备缓冲区

驱动程序与中断处理程序的关系: 中断处理程序通常属于驱动程序的一部分,负责响应设备的中断请求,完成数据的读取或写入。

第47题(计算机网络·TCP与FTP)

题目

主机H登录FTP服务器后,向服务器上传一个大小为18000 B的文件F。假设H为传输F建立数据连接时,选择的初始序号为100,MSS = 1000 B,拥塞控制初始阈值 ssthresh = 4 MSS,RTT = 10 ms,忽略TCP段的传输时延。在F的传输过程中,H以MSS段向服务器发送数据,且始终没有错误、丢包和乱序。

(1) FTP的控制连接是持久的还是非持久的?FTP的数据连接是持久的还是非持久的?H登录FTP服务器时,建立的TCP连接是控制连接还是数据连接?

(2) H通过数据连接发送F时,F的第一个字节序号是多少?在断开数据连接的过程中,FTP服务器发送的第二次挥手的ACK序号是多少?

(3) F发送过程中,当H收到确认序号为2101的确认段时,H的拥塞窗口调整为多少?收到确认序号为7101的确认段时,H的拥塞窗口调整为多少?

(4) H从请求建立数据连接开始,到确认F已被服务器全部接收为止,至少需要多长时间?期间应用层数据的平均发送速率是多少?

答案

小问答案
(1)控制连接持久;数据连接非持久;登录时建立控制连接
(2)F第一个字节序号 = 101;第二次挥手ACK序号 = 18102
(3)确认序号2101时 cwnd = 3 MSS;确认序号7101时 cwnd = 5 MSS
(4)至少 6 RTT = 60 ms;平均发送速率 ≈ 2.4 Mbps

详细解析

第(1)问:FTP连接性质

FTP协议使用两条独立的TCP连接:

连接类型端口建立时机持久性
控制连接21客户端登录时建立持久(整个会话期间保持)
数据连接20(主动模式)每次文件传输时临时新建非持久(传完即关)

H登录服务器时,首先建立的是控制连接,用于发送用户名、密码等身份验证命令。控制连接在整个FTP会话期间一直保持,即使数据连接被大文件占满,用户仍能在控制连接上发送ABOR等紧急命令。

第(2)问:序号计算

① F的第一个字节序号: H选择的初始序号为100。三次握手中,SYN段消耗1个序号。因此数据连接建立后,第一个数据字节的序号 = 100 + 1 = 101。

② 第二次挥手的ACK序号:

  • H发送的最后一个数据字节序号 = 101 + 18000 - 1 = 18100
  • FIN段消耗1个序号,H的FIN段序号 = 18100 + 1 = 18101
  • FTP服务器第二次挥手的ACK序号 = 18101 + 1 = 18102
挥手次序方向报文序号/确认号
第1次H → SFINseq = 18101
第2次S → HACKack = 18102
第3次S → HFINseq = Y
第4次H → SACKack = Y + 1

第(3)问:拥塞窗口调整

确认序号N的含义: N表示"对方已收到字节[101, N-1]",期望下一个收到字节N。已收到字节数 = N - 101。

① 收到确认序号2101时: 已收到 (2101 - 101 = 2000) B = 2 MSS,即已收到2个ACK。

  • 初始cwnd = 1 MSS
  • 第1个ACK:cwnd = 1 + 1 = 2 MSS(慢启动)
  • 第2个ACK:cwnd = 2 + 1 = 3 MSS(慢启动)

② 收到确认序号7101时: 已收到 (7101 - 101 = 7000) B = 7 MSS,即累计收到7个ACK。

收到第几个ACKcwnd变化阶段
第3个ACK3 → 4 MSS慢启动(达到ssthresh = 4 MSS)
第4个ACK4 → 4.25 MSS拥塞避免
第5个ACK4.25 → 4.5 MSS拥塞避免
第6个ACK4.5 → 4.75 MSS拥塞避免
第7个ACK4.75 → 5 MSS拥塞避免

第(4)问:总时间与平均速率

① 总时间计算:

RTT轮次本轮cwnd本轮发送段数累计已发段数状态
11 MSS11慢启动
22 MSS23慢启动
34 MSS47慢启动→拥塞避免
45 MSS512拥塞避免
56 MSS618 ✓拥塞避免,文件刚好发完

文件共18000 B ÷ 1000 B/MSS = 18个MSS段,在第5个RTT轮次刚好全部发出。

[
\text{总时间} = \text{建立连接(1 RTT)} + \text{数据传输(5 RTT)} = 6 \text{ RTT} = 6 \times 10 = \mathbf{60 \text{ ms}}
]

② 平均发送速率:

[
\text{平均速率} = \frac{18000 \times 8 \text{ b}}{0.06 \text{ s}} = 2.4 \times 10^6 \text{ b/s} = \mathbf{2.4 \text{ Mbps}}
]

涉及知识点详解

知识点1:FTP协议

FTP(文件传输协议) 是应用层协议,使用两条TCP连接:

  • 控制连接(端口21): 持久连接,传输命令和响应(USER、PASS、RETR、STOR、ABOR等)。
  • 数据连接(端口20): 非持久连接,每次文件传输时临时建立,传完即关。

FTP工作模式:

  • 主动模式(PORT): 服务器主动连接客户端的数据端口。
  • 被动模式(PASV): 客户端主动连接服务器的数据端口。

知识点2:TCP序号与确认号

序号(seq): 本报文段所发送数据的第一个字节的序号。

确认号(ack): 期望收到的下一个报文段的第一个字节的序号。

关键规则:

  • SYN段和FIN段各消耗1个序号(虽然不携带数据)。
  • 普通数据段消耗的序号数 = 数据字节数。
  • 确认号 = 已收到的最后一个字节序号 + 1。

三次握手序号变化:

  • 客户端:SYN(seq = x)→ 服务器:SYN+ACK(seq = y, ack = x+1)→ 客户端:ACK(seq = x+1, ack = y+1)

四次挥手序号变化:

  • 主动方:FIN(seq = u)→ 被动方:ACK(ack = u+1)→ 被动方:FIN(seq = v)→ 主动方:ACK(ack = v+1)

知识点3:TCP拥塞控制

TCP拥塞控制包含四个算法:

① 慢启动:

  • 初始cwnd = 1 MSS。
  • 每收到一个ACK,cwnd加1 MSS(指数增长)。
  • 当cwnd ≥ ssthresh时,转入拥塞避免。

② 拥塞避免:

  • 每经过一个RTT,cwnd加1 MSS(线性增长)。
  • 具体实现:每收到一个ACK,cwnd加 (1/\text{cwnd}) MSS。

③ 快重传:

  • 收到3个重复ACK时,立即重传丢失的报文段,不等超时。

④ 快恢复:

  • 快重传后,ssthresh = cwnd/2,cwnd = ssthresh,进入拥塞避免。

cwnd变化曲线:

cwnd
 ^
 |        /|
 |      /  |
 |    /    |___________
 |  /                  |
 |/                    |
 +-------------------> 时间
   慢启动  拥塞避免

知识点4:RTT与传输时间

RTT(往返时间): 从发送方发送数据到收到确认的时间。

总传输时间的组成:

  • 建立连接时间(1 RTT)
  • 数据传输时间(取决于cwnd增长和文件大小)
  • 断开连接时间(通常忽略或额外计算)

计算要点: 逐轮追踪cwnd变化,确定文件在哪一轮发完,并加上最后一段的ACK返回时间。

总结

七道题知识点分布

题号科目核心知识点
41数据结构图的邻接矩阵存储、顶点度计算、算法设计
42数据结构外部排序、置换-选择排序、初始归并段生成
43计算机组成原理虚拟存储、请求调页、Cache组相联映射、命中率
44计算机组成原理指令格式、寻址方式、大小端、虚拟地址转换
45操作系统临界区互斥、swap原子指令、忙等待
46操作系统系统调用、进程状态转换、中断处理、设备驱动
47计算机网络FTP双连接、TCP序号、拥塞控制、传输时间计算

重点知识关联

虚拟存储与Cache的联合分析(第43题): 虚拟地址 → 虚页号 + 页内偏移 → 物理地址 → 标记 + 组号 + 块内地址。需要同时理解页式管理和Cache映射两个层次的地址划分。

TCP序号与拥塞控制的联合计算(第47题): 序号计算涉及SYN/FIN消耗序号;拥塞窗口增长涉及慢启动和拥塞避免的切换。两者结合,需要逐步追踪每个ACK对cwnd的影响。

原子操作与并发的本质(第45题): swap指令的原子性由硬件保证,普通函数调用无法保证。这是理解自旋锁、互斥锁等同步机制的基础。

系统调用与中断处理的层次(第46题): 用户程序 → 系统调用 → 内核 → 阻塞进程 → 硬件中断 → 驱动处理 → 唤醒进程 → 返回用户态。需要清晰区分硬件动作、驱动代码、内核调度三个层次。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐