上课用到的讲解

王道模拟卷第一套

这是一道非常经典的计算机组成原理题目,考查的是单周期CPU设计中**取指阶段(Instruction Fetch)**的数据通路和控制逻辑。

下面我针对题目中的5个问题逐一进行详细分析和解答。


1) 以上取指部件的输入信号有哪些?各有什么作用?(不考虑时钟信号)

观察图中虚线框(取指部件/下地址逻辑)的边界,找出从外部进入该区域的信号线:

输入信号列表及作用:

  1. Jump (跳转控制信号)
    • 作用:控制最右侧的多路选择器(MUX)。当 Jump = 1 时,选择跳转目标地址(Jump Target)作为下一条指令的地址;当 Jump = 0 时,选择顺序执行或分支跳转的结果。
  2. Branch (分支控制信号)
    • 作用:表示当前指令是否为条件分支指令(如 beq)。它作为与门的一个输入,参与控制中间的多路选择器。
  3. Zero (零标志位信号)
    • 作用:来自ALU(算术逻辑单元)的输出结果。表示比较结果是否为0(例如两数相等时为1)。它与 Branch 信号做“与”运算,决定是否满足分支跳转条件。
  4. imm16 (16位立即数)
    • 作用:来自指令的低16位。用于计算分支指令(Branch)的目标地址偏移量。
  5. Target<25:0> (26位目标地址)
    • 作用:来自指令的低26位。用于生成无条件跳转指令(Jump)的绝对目标地址。

2) 给出以上三种情况下的输入信号,信号有效为1,无效为0

题目要求针对三种情况给出 Jump, Branch, Zero 的值。特别注意:分支指令需要区分“条件满足”和“条件不满足”两种情况。

逻辑分析:

  • MUX1 (中间那个):控制信号是 Branch AND Zero。为1时选分支地址,为0时选PC+1。
  • MUX2 (右边那个):控制信号是 Jump。为1时选Jump地址,为0时选MUX1的输出。

信号状态表:

情况 Jump Branch Zero 结果分析
顺序执行指令 0 0 0 (或X) Jump=0 选下路,Branch=0 导致与门输出0,选 PC+1。注:非分支指令 Branch 必须为0,Zero 此时通常无效,但在题目”无效为0”的要求下填0。
Branch指令 (条件满足) 0 1 1 Jump=0 选下路,Branch=1Zero=1,与门输出1,选 PC+1+Offset
Branch指令 (条件不满足) 0 1 0 Jump=0 选下路,Branch=1Zero=0,与门输出0,选 PC+1 (即顺序执行)。
Jump指令 1 0 0 (或X) Jump=1 直接选上路Jump目标。Jump指令不是Branch指令,故 Branch 设为0。

(注:对于“无效为0”的要求,表格中Don’t Care的情况都填了0)


3) 为什么在该数据通路中 PC 不需要写“使能”控制信号?

答案:
因为这是单周期 CPU(Single Cycle CPU)。

  • 在单周期设计中,每一条指令的执行都刚好占用一个时钟周期。
  • 在每一个时钟周期的末尾(通常是时钟上升沿),PC 必须更新,指向下一条指令(无论是顺序的下一条、分支目标还是跳转目标)。
  • 并不存在需要保持 PC 值不变跨越多个周期的情况(不像多周期CPU或流水线中遇到冒险时需要冻结PC),因此不需要“写使能”信号来控制是否写入,而是每个时钟沿都会无条件写入新的值

4) 对于无条件跳转指令,当前可跳转的最大和最小地址之间共包含多少条指令?

分析:

  • Jump 指令的目标地址计算公式为:PC_new<31:2> = PC_old<31:28> || Target<25:0>
  • 其中高4位由当前PC决定,这意味着跳转范围被限制在当前的 256MB ($2^{28}$ 字节) 区域内。
  • 在这个区域内,具体的地址由指令中的 26位 Target 字段决定。
  • 题目问的是“包含多少条指令”。由于指令是字对齐的(4字节一条),且 PC 中存储的是 字地址(即题目中的 PC<31:2>),26位的 Target 直接对应了指令的索引。
  • 这26位可以从全0变化到全1。

计算:

  • 变化范围是 26位二进制数。
  • 包含的指令数 = $2^{26}$。
  • 数值计算:$2^{26} = 2^6 \times 2^{20} = 64 \text{ M}$ (64兆)。

