图源:文心一言
考研笔记整理,纯复习向,思维导图基本就是全部内容了,不会涉及较深的知识点~~
编辑: 梅头脑
参考用书:王道考研《2024年 计算机组成原理考研复习指导》
参考视频:5.1_CPU的功能和基本结构_哔哩哔哩_bilibili
目录
前言
思维导图
目录
中央处理器
CPU的功能和基本结构
组成
功能
指令执行过程
指令周期
备注
指令周期的数据流
取指周期【FE】
间址周期【IND】
执行周期【EX】
中断周期【INT】
指令执行方案
数据通路的功能和基本结构
功能
基本结构
控制器的功能和工作原理
控制器的主要功能
硬布线控制器【组合逻辑控制器】
微操作控制器
指令流水线
提高处理机并行性
指令的执行过程
流水线的设计原则
流水线的基本实现
流水线的冒险与处理
流水线的性能指标
高级流水线技术
多处理器的基本概念
计算机体系
硬件多线程
多核处理器
结语
- 负责协调并控制计算机各部件执行的指令序列,包括取指令、分析指令和执行指令
- 硬布线控制器
- 微程序控制器
- 程序计数器【PC】
- 功能
- 欲执行指令在主存中的存放地址,在取值周期末修改,可自增或执行无条件转移指令【例,JMP】
- 位数
- 存储字长【PC存放的地址,指向存储器的存储单元;存储容量决定存储单元总数,编址与寻址方式决定每次访问几个存储单元】
- 单位
- 精简指令集【RISC】:指令长度相同,以指令字为单位,也可以以字节为单位
- 复杂指令集【CISC】:指令长度不同,以字节为单位
- 备注1:PC自增
- RISC,有时"+1"为地址增加1条指令长度【+指令字长/编址单位】,而非地址增加1个编址单位
- 可通过自增硬件实现【图中标注PC+1】,或者使用多路选择器MUX连接ALU实现【15年考题】,或者使用加法器实现
- 备注2:PC跳转
- 在一条无条件跳转指令【JMP】的指令周期内,PC的值被修改2次:PC自增(+指令字长/编址单位)与跳转
- PC跳转的目标地址=指令字长/编址单位 x(1+OFFSET偏移字段),其中OFFSET表示偏移指令条数
- 指令译码器
- 仅对操作码字段进行译码,向控制器提供特定的操作信号
- 指令寄存器【IR】
- 功能:保存当前正在执行的那条指令
- 位数:取决于指令字长
- 存储器地址寄存器【MAR】
- 存放要访问的主存单元的地址
- 存储器数据寄存器【MDR】
- 存放向主存写入的信息或从主存读出的信息
- 微操作信号发生器【CU】
- 根据IR的内容(指令),PSW的内容(状态信息)及时序信号,产生控制信号
- 时序系统
- 产生各种时序信号,他们都由统一时钟分频得到
- 逻辑元件
- 一条线路的通断
- 三态门:控制功能部件是否输出,控制信号为In、out结尾
- 多条通路的选择
- 地址译码器:n位可控制2^n个设备
- 多路选择器:n位可控制n个设备
- 功能
- 接收从控制器从来的命令并执行相应的动作,对数据进行加工和处理
- 组成
- 算数逻辑单元【ALU】
- 备注:有可能和第2章 数据的表示和运算 以及第4章 指令系统 联合考试
- 进行算数/逻辑运算
- 暂存寄存器
- 功能:暂存从主存读来的数据
- 用途:CPU内部单总线方式,防止总线在两个输入端输入相同的信号
- 累加寄存器【ACC】
- 通用寄存器,用于存放ALU运算的结果信息,可以作为加法运算的一个输入端,例,执行加法、乘法指令
- 通用寄存器组
- 功能:用于存放操作数和各种地址信息等,可通过编程制定各种功能
- 位数:取决于机器字长
- 程序状态寄存器【PSW】
- 保存由算数逻辑运算指令或测试指令的结果而建立的各种状态信息,如进位【CY】、溢出【OV】
- 控制信息,如允许中断【IF】,中断置位指令
- 为CPU做出判断,如执行条件转移指令【IF、CMP】
- 移位器
- 对操作数或运算结果进行移位运算
- 计数器【CT】
- 控制乘除运算的操作步骤
- 指令控制:完成取指令、分析指令和执行指令的操作
- 操作控制:管理并产生由内存取出的每条指令的操作信号,把操作信号送往相应的部件,控制部件按要求动作
- 时间控制:为每条指令按时间顺序提供应有的控制信号
- 数据加工:对数据进行算数和逻辑运算
- 中断处理:对计算机运行过程中的异常情况和特殊请求进行处理
图源:CPU工作原理和电路图_百度知道
图源:【精选】(计算机组成原理5.1CPU的功能和基本结构)_-CSDN博客
- 指令周期
- CPU从主存中取出并执行一条指令的时间
- 不同指令的指令周期可能不同
- 机器周期
- 也称CPU周期,通过一次总线事务访问一次主存或I/O的时间
- 一个指令周期常用若干机器周期表示【例,取指、间址、执行、中断】,周期数根据指令的不同可相应调整
- 分类
- 定长的机器周期,每个机器包含的节拍数相等,通过计时区分指令阶段的转换
- 不定长的机器周期,每个机器包含的节拍数不等,通过设定操作区分指令阶段的转换
- 时钟周期
- 一个机器周期由包含若干时钟周期,是计算机工作的最小时间周期
- 工作脉冲
- 控制器的最小时间单位,起定时触发作用,一个时钟周期有一个工作脉冲
- 存储周期
- 存储器进行两次独立的存储器操作(两次读或写操作)所需的最小间隔时间
图源:404 - 墨天轮
- 以下指令的注释风格大概为唐朔飞老师的版本,感兴趣也可参考袁春风老师的版本
- R1寄存器间接寻址 减去 R2寄存器直接寻址 的数值 存入 R3寄存器
- 唐老师:((R1)) - (R2) → R3
- 袁老师:M[R[1]] - R[2]→R[3]
- MDR的内容写入PC
- 唐老师:(MDR) → PC
- 袁老师:MDR → PC
- 根据 【PC】中存放的指令地址 从【主存】中取出指令字 并放入【IR】中
- 取指操作是控制器固有的功能,不需要在操作码的控制下完成
- 可能访存,也可不访存【例,如果没有Cache与指令预取技术,则指令在主存,每个指令周期需要访存至少1次】
- 以下为单字长指令访存举栗【多字长指令需要多次访存】
- 取指
- 将PC存放的指令地址,放入地址寄存器MAR:(PC)→MAR 或 (PC)→BUS→MAR | PCout和MARin有效
- CU发出主存读信号:1→R
- 根据地址从主存取回指令,放入数据寄存器MDR等待信号稳定:M(MAR)→MDR | MDRinE、MemR有效
- 数据寄存器MDR信号稳定后,放入指令寄存器:MDR→IR | MDRout和IRin有效
- 其它
- 指令译码:OP(IR)→CU
- PC自增:(PC)+1→PC | PC+1有效
图源:计算机组成原理——指令执行过程 - 掘金
- 取操作数,根据IR中的指令字的操作码通过ALU操作产生执行结果
- 不同指令的执行周期操作不同,因此没有统一的数据流向
- 常见功能
- 运算
- 算数运算、逻辑运算、清零、移位、取反、取补码 等
- 转移
- 有条件转移【BAN、BRANCH】,无条件转移【JMP】等
- 传送
- 访存:存数【STORE】、取数【LOAD】
- 不访存:寄存器传送【MOVE】
- 可能访存,也可不访存【例,JMP指令不访存】
- 以下为加法举栗【好像真题很喜欢考访存】
- ALU、加法器、乘法器、除法器、符号扩展器/零扩展器
- 移位寄存器、取反寄存器、自增自减寄存器、特殊功能寄存器
- 门电路
- 取数【需访存】
- 将指令或寄存器中的有效地址,放入MAR:Ad(IR)→MAR,或MDR→MAR | MDRout与MARin有效
- CU发出主存读信号:1→R
- 根据有效地址取回数据,放入数据寄存器:M(MAR)→MDR | MDRinE、MemR有效
- 运算【不访存】
- 累加寄存器 + MDR的数据,求和放回累加寄存器:(ACC)+MDR→ACC | ACCout和ALUin有效
- 存数【需访存】
- 将指令中的有效地址,放入MAR:Ad(IR)→MAR | MARin有效
- CU发出主存写信号:1→W
- 将ACC寄存器的内容,放入数据寄存器:(ACC)→MDR | ACCout、MDRin和MDRinE有效
- 根据有效地址,将数据寄存器的数据存入内存指定位置:(MDR)→M(MAR)| MDRout和MemW有效
图源:CPU执行程序的原理(简化过程) - 知乎
- 比较【CMP】,生成标志位CF、ZF、OF、SF;指令寄存器与标志寄存器相与,通过或门输出标志
- 指令寻址方式,根据地址码:(1)相对寻址,PC+偏移量;(2)直接寻址
- 任务
- 与I/O设备交互时,每条执行周期结束前需查询有无中断信号,是否需要进入中断响应阶段
- 备注
- 可能访存,也可不访存【例,若有中断信号,保护程序断点需访存,若无中断信号则无需响应】
- 非指令周期必须包含的阶段
- 以下为进入中断响应举栗
- 数据流向
- 栈顶指针
- 修改栈顶指针【向低地址增加】:(SP)-1→SP
- 栈顶指针位置,存入地址寄存器:(SP)→MAR
- 保存断点
- CU发出主存写信号:1→W
- 程序断点,存入数据寄存器:(PC)→MDR | PCout和MDRin有效
- 程序断点,存入主存的栈顶位置:(MDR)→M(MAR)| MDRout和MemW有效
- 中断地址
- PC指向中断处理地址:向量地址→PC | PCin有效
- 单指令周期
- 所有指令的机器周期数相等
- 指令之间串行执行
- 多指令周期
- 不要求指令的执行时间相等
- 指令之间串行执行
- 流水线
- 指令之间并行执行
- 数据在功能部件之间传送的路径称为数据通路,包括数据通路上流经的部件
- CPU内部单总线方式
- 描述:所有寄存器的输入端和输出端都链接到一条公共通路上
- 特点:结构简单,数据容易冲突,性能较低
- 备注
- ALU需要配合暂存器使用
- 不能配合单周期处理器工作,无法同时传送不同部件需要的控制信号
- CPU内部多总线方式
- 描述:所有寄存器的输入端和输出端都连接到多条公共通路上
- 特点:相比单总线提高效率,相比专用通路降低设计成本
- 专用数据通路方式
- 描述:指令执行过程中的数据和地址的流动方向安排连接线路
- 特点:性能较高,但硬件量大
- 从主存中取出指令,并指出下一条指令在主存中的位置
- 对指令进行译码或测试,产生相应的操作控制信号,以便启动规定的动作
- 指挥并控制CPU、主存、输入和输出设备之间的数据流动方向
- CU输入信号来源
- 指令信息
- 指令的操作码是控制单元的输入信号,它与时钟配合产生不同的控制信号
- 时序系统
- 产生机器周期信号和节拍信号,一个时钟脉冲使控制单元发送一个操作命令,或发送一组需要同时执行的操作命令
- 时钟周期
- 用时钟信号控制节拍发生器,可以产生节拍,每个节拍的宽度正好对应一个时钟周期
- 机器周期
- 机器周期可视为所有指令执行过程中的一个基准时间
- 定长的机器周期:以可能出现的最大节拍数【通常是访存节拍数】作为参考
- 指令周期
- 取指、间址、执行、中断
- 反馈信号【标志】
- 控制单元有时需依赖CPU当前所处的状态产生控制信号
- 可能的来源:ACC、PSW、I/O、主存
- CPU的控制方式
- 【总线定时】:同步控制方式 | 异步控制方式 | 联合控制方式
- 硬布线控制单元设计
- 列出微操作命令的操作时间表
- 机器周期
- 每条指令完成的微操作控制信号
- 进行微操作信号综合
- 机器周期^节拍^脉冲^操作码^机器状态条件
- 画出微操作命令的逻辑视图
- 画出每个微操作信号的逻辑电路图,并用逻辑门电路实现
图源:硬布线控制器(CU)一个很懒的人的博客-CSDN博客
- 把每条机器指令编写成一个程序,每个微程序包括若干微指令,每条微指令对应一个或几个微操作命令【控制序列的最小单位】
- 微指令←微程序←指令←程序
- 微操作:执行过程
- 微命令:控制信号
- 相容性微命令:同时产生,共同完成某一些微操作
- 互斥性微命令:机器中不允许同时出现的微命令
- 备注
- 机器指令的地址码字段——操作数位置
- 机器指令的操作码字段——微程序入口地址
- 微指令的微地址码字段——下一条微指令的位置
- 微指令的微操作数字段——这一条微指令的微操作
- 微地址形成部件
- 产生初始微地址和后继微地址,以保证微地址的连续运行
- 顺序逻辑
- 控制指令执行顺序
- 可以处理中断、转移等特殊情况
- 微地址寄存器【CMAR】
- 也称微PC,接收微地址形成部件送来的微地址,为在CM中读取微指令作准备
- 控制存储器【CM】
- 微程序控制器的核心部件,用于存放个指令对应的微程序
- 控制存储器用于存放微程序,在CPU内部,用ROM实现
- 控制存储器的微程序个数 = 机器指令数+取指、间址和中断等公共的微程序数
- 微指令寄存器【CMDR】
- 也称微IR,存放从CM中取出的微指令
- 位数同微指令字长相等
- 地址译码
- 将地址码转化为存储单元的控制信号
- 微指令入口
- 执行取微指令公共操作,将 取指微程序的入口地址 送入 微地址寄存器【CMAR】
- 根据微地址寄存器【CMAR】的存放地址,微指令寄存器【CMDR】从控制存储器【CM】读出微指令
- 根据程序计数器【PC】的内容,从主存中取出机器指令,并存入指令寄存器【IR】
- 指令寄存器【IR】存放指令的操作码字段【OP】通过 微地址形成部件 产生该机器指令所对应的微指令序列的起始地址
- 微指令序列
- 根据【顺序逻辑】的标志信息,确定下一条微指令的存放地址,并送入微地址寄存器【CMAR】
- 微地址寄存器【CMAR】的信息经过地址译码,选中地址在控制寄存器【CM】中指向的指令
- 微指令寄存器【CMDR】
- 功能:从控制存储器【CM】中逐条取出对应的微指令并执行
- 操作码:微指令的操作码字段解析为 【CPU内部和系统总线】的控制信号
- 下地址:的地址码字段指出下一条指令所在的地址,流入【顺序逻辑】循环执行
- 执行完对应于一条微指令的一个微程序后,又返回到取指微程序的入口地址,开始下一条机器指令的循环
图源:微程序控制器图片_百度百科
- 直接编码方式
- 无须译码,微指令字段中每位都代表一个微命令
- 直观、执行速度快,但是指令字长过长,造成控制存储器容量极大
- 字段直接编码方式
- 把微命令分成若干小字段,把互斥性微命令组合在同一字段中,相容性微命令组合在不同字段中
- 可以缩短指令字长,但因为要通过译码电路再发出微命令,因此比直接编码方式慢
- 字段间接编码方式
- 一个字段的微命令需由拎一个字段中的某些微命令来解释
- 可进一步缩短微指令字长,但削弱了微指令的并行控制能力
- 微指令的格式
- 水平型微指令
- 指令中的一位对应一个控制信号,有时输出为1,否则为0
- 一条水平型微指令定义并执行几种并行的基本操作
- 预留每组的全0状态:表示什么都不做
- 垂直型微指令
- 一条垂直型微指令只能定义并执行一种基本操作
- 混合型微指令
- 在垂直型微指令的基础上增加一些不太复杂的并行操作
- 微程序控制单元的设计步骤
- 写出对应的机器指令的微操作命令及节拍安排
- 确定微指令格式
- 编写微指令码点
- 时间并行【流水线技术】:将一个任务分为不同的子阶段,每个阶段在不同的功能部件上并行执行,不一定需要设置重复的部件
- 空间并行【超标量处理机】:在一个处理机馁设置多个执行相同任务的功能部件,并让这些部件并行工作,例如设置很多CPU同时工作
- 备注:所有的RISC与部分CISC采用流水线技术
- 【MIPS架构】:取指【IF】、译码/读寄存器【ID】、执行/计算地址【EX】、访存【MEM】、写回【WB】
- 指令流水段个数:以最复杂指令所用的功能段个数为准
- 流水段的长度:以最复杂的操作所花的时间为准【单周期CPU】
- 指令集的特征
- 指令长度尽量一致:有利于简化取指令和译码操作
- 指令格式尽量规整:尽量保证源寄存器的位置相同
- 采用Load/Store指令:其它指令都不能访问存储器,减少操作步骤
- 数据和指令在存储器内“对齐”存放:减少访存次数
- 流水线的表示方法:时空图,横坐标表示时间,纵坐标表示空间
- 取指【IF】
- 程序计数器【PC】
- PC值作为地址从指令寄存器中取出第一条指令字
- 指令存储器【RD】
- 取出的指令字通过指令存储器【RD】的输入端送入 IF/ID 流水寄存器
- 下条指令地址的计算逻辑
- 计算PC+4
- 送入PC输入端,以便在下一条时钟周期取下条指令
- 送入 IF/ID 流水寄存器,以备后续使用
- IF/ID 流水寄存器
- 锁存从指令存储器取出的指令字
- PC+4的值
- 译码/读寄存器【ID】
- 操作控制器【OP】
- 根据 IF/ID 流水寄存器 的指令字生成各段需要的控制信号
- 寄存器堆【WE】
- 对于1w访存指令,根据指令字中的rs、rt取出寄存器堆中的值RS和RT
- 符号扩展单元
- 将指令字中的16位立即数符号扩展为32位
- 多路选择器
- 根据指令字生成指令可能的写寄存编号WriteReg#
- ID/EX 流水寄存器
- 锁存从寄存器堆中取出的两个数RS和RT(指令中两个操作数字段对应的寄存器值)
- 写寄存器编号WriteReg#
- 立即数符号扩展的值
- PC+4等后端可能用到的操作数
- 控制信号
- 执行/计算地址【EX】
- 算数逻辑单元【ALU】
- 不固定,由具体指令确定
- 例如,1w指令,EX主要计算访存地址 : ID/EX 流水寄存器 + 符号扩展后的立即数 = 访存地址 → EX/MEM流水寄存器
- 分支地址计算模块
- 计算分支地址,生成分支跳转信号 BranchTaken
- EX/MEM 流水寄存器
- ALU运算结果
- RT作为数据存储器待写入数据WriteData
- 写寄存器编号WriteReg#
- 访存【MEM】
- 数据存储器读写模块
- 不固定,由具体指令确定
- 例如,1w指令,根据 EX/MEM 流水寄存器中锁存的访存地址、写入数据和内存读写控制信号MemWrite对存储器进行读或写操作
- MEM/WB 流水寄存器
- 数据存储器读出数据
- ALU运算结果
- 写寄存器编号WriteReg#
- 写回【WB】
- 寄存器写入控制模块
- 将 MEM/WB 流水寄存器中的数据存储器读出的数据 写回 指令寄存器WriteReg#
- 备注:以上主要包含数据通路的组合逻辑。实际上,时序逻辑,以及一大堆看着脑袋疼的控制信号还没有写——
图源:体系结构笔记------MIPS流水线的简单实现 - 走看看
- 资源冲突
- 多条指令在同一时刻争用统一资源
- 解决方法
- 前一指令访存时,使后一条相关指令(以及后续指令)暂停一个时钟周期
- 单独设置数据寄存器和指令寄存器,使取数和取指令操作各自在不同的存储器中进行【数据Cache与指令Cache分离】
图源:指令相关与流水线冒险 | 哈工大(深圳) (gitee.io)
- 数据冲突
- 下一条指令会用到当前计算的结果,可分为写后读、读后写、写后写
- 主要为ID段【读寄存器】、WB段【写寄存器】与MEM段【写内存】的冲突
- 若采用按序发射、按序完成时,只可能出现“写后读”冲突【RAW相关】
- 最常见的是同一变量在WB段【写寄存器】与ID段【读寄存器】的冲突
- 简单情况:可以检查箭头的右侧(写),与其后指令的左侧(读)是否有冲突,若相邻且不采用转发技术一般都会冲突
- 复杂情况:画流水线时序图,通常间隔3条无阻塞的指令后(与第1条指令WB时间并行的MEM、EX、ID),第4条指令不会出现冲突
- 若采用乱序发射,可能出现“读后写”冲突
- 若采用多个功能部件,例如同时使用运算器,可能出现“写后写”冲突
- 解决方法
- 数据相关的指令及其后续指令都暂停一至几个周期,可分为 硬件堵塞stall 和 软件插入NOP指令(空指令)两种方法
- 设置相关专用通路,直接把前一条指令的ALU计算结果作为自己的输入数据开始计算过程,无需等到写回WB段【数据旁路技术 | 转发技术】
- 对数据相关的指令编译优化,调整指令顺序
图源:数据冒险的处理 - 哈工大(深圳) (gitee.io)
- 控制冒险
- 指令通常是顺序执行的,有时遇到改变指令执行顺序,例如执行转移、调用或返回等指令,会改变PC的值
- 解决方法
- 对转移指令进行分支预测,尽早转移目标地址
- 预取转移成功和不成功两个控制流方向上的目标指令,就是两个方向同时执行
- 加快和提前形成条件码,适用于条件转移指令
- 提高转移方向的猜准率
图源:指令相关与流水线冒险 - | 哈工大(深圳) (gitee.io)
- 空指令和暂停时钟周期属于解决冒险的万金油技术(感觉实际相当于这段串行处理:若我不用流水线,阁下又如何用流水线的冒险为难我?狗头.jpg)
- 基本公式
- ,n:任务数,Tk:完成n的任务所用的总时间
- 流水线
- 理想:,n:任务数,k:流水线段数,k+n-1:k段流水线消耗的时钟周期个数,t:时钟周期
- 极限:,n:任务数,t:时钟周期
- 基本公式
- ,T0:顺序执行的时间,Tk:使用流水线的总时间
- 流水线
- 理想:,n:任务数,k:流水线段数,k+n-1:k段流水线消耗的时钟周期个数,t:时钟周期
- 极限:,n:任务数,t:时钟周期
- 内置多条流水线(例如,度为4就是4条流水线并行),每个时钟周期内可并发多条独立指令【CPI<1】,其实质是以空间换取时间
- 需要结合动态调度技术减少并行指令的冲突,提高指令并行性
- 将多条能并行操作的指令组合成一条具有多个操作码字段的超长字指令(可达几百位),对于优化编译器和指令寄存器的要求更高
- 通过提高流水线主频的方式来提升流水线性能,能缩短流水线功能段的处理时间
- 处理器在一段时间内仅执行一条指令,因此不是数据级并行
- 仅包含一个处理器+一个存储器,可以采用流水线、多个功能部件
- 计组学习基本就是这种的
- 一个指令流同时对多个数据流进行并行处理,一般称为数据级并行技术
- 通常采用一个指令控制部件+多个处理单元,每个单元都有自己的地址寄存器,不同处理单元执行同一条指令所处理的数据是不同的
- 擅长使用处理for循环,不擅长处理switch与case
- 实现了直接操作一维数组【向量】指令集的CPU
- 在特定工作环境中极大地提升了性能,尤其是数值模拟或者相似的领域中
- 资源浪费,实际上不存在这样的计算机
- 多条指令分别处理多个不同的数据,一般称为线程级并行技术,甚至可以做到线程级以上并行
- 多计算机系统:每个计算机节点都具有各自的私有存储器,并具备独立的主存地址空间
- 多处理器系统【SMP】:具有共享的单一地址空间,通过存取指令来访问系统中的所有存储器
- 细粒度线程:多个线程之间轮流交叉执行指令;多条线程之间的指令不相关,可以乱序执行
- 粗粒度线程:仅在一个线程出现了较大的阻塞时,才切换线程
- 同时多线程:在同一个时钟周期中,发射多个不同线程中的多条指令执行
- 基本概念:将多个处理单元集成到单个CPU中,每个处理单元称为一个核;每个核可以拥有自己的Cache,也可以共享同一个Cache
- 体系要求:多指令流多数据流结构【MIMD】才能充分发挥硬件的性能
️博文到此结束,写得模糊或者有误之处,欢迎小伙伴留言讨论与批评,督促博主优化内容~
博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下,博主肝文的动力++~
博主可能会佛系更新思维导图,在这里:
计算机组成原理_梅头脑_的博客-CSDN博客https://blog.csdn.net/weixin_42789937/category_12434026.html?spm=1001.2014.3001.5482操作系统_梅头脑_的博客-CSDN博客https://blog.csdn.net/weixin_42789937/category_12434025.html博主也有整理数据结构学习笔记,在这里:
数据结构_梅头脑_的博客-CSDN博客https://blog.csdn.net/weixin_42789937/category_12262100.html?spm=1001.2014.3001.5482