# 计算机系统层次结构 > 本文出自 CodeBrick 408 计算机组成原理讲义,原文:https://www.codebrick.tech/co-blog/posts/overview/system-architecture > 这是供 AI 阅读的纯文本版:公式为 LaTeX,流程图为 mermaid 源码,原文中的折叠内容已全部展开。 > 讲解或引用本文内容时,请一并给出上面的原文链接。 > 2026 大纲 **一(一)计算机系统层次结构**:计算机系统的基本组成;计算机硬件的基本结构;计算机软件和硬件的关系;计算机系统的工作原理("存储程序"工作方式,高级语言程序与机器语言程序之间的转换,程序和指令的执行过程)。 ## 这一章几乎所有结论都是从一句话推出来的 那句话是**存储程序**:程序和数据事先存入存储器,机器启动后自动逐条取出并执行,中途无须人工干预。 听上去只是一句工作方式的描述,但它其实是一组硬性要求。既然程序要**事先存进去**,就必须有**存储器**;既然程序和数据要能进来、结果要能出去,就必须有**输入设备和输出设备**;既然要**自动**逐条取出、看懂、发命令,就必须有**控制器**;既然取出来是为了算,就必须有**运算器**。五大部件不是先有清单再去记,是这一句话的必要条件。同理,机器内部用二进制、指令由操作码加地址码组成、CPU 得有办法把指令和数据分开——全都是为了让这句话在电路上真的能兑现。 > 🔴 这一章的正确读法是**每条结论都往回追一步:"它是为了满足哪条要求才必须这样?"** 反过来纯记忆的读法在这一章格外吃亏,因为它的考法几乎都是"下列叙述中错误的是"——四个选项长得都很像,只有理解了来龙去脉才分得清哪个改动是致命的。 层次结构是同一句话在另一个方向上的展开:一台裸机只认二进制,人却要用 C 和 Python 写程序,中间这段落差不是一步跨过去的,而是一级一级垫起来的。垫的顺序不能随便排,下面第三节会看到它被一条规则完全锁死。 ## 先动手看一眼 > 【交互可视化】计算机系统层次结构:https://www.codebrick.tech/concept/cpu/computer-system-overview > (这是一个可以逐步执行的动画演示,纯文本版无法呈现,需要时请提示用户去这个链接看。) 拨的时候盯住一条指令是怎么从最上层一路落到电路的:每往下一层,它就被换成更多、更简单的东西——这正是"每一级由紧邻的下一级实现"这句话的样子。 ## 一、冯·诺依曼结构 五大部件、二进制表示、"靠指令周期的阶段区分指令与数据",前面已经说过全是"存储程序"的必要条件。这里补三条最容易被追问到的细节。 **第一,地址码给的是地址,不是操作数。** 一条指令 = 操作码 + 地址码,操作码指出操作类型,地址码指出**操作数存在哪儿**。所以取一个操作数通常意味着一次**额外的访存**。只有立即寻址的操作数才真的写在指令字里——"数据都在指令中直接给出"这种说法把特例当成了通例。 **第二,用二进制有三条理由**,缺一不可地支撑着这个选择:两稳态的物理器件**容易制造**(多稳态器件难做且不稳),编码、计数与运算的**规则简单**、可直接用开关电路实现,以及 1 和 0 与逻辑的真假**天然对应**、便于用逻辑门电路做算术。 **第三,区分指令与数据靠的是时序,不是内容。** 指令和数据形式上都是 0/1 串,共用同一个存储器与同一套地址空间,光看内容分不出来。CPU 的办法是看自己当前处在指令周期的哪个阶段:**取指周期**取回的一律当指令,**执行周期**取回的一律当数据——阶段是控制器的内部状态,CPU 自己当然知道。 这条判据之所以是唯一可行的,是因为别的候选都过不了"先有鸡还是先有蛋"这一关:**操作码译码**发生在"已经确定它是指令之后",是结果不是原因;**寻址方式**是指令内部的字段,同样要先把指令取回来才谈得上;**按存储位置划分**更不成立——冯·诺依曼机的指令和数据本来就共用同一个存储器、同一套地址空间。 还有一处口径差异要留意:**冯·诺依曼机的原始特征是以运算器为中心**(I/O 与存储器之间传送数据要经运算器中转),而**现代计算机改为以存储器为中心**,代表就是 [DMA](/posts/io/dma)。两种说法在各自的语境里都对,看题面问的是哪一个。寻址方式如何从地址码算出操作数地址见 [数据寻址方式](/posts/instruction/addressing-modes)。 **〔原文中此段为可折叠内容〕教材归纳的四条基本思想原文(想逐字核对四条原文时展开)** 1. 采用**"存储程序"**的工作方式; 2. 由**运算器、控制器、存储器、输入设备、输出设备**五个基本部件组成; 3. 存储器不仅能存放数据,也能存放指令,**形式上数据和指令没有区别,但计算机应能区分它们**;控制器应能控制指令的自动执行;运算器应能进行加减乘除四种基本算术运算,也能进行逻辑运算;操作人员通过输入/输出设备使用计算机; 4. 计算机内部**以二进制形式**表示指令和数据;每条指令由**操作码**和**地址码**两部分组成,操作码指出操作类型,**地址码指出操作数的地址**;由一串指令组成程序。 ## 二、计算机硬件的基本结构 现代计算机把运算器、控制器和各类寄存器做在一个芯片里,称为 **CPU**。CPU 内部分两块:**数据通路**(ALU + 通用寄存器等)负责执行运算并暂存操作数,**控制器**负责对指令译码、生成控制信号去指挥数据通路。 CPU 之外,**存储器**分内存与外存——内存含主存与 Cache,外存指磁盘、固态硬盘这类直接与主存交换信息的存储器以及海量后备存储。**外部设备**由机械部分(设备本身)和电子部分(设备控制器)组成,两部分通常可以分开;连接外设与主机的那个电子部分就是 **I/O 接口**,也叫设备控制器、I/O 控制器或 I/O 模块,键盘接口、显卡、网卡都属此列。把这些部件互连起来的传输介质是**总线**。 CPU 与主存之间靠**地址线、数据线、控制线**相连:MAR 送地址线,读/写信号走控制线指出传送方向,MDR 与数据线双向相接。各寄存器的功能与位数依据见 [CPU 的功能和基本结构](/posts/cpu/cpu-overview)。 **〔原文中此段为可折叠内容〕软件和硬件的关系:双向依赖与两类软件的划分(想核对"系统软件都包含哪些"、或答"软硬件关系"这一子项时展开)** **硬件**是计算机的实体部分,由各种电子元器件、光电机设备的实物组成,主机与外部设备都在其内;**软件**看不见摸不着,是事先编制的、具有各类特殊功能的程序,通常存放在主存或辅存中。 两者的依赖是**双向**的,这比"计算机由两部分组成"这句话本身更要紧:往上看,软件的发展**充分发挥硬件已有的功能**——同一批硬件配上更好的编译器和操作系统,实际效能差很多;往下看,软件性能的发挥又**必须依托硬件的支撑**——再好的算法也跑不过给定的时钟周期和存储带宽。所以评价一台机器的好坏,看的是**软硬件功能的总和**。这也解释了为什么性能评价必须靠实际跑程序来测,见 [计算机的性能指标](/posts/overview/performance-metrics) 里的基准程序。 | 类别 | 定义 | 举例 | |:---|:---|:---| | 系统软件 | 管理整个计算机系统、监控服务,使系统资源得到合理调度,是其他软件运行的环境 | 操作系统、语言处理程序(编译器/汇编器/解释器)、标准程序库、数据库管理系统、网络软件、服务程序(诊断、调试、连接程序) | | 应用软件 | 用户根据自身任务需要所编制的各种程序 | 科学计算程序、数据处理程序、过程控制程序、办公软件、浏览器 | 划分的判据不在谁写的,而在**服务对象是机器本身还是用户的某个具体任务**——编译器不解决任何具体应用问题,所以是系统软件。 再往深一层:软硬件在**逻辑功能上是等价的**,同一个功能软硬件都能实现(浮点运算是典型例子),所以**软硬件的分界线不固定、而且一直在向上移动**——把操作系统的一部分固化进 ROM,它就成了"固件"。软硬件功能划到哪里合适,是成本与性能的权衡,也是计算机体系结构研究的核心问题之一。指令集设计上的 [CISC 与 RISC 之争](/posts/instruction/cisc-vs-risc),本质上就是这条界线画在哪里的分歧。 ## 三、多级层次结构 计算机系统自底向上分为多级,每一级都是一台"机器",每一级都有自己的语言: | 级 | 这一级是什么机器 | 它的语言由谁执行 | |:---|:---|:---| | 第五级 | **高级语言机器 M4** | 用编译程序翻译成汇编语言程序 | | 第四级 | **汇编语言机器 M3** | 用汇编程序翻译成机器语言程序 | | 第三级 | **操作系统机器 M2** | 用机器语言解释操作系统 | | 第二级 | **传统机器 M1**(机器语言机器) | 用微程序解释机器指令 | | 第一级 | **微程序机器 M0**(微指令系统) | **由硬件直接执行微指令** | M4 再往上还可延伸出**应用语言机器**,由应用程序包翻译到下一级。往上的延伸没有终点,往下的终点就是硬件。 这张表不需要硬记,因为顺序被**一条规则**完全锁死:**每一级只能由紧邻的下一级来实现**——上一级的每条语句,要么被下一级**翻译**成一串下一级的语句,要么被下一级的程序逐条**解释**执行。**谁被谁实现,谁就在上面。** 拿这条规则去检查任意相邻两级,顺序都是唯一的:一条机器指令要拆成一串微操作才能落到电路上,所以 M1 在 M0 之上;操作系统本身就是一批用机器指令写成的程序,所以 M2 在 M1 之上;汇编器只是操作系统上的一个普通进程,所以 M3 在 M2 之上;高级语言与机器结构无关,必须先落到与机器绑定的符号层才能继续往下,所以 M4 在 M3 之上。 两处最容易被拿来出题的对调,用同一条规则一秒钟就能否掉:**汇编与高级语言不能对调**——汇编语言的每条语句与机器指令一一对应、摆脱不了具体机器,高级语言则与机器结构无关,把"不需要知道机器长什么样"的那层放到下面,等于让它去实现上面那层,做不到;**操作系统与汇编不能对调**——汇编器是操作系统上的一个进程,反过来操作系统不可能运行在汇编器之上。 > 🔴 **只有 M0 是实际机器,其上各级都是虚拟机器**(由下一级用软件或固件装出来)。而 M0 本身**只在微程序控制器的 CPU 上存在**——硬布线控制器直接由组合逻辑译出控制信号,M1 下面就直接是电路,没有微程序这一级。 **〔原文中此段为可折叠内容〕另一种画法(抽象层)、四类用户各自看到的是哪台"计算机"(对不上"抽象层"这套叫法、或题面问"某类用户工作在哪一层"时展开)** 有的教材不按"虚拟机器"来分,而按**抽象层**来分。两种画法说的是同一件事,只是抽象层把硬件一侧拆得更细——微体系结构由**逻辑电路**实现,每个基本的逻辑电路又按特定的**器件技术**实现: | 多级机器的画法 | 抽象层的画法 | |:---|:---| | 应用语言机器 / 高级语言机器 M4 | 应用程序 / 高级语言 | | 汇编语言机器 M3 | 汇编语言 | | 操作系统机器 M2 | 操作系统 | | 传统机器 M1 | **ISA(指令集体系结构)** | | 微程序机器 M0 | **微体系结构** | | —— | 逻辑电路 | | —— | 器件 / 物理实现 | 层次结构的价值正在于**透明性**——某个事物从某个角度看似乎不存在、感觉不到它实际存在,就称它对这个角度是透明的。每一层向上提供一个简洁接口,把自己的实现细节藏起来;系统程序员看到的底层机器级结构,对应用程序员来说就是透明的。不同的人工作在不同层次上,看到的"计算机"因此并不是同一台: | 用户 | 工作在哪一层 | 他看到的机器 | |:---|:---|:---| | 最终用户 | 应用程序层 | 一个应用软件 | | 应用程序员(高级语言程序员) | 语言处理系统层 | 高级语言虚拟机 | | 汇编语言程序员 | 汇编程序层 | 汇编语言虚拟机 | | 系统管理员 | 操作系统层 | 操作系统虚拟机 | | 系统程序员(机器语言程序员) | **ISA 层** | 配置了指令系统的机器语言机器 | ## 四、ISA:软件和硬件的分界面 软件与硬件之间需要一个界面,这就是 **ISA**。它规定的是**做什么**——有哪些指令、每条指令干什么、操作数放在哪里、有哪些寄存器和寻址方式;至于**怎么做**,是微体系结构的事。判别的口子很好用:**ISA 是软件可见的部分,微体系结构软件不可感知**。加法器内部用串行进位还是并行进位属于微体系结构,因为无论用哪种,加法指令的语义完全一样、程序看不出区别。 同一套 ISA 可以有很多种微架构:x86 下历代处理器的组织方式各不相同,却都能跑同一份可执行文件。相关的一组说法是**体系结构与组成的区别**:一台机器**有没有**乘法指令是结构问题,乘法指令**怎么实现**(用专门的乘法器还是用加法器循环)是组成问题——所以"指令系统相同"只能说明结构相同,说明不了组成相同。 而微体系结构与物理实现之间还隔着一层,这是本节最容易绕进去的一处: | 层 | 回答的问题 | 例子 | |:---|:---|:---| | ISA | 有哪些指令、寄存器、寻址方式? | x86-64、RISC-V | | **微体系结构** | 用什么数据通路和控制器执行这些指令? | 几级流水线、Cache 多大、是否乱序 | | 逻辑电路 | 这些部件用什么门电路搭? | 加法器用超前进位还是行波进位 | | **物理实现** | 用什么器件、工艺、版图造出来? | 晶体管尺寸、制程 | **微体系结构是 ISA 的逻辑实现层,不是物理实现层。** 两者中间还隔着逻辑电路:微架构回答"用什么样的数据通路和控制器去执行 ISA",物理实现回答"用什么晶体管、什么工艺、什么版图把这些电路造出来"。同一套微架构可以换不同工艺生产,两层关注的是两件事。 **〔原文中此段为可折叠内容〕这份契约为什么必须存在,以及 ISA 完整规定了哪九项(想核对"ISA 到底管到哪"、判断某个东西属不属于软件可见部分时展开)** **为什么必须有这样一个界面**:因为软件和硬件的演进速度完全不同,而且必须能各自独立地往前走。ISA 承担的是一份**契约**的角色——对硬件设计者,只要实现了 ISA 规定的全部指令语义,内部怎么组织完全自由;对软件,只要程序由 ISA 规定的指令组成,就能在任何遵循该 ISA 的处理器上运行,不必因为换了一代实现而重写。没有这份契约,每换一次硬件实现,全部软件都要重做一遍。 ISA 是软硬件之间接口的一个**完整定义**,具体包括: - 指令格式、指令操作类型 - 指令的操作数类型、寻址方式、可访问地址空间大小 - 程序可访问的寄存器个数、位数和编号 - 控制寄存器的定义 - I/O 空间的编址方式 - 中断结构 - 机器工作状态的定义和切换 - 输入/输出结构和数据传送方式 - 存储保护方式 哪些寄存器属于 ISA、哪些是实现的脚手架,判据见 [CPU 的功能和基本结构](/posts/cpu/cpu-overview) 里"程序员可见"那一节。 ## 五、高级语言程序与机器语言程序之间的转换 编程语言分两类:**高级语言**与底层计算机结构无关;**低级语言**与机器底层结构密切相关,也称**机器级语言**——机器语言和汇编语言都属于它。**硬件能直接执行的只有机器语言**:它是二进制编码的机器指令,本身就是电路认的东西;汇编语言只是机器语言的符号表示,必须先由汇编程序翻译;高级语言就更远了。至于 Verilog / VHDL 这类**硬件描述语言,根本不是编程语言**——它描述的是电路,综合之后变成电路而不是变成指令,不在这三层语言的表里。 **翻译程序**把一种编程语言的程序转换为等价的另一种编程语言程序。它是个统称,下辖三类: | | 输入 | 输出 | 一一对应? | |:---|:---|:---|:---| | **汇编程序**(assembler) | 汇编语言 | 机器语言目标程序 | **是**,几乎是查表替换 | | **编译程序**(compiler) | 高级语言 | 汇编语言或机器语言,**生成目标程序** | 否,一条语句可展开成多条 | | **解释程序**(interpreter) | 源程序 | 逐条翻译成机器指令并**立即执行**,**不生成目标程序** | 否 | 编译与解释的分界不在"翻不翻译"——两者都翻译——而在**翻译的成果留不留下来**。编译一次性处理整个源程序、产生落成文件的目标程序,源程序不变就不必再译;解释逐条处理、不产生目标程序,同一段代码重复执行时每次都要重新翻译,翻译开销分摊不掉、所以效率低。**所以"把高级语言源程序转换成机器目标代码文件"这件事,只有编译程序做得到。** 另外要把**链接程序**从翻译程序里摘出去:它的输入输出都是机器语言,做的是多个模块的合并与符号解析,没有发生语言转换。而且**链接这一步必不可少**,哪怕你的源程序只有一个文件——`printf` 躺在标准库的可重定位目标模块里,不链接进来程序跑不了。 汇编语言的语法与伪指令见 [汇编语言基础](/posts/instruction/assembly-basics)。以 C 语言为例,源程序到可执行文件是四个阶段: ``` hello.c → [预处理 cpp] → hello.i → [编译 cc1] → hello.s → [汇编 as] → hello.o → [链接 ld] → hello ``` | 阶段 | 工具 | 输入 → 输出 | 做了什么 | |:---|:---|:---|:---| | 预处理 | cpp | .c → .i | 处理以 `#` 开头的命令,如把 `#include` 的 .h 内容嵌入源程序。**输出仍是源程序文件** | | 编译 | cc1 | .i → .s | 生成汇编语言源程序文件 | | 汇编 | as | .s → .o | 生成**可重定位目标文件**。二进制文件,代码已是机器指令,打开显示乱码 | | 链接 | ld | .o → 可执行文件 | 把多个可重定位目标文件和标准库函数**合并**成一个**可执行目标文件** | 这个顺序被文件类型锁死,不用记:每一步的输出正好是下一步的输入,把汇编排到编译前面,汇编器根本拿不到 `.s` 文件。 ## 六、程序和指令的执行过程 可执行文件生成后放在外存里。启动执行时,操作系统内核把它加载到主存,把第一条指令的地址放进 PC,随后 CPU 就开始自动循环。一条指令的执行分四步: | 步 | 做什么 | 涉及的部件 | |:---|:---|:---| | 1 | **取指令**:PC 的内容送 MAR → 地址线;控制器送出读信号;主存读出指令送数据线;MDR 接收后传送到 IR | PC → MAR → 主存 → MDR → IR | | 2 | **指令译码**:控制器根据 IR 中指令的操作码生成控制信号,送到各执行部件 | IR → 控制器 → 各部件 | | 3 | **取操作数并运算**:按 IR 中指定的寄存器编号选出操作数送 ALU 输入端,在 ALUop 控制下完成运算 | 通用寄存器组 → ALU | | 4 | **写结果或访存**:运算类指令把结果写回寄存器并置标志位;取数/存数指令把 ALU 结果作为地址送地址线,读写主存 | ALU → 寄存器组 / 主存 | 一条指令执行结束时,**PC 里一定是下一条指令的地址**。定长指令字的机器在取指的同时就能给 PC 加上指令长度;变长指令字要等译码后知道这条指令有多长才能加。转移指令则是在执行阶段直接改写 PC——这也是它能改变控制流的全部机制。 这些操作都有先后顺序,由**时钟信号**定时,时钟信号的宽度就是一个**时钟周期**。一条指令的执行时间包含**一个或多个**时钟周期——这正是 CPI 存在的原因,接着看 [计算机的性能指标](/posts/overview/performance-metrics)。 ## 考点速记 1. 五大部件、二进制表示、"靠指令周期的阶段区分指令与数据",全部是**"存储程序"这一条前提的必要条件**,往回追一步就能推出来,不必背。 2. 层次顺序被"**每一级只能由紧邻的下一级实现**"锁死(硬件 → M0 → M1 → M2 → M3 → M4),**只有 M0 是实际机器**,且它只在微程序控制器的 CPU 上存在。 3. **ISA 定义"做什么"、是软件可见部分;微体系结构定义"怎么做"、软件不可感知**,且微架构是 ISA 的**逻辑**实现层,与物理实现之间还隔着逻辑电路。翻译程序含汇编 / 编译 / 解释三类,编译**生成**目标程序、解释不生成,链接不属于翻译程序。 **这一节在真题里被考过的形式**(下方「真题练习」逐题对应): - **问 CPU 靠什么区分指令与数据**:答"指令周期的不同阶段"。要能当场驳掉另外三个——操作码译码是结果不是原因,寻址方式同样要先取回指令,按存储位置划分则违背"共用同一存储器"。 - **问硬件能直接执行的是哪几种语言**:只有机器语言。汇编语言要经汇编程序翻译;硬件描述语言压根不是编程语言,是描述电路的。 - **问把高级语言源程序转换为机器目标代码文件的是哪个程序**:编译程序。判据是"留不留下文件"——解释程序边翻边执行、不生成目标程序,汇编程序的输入是汇编语言而不是高级语言,链接程序不做语言转换。 - **问采用二进制表示的原因(多选式 I/II/III)**:器件易造、运算规则简单、便于用逻辑门实现算术——三条都成立,通常全选。 - **挑冯·诺依曼基本思想中错误的叙述**:常设的错点是"数据都在指令中直接给出"——地址码给的是**地址**,取操作数要额外访存,只有立即寻址例外。 - **问高级语言源程序到可执行目标文件的过程顺序**:预处理 → 编译 → 汇编 → 链接。不必记,按文件类型推:`.c`→`.i`→`.s`→`.o`→可执行,每步的输出正好是下一步的输入。 - **挑计算机系统层次叙述中错误的一项**:常设的错点是把**微架构说成物理实现层**——它是 ISA 的逻辑实现层,中间还隔着逻辑电路层。 > **易错**:地址码给的是操作数的**地址**不是操作数本身,只有立即寻址是例外。"指令按地址访问、数据在指令中直接给出"这半句话正是靠这一点判错的。 > **易错**:微体系结构 ≠ 物理实现。ISA → 微体系结构 → 逻辑电路 → 物理实现,四层,别把中间两层压成一层。 > **易错**:链接程序不是翻译程序(输入输出都是机器语言),但链接这一步**必不可少**,源程序只有一个文件时也要链接标准库。 > **易错**:硬件描述语言(Verilog / VHDL)不是编程语言,不参与"机器语言 / 汇编语言 / 高级语言"这套划分。 > **易错**:只有 M0 是实际机器,其余都是虚拟机器;而 M0 只存在于微程序控制器的 CPU 上,硬布线控制器的机器里 M1 下面直接是电路。 **〔原文中此段为可折叠内容〕教材出处** - 冯·诺依曼结构的四条基本思想、计算机硬件的基本组成(CPU / 存储器 / 外设与设备控制器 / 总线):袁春风《计算机组成与系统结构》第 3 版 §1.2.1,印刷页 p4–p5 - 三类翻译程序(汇编程序 / 解释程序 / 编译程序)、ISA 规定的内容、ISA 与微体系结构的分界(串行进位与并行进位的例子):同上 §1.3.1,印刷页 p8–p9 - 计算机系统的层次化结构与四类用户、透明性的定义:同上 §1.3.2,印刷页 p10–p11 - 从 hello.c 到可执行文件的四步转换(cpp / cc1 / as / ld)与各阶段产物:同上 §1.4.1,印刷页 p11–p12 - 模型机的部件连接、一条指令的四步执行过程、定长与变长指令字下 PC 的更新、时钟周期与指令执行时间的关系:同上 §1.4.4,印刷页 p15–p16 - 采用二进制表示的三点原因:同上 §2.1.1,印刷页 p23 - 计算机系统由硬件与软件两大部分组成、硬件与软件各自的界定、"软件的发展可充分发挥硬件功能而软件性能的发挥必须依托硬件支撑"、"计算机性能的好坏取决于软硬件功能的总和"、系统软件与应用软件的划分及举例:唐朔飞《计算机组成原理》第 3 版 §1.1,印刷页 p3 - 多级层次结构(微程序机器 M0 → 传统机器 M1 → 操作系统机器 M2 → 汇编语言机器 M3 → 高级语言机器 M4,图 1.6)、虚拟机器的含义、软硬件交界面向上移动:同上 §1.1,印刷页 p4–p7 - 计算机体系结构与计算机组成的区别(乘法指令的例子):同上,印刷页 p7 ## 相关知识 [计算机的性能指标](/posts/overview/performance-metrics)|[指令执行过程](/posts/cpu/instruction-cycle)|[CPU 的功能和基本结构](/posts/cpu/cpu-overview) ## 真题练习 ## 本节对应的历年 408 真题 - 2009 年第 11 题:https://www.codebrick.tech/practice/q/co-2009-11 - 2015 年第 12 题:https://www.codebrick.tech/practice/q/co-2015-12 - 2016 年第 12 题:https://www.codebrick.tech/practice/q/co-2016-12 - 2018 年第 12 题:https://www.codebrick.tech/practice/q/co-2018-12 - 2019 年第 12 题:https://www.codebrick.tech/practice/q/co-2019-12 - 2022 年第 20 题:https://www.codebrick.tech/practice/q/co-2022-20 - 2026 年第 12 题:https://www.codebrick.tech/practice/q/co-2026-12