答案:
共包含 $2^{26}$ 条指令(或者写 64M 条指令)。


5) 图中的 SignExt 部件起什么作用?

答案:
SignExt符号扩展单元(Sign Extender)。

  • 作用:它将指令中的 16位立即数(imm16) 扩展为 30位(根据题目PC位宽为30位),以匹配加法器的输入位宽。
  • 目的:在计算分支跳转地址时(PC + Offset),偏移量(Offset)通常是有符号数(可以向前跳转也可以向后跳转)。符号扩展能够保留原始数据的正负号(即把16位数的最高位复制填充到高位),从而保证加法运算后的地址计算结果是正确的。

这是一个非常经典的操作系统 PV操作(信号量机制) 题目,属于“生产者-消费者”问题的变种。

这个问题的核心难点在于:除了常规的互斥访问仓库和消费者同步外,还需要处理两个生产者之间的 数量制约关系

下面我将分步为你解析并给出解答。

1. 题目分析与信号量定义

我们需要解决三个层面的同步/互斥问题:

第一层:仓库的互斥访问
题目要求“不允许同时入库,也不允许边入库边出库”。

  • 这意味着 A、B、C 三个进程对仓库的访问是互斥的。
  • 信号量: mutex,初值为 1。

第二层:消费者与生产者的同步
消费者 C 需要等待仓库里有货才能取。

  • 因为仓库是“无限大”的,所以生产者 A 和 B 不需要等待“空位”,但消费者 C 需要等待“产品”。
  • 信号量: full(或 count),初值为 0,代表仓库中当前产品的数量。

第三层:生产者 A 和 B 之间的制约关系(核心难点)
题目给出的公式是:$-n \le (A\text{的件数} - B\text{的件数}) \le m$。
这其实包含两个不等式,我们需要分别设置信号量来控制。

  • 不等式 1: $A - B \le m \Rightarrow A \le B + m$

    • 这意味着:A 的产量不能比 B 多超过 $m$ 个
    • 换句话说,A 想生产,需要消耗一个“允许 A 领先的额度”。这个额度由 B 生产来补充。初始额度为 $m$。
    • 信号量: Sa,初值为 $m$。
    • 操作: A 生产前 P(Sa),B 生产后 V(Sa)
  • 不等式 2: $-n \le A - B \Rightarrow B - A \le n \Rightarrow B \le A + n$

    • 这意味着:B 的产量不能比 A 多超过 $n$ 个
    • 换句话说,B 想生产,需要消耗一个“允许 B 领先的额度”。这个额度由 A 生产来补充。初始额度为 $n$。
    • 信号量: Sb,初值为 $n$。
    • 操作: B 生产前 P(Sb),A 生产后 V(Sb)

2. 信号量设置总结

1
2
3
4
semaphore mutex = 1;  // 互斥信号量,保护仓库
semaphore full = 0; // 同步信号量,仓库中现有的产品数量
semaphore Sa = m; // 同步信号量,限制 A 比 B 多生产的数量 (A - B <= m)
semaphore Sb = n; // 同步信号量,限制 B 比 A 多生产的数量 (B - A <= n)

3. 进程工作流程代码

下面是 A、B、C 三个进程的伪代码逻辑:

生产者进程 A

A 进程生产 A 产品。它受到“A不能比B多太多”的限制,同时它的生产会解除“B不能比A多太多”的限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Process Producer_A() {
while(1) {
生产一个产品 A;

P(Sa); // 检查约束:A 是否已经比 B 多出了 m 个?如果是,等待。
// 逻辑:(A - B) 增加,消耗 Sa 配额。

P(mutex); // 申请进入仓库
将产品 A 放入仓库;
V(mutex); // 离开仓库

V(full); // 增加可供消费的产品数量,唤醒 C
V(Sb); // 关键点:A 生产了一个,意味着 (B - A) 变小了,
// 或者说 A 给了 B 更多的“领先空间”,所以增加 Sb 配额。
}
}

生产者进程 B

