一、计算机结构
冯·诺依曼模型(Von Neumann architecture)是一种计算机体系结构的基本框架,由冯·诺依曼于1945年提出。它是现代计算机设计和实现的基础,被广泛应用于大多数通用计算机系统。
冯·诺依曼模型的主要特点包括:
- 存储程序:冯·诺依曼模型采用了存储程序的概念,即指令和数据都以二进制形式存储在存储器中。这使得计算机能够按照程序的顺序依次执行指令,而不需要在每个指令之间手动重新连接线路。
- 单一总线结构:冯·诺依曼模型使用单一总线结构,将处理器、存储器和输入输出设备等组件连接到同一个总线上。总线负责在这些组件之间传输数据和控制信号。
- 指令执行周期:冯·诺依曼模型按照指令执行周期的概念,将计算机的操作划分为多个阶段。通常包括取指令(Instruction Fetch)、指令解码(Instruction Decode)、执行(Execute)、访存(Memory Access)和写回(Write Back)等阶段。
- 存储器和处理器分离:冯·诺依曼模型将存储器和处理器作为独立的组件进行设计。处理器负责执行指令和计算操作,而存储器用于存储指令和数据。
- 顺序执行:冯·诺依曼模型中的指令按照顺序依次执行,每条指令的执行顺序由程序控制。指令的执行结果可以作为下一条指令的输入数据。
运算器、控制器是在中央处理器里的,存储器就是内存,输入输出设备则是计算机外接的设备,比如键盘就是输入设备,显示器就是输出设备。大部分现代计算机结构仍然基于冯·诺依曼模型。
内存
内存是计算机数据存储的地方,存储的区域是线性的,存储数据的基本单位是字节(byte),1 字节等于 8 位(8 bit),每一个字节都对应一个内存地址。
内存的地址是从 0 开始编号的,然后自增排列,最后一个地址为内存总字节数 - 1,内存的读写任何一个数据的速度都是一样的。
线性(linear):通常指的是一种顺序排列的、连续的、有序的结构或空间。
中央处理器(Central Processing Unit)
即常说的CPU,是计算机系统中的核心组件,也被称为处理器或微处理器。它主要负责执行计算任务和控制计算机的各种操作。
CPU 的结构可以分为以下几个主要组成部分:
- 控制单元(Control Unit):控制单元是 CPU 的一部分,负责解释和执行指令。它从内存中获取指令,并根据指令的要求控制其他组件的操作。控制单元包括指令寄存器(Instruction Register)和程序计数器(Program Counter)等。
- 算术逻辑单元(Arithmetic Logic Unit,ALU):ALU 是 CPU 的核心计算部分,执行各种算术和逻辑运算,如加法、减法、乘法、除法和逻辑比较等。ALU 通常包括运算器(Adder)、逻辑门(Logic Gates)和状态寄存器(Status Register)等。
- 寄存器(Registers):寄存器是 CPU 内部的高速存储器,用于暂时存储指令、数据和中间结果。寄存器提供了快速的数据访问,对于 CPU 的运算和控制非常重要。常见的寄存器包括累加器(Accumulator)、通用寄存器(General Purpose Registers)、指针寄存器(Pointer Registers)和标志寄存器(Flag Register)等。
- 数据通路(Data Path):数据通路是 CPU 中用于传输和处理数据的路径。它由多个数据总线(Data Bus)和控制总线(Control Bus)组成,用于在寄存器、ALU 和内存之间传递数据和控制信号。
- 缓存(Cache):缓存是 CPU 内部的高速存储器,用于暂时存储频繁访问的数据。缓存通过提供快速的数据访问,减少对主内存的访问延迟,提高 CPU 的性能。
- 时钟(Clock):时钟是 CPU 的定时器,用于同步 CPU 中的各个组件的操作。时钟以固定的速度发出周期性的信号,控制 CPU 的节奏和操作的顺序。
- 总线(Bus):总线是 CPU 内部各个组件之间进行数据和控制信号传输的通道。它包括地址总线(Address Bus)、数据总线(Data Bus)和控制总线(Control Bus),用于在 CPU 内部和外部设备之间传递数据和指令。
总线
总线(Bus)是计算机系统中用于数据传输和通信的一组电子线路。它在计算机内部连接了不同的组件,如处理器、内存、输入输出设备等,以便它们之间可以进行数据和控制信号的交换。
总线通常由多个并行的电子线路组成,每条线路可以传输一个二进制位(bit)。这些线路包括数据线、地址线和控制线。
- 数据线(Data Bus):数据线用于在计算机组件之间传输数据。它的宽度决定了每次传输的数据量,例如,一个32位宽的数据线可以同时传输32个二进制位,而一个64位宽的数据线可以同时传输64个二进制位。
- 地址线(Address Bus):地址线用于指定内存或设备的位置。它的宽度决定了计算机的寻址能力,即可以寻址的内存或设备的数量。例如,一个32位宽的地址线可以寻址的内存空间大小为2^32(约为4GB)。
- 控制线(Control Bus):控制线用于传输控制信号,例如读写控制、中断请求、时钟信号等。它负责控制计算机各个组件的操作和协调。
线路位宽与 CPU 位宽
在计算机系统中,位(bit)是计量信息的最小单位,它可以表示二进制的0或1。位宽(bit width)指的是计算机系统中用于表示数据的二进制位的数量。
以数据总线为例,如果一个计算机系统的数据总线宽度为32位,那么它可以同时传输32个二进制位的数据。这意味着在每个时钟周期内,该系统可以在数据总线上传输32位的数据。
类似地,如果一个计算机系统的数据总线宽度为64位,那么它可以同时传输64个二进制位的数据。
CPU位宽(CPU Word Size):CPU位宽指的是中央处理器(CPU)内部用于处理整数数据的寄存器的位宽。它决定了CPU一次能够处理的最大整数数据的位数。例如,一个32位的CPU可以处理32位的整数数据,而一个64位的CPU可以处理64位的整数数据
线路位宽主要与数据传输和通信相关,它影响数据总线的传输能力和带宽。而CPU位宽主要涉及到CPU内部的寄存器和数据处理能力,它决定了CPU一次能够处理的最大整数数据的大小。
8bit = 1byte,所以,32位cpu一次可以计算4字节,64位cpu一次可以计算8字节。
因此,64位cpu可以兼容32位,但是32位不兼容64位,因为它无法计算超过32位数字的数据;而如果计算的数额不超过 32 位数字的情况下,32位和64位cpu之间没什么区别的,只有当计算超过 32位数字的情况下,64位的优势才能体现出来。
注:线路位宽和CPU位宽可以不同。例如,一个计算机系统的数据总线宽度可以是32位,但它的CPU位宽可以是64位。这意味着数据在传输时可以使用32位的线路进行传输,但CPU在内部处理数据时可以使用64位的寄存器进行操作。
1.1 CPU指令周期
CPU的指令周期(Instruction Cycle)是指执行一条机器指令所经历的基本步骤或阶段。它描述了CPU执行指令的基本流程,包括获取指令、解码指令、执行指令和存储结果等操作。
通常,CPU的指令周期由以下几个阶段组成:
- 取指令(Instruction Fetch):从内存或缓存中获取下一条要执行的指令,并将其存储在指令寄存器中。
- 指令解码(Instruction Decode):对取到的指令进行解码,确定指令的类型和操作数。
- 执行指令(Instruction Execution):根据指令的操作类型,执行相应的操作,例如算术运算、逻辑运算、存储访问等。
- 存储结果(Write-back):将执行指令的结果存储到寄存器或内存中,以便后续指令使用。
在每个指令周期内,CPU会按照上述流程依次执行指令。不同的指令可能需要不同的指令周期数来完成。指令周期的长度取决于CPU的设计和实现,它通常由CPU的时钟频率和指令的复杂性决定。
1.1.1 指令
指令(Instruction)是计算机程序中的基本单元,用于告诉计算机执行特定的操作或完成特定的任务。指令定义了计算机的操作行为,包括算术运算、逻辑运算、数据传输、控制流程等。
指令通常由操作码(Opcode)和操作数(Operand)组成。操作码用于指定要执行的操作类型,如加法、乘法、跳转等。操作数则提供了操作所需的数据或地址。
指令的格式和编码方式依赖于计算机体系结构和指令集架构。常见的指令集架构包括x86、ARM、MIPS等。每种指令集架构都有自己的指令集,其中定义了一组特定的指令和操作码,以及对应的操作数格式和编码规则。
指令的执行是由CPU负责的。CPU根据指令的操作码和操作数,执行相应的操作。执行过程中,CPU可能需要从寄存器或内存中读取数据,并进行计算、存储结果等操作。
编程语言中的代码被编译或解释成机器指令,然后由CPU执行。指令的顺序和组合形成了程序的逻辑和功能。
1.1.2 指令类型
计算机指令可以根据其功能和操作类型进行分类。以下是一些常见的指令类型:
- 数据传输指令:数据传输指令用于在寄存器和内存之间传输数据,包括将数据从内存加载到寄存器(Load)以及将数据从寄存器存储到内存(Store)的指令。
- 算术和逻辑指令:算术和逻辑指令用于执行数学运算和逻辑运算,例如加法(Add)、减法(Subtract)、乘法(Multiply)、除法(Divide)、与(AND)、或(OR)、非(NOT)等。
- 控制流指令:控制流指令用于控制程序的执行流程,包括条件分支(Branch)指令和无条件跳转(Jump)指令。条件分支指令根据条件的满足与否来决定下一条要执行的指令,而无条件跳转指令直接跳转到指定的地址。
- 函数调用和返回指令:函数调用和返回指令用于实现程序的模块化和函数调用,包括调用函数(Call)和返回(Return)指令。调用指令将程序控制权转移到指定的函数,并将函数参数传递给被调用函数,而返回指令将程序控制权返回到调用函数的位置。
- 位操作指令:位操作指令用于对数据的位进行操作,包括位与(Bitwise AND)、位或(Bitwise OR)、位异或(Bitwise XOR)、位移(Bitwise Shift)等。
- 浮点运算指令:浮点运算指令用于执行浮点数运算,包括浮点加法(Floating Point Add)、浮点减法(Floating Point Subtract)、浮点乘法(Floating Point Multiply)、浮点除法(Floating Point Divide)等。
- 字符串操作指令:字符串操作指令用于对字符串数据进行操作,如复制字符串(Move String)、比较字符串(Compare String)、填充字符串(Fill String)等。
- 系统调用指令:系统调用指令用于向操作系统请求服务,例如打开文件、读取数据、写入数据等。这些指令允许程序与底层操作系统进行交互。
1.2 时钟周期,时钟频率
时钟周期:指时钟信号在一个完整的周期内完成一个高低电平的转换,每次从高电平到低电平再到高电平的转换被称为一个时钟周期。
时钟频率:指在单位时间内时钟信号的发生次数,通常以赫兹(Hz)为单位表示,表示每秒钟发生的时钟周期数,也称主频。如1 GHz的时钟频率表示每秒发生10^9个时钟周期(每秒钟发生1G次时钟周期)。
时钟周期和时钟频率之间的关系:
时钟周期 = 1 / 时钟频率
时钟频率 = 1 / 时钟周期
查看时钟频率:
lscpu
CPU MHz: 799.890 #处理器的时钟频率,以兆赫兹(MHz)为单位
CPU max MHz: 3000.0000 #处理器的最大时钟频率
CPU min MHz: 800.0000 #处理器的最小时钟频率
1.3 程序的CPU执行时间
程序的 CPU 执行时间是指一个程序在 CPU 上执行所需的时间。它是衡量程序运行速度的指标之一,通常以时钟周期或秒为单位表示。
计算公式:CPU执行时间 = 指令数 × CPI × 时钟周期
- 指令数(Instruction Count):表示程序中的指令总数,它反映了程序的复杂度和规模。可通过编译器优化
- CPI(Cycles Per Instruction):表示每条指令的平均执行周期数,即每个指令执行所需的时钟周期数的平均值。可通过流水线技术(Pipeline),让一条指令需要的 CPU 时钟周期数尽可能的少
- 时钟周期(Clock Cycle):表示每个时钟周期的时长。通过加大主频优化,如超频技术。
超频(Overclocking):指将计算机硬件(通常是处理器、内存、显卡等)的工作频率提高到超过其标称频率的操作,通过增加时钟频率或者改变时钟倍频等参数,超频可以使硬件组件以更高的频率运行。
超频的目的是提高计算机性能,使其能够更快地执行任务;但是,超频会造成增加能耗,产生过多的热量,对硬件组件造成损害等问题。
二、单核,多核
在日常工作中,关于服务器性能指标,常提到cpu是双核,四核,八核,物理核和虚拟核等,不熟悉的话容易搞混,接下来了解下相关概念。
路(Socket):路是指主板上的物理插槽,用于安装和连接处理器(CPU)。每个路通常对应一个物理处理器插槽,可以容纳一个或多个物理处理器。通常有单路,两路,四路,多路。
CPU(Central Processing Unit):CPU是计算机中的中央处理器,负责执行计算机指令和处理数据。它是计算机系统的核心组件,包含算术逻辑单元(ALU)、控制单元和寄存器等部件。
物理核(Physical Core):物理核是指处理器中实际的物理处理单元。每个物理核都能够独立执行指令流,并包含独立的执行单元和缓存。通常有单核,双核,多核,多核处理器具有多个物理核。
逻辑核(Logical Core):也称虚拟核或逻辑CPU,是通过超线程技术实现的。超线程是一种在物理核上模拟多个逻辑核的技术。通过超线程,一个物理核可以模拟出多个逻辑核,从而在某种程度上提高并发性能。每个逻辑核都可以独立执行指令流,但它们共享物理核的执行单元和缓存。
超线程(Hyper-Threading):超线程是一种在物理核上实现多线程并发执行的技术。它通过在物理核上模拟多个逻辑核,使得一个物理核可以同时执行多个线程。超线程可以提高并发性能,尤其在多任务和多线程应用程序中。
注:开启超线程后,逻辑核个数是通常物理核数的2倍。
查看路的相关信息(需root权限):
dmidecode -t processor
查看路的数量,即CPU插槽数量,也就是物理CPU的个数:
lscpu | grep "Socket(s):"
cat /proc/cpuinfo | grep "physical id" | sort -u | wc -l
dmidecode -t processor | grep "Socket Designation:" | wc -l
查看每个cpu的物理核数,及总核数:
[root@test ~]# lscpu | grep -E "Socket\(s\)|Core\(s\) per socket|Thread\(s\) per core"
Thread(s) per core: 2 #每个物理核的逻辑核数
Core(s) per socket: 10 #每个cpu的物理核数
Socket(s): 2 #物理cpu个数
#物理核总核数(不包含逻辑核)
[root@test ~]# lscpu | grep "Core(s) per socket" | awk '{print $NF}'
查看逻辑核总核数:
lscpu | grep "^CPU(s):"
查看超线程是否开启:
#每个物理核的逻辑核数量,如果大于1,即开启了超线程
lscpu | grep "Thread(s) per core"
例:双核四线程cpu
2.1 单核,多核的应用场景
多核多线程并不一定比单核快
多核多线程的优势:
- 并行处理能力:多核多线程系统可以同时执行多个线程,从而提供更好的并行处理能力,适用于多线程或多任务的工作负载。
- 资源共享:多核心系统可以共享一些资源,如缓存和内存带宽,减少资源竞争,提高整体性能。
- 响应能力:多核系统可以同时处理多个任务或请求,提供更快的响应时间和更好的用户体验。
单核的优势:
- 单线程性能:在某些情况下,单个核心的时钟频率和性能可能比多核心的单个核心更高。对于单线程任务,单核心系统可能会有更好的性能表现。
- 较低的功耗和热量:单个核心的系统通常会消耗较少的功耗并产生较少的热量,适用于对功耗和散热有严格要求的场景。
- 较低的成本:单核心系统通常比多核心系统更便宜,适用于对成本敏感的应用。
应用场景的选择取决于具体的需求和任务类型:
- 多核多线程适用于多线程密集型任务,如并行计算、大规模数据处理、虚拟化等。
- 单核适用于单线程任务,如游戏、单线程应用程序、简单办公任务等。
- 对于需要兼顾多线程和单线程性能的任务,需要综合考虑处理器的核心数、单核性能和功耗等因素。
三、CPU缓存(Cache)
CPU缓存是在CPU内部的高速存储器,用于临时存储指令和数据,以提高内存访问速度。它位于CPU核心和主存之间,通过减少CPU访问主存的次数和延迟,加快了数据的获取和处理速度。
CPU缓存一般分为多级缓存,包括L1缓存、L2缓存和L3缓存,其容量和访问速度逐级递减。每个级别的缓存都具有不同的作用和特点。
- L1缓存(一级缓存):L1缓存是与CPU核心紧密集成的缓存,分为L1指令缓存(L1i)和L1数据缓存(L1d)。L1缓存的容量较小,但它非常接近CPU核心,速度非常快,可以直接供给CPU核心使用。
- L2缓存(二级缓存):L2缓存位于L1缓存和主存之间,容量较大,速度较L1缓存稍慢。L2缓存可以扩展到多个CPU核心共享使用,以提供更大的容量和更好的性能。
- L3缓存(三级缓存):L3缓存位于L2缓存和主存之间,容量更大,但速度相对较慢。L3缓存通常是多个CPU核心共享使用的,用于提供更大的容量和更好的多核性能。
缓存工作原理:当CPU需要访问数据时,它首先检查L1缓存,如果所需数据在L1缓存中命中(Hit),CPU可以立即获取数据,如果未命中(Miss),CPU将继续检查L2缓存,然后是L3缓存,最后才是主存。每一级缓存都会根据特定的缓存算法和替换策略来管理缓存的数据。
由于缓存的高速存取特性,访问缓存的时间比访问主存要快得多。这样,CPU可以更快地获取所需的数据和指令,提高了计算机系统的整体性能。
查看缓存大小
lscpu
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 14080K
或者
#L1 数据缓存
cat /sys/devices/system/cpu/cpu0/cache/index0/size
#L1 指令缓存
cat /sys/devices/system/cpu/cpu0/cache/index1/size
#L2
cat /sys/devices/system/cpu/cpu0/cache/index2/size
#L3
cat /sys/devices/system/cpu/cpu0/cache/index3/size
3.1 数据结构
cpu缓存是由多个缓存行(cache line)组成的,缓存行是cpu从内存读取数据的基本单位,而缓存行是由各种标志(Tag)+ 数据块(Data Block)组成
缓存行:是缓存中的最小单位,用于存储从主内存中读取的数据块,它是计算机系统中进行缓存操作的基本单元。缓存行的大小是固定的,并且通常是2的幂次方大小,如64字节或128字节。每个缓存行可以存储一个完整的数据块或者多个数据块的一部分。
数据块:是从主内存中读取的连续数据单元,可以是字节、字或者更大的数据单位。数据块的大小可以根据具体的系统和应用进行定义。
字:表示在特定计算机体系结构中处理和传输数据的固定大小,可以由4个字节、8个字节或其他大小的字节组成,通常用于处理整数、浮点数和内存地址等数据类型。
查看缓存行大小(单位字节):
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
3.2 缓存读取过程
当cpu需要读取一个地址中的数据时,它首先会检查缓存中是否存在该数据的副本(从L1~L3),CPU会将要读取的地址与缓存中的标签(Tag)进行比较,以确定是否存在匹配的缓存行。
如果CPU在缓存中找到了与要读取的地址匹配的缓存行,就发生了缓存命中(Cache Hit),在这种情况下,CPU可以直接从缓存行中读取所需的数据,而无需访问主内存。
如果CPU在缓存中没有找到与要读取的地址匹配的缓存行,就发生了缓存未命中(Cache Miss),在这种情况下,CPU会从主内存中读取整个缓存行的数据,并将其存储到缓存中,这样,下次如果CPU再次访问同一缓存行中的其他数据,就可以从缓存中直接读取。
3.2.1 如何确认缓存行里的数据就是目前所需的数据呢?
- 计算缓存行的索引:根据内存地址和缓存映射方式,确定所需数据所对应的缓存行索引。不同的缓存映射方式有不同的计算方法,如直接映射使用地址的低位作为索引,组相联映射使用地址的一部分作为索引等。
- 检查缓存标签:每个缓存行通常都有一个标签,用于标识其中存储的数据对应的内存地址范围。将所需数据的内存地址与缓存行的标签进行比较,如果匹配,则说明缓存行中的数据可能是所需的数据。
- 检查有效位:缓存行中通常还包含一个有效位,用于指示该缓存行是否有效。检查缓存行的有效位,如果为有效,则说明缓存行中的数据可能是所需的数据。
- 检查数据标记:在一些高级缓存系统中,为了支持缓存一致性和多级缓存,缓存行中可能还包含数据标记或版本号等信息。可以检查数据标记与所需数据的标记进行比较,以确认缓存行中的数据是否为所需的数据。
在缓存映射机制中,将内存分为多个内存块,通过不同的映射机制来找到对应的缓存组,再根据缓存组内缓存行的有效位确定该缓存是否有效,有效则读取缓存,无效则访问主存。
内存块(Memory Block):是内存中一块连续的存储空间,用于存储数据或指令。内存块是计算机系统中最小的存储单元,其大小通常以字节(Byte)为单位。
缓存组:缓存组(Cache Set)是计算机中缓存的逻辑单元之一,用于存储缓存行(Cache Line),每个缓存组在直接映射缓存和组相联缓存中都有一个唯一的索引。
不同的缓存映射方式,查找缓存行的方式也不同,常见的缓存映射方式有直接映射、组相联映射或全相联映射。
直接映射缓存是最简单和最常见的缓存映射方式之一,因此在一些低功耗设备和嵌入式系统中仍然广泛使用。但在现代高性能计算机系统中,直接映射缓存并不是主流选择,而是使用更复杂的映射方式。
注:在直接映射缓存中,每个缓存组通常只包含一个缓存行。这意味着具有相同索引的主存地址将映射到同一个缓存组中的相同缓存行。这可能导致缓存行冲突和冲突缓存失效。
3.2.2 cpu缓存映射方式与内存管理中页表映射的关系
CPU缓存映射方式和内存管理中页表映射是两个不同但相关的概念,它们在计算机系统中扮演着不同的角色:
- CPU缓存映射方式:CPU缓存映射方式是用于将内存地址映射到缓存的方式。常见的缓存映射方式包括直接映射、组相联映射和全相联映射。这些映射方式决定了内存地址如何与缓存行进行对应关联。缓存的目的是为了提高数据访问的速度和效率,通过缓存可以减少对主存的访问次数,提高数据的读取和写入性能。
- 内存管理中的页表映射:内存管理中的页表映射是用于将虚拟地址映射到物理地址的方式。在虚拟内存系统中,内存地址被划分为固定大小的页(page)或页框(page frame),而物理内存也被划分为相同大小的物理页框。页表被用于记录虚拟页和物理页框之间的映射关系。通过页表的映射,操作系统可以在不同的进程之间共享物理内存,并实现虚拟内存的管理和保护。
虽然CPU缓存映射方式和内存管理中的页表映射是不同的概念,但它们之间存在一定的关系:
- 缓存与页表:在CPU缓存中,缓存行存储的是从内存中读取的数据块。而在虚拟内存系统中,内存地址被划分为页,并通过页表将虚拟页映射到物理页框。当CPU访问一个内存地址时,首先会检查缓存中是否存在对应的缓存行,如果存在则发生缓存命中,否则发生缓存未命中。如果发生缓存未命中,CPU需要从主存中加载数据到缓存中。而页表的作用是将虚拟页映射到物理页框,当虚拟页不在内存中时,操作系统会触发缺页异常,并将相应的物理页框加载到内存中。
- 缓存一致性:当存在多级缓存时,需要考虑缓存与内存数据的一致性。在修改缓存中的数据时,需要保证相应的内存数据也被更新。为了实现缓存与内存的一致性,常见的做法是使用写回(write-back)或写直达(write-through)策略,并结合使用缓存一致性协议(如MESI协议)来确保缓存与内存的数据一致。
3.2.3 cpu缓存与缓存区,缓冲区的关系
- CPU缓存:CPU缓存是位于CPU内部的一种高速存储器,用于存储CPU频繁访问的数据和指令。CPU缓存分为多级,通常包括L1、L2和L3等级别。它们的作用是提供快速的数据访问,以减少CPU访问主存的延迟,提高计算机系统的性能。
- 缓存区(Cache):缓存区是一种用于临时存储数据的内存区域。在计算机系统中,缓存区通常用于存储数据的中间结果、临时数据或待处理的数据。它可以减少对慢速设备(如硬盘或网络)的频繁访问,提高数据访问的效率。
- 缓冲区(Buffer):缓冲区是一种用于临时存储数据的内存区域,用于平衡不同速度的设备之间的数据传输。在输入/输出(I/O)操作中,缓冲区可以作为数据的中转站,将数据从一个设备(如硬盘)复制到另一个设备(如内存),以提高数据传输的效率。
缓存区和缓冲区是广义的概念,用于描述数据的临时存储。它们可以是内存中的一块区域,也可以是磁盘上的一部分空间。缓存区和缓冲区的主要目的是在不同速度的设备之间提供数据传输的中间层,以提高数据访问和传输的效率。
在计算机系统中,CPU缓存可以被视为一种特殊的缓存区,用于存储CPU频繁访问的数据和指令。CPU缓存的设计目的是减少对主存的访问,提高数据访问速度。因此,CPU缓存可以看作是一种高速且特定于CPU的缓存区。
总结来说,CPU缓存是位于CPU内部的高速存储器,用于存储频繁访问的数据和指令。缓存区和缓冲区是广义的概念,用于描述临时存储数据的内存区域,用于提高数据访问和传输的效率。在特定上下文中,CPU缓存可以被视为一种特殊的缓存区。
3.3 映射方式
3.3.1 直接映射
直接映射(Direct Mapping)是一种常见的缓存映射方式,用于将主存中的数据存储到缓存中。在直接映射中,每个主存地址只能映射到缓存中的一个特定位置,也称为缓存行(Cache Line)或缓存块(Cache Block)。
在一个直接映射缓存中,内存地址可以分为三个部分:标记位(Tag)、索引位(Index)和块偏移位(Block Offset)。
- 标记位(Tag):标记位用于标识内存地址的高位,用于唯一标识一个缓存行。当一个内存地址被访问时,其标记位与缓存中的标记位进行比较,以确定是否命中缓存。
- 索引位(Index):索引位用于确定将内存地址映射到哪个缓存组(Cache Set)。索引位的值确定了内存地址可以映射到的缓存组的范围。
- 块偏移位(Block Offset):块偏移位用于确定内存地址在缓存行中的位置。块偏移位的值指示了内存地址在缓存行中的偏移量。
缓存组(行)被分为三个部分:有效位,标记为,数据块。
- 有效位(Valid Bit):用于表示缓存行是否有效,以决定是否需要进行主存访问,取值为0或1,分别表示无效和有效;有效时直接读取缓存,无效时访问内存。
- 标记位(Tag):标记位是用于标识缓存行所对应的内存块的高位地址信息。每个缓存行都有一个与之对应的标记位,用于确定缓存行是否有效以及与内存地址的映射关系。
- 数据块(Data Block):数据块是缓存行中存储实际数据的部分。它是用于存储从主存中读取的数据,以及在缓存命中时用于存储新数据的地方。数据块的大小与缓存行的大小相对应,可以是几个字节到几十个字节不等。
优点:
- 简单性:直接映射缓存是最简单的缓存映射方式之一。每个内存块只能映射到特定的缓存组,使得缓存管理相对简单。
- 快速定位:由于每个内存块只能映射到一个特定的缓存组,处理器可以快速定位到目标数据所在的缓存行,减少了地址查找的开销。
- 低冲突率:由于每个内存块只能映射到一个特定的缓存组,不会发生缓存行之间的冲突。这可以降低缓存访问的冲突率,减少缓存未命中的概率。
缺点:
- 冲突决策:直接映射缓存对于具有高度冲突访问模式的程序可能会导致较高的冲突率。当多个内存块被映射到同一个缓存组时,会发生冲突,导致缓存未命中增加。
- 容量限制:直接映射缓存的缓存容量相对较小。由于每个内存块只能映射到一个特定的缓存组,可能会导致某些内存块无法被缓存,从而增加缓存未命中的概率。
- 不灵活:直接映射缓存无法灵活地适应不同的访问模式和内存访问模式的变化。对于具有复杂的访问模式或具有特定访问模式的程序,直接映射缓存可能无法提供良好的性能。
3.3.2 组相连映射
组相联映射(Set-Associative Mapping)是一种常见的缓存映射方式,介于直接映射和全相联映射之间。在组相联映射中,缓存被划分为多个组(sets),每个组包含多个缓存行(cache lines)。
每个内存地址通过某种哈希算法或索引计算得到一个组索引,然后在该组内进行查找。每个组内有多个缓存行,可以容纳多个数据块。当一个数据块需要被缓存时,它可以被放置在组内的任意一个空闲缓存行中。
组相联映射的主要特点如下:
- 组数和相联度:组相联映射中,需要配置组数和每个组内的相联度(associativity)。组数决定了缓存的并行度,而相联度决定了每个组内有多少缓存行可供选择。常见的相联度是2、4、8等。
- 哈希算法:组相联映射使用哈希算法或索引计算来确定内存地址对应的组索引。哈希算法将内存地址映射到特定的组。
- 替换策略:当缓存满时,需要选择一个缓存行进行替换以为新的数据腾出空间。组相联映射通常使用一些替换策略,如最近最少使用(Least Recently Used, LRU)或随机替换等。
组相联映射相对于直接映射具有更好的灵活性和命中率。它允许在一组内选择多个缓存行,从而减少了缓存行之间的竞争,提高了命中率。然而,相对于全相联映射,组相联映射的硬件实现和替换策略会更加复杂,因为需要在组内进行查找和替换操作。
优点:
- 更高的灵活性:相比于直接映射,组相连映射提供了更高的灵活性。它允许一部分数据可以在同一个组中的不同缓存行中存储,使得缓存的使用更加高效。
- 减少冲突:相比于直接映射,组相连映射减少了缓存冲突的可能性。每个地址可以在同一个组的多个缓存行中选择,如果一个缓存行被占用,可以选择其他空闲的缓存行,减少了冲突带来的性能损失。
- 提高命中率:相比于直接映射,组相连映射提供了更大的缓存容量。当一个地址未命中时,可以在组内的其他缓存行中寻找,提高了命中率,减少了对主存的访问次数,提高了性能。
缺点:
- 更复杂的硬件实现:相比于直接映射,组相连映射需要更复杂的硬件实现,包括组索引和组内的缓存行选择逻辑。这增加了硬件成本和设计复杂性。
- 更高的访问延迟:相比于直接映射,组相连映射通常会引入更高的访问延迟。在确定要访问的缓存行时,需要进行组内的比较和选择操作,这可能会增加访问时间。
- 更高的能耗:相比于直接映射,组相连映射通常需要更多的比较和选择逻辑,这可能导致更高的功耗。
3.3.3 全相连映射
全相联的结构只包含一个组,里面包含了所有的缓存行;对于全相连映射,所有的缓存行都会进行标记的比较,比较标记是在缓存的所有缓存行中进行的,以确定是否存在与之匹配的标记。
如果缓存命中,根据块内偏移计算相应的数据位置,并将数据传输给CPU;
如果缓存未命中,则表示需要从主存中获取数据,此时需要选择一个空闲的缓存行用于存储新的数据,并更新标记和有效位。如果所有缓存行都被占用,需要进行替换策略,选择一个缓存行进行替换。常见的替换策略包括最近最少使用(Least Recently Used,LRU)和随机替换等。
因为高速缓存电路必须并行的搜索许多相匹配的标记,构造一个很大很快的全相联高速缓存是很昂贵的,因此它只适合做小的高速缓存。例如Linux系统中的快表TLB就是全相联的,它是用来缓存页表项的,用于快速地址翻译。
注:全相连映射中,内存地址不再有索引位。
优点:
- 最大化命中率:全相连映射可以最大程度地减少缓存冲突,因为每个地址都可以映射到任意一个缓存行。这意味着无论访问模式如何,每个数据块都有独立的位置,可以避免冲突带来的性能下降。
- 灵活的缓存管理:全相连映射允许任意地址映射到任意缓存行,这使得缓存管理更加灵活。新的数据块可以直接映射到空闲的缓存行,无需考虑索引位或组的限制。这降低了缓存管理的复杂性。
缺点:
- 更高的硬件成本:全相连映射需要更多的硬件资源来实现,因为需要比较所有缓存行的标记来确定命中。这增加了缓存的复杂性和成本,包括较大的标记存储和额外的比较逻辑。
- 高延迟:由于全相连映射需要并行搜索多个缓存行以进行标记比较,因此访问延迟相对较高。这是因为需要额外的比较操作来确定缓存命中,可能会对访问速度产生一定的影响。
- 较低的存储容量:由于全相连映射不需要索引位,每个地址都可以映射到任意缓存行,这导致相同的地址可能会被映射到不同的缓存行。相比之下,直接映射和组相连映射可以提供更大的存储容量,因为它们可以利用索引位来确定缓存行的位置。
3.4 缓存一致性
这里先补充下局部性原理的概念:
局部性原理(Principle of Locality)是计算机系统设计中的一个重要原则,指出在程序的执行过程中,数据和指令往往呈现出一定的局部性特征,即在一段时间内,程序更倾向于访问附近的数据和指令。
局部性原理主要包括以下两个方面:
- 时间局部性(Temporal Locality):指在程序的执行过程中,如果某个数据或指令被访问,那么在不久的将来它很可能会再次被访问。这是因为程序中存在着循环、迭代等结构,同一段代码或数据往往会被多次执行或访问。
- 空间局部性(Spatial Locality):指在程序的执行过程中,如果某个数据或指令被访问,那么与之相邻的数据或指令也很可能会被访问。这是因为程序中的数据和指令通常以连续的方式存储,并且在执行过程中会以块或段的形式被访问。
局部性原理对计算机系统设计有重要的影响:
- 缓存优化:由于局部性原理,缓存系统可以利用数据和指令的局部性特征,将经常访问的数据和指令存储在高速缓存中,以提高访问速度。
- 磁盘访问优化:由于局部性原理,磁盘访问往往采用预取(Prefetching)和缓冲(Caching)等技术,通过一次读取多个连续的数据块,以提高磁盘访问效率。
- 编译优化:编译器可以根据局部性原理对代码进行优化,例如通过代码重排、循环展开等技术,以减少缓存未命中和提高执行效率。
根据程序的空间局部性和时间局部性原理,缓存命中率可以达到 70~90% 。
3.4.1 缓存一致性
缓存一致性是指在具有多级缓存或多个处理器(多核)的计算系统中,确保各级缓存中存储的数据与主存中的数据保持一致的特性。缓存一致性是为了解决多个处理器或核心同时访问共享数据时可能出现的数据不一致的问题。
在多级缓存系统中,每级缓存都有自己的缓存副本,当一个处理器或核心修改了共享数据时,需要保证其他处理器或核心能够看到最新的数据。否则,如果不同的处理器或核心看到的数据不一致,就会导致程序错误和系统不可预测的行为。
为了确保缓存一致性,常见的缓存一致性协议包括:
- 总线嗅探(Bus Snooping)协议:在基于总线的系统中,每个缓存控制器监视总线上的事务,并嗅探到其他处理器或核心的读写操作。当一个处理器或核心写入共享数据时,总线嗅探协议会通知其他缓存控制器使其相应的缓存副本无效,保证数据的一致性。
- 直接写(Write-through)和写回(Write-back)策略:这两种策略用于处理缓存数据的写操作。在直接写策略中,每次写操作都会直接更新主存,以保持各级缓存和主存数据的一致性。而在写回策略中,只有在缓存行被替换出缓存时,才会将修改后的数据写回主存,这要求在缓存行替换前,保证缓存和主存数据的一致性。
- MESI协议(Modified-Exclusive-Shared-Invalid):MESI协议是一种常见的缓存一致性协议,它基于缓存行的状态来管理缓存数据的一致性。每个缓存行可以处于修改(Modified)、独占(Exclusive)、共享(Shared)或无效(Invalid)四种状态之一。当一个处理器或核心修改一个缓存行时,其他处理器或核心的相应缓存行会被置为无效状态,以保证数据的一致性。
3.4.2 写策略
写直达(写直通)
当CPU每次执行写操作时,数据会同时写入CPU内部缓存和主内存,确保缓存和主内存中的数据保持一致。
优点:
- 数据一致性:写直通策略确保了缓存和主内存中的数据保持一致。每次写操作都会立即将数据传递到主内存,避免了缓存和主内存数据不一致的情况。
- 数据可靠性:由于每次写操作都会直接更新主内存,即使发生系统崩溃或断电等异常情况,数据仍然能够保存到主内存中,不容易丢失。
- 简化一致性维护:写直通策略减少了缓存一致性维护的复杂性。由于每次写操作都直接更新主内存,不需要额外的机制来处理缓存和主内存之间的数据同步。
缺点:
- 高延迟:写直通策略可能会增加访存延迟。每次写操作都需要等待数据传输到主内存完成,这增加了指令执行的等待时间,对处理器性能有一定的影响。
- 低效的写操作:由于每次写操作都要更新主内存,写直通策略会导致更多的写操作发生在主内存上,而不是在快速的缓存中进行。这可能会降低写操作的效率,特别是对于频繁的写操作。
- 流量增加:写直通策略会增加对内存总线和主内存的访问负载,因为每次写操作都需要进行数据传输。这可能会导致内存带宽的压力增加,影响整体系统的性能。
写直通策略适用于对数据一致性要求较高的应用场景,如数据库系统。
写回
写回策略(Write-back)是一种常见的CPU缓存写策略。与写直通策略不同,写回策略将修改的数据首先写入缓存,而不是立即写入主内存。只有当缓存中的数据块被替换出缓存时,才会将该数据块的内容写回主内存。
当CPU执行写操作时,数据会被写入缓存,而不是立即写入主内存,缓存中的数据被标记为"已修改"(dirty)状态,表示该数据在缓存中已被修改但尚未写回主内存。
如果被写入的数据块在缓存中存在(缓存命中),CPU会将数据写入缓存,并更新缓存中的对应数据块。
如果被写入的数据块不在缓存中(缓存未命中),CPU会将该数据块从主内存读取到缓存中,并将数据写入缓存。
当缓存中的数据块被替换出缓存时,如果该数据块处于"已修改"状态,即缓存中的数据与主内存中的数据不一致,那么该数据块的内容会被写回主内存,以保持数据的一致性。
优点:
- 减少主内存访问:写回策略将修改的数据先写入缓存,而不是立即写入主内存。这可以减少对主内存的访问次数,从而提高存储系统的性能。
- 提高写操作效率:由于数据先写入缓存,写回策略可以在较长时间内累积多个写操作,并一次性写回主内存。这减少了频繁的主内存写操作,提高了写操作的效率。
- 缓解内存带宽压力:写回策略可以减少对内存总线和主内存的数据传输量。只有在数据块被替换出缓存时才会发生写回操作,相对于写直通策略,减少了对内存带宽的占用,缓解了内存带宽压力。
缺点:
- 数据一致性的复杂性:由于写回策略延迟了数据的写回主内存,可能会导致缓存中的数据与主内存中的数据不一致。这就需要在缓存替换时,确保脏数据(已修改的数据)被正确写回主内存,以保持数据的一致性。
- 需要额外的控制逻辑:写回策略需要额外的控制逻辑来跟踪和管理缓存中的脏数据,并确保在缓存替换时进行正确的写回操作。这增加了硬件的复杂性和成本。
写回策略适用于对写操作频繁、读操作相对较少的场景,可以提高写操作的效率和存储系统的性能。
3.4.3 总线嗅探
写策略解决的是一个核心内的缓存一致性问题,但目前的CPU基本都是多核的,仍需别的策略来保障多核之间的缓存数据一致。
比如:两核CPU有A,B两个核心,各运行着一个线程,操作变量i(初始值为1),A核心操作了 i++
而CPU缓存中的总线嗅探是一种用于维护缓存一致性的机制。当多个处理器核心或缓存层级共享同一个总线时,总线嗅探允许处理器核心观察(嗅探)总线上的数据传输和信号,以维护缓存的一致性状态。
总线嗅探在多处理器系统和多核系统中非常重要,其中不同的处理器核心或缓存层级可以同时访问相同的内存地址或缓存行。当一个处理器核心或缓存层级执行写操作时,总线嗅探机制会通知其他处理器核心或缓存层级,使它们能够检测到并响应该写操作。
总线嗅探的基本原理如下:
- 当一个处理器核心或缓存层级执行写操作时,该写操作会发送到总线上。
- 其他处理器核心或缓存层级通过总线嗅探机制观察总线上的写操作。
- 如果某个处理器核心或缓存层级嗅探到自己的缓存中保存的缓存行被写入,那么它会使该缓存行无效,即标记为无效状态。
- 当该处理器核心或缓存层级后续访问被标记为无效的缓存行时,它会从其他缓存或主内存中重新获取最新的数据。
优点:
- 数据一致性:总线嗅探机制确保了多个处理器核心或缓存层级之间的数据一致性。当一个处理器核心修改了共享数据时,总线嗅探机制可以使其他处理器核心或缓存层级意识到这一变化,并更新它们自己的缓存状态,以保持数据的一致性。
- 实时更新:总线嗅探机制能够及时通知其他处理器核心或缓存层级有关数据的修改。这意味着处理器核心可以获得最新的数据,而不需要等待其他处理器核心的写操作完成并刷新缓存。
- 灵活性:总线嗅探机制可以适应多处理器系统和多核系统的变化。当处理器核心的数量或缓存层级的数量发生变化时,总线嗅探机制可以相应地调整和管理缓存的一致性。
缺点:
- 性能开销:总线嗅探机制需要消耗一定的处理器资源和总线带宽。当多个处理器核心或缓存层级频繁地进行读写操作时,总线嗅探机制可能会引入一定的延迟和开销,从而影响系统的整体性能。
- 内存带宽压力:总线嗅探机制可能导致频繁的内存读取和写入操作。当多个处理器核心或缓存层级同时访问相同的内存地址时,总线嗅探机制可能会引起内存带宽的竞争和压力,限制了系统的吞吐量。
- 复杂性和功耗:实现和管理总线嗅探机制需要复杂的硬件支持和控制逻辑。这增加了处理器的复杂性,并可能导致额外的功耗开销。
总线嗅探机制虽然可以同步多核cpu之间的共享数据,但无法确认缓存操作的顺序;比如:
A核心操作 i=100 ,B核心操作 i=200 ,C核心距离A核心较近,先收到A的操作,再收到B的,所以C核心内 i=200 ,而D核心先接到B操作,再收到A的,D核心内 i=100
所以,在总线嗅探机制下,拥有多种不同的协议,来保证共享数据的一直性,MESI协议就是常见的一种。
3.4.4 MESI协议(Modified-Exclusive-Shared-Invalid)
MESI协议(Modified-Exclusive-Shared-Invalid)是一种常见的总线嗅探协议,用于多处理器系统或多核系统中管理共享数据的一致性。
MESI协议定义了缓存行的四种状态:
- Modified(已修改):当一个处理器核心修改了一个缓存行时,该缓存行的状态变为Modified。此时,该缓存行的内容已被修改,与主内存中的对应数据不一致。其他处理器核心拥有该缓存行的副本的缓存会被标记为Invalid。
- Exclusive(独占):当一个处理器核心拥有一个缓存行的独占访问权时,该缓存行的状态为Exclusive。此时,该缓存行的内容与主内存中的对应数据一致,并且其他处理器核心没有该缓存行的副本。
- Shared(共享):当多个处理器核心共享同一个缓存行时,该缓存行的状态为Shared。此时,该缓存行的内容与主内存中的对应数据一致,并且其他处理器核心也拥有该缓存行的副本。
- Invalid(无效):当一个缓存行的内容无效或者失效时,该缓存行的状态为Invalid。此时,该缓存行的内容与主内存中的对应数据不一致,或者其他处理器核心已经修改了该缓存行。
MESI协议通过总线嗅探机制来监测和维护缓存行的状态。当一个处理器核心修改了一个缓存行时,其他处理器核心的总线嗅探机制会检测到这个修改操作,并采取相应的行动来更新或失效它们自己的缓存中的该缓存行的副本,以保持数据的一致性。
注:当一个缓存行的状态修改为Modified时,该缓存行的数据一般不会立即写回主存,而是根据其他策略来决定其写回主存的时机。如写回策略,只有当该缓存行被替换时,才将数据写入主存,以减少写回的次数,这样可以提高效率并减少对主存的访问。
3.4.5 伪共享
伪共享(False Sharing)是指在多核处理器系统中,由于不同的处理器核心在同一缓存行中进行独立的写操作,导致缓存一致性协议无法区分这些独立的写操作,从而引发性能下降的现象。
当不同的处理器核心同时访问同一缓存行时,由于缓存一致性协议的机制,该缓存行会被标记为共享状态,以确保多个核心之间的一致性。如果这些核心只进行读操作,那么共享状态不会引发问题,因为缓存行的数据可以被多个核心共享。
然而,当一个或多个核心对同一缓存行进行写操作时,由于缓存行的状态被标记为共享,缓存一致性协议会迫使其他核心将该缓存行的数据从缓存中无效化,以确保数据的一致性。这导致即使其他核心只是对缓存行中的其他数据进行写操作,也会引发缓存行的无效化和重新加载,增加了缓存同步的开销,降低了性能。
伪共享的问题通常出现在紧密排列的共享数据结构或数组中,这些数据结构或数组的大小通常小于一个缓存行的大小。当不同的核心对这些紧密相邻的数据进行写操作时,由于缓存行的粒度比数据结构小,导致伪共享问题的产生。
为了解决伪共享问题,可以采取以下策略:
- 使用填充(Padding):在共享数据结构或数组的每个元素之间增加填充字段,使得每个元素都占据一个缓存行,避免多个核心在同一缓存行上进行写操作。
- 使用本地化(Localization):将共享数据结构或数组复制到每个核心的本地缓存中,并在本地缓存中进行操作,避免多个核心之间的竞争和同步。
伪共享是一个与多核处理器和缓存一致性相关的性能问题,需要在设计和编程中考虑,以避免或减少其影响。
四、中断
在计算机系统中,CPU中断(Interrupt)是一种机制,用于在特定事件发生时打断CPU的正常执行流程,以便处理相应的事件或请求。中断可以是硬件引发的(如设备输入/输出完成、定时器到期、硬件故障等),也可以是软件生成的(如系统调用、异常、信号等)。
当发生一个中断时,CPU会立即停止当前正在执行的指令,保存当前的上下文(也称为中断现场),并根据中断类型执行相应的中断处理程序(也称为中断服务例程)。中断处理程序是预先定义好的一段代码,用于处理中断事件或请求。
中断处理程序的执行完成后,CPU会根据保存的上下文信息恢复到被中断的位置,继续执行原来的指令或从新的位置开始执行。
中断机制的优点是:
- 实现异步事件处理:中断允许处理异步事件,而不需要CPU主动轮询或等待。
- 提高系统的响应性:通过及时响应重要事件,可以提高系统的响应速度和实时性。
- 实现设备和CPU的解耦:中断机制使得设备和CPU之间可以解耦,设备可以独立地发出中断请求,而CPU可以根据需要处理中断。
常见的中断包括:
- 硬中断:由硬件设备触发的中断,如设备输入/输出完成中断、时钟中断、硬件故障中断等。
- 软中断:由软件生成的中断,如系统调用、异常、信号等。
操作系统负责管理和处理中断,它会分配中断处理程序的执行顺序、维护中断向量表(用于将中断类型映射到相应的中断处理程序地址)等。
通过中断机制,操作系统可以实现对设备的管理、实现进程间的通信、处理异常情况、调度任务等功能。
linux上查看中断相关信息:
查看硬中断:
cat /proc/interrupts
查看软中断:
cat /proc/softirqs
第一列:中断的类型,同一类型在不同 CPU 上的累计次数相差不多
数值:系统运行以来的累计中断次数,本身无参考价值;但可通过 watch -d cat /proc/softirqs
CPU0 CPU1 CPU2 CPU3
HI: 0 0 0
TIMER: 523155184 256358234 1443136348
NET_TX: 172243 75031 68000
NET_RX: 9924464 2804043078 3303169700
BLOCK: 75190280 52626395 91968298
BLOCK_IOPOLL: 0 0 0
TASKLET: 893439330 320968609 397785257
SCHED: 1873966301 655034300 625075280
HRTIMER: 0 0 0
top:
hi为硬中断,si为软中断,按1可显示所有cpu核心的中断情况
top - 16:50:17 up 1622 days, 22:52, 1 user, load average: 0.03, 0.14, 0.20
Tasks: 419 total, 1 running, 417 sleeping, 0 stopped, 1 zombie
%Cpu(s): 0.3 us, 0.3 sy, 0.0 ni, 99.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 98496192 total, 85523456 free, 3238308 used, 9734432 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 90332496 avail Mem