B 进程生产 B 产品。它受到“B不能比A多太多”的限制,同时它的生产会解除“A不能比B多太多”的限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Process Producer_B() {
while(1) {
生产一个产品 B;

P(Sb); // 检查约束:B 是否已经比 A 多出了 n 个?如果是,等待。
// 逻辑:(B - A) 增加,消耗 Sb 配额。

P(mutex); // 申请进入仓库
将产品 B 放入仓库;
V(mutex); // 离开仓库

V(full); // 增加可供消费的产品数量,唤醒 C
V(Sa); // 关键点:B 生产了一个,意味着 (A - B) 变小了,
// 或者说 B 给了 A 更多的“领先空间”,所以增加 Sa 配额。
}
}

消费者进程 C

C 进程只负责取货,它不需要关心 A 和 B 的数量差(题目说“对仓库中产品A和B的件数无上述要求”),它只关心有没有货。

1
2
3
4
5
6
7
8
9
10
11
Process Consumer_C() {
while(1) {
P(full); // 检查仓库是否有货,没货则等待

P(mutex); // 申请进入仓库
从仓库取出一件产品;
V(mutex); // 离开仓库

消费/销售产品;
}
}

4. 关键点解说(为什么这样写是对的?)

  1. 互斥锁的位置P(mutex)V(mutex) 必须紧紧包裹住“入库”和“出库”的动作,不能把 P(Sa) 或 P(Sb) 包进去,否则可能导致死锁(例如 A 占了 mutex 但因为 Sa 不足而阻塞,B 想要生产增加 Sa 却进不去 mutex)。
  2. 交叉唤醒
    • A 生产后执行 V(Sb):因为 $B-A \le n$,当 $A$ 增大,$B$ 能够合法生产的数量就增加了。
    • B 生产后执行 V(Sa):因为 $A-B \le m$,当 $B$ 增大,$A$ 能够合法生产的数量就增加了。
  3. 消费者的无关性:题目特意强调“对仓库中产品 A 和 B 的件数无上述要求”。这意味着消费者取走 A 还是取走 B,都不会影响“A 总生产数”和“B 总生产数”之间的差值关系。因此,消费者 C 不需要操作 Sa 或 Sb

这是一个关于操作系统内存管理(分页机制)的计算题,主要考查了多级页表大页(4MB页)与普通页(4KB页)混合管理地址转换以及页面置换算法

下面是简洁的步骤解析:

1) 访存次数分析

  • 013FF35AH
    • 将十六进制转为二进制,高10位是 0000 0001 00 (即索引 4H)。
    • 查表得索引 4HPage_Size0,说明是 4KB页
    • 4KB页采用二级分页,访存路径为:①访问页目录 $\rightarrow$ ②访问二级页表 $\rightarrow$ ③访问目标数据。
    • 结论:至少需要 3 次访存。
  • 015F123DH
    • 高10位是 0000 0001 01 (即索引 5H)。
    • 查表得索引 5HPage_Size1,说明是 4MB页
    • 4MB页采用一级分页(绕过内层页表),访存路径为:①访问页目录 $\rightarrow$ ②访问目标数据。
    • 结论:至少需要 2 次访存。

2) 地址转换与页框大小

  • 虚拟地址015F123DH
  • 页框大小:根据上题分析,索引 5H 对应的 Page_Size=1,所以页框大小是 4MB
  • 物理地址计算
    • 对于4MB大页,逻辑地址的高10位是页号,低22位是页内偏移。
    • 页内偏移015F123DH 的低22位。
      • 015 Hex = 0000 0001 0101 Bin。
      • 去掉高10位(0000 0001 01),剩下的高位部分是 01(二进制)。
      • 拼接剩下的 F123DH,偏移量部分为 1F123DH
    • 页框基址:表中索引 5H 对应的页框号是 163H。对于4MB页,物理基址 = 页框号 $\times$ 4MB (即左移22位)。
    • 拼接/计算
      • 物理地址高10位由 163H 提供,低22位由偏移量提供。
      • 163H = 01 0110 0011 (二进制)
      • 拼接 01 1111 0001 0010 0011 1101 (偏移量)
      • 组合二进制:0101 1000 1101 1111 0001 0010 0011 1101
      • 转十六进制:58DF123DH

3) FIFO置换后的物理地址

  • 分配策略:该进程有两个4KB页框和两个4MB页框
  • 当前状态:我们需要访问 00D40866H (高10位是 0H 0H D…即 0000 0000 11 -> 索引 3H)。
  • 缺页分析:表项 3HValid 位为0,且 Page_Size 为1(4MB页),发生缺页。
  • 置换选择 (FIFO)
    • 我们需要在已有的有效4MB页中选择一个淘汰。
    • 查看表中有效的4MB页(Valid=1Page_Size=1):
      • 索引 2H:装入时刻 180
      • 索引 5H:装入时刻 300
    • 180 < 300,索引 2H 最早装入,被淘汰
    • 新页面(3H)将使用被淘汰页面(2H)的物理页框,即页框号 254H
  • 物理地址计算
    • 虚拟地址 00D40866H
    • 偏移量(低22位):D1101,去掉高2位(属于页号),剩 01,后接 40866。即偏移量 140866H
    • 新页框号:254H
    • 拼接:
      • 254H = 10 0101 0100
      • 140866H = 01 0100 0000 1000 0110 0110
      • 组合:1001 0101 0101 0100 0000 1000 0110 0110
    • 结果95540866H

4) 页目录表项的物理地址

题目问的是**“本次更新的页目录表项的物理地址”**,也就是指向该页表项本身的指针地址,而不是缺页数据的物理地址。

  • 虚拟地址2EBCA234H
  • 页目录索引
    • 2EB... = 0010 1110 1011...
    • 高10位为 0010 1110 10 = 0BAH
  • 计算公式:页目录表项物理地址 = 页目录基址 (PDBR) + (索引 $\times$ 表项大小)
  • 数据代入
    • PDBR = 7F65 4000H
    • 索引偏移 = BAH $\times$ 4字节
    • BAH = 186 (十进制) $\rightarrow$ $186 \times 4 = 744$ = 2E8H
  • 最终结果
    7F65 4000H + 2E8H = 7F65 42E8H

这是一道非常经典的计算机组成原理题目,考查的是数据的机器级表示二进制算术运算以及**标志位(Condition Codes)**的生成机制。

题目背景明确指出:字长8位,无符号整数(Unsigned Integer),$x=68, y=80$。

以下是针对这5个小问的详细解析:


1) 寄存器 A 和 B 中的内容分别是什么?

我们需要将十进制数转换为8位的十六进制数。

  • x = 68
    • 计算:$68 = 4 \times 16 + 4$
    • 十六进制:44H
    • 二进制验证:$0100\ 0100_2$
  • y = 80
    • 计算:$80 = 5 \times 16 + 0$
    • 十六进制:50H
    • 二进制验证:$0101\ 0000_2$

答案:

  • 寄存器 A 中的内容:44H
  • 寄存器 B 中的内容:50H

2) 若 $x$ 和 $y$ 相加,寄存器 C 内容?结果是否正确?Cout?ZF?CF?

执行运算:$x + y$ 即 $44H + 50H$。

  • 计算过程:
    1
    2
    3
    4
      0100 0100  (44H, 十进制68)
    + 0101 0000 (50H, 十进制80)
    -----------
    1001 0100 (94H, 十进制148)
  • 结果分析:
    • 内容:$94H$。
    • 正确性:无符号数范围是 $0 \sim 255$ ($2^8-1$)。$68 + 80 = 148$,没有超出范围,所以结果正确
    • Cout (最高位进位):最高位(第7位)计算是 $0+0=0$,没有产生进位,所以 $Cout = 0$。
    • ZF (零标志):结果是 $94H$(不为0),所以 $ZF = 0$。
    • CF (进位标志):在加法中,无符号数的溢出由 CF 表示,且 $CF = Cout$。因为没有进位,所以 $CF = 0$。

答案:

  • 寄存器 C 的内容:94H
  • 运算结果是否正确:正确
  • Cout:0
  • ZF:0
  • CF:0

3) 若 $x$ 和 $y$ 相减,寄存器 D 内容?结果是否正确?Cout?ZF?CF?

执行运算:$x - y$。在计算机底层,减法通常通过加补码来实现,即 $A - B = A + (\sim B + 1)$(这里的 $\sim B$ 指按位取反)。

  • 计算过程:
    • $y = 50H (0101\ 0000)$
    • $-y$ 的机器数(补码形式) = $1010\ 1111 + 1 = 1011\ 0000$ (B0H)
    • 执行加法:$44H + B0H$
    1
    2
    3
    4
      0100 0100  (44H)
    + 1011 0000 (B0H)
    -----------
    1111 0100 (F4H)
  • 结果分析:
    • 内容:$1111\ 0100$ 即 F4H
    • 正确性:十进制实际上是 $68 - 80 = -12$。但是这是无符号数运算,无符号数不能表示负数。结果 $F4H$ 对应十进制 $244$,这显然不是 $-12$。发生了借位(Underflow),所以结果不正确
    • Cout (最高位进位):最高位计算 $0+1=1$,没有产生向更高位的进位(即没有进位输出),所以 $Cout = 0$。
    • ZF (零标志):结果 $F4H \neq 0$,所以 $ZF = 0$。
    • CF (进位/借位标志)
      • 在减法中,CF 表示借位
      • 逻辑判断:因为 $x < y$ ($68 < 80$),不够减,必然发生借位,所以 $CF = 1$。
      • 硬件关系:在大多数处理器(如x86、常见教材模型)中,减法的 $CF$ 标志通常定义为 $CF = \text{Cout} \oplus 1$(即 Cout 取反)。因为加法器做减法时没有产生进位($Cout=0$),说明不够减,需要“借位”,因此 $CF = 1$。

答案:

  • 寄存器 D 的内容:F4H
  • 运算结果是否正确:不正确(发生了下溢)
  • Cout:0
  • ZF:0
  • CF:1

4) Cout 的含义是什么?它与 CF 标志的关系是什么?

这是一个考察底层原理的问题。

答案:

  • Cout 的含义:$Cout$ 是加法器(ALU)最高位(MSB)运算后产生的物理进位输出信号。它仅表示最高位是否向更高位进位。
  • 与 CF 标志的关系
    • 执行加法时:$CF = Cout$。即如果最高位有进位,说明无符号数加法溢出(结果太大,超过了255)。
    • 执行减法时:$CF = \overline{Cout}$ (Cout 取反)或者说 $CF = 1 - Cout$。
      • 减法是通过“加补码”实现的。
      • 如果 $A - B$ 过程中 $Cout=1$,说明 $A \ge B$,没有借位,此时 $CF=0$。
      • 如果 $A - B$ 过程中 $Cout=0$,说明 $A < B$,需要借位,此时 $CF=1$。

5) 无符号整数通常用来表示什么?为什么通常不对无符号整数的运算结果判断溢出?

这里需要区分“溢出标志 OF (Overflow Flag)”和“进位标志 CF (Carry Flag)”。

答案:

  • 用途:无符号整数通常用来表示内存地址索引计数器、或者图像像素值等只有非负值的物理量。
  • 为什么不判断“溢出”(指OF标志)
    • 概念区分:在计算机术语中,“溢出(Overflow, OF标志)”专门指**带符号整数(Signed Integer)**运算结果超出了补码能表示的范围(破坏了符号位)。
    • 无符号数的特性:无符号数没有符号位,所有位都是数值位。
    • 判断依据:对于无符号数,结果超出范围(比如超过255或低于0)是由**进位标志(CF)**来指示的,而不是由溢出标志(OF)来指示的。因此,我们关注的是 CF,而不是 OF。

这是一道非常经典的计算机组成原理题目,主要考察的是MIPS指令格式大立即数的构建以及符号扩展带来的影响。

下面我分步为你详细解析这三个问题。


第一问:立即数位数与为何不能直接送入

问题分析:

  1. 立即数占多少位?
    观察代码中的 lui(Load Upper Immediate)和 ori(Or Immediate)指令。题目设定 $A$ 是32位地址,被分成了 $A_upper$ 和 $A_lower$ 两个16位的部分。

    • lui 指令加载的是 $A_upper$(16位)。
    • ori 指令操作的是 $A_lower$(16位)。
    • 结论:该指令系统(通常指MIPS)中的立即数占 16 位
  2. 为什么不能直接将 32 位地址 $A$ 送入寄存器?
    这是由指令字长决定的。

    • 在标准的 32 位指令集(如 MIPS)中,一条指令的总长度固定为 32 位。
    • 一条指令通常包含操作码(Opcode)、目标寄存器、源寄存器等字段。如果想在指令中直接包含一个 32 位的立即数(地址),那么光这一个数就占满了 32 位,完全没有空间去放操作码和寄存器编号了。
    • 结论:因此,必须将 32 位的大常数拆分成两个 16 位的部分,分两次加载。

答案总结:

  • 立即数占 16 位。
  • 因为一条指令的总长度有限(通常为32位),无法在一条指令中同时容纳操作码、寄存器地址和完整的32位立即数,所以需要分两次操作来合成32位地址。

第二问:填空与操作解释

代码逻辑分析:

  • lui t0, A_upper:它的功能是将 16 位立即数 $A_upper$ 放到寄存器 t0高 16 位,并将低 16 位清零。
    • 题目注释:“将A_upper 的 (①) 添加16个0”。因为数据放到了高位,所以是在低位补了0
  • ori t0, t0, A_lower:它的功能是将 t0 与 16 位立即数 $A_lower$ 进行“或”运算。
    • 在进行逻辑运算时,16位立即数通常进行无符号扩展(Zero Extension),即高 16 位补 0。
    • 题目注释:“将A_lower 的 (②) 添加16个0”。因为是无符号扩展,所以是在高位补了0
    • lui 之后,t0 的状态是 [A_upper][0000]ori 的立即数扩展后是 [0000][A_lower]
    • 执行**“或”(OR)**操作:[A_upper][0000] OR [0000][A_lower] = [A_upper][A_lower]。这正是我们想要的拼接效果。

答案总结:

  • ① 低位lui 把立即数移到高位,低位补0。
  • ② 高位ori 对立即数进行零扩展(Zero Extension),高位补0。
  • ③ “或”:通过逻辑或运算将高位部分和低位部分拼合在一起。

第三问:A_upper_adjusted 的计算 (核心难点)

问题背景:
第二种方法使用指令 lw s0, A_lower(t0)
这条指令的执行过程是:Effective_Address = Reg[t0] + SignExtend(A_lower)
关键在于 SignExtend(符号扩展)

原理解析:

  1. 符号扩展的陷阱
    lw 指令在计算地址时,会将 16 位的偏移量($A_lower$)视为有符号数,并进行符号扩展至 32 位,然后再与基址寄存器(t0)相加。
  2. 分类讨论
    • 情况 1:$A_lower$ 的最高位(第15位)是 0
      • 符号扩展后,高 16 位全是 0。
      • 加法运算:Address = (A_upper << 16) + A_lower
      • 此时无需调整,A_upper_adjusted = A_upper
    • 情况 2:$A_lower$ 的最高位(第15位)是 1
      • 符号扩展后,高 16 位全是 1(即 0xFFFF)。这意味着 $A_lower$ 被当成了一个负数
      • 例如:如果 $A_lower$ 是 0x8000,符号扩展后变成 0xFFFF8000
      • 此时如果直接相加:Total = (A_upper << 16) + 0xFFFF8000
      • 注意 0xFFFF8000 其实等于 -0x8000 (如果视作整体32位偏移) 或者理解为它向高位“借了1”。
      • 实际上,加法会使得高 16 位的值减 1。
      • 修正方法:为了抵消这个“减 1”的影响,我们需要预先在 $A_upper$ 上 加 1

答案总结:

  • 计算方法
    • 若 $A_lower$ 的最高位为 0,则 A_upper_adjusted = A_upper
    • 若 $A_lower$ 的最高位为 1,则 A_upper_adjusted = A_upper + 1
  • 理由
    lw 指令在计算有效地址时,会对 16 位偏移量 $A_lower$ 进行符号扩展
    如果 $A_lower$ 的最高位为 1,符号扩展会使其对应的高 16 位全为 1(相当于从高位借位/减去了一个高位的1)。为了补偿这个借位,必须预先将高位地址 $A_upper$ 加 1,以保证最终计算出的物理地址正确。

这是一个经典的**多生产者-单消费者(Producer-Consumer)**问题的变种。

这个问题的核心在于处理资源的数量限制(缓冲区大小)和配对关系(1个A配3个B)。我们需要用信号量来控制缓冲区是否已满(生产者等待)以及缓冲区是否有足够的零件(消费者/装配线等待)。

下面是详细的解答:

1. 信号量定义 (Semaphore Definition)

我们需要定义两类信号量:

  1. 同步信号量(表示资源数量): 用来告诉装配线现在有多少个零件A和零件B。
  2. 资源限额信号量(表示缓冲区空位): 用来限制生产者的生产,防止堆积超过题目要求的上限(A最多1个,B最多3个)。
1
2
3
4
5
6
7
8
9
10
11
// 信号量定义
semaphore full_A = 0; // 缓冲区中已有的零件A的数量,初值为0
semaphore empty_A = 1; // 零件A缓冲区的空位数,初值为1(题目规定最多等待1个A)

semaphore full_B = 0; // 缓冲区中已有的零件B的数量,初值为0
semaphore empty_B = 3; // 零件B缓冲区的空位数,初值为3(题目规定最多等待3个B)

// 可选:互斥信号量(如果认为放入传送带需要互斥访问)
// 在此类题目中,重点通常在数量同步上,但加上mutex更严谨
semaphore mutex_A = 1; // 保护A的缓冲区
semaphore mutex_B = 1; // 保护B的缓冲区

2. 进程工作流程 (Process Logic)

题目中有三类进程:零件A生产线(1个)、零件B生产线(3个)、装配生产线(1个)。

(1) 零件A生产线进程 (Producer A)

它的逻辑是:看A区有没有空位 -> 有空位则放入 -> 告诉装配线A来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
Process Producer_A() {
while(1) {
生产一个零件 A;

P(empty_A); // 检查A区是否有空位。若已有1个A在等待,这里会阻塞

// P(mutex_A); // (可选) 进入临界区
将零件 A 放入装配线输入区;
// V(mutex_A); // (可选) 退出临界区

V(full_A); // 增加A的计数,通知装配线有一个A可用了
}
}

(2) 零件B生产线进程 (Producer B)

共有3条这样的生产线,逻辑相同。它的逻辑是:看B区有没有空位 -> 有空位则放入 -> 告诉装配线B来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
Process Producer_B() { // 共有3个这样的进程并行执行
while(1) {
生产一个零件 B;

P(empty_B); // 检查B区是否有空位。若已有3个B在等待,这里会阻塞

// P(mutex_B); // (可选) 进入临界区
将零件 B 放入装配线输入区;
// V(mutex_B); // (可选) 退出临界区

V(full_B); // 增加B的计数,通知装配线有一个B可用了
}
}

(3) 装配生产线进程 (Assembler)

它的逻辑是:等待集齐1个A和3个B -> 取走零件进行组装 -> 释放空位给生产者(激活生产者)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Process Assembler() {
while(1) {
// 1. 获取零件 A
P(full_A); // 等待有一个A可用

// 2. 获取零件 B (需要3个)
P(full_B); // 等待第1个B
P(full_B); // 等待第2个B
P(full_B); // 等待第3个B

// 注意:物理上取走零件的动作通常在这里发生
// 此时已集齐 1A + 3B

完成一个产品的组装;

// 3. 释放空间,激活等待的生产线
V(empty_A); // 产品取走了,A区空出一个位置,允许A生产线继续生产

V(empty_B); // 产品取走了,B区空出第1个位置
V(empty_B); // 产品取走了,B区空出第2个位置
V(empty_B); // 产品取走了,B区空出第3个位置
}
}

3. 代码注解与逻辑分析

  1. 关于 P(empty_A)P(empty_B)

    • 题目规定“若已有1个A在等待,A生产线需等待”,这正是 empty_A 初值设为1的作用。当A生产了一个并执行 P 后,empty_A 变为0。如果它再生产一个想放入,再次执行 P 时就会阻塞,直到装配线取走原来的A并执行 V(empty_A)
    • 同理,empty_B 初值为3,允许缓冲区最多堆积3个B。
  2. 关于装配线的 P 操作

    • 装配线需要 1个A 和 3个B。因此它必须执行一次 P(full_A) 和 三次 P(full_B)。只有当这四个条件都满足时(即仓库里确实有货),它才能进行组装。如果货不够,装配线就会阻塞在相应的 P 操作上。
  3. 关于装配线的 V 操作

    • 题目要求“然后激活等待的其他零件生产线”。
    • 当装配完成后,实际上是把缓冲区里的零件取走了(清空了)。
    • 执行 V(empty_A) 会让 empty_A + 1,如果此时 Producer_A 正在阻塞,它就会被唤醒。
    • 同理,需要连续执行三次 V(empty_B),把占用的3个B的名额全部释放出来,这样如果有被阻塞的 B 生产线,它们就能继续送入零件。

上课用到的讲解
http://cathylove47.github.io/2025/11/24/上课用到的讲解/
作者
cathy
发布于
2025年11月24日
许可协议