# CPU

  • CPU(Central Processing Unit,中央处理单元),又称为中央处理器(Central Processor)。是计算机的核心元件,负责执行指令、运行软件程序。

# 硬件结构

# 计算机结构

  • 1945 年,冯·诺依曼(Von Neumann)在论文中提出了一种可以存储指令并执行的计算机结构,被称为冯·诺依曼结构,是目前最流行的计算机结构。
  • 冯诺依曼结构在逻辑上分为五个部分:
    • 运算器(Arithmetic Unit)
      • :负责执行指令、进行运算。
      • 核心部件是算术逻辑单元(Arithmetic Logic Unit ,ALU),能进行算术运算、逻辑运算、位运算等操作。
      • 运算结果会保存到存储器中,或者暂存在累加器中。
    • 控制器(Control Unit)
      • :负责从存储器读取指令,进行译码分析(即编码的逆过程),然后调用相关部件执行该指令。
      • 例如读取到一个跟运算相关的指令,就取出操作数,发送到运算器进行运算。
    • 存储器(Memory Unit)
      • :负责用于存储数据和指令。
      • 主要分为内部存储器、外部存储器两种,简称为内存、外存。
    • 输入设备
      • :负责接收用户或其它设备输入的信息,转换成计算机能识别的数据,然后保存到存储器中。
      • 例如鼠标、键盘。
    • 输出设备
      • :负责将计算机的数据转换成适当形式的信息,输出给用户或其它设备。
      • 例如显示屏、打印机。
      • 输入、输出设备统称为 IO 设备。

# 主板

  • 现代计算机的各个硬件通常集成在一大块集成电路板上,称为主板。其核心元件为 CPU ,是一小块芯片。
  • 主板上,CPU 与其它元件之间通过总线进行数据传输。总线根据用途分为三种:
    • 地址总线(Address Bus)
      • :用于传输地址信息。
      • 只能单向传输,只支持 CPU 发送信号到存储器或 I/O 接口电路。
      • 地址总线的宽度决定了 CPU 直接寻址的最大范围。例如 8 位机的地址总线由 16 根线组成,可以并行传输 16 bits 的信号,因此 CPU 最大可以对 2^16 个存储单元进行直接寻址。
        • 如果存储单元的数量超过该范围,CPU 就无法访问超过的部分。
        • 内存每个存储单元的大小为 1 Byte ,因此 8 位机最多使用 2^16/1024=64 KB 的内存空间。同理,32 位机最多使用 2^32=4 GB 的内存空间。
    • 控制总线(Control Bus)
      • :用于传输 CPU 对其它部件的控制信号,以及其它部件的应答、请求等信号,支持双向传输。
    • 数据总线(Data Bus)
      • :用于传输数据,支持双向传输。
      • 数据总线的宽度通常是字长的倍数。

# 寄存器

  • 寄存器(Register):是 CPU 内置的一些容量很小、读写速度超快的存储器,用于暂存 CPU 当前处理的数据、指令。

    • 断电时会丢失数据。
  • 寄存器根据用途分为多种类型:

    • 数据寄存器
      • :用于暂存一些通用的数据。
      • 例如 8086 CPU 有多个 16 位的数据寄存器 AX、BX、CX、DX 。每个寄存器也可分为两个 8 位的寄存器使用,比如将 AX 分为成 AH、AL 。
    • 段寄存器
      • :用于暂存程序的代码段、数据段、栈等内容。
    • 指令指针寄存器(Instruction Pointer)
      • :用于暂存 CPU 下一条待执行指令在代码寄存器中的偏移地址。
    • 标志位寄存器(Flag)
      • :用一组二进制位记录当前指令的执行状态。
      • 例:
        • CF(Carry Flag ,进位标志):若加减运算时,最高位向前发生了进位或借位,则设置 CF=1 ,否则为 0 。只有两个操作数为无符号数时 CF 标志才有意义。
        • SF(Sign Flag ,符号标志):若运算结果的最高位为 1 ,则设置 SF=1 ,表示负数。只有两个操作数为带符号数时 SF 标志才有意义。
        • OF(Overflow Flag ,溢出标志):若运算结果发生了溢出,则设置 OF=1 ,表示运算结果错误。只有两个操作数为带符号数时 OF 标志才有意义。
        • IF(Interrupt Flag ,中断标志):若设置了 IF=1 ,则允许 CPU 响应来自 CPU 外部的可屏蔽中断的中断请求。
  • CPU 执行程序的一般过程:

    1. 从磁盘读取程序,载入内存。
    2. 从内存或缓存读取程序,载入寄存器。
    3. 从寄存器读取程序,解析成指令,然后执行。
      • 执行过程中,如果需要用到某些数据,则从内存载入。
      • 执行过程中,可能发生上下文切换,转去执行其它程序。

# Cache

:CPU 芯片中的一个高速存储器,用于缓存 CPU 从内存经常读取的部分数据。

  • CPU 执行某个指令时,如果需要读取某数据,会先尝试到 Cache 中查找该数据。此时分为两种情况:

    • Cache Hit :在 Cache 中找到了,则可以立即读取该数据。
      • 内核会根据 LRU 算法来自动清理缓存的数据,提高 CPU 读取数据时的 Hit 命中率。
    • Cache Miss :在 Cache 中没找到,则到内存中查找该数据。
  • 目前的 CPU Cache 一般采用 SRAM 介质,容量为几 MB 。

    • 通常存在多级缓存。例如 L1、L2、L3 三级缓存:
      • CPU 先到 L1 Cache 中读取数据,如果 Miss 了再到 L2 Cache 中读取数据,以此类推。
      • L1、L2、L3 Cache 的读写速度大概为 500 GB/s 、300 GB/s 、200 GB/s 。
    • 多核 CPU 一般各有一个独立的 L1 缓存,然后共享同一个 L2、L3 缓存。
    • 例:查看本机的 CPU Cache 容量
      [root@CentOS ~]# lscpu | grep cache
      L1d cache:             32K
      L1i cache:             32K
      L2 cache:              1024K
      L3 cache:              36608K
      

# Write Buffer

:CPU 芯片中的一个高速存储器,用于缓冲 CPU 写入内存的所有数据。

  • 优点:让 CPU 与内存异步工作,减少等待内存 IO 的耗时。

# 多核心

  • 现代 CPU 的时钟频率通常为 3~4 GHz ,执行指令的速度存在上限。为了更快地执行指令 ,通常在一个 CPU 芯片中包含多个核心(Core)处理器,从而能并发执行多个指令。简称为多核 CPU 。

    • 通常包含偶数个 Core 。
    • 每个 Core 独立工作,分别包含一份运算器、控制器、寄存器、缓存等元件。
    • 每个 Core 通常分别包含一份 L1~L2 缓存,共用一份 L3~L4 缓存。
  • 常见架构:

    • 对称多处理器(Symmetric Multi-Processor ,SMP)
      • :各个 CPU 之间平等,共享内存、IO 设备等资源。
      • 同时只能有一个 CPU 通过内存总线访问内存,因此 CPU 为 2~4 核时的利用率最高。增加 CPU 数量时,则 CPU 越来越可能因为等待访问内存而阻塞,导致利用率越来越低。
    • 非一致内存访问(Non-Uniform Memory Access ,NUMA)
      • :在计算机中划分多个节点(node),每个节点包含多核 CPU 、独立内存。
      • 各节点的 CPU 可以并发访问本节点的内存,也可以通过互联模块访问其它节点的内存,但比本地内存的访问速度慢。
      • SMP 属于一致内存访问。与 SMP 相比,NUMA 大幅提高了 CPU 利用率,但跨节点访问内存时慢。
    • 大规模并行处理(Massive Parallel Processing ,MPP)
      • :将多个 SMP 服务器通过网络连通,组成一个计算机系统。
      • 与 NUMA 相比,MPP 不存在跨节点的内存访问。增加 CPU 时,系统性能会线性提升。
  • 例:查看本机的 NUMA 节点

    [root@CentOS ~]# lscpu | grep NUMA
    NUMA node(s):          2
    NUMA node0 CPU(s):     0-15
    NUMA node1 CPU(s):     16-31
    
    • 上例中有 2 个 NUMA 节点,分别包含 16 核 CPU 。
    • 如果只有 1 个 NUMA 节点,则属于 SMP 架构。

# 型号

# x86

  • :美国 Intel 公司发布的一系列 CPU 型号。
    • 指令集属于 CISC 。
    • x86 CPU 的对外授权较少,主要由 Intel 公司与 AMD 公司交叉授权,两家公司掌控了设计、生产、销售 CPU 的全部流程。
      • 有些技术专利已过期,任何公司都可以使用。
  • 1987 年,Intel 公司发布了 8086 型号的 CPU ,被 IBM PC 采用而流行起来。此后的 80186、80286、80386 等型号的 CPU 都沿用这种架构,它们都以 86 结尾,因此称为 x86 架构。
    • 8086 CPU 的字长为 16 ,地址总线的宽度为 20 bits ,数据总线的宽度为 16 bits 。
    • 80386 CPU 的字长为 32 。
  • 2003 年,AMD 公司将 x86 架构的字长扩展为 64 ,命名为 AMD64 ,又称为 x86_64、x64 。

# ARM

  • :进阶精简指令集机器(Advanced RISC Machine),指英国 ARM 公司发布的一系列 CPU 型号。
    • 字长为 32 ,指令集属于 RISC 。
    • 成本低、功耗低、散热低,因此用于手机、平板等小型电子设备比 x86 更有竞争力。
    • ARM 公司只负责设计 ARM CPU 架构、指令集,不实际生产 CPU ,而是并出售许可证给其它公司,允许其研发、生产 ARM 芯片。
  • 2011 年,ARM 公司发布了 ARMv8-A 架构,字长为 64 ,并且重新实现了 ARM 32 位的指令集。
    • ARMv8-A 架构划分了 AArch32、AArch64 两种执行状态,分别用于执行 32 位、64 位的指令。
  • 2020 年,Apple 公司发布了一款基于 ARMv8-A 架构的 CPU ,称为 Apple Silicon M1 ,用于此后的 MacBook、iPad 等设备。

# 指令

  • 指令:是让 CPU 进行某一操作的命令代码,由操作码和操作数组成。

    • 操作码:表示操作符即该操作的类型,比如数据传送、算术运算、逻辑运算、位运算等。
    • 操作数:表示该操作的对象,或者对象的地址。
      • 有的指令没有操作数,有的指令有 1 个操作数,有的指令有 2 个操作数。
    • 8086 CPU 的指令示例:
      MOV   AL, 18H     ; 将源操作数 18H 存入目的操作数中,这里的目的操作数是一个数据寄存器 AL
      ADD   AL, 01H     ; 加法:计算目的操作数 AL 加上源操作数,结果存入目的操作数所指的寄存器中
      SUB   AL, 01H     ; 减法:计算目的操作数 AL 减去源操作数,结果存入目的操作数所指的寄存器中
      INC   AL          ; 增量:使操作数的值加 1
      MUL   2H          ; 无符号数的乘法:计算 AX 中的值乘以该操作数,结果存入 AX
      DIV   2H          ; 无符号数的除法:计算 AX 中的值除以该操作数,结果存入 AX
      
  • 现代计算机中,运行一个程序时,需要将该程序编译成二进制代码,载入物理内存,让 CPU 读取并执行。

    • 这些二进制代码,实际上是一连串能被本机 CPU 识别的指令,称为指令流。

# 指令集

  • 指令集:指某个型号的 CPU 可以识别和执行的所有指令,又称为指令系统。
  • 常见的 CPU 指令集架构(Instruction Set Architecture ,ISA):
    • CISC(Complex Instruction Set Computer ,复杂指令集)

    • RISC(Reduced Instruction Set Computer ,精简指令集)

      • 精简了指令数,每个时钟周期执行一条指令。
      • 指令的长度统一。
      • 精简了寻址方式。
    • EPIC(Explicitly Parallel Instruction Computing ,显式并行指令集)

    • VLIW(Very Long Instruction Word ,超长指令集)

# 调度

  • 操作系统中通常同时运行了大量程序,需要决定将每个程序分配到 CPU 的哪个核来执行、执行多长时间。CPU 调度就是针对该问题。

    • 例如有的线程处于 sleep 状态,则暂时不执行。有的线程有高优先级,则可以插队,抢占 CPU 。
  • 线程是 CPU 调度的基本单位。

    • 对于每个程序而言,启动程序时需要创建至少一个进程,每个进程包含至少一个线程,从而控制该程序的运行状态。
    • 对于操作系统而言,决定哪些内存资源给哪个程序使用时,是以进程为单位调度。决定哪些 CPU 资源给哪个程序使用时,是以线程为单位调度。
    • 对于 CPU 而言,它只关系操作系统让它执行什么任务(task)。可能一会执行线程 A 的指令流,一会执行线程 B 的指令流。

# 任务

  • 对于每个任务而言,它们使用 CPU 的流程如下:

    1. 调用系统接口,请求使用 CPU 。
    2. 等待系统同意。
    3. 使用 CPU 一段时间,然后结束任务。
  • 每个任务主要关心以下指标:

    • 响应时间
      • :从该任务开始等待使用 CPU ,到开始使用,这个过程的时长。
    • 周转时间
      • :从该任务开始等待使用 CPU ,到使用完毕,这个过程的时长。
  • CPU 执行多个任务时,有多种方案:

    • 串行工作(Serial)
      • :同时只能开始、执行一个任务,执行完之后才能执行下一个任务。
    • 并行工作(Parallel)
      • :同时开始、执行多个任务。
      • 例如多个线程,可以同时运行在 CPU 的不同核心上。
    • 并发工作(Concurrent)
      • :同时只能执行一个任务,但可以同时开始多个任务,交替执行,从而看起来像同时执行。
      • 例如多个线程,可以交替运行在 CPU 的同一核心上。

# 优先级

  • 如何控制不同进程使用 CPU 的优先级?Linux 使用以下两个参数:
    • Priority
      • 取值范围为 -100~39 ,取值越小表示优先级越高。
      • 对于普通进程,其 Priority = Nice + 20 ,取值范围为 0~39 。
      • 对于 RT 类型的进程,其 Priority = -1 - rt_prior ,取值范围为 -100~-1 。
        • rt_prior 取值范围为 0~99 ,取值越大,优先级越高。
    • Nice
      • Nice 表示谦让值。如果一个进程增加其 Nice ,就是降低优先级,对其它进程更友好。
      • 取值范围为 -20~19 ,取值越小表示优先级越高。默认为 0 。
      • 用户想调整进程的优先级时,一般只能修改进程的 NICE 谦让值,不能直接修改 Priority 优先级。

# 上下文切换

  • CPU 中的每个 Core 同时只能执行一个指令。但可以在执行一个指令流的过程中,转去执行其它指令流。该过程称为上下文切换(Context Switch)。

    • 从软件的角度来看:CPU 中的每个 Core 同时只能执行一个程序(表现为一个线程),但 CPU 可以在执行一个程序的过程中,转去执行其它程序。
    • 上下文切换时,需要暂存当前程序的执行信息(主要是寄存器中的内容),称为上下文,以供之后 CPU 跳转回来继续执行。
    • 上下文切换总是会发生,可以提高 CPU 执行多个程序的效率。例如当前程序在等待磁盘 IO 时,就可以转去执行其它程序,避免 CPU 处于空闲。
    • 上下文切换过于频繁时,切换产生的开销也会过大,可能降低 CPU 的总体执行效率。
  • CPU 上下文切换分为几种场景:

    • 进程上下文切换
      • CPU 从一个进程转去执行另一个进程时,需要切换寄存器、内核堆栈等内核态资源,以及虚拟内存、全局变量、栈区等用户态资源。
    • 线程上下文切换
      • CPU 从一个线程转去执行另一个线程时,需要切换寄存器、栈区等资源,开销比进程上下文切换小得多。
      • 这里是指在同一进程下切换线程。如果切换到不同进程,则属于进程上下文切换。
    • 系统调用上下文切换
      • 用户态的进程通过调用系统 API 可以切换到内核态执行,此时会发生一次上下文切换。调用结束之后,要继续执行用户态进程,又会发生一次上下文切换。
    • 中断上下文切换
      • 优先级最高,会打断一般进程的执行。
      • 切换时只需要暂存进程的内核态资源,不影响用户态资源。

# 中断

  • 中断(interrupt):指 CPU 在执行一般进程时,突然出现某个重要任务,然后转去执行该重要任务。

  • 引发 CPU 中断的原因称为中断源,主要分为两大类:

    • 内部中断:是 CPU 内部的中断,例如除法出错中断、溢出中断、软件中断指令 INT n 。
    • 外部中断:又称为硬件中断,是计算机的其它元件通过 CPU 的芯片引脚传入的中断信号。
  • CPU 处理中断的策略:

    • 如果是硬中断,则立即处理其中不能延误的任务,然后将剩下的任务转换成软中断。
    • 软中断通常是不紧急的任务,可以延后处理。比如创建内核线程来处理,该内核线程可能被 CPU 的其它 core 执行。
  • 例如,网卡接收数据时会这样触发中断:

    1. 网卡收到一个或多个数据帧,发出一个硬中断,使得网卡驱动程序将数据帧拷贝到内核缓冲区中。
    2. 网卡驱动程序发出一个软中断,使得某个内核线程来解析数据帧,转换成 IP、TCP 等协议包,供监听 Socket 的程序读取。

# 性能优化

  • 假设用 tar 命令压缩文件,则主要流程如下:

    1. 从磁盘的源文件中读取数据,然后依次拷贝到内存、CPU Cache、CPU Register 。
    2. CPU 从 CPU Register 读取源数据,执行压缩算法,计算出压缩后的数据。
    3. CPU 输出压缩后的数据到 CPU Register ,然后依次拷贝到 CPU Cache、内存、磁盘。
  • 可见,CPU 执行程序的耗时主要受以下因素影响:

    • CPU 读写数据的速度
    • CPU 执行指令的速度

# 读写速度

  • 计算机中存在多种存储设备,读写速度从高到低分别为:CPU Register > CPU Cache / Buffer >> 内存 >> 磁盘

    • 时钟频率、访问延迟也是这样的顺序。
    • 成本则相反顺序。一般磁盘的 IO 速度最慢,但成本最低,因此相同价格时的容量最大。
    • 除了读写速度的差异,外存在断电之后能持久保存数据,而其它存储设备通常不能。
  • CPU 处理数据的速度,比外存读写数据的速度,快很多倍。让 CPU 同步读写外存时,会浪费时间等待 IO 。因此 CPU 采用异步读写,通过内存、Cache 中转数据。

    • 读取文件的流程示例:
      1. CPU 要求读取一个文件,发送指令给磁盘驱动器。然后 CPU 可以执行其它任务,不必浪费时间等待。
      2. 磁盘驱动器寻址到文件数据,拷贝到磁盘驱动器的内部缓冲区。然后发送中断通知 CPU ,于是 CPU 发生上下文切换,回来执行当前任务。
      3. CPU 从磁盘拷贝数据到内存。
        • 先拷贝到内存中的 Page Cache 空间,此时只能被内核态进程访问。
        • 从 Page Cache 拷贝到进程内存空间,此时才能被用户态进程访问。
        • 为了避免 CPU 亲自拷贝数据的耗时,通常在计算机中加入 DMA(Direct Memory Access,直接内存访问)控制器,代替 CPU 接收第 2 步的中断信号,将数据拷贝到 Page Cache ,然后发送中断通知 CPU 。
      4. CPU 从内存拷贝数据到 CPU Cache ,再拷贝到 CPU Register,供 CPU 直接访问。
    • 写入文件的流程相反,先由 CPU 从进程内存空间拷贝数据到 Page Cache ,再由 DMA 拷贝到磁盘。

# 执行速度

  • 时钟周期(Clock Cycle):CPU 的振荡器发出时钟脉冲的间隔时长。

    • 其倒数称为时钟频率。
    • 例如:一个 4 GHz 的 CPU ,每秒产生 4*10^9 个时钟脉冲,时钟周期为 0.25*10*-9 秒。
    • 现代 CPU 的时钟频率通常为 3~4 GHz 。如果继续提升时钟频率,则耗电量、散热难度大幅增加。
  • 指令周期:CPU 执行一条指令所需的时长。

    • 不同指令的指令周期不同,因此通常是计算平均值。
    • 早期的 CPU ,每个时钟周期只能执行一条指令。现代的 CPU ,每个时钟周期可能执行多条指令。
    • 将 CPU 的时钟频率,乘以每个时钟周期平均执行的指令数(Instructions Per Cycle ,IPC),就得到每秒平均执行的指令数(Instructions Per Second ,IPS)。
  • 字长(Word Size):又称为位元,指 CPU 的算术逻辑单元每次最多能处理多少位二进制数据。

    • 现代 CPU 的字长通常是 32 位、64 位。

# 状态指标

# CPU 使用率

:指一定时间范围内的 CPU 平均使用率,表达式为 %CPU = ( 1 - CPU 空闲时长 / 统计时长 ) × 100%

  • CPU 平时会执行多种任务,它们占用的时间会被内核分别记录:
    user(us)                # 用户态进程占用的时间,它不包括 nice 时间,包括 guest 时间
      guest                 # 当操作系统运行在虚拟机中时,CPU 运行 guest 操作系统的时间
    system(sy)              # 内核态进程占用的时间
    nice(ni)                # 设置了 NICE 谦让值的用户态进程占用的时间
    idle(id)                # CPU 的空闲时间,此时没有执行任务
    iowait(wa)              # CPU 等待磁盘读写数据的时间
    hardware interrupt(hi)  # 硬件中断占用的时间
    software interrupt(si)  # 软件中断占用的时间
    steal(st)               # 当操作系统运行在虚拟机中时,CPU 运行其它虚拟机的时间,即被偷走的时间
    
    • 虚拟机本身需要运行一个 guest 操作系统,用于运行、管理其它虚拟操作系统。
    • 正常情况下 iowait 会在很短时间内结束,如果 iowait 长时间较高,可能是磁盘 IO 量太大,或系统发生故障,这会导致某些进程一直处于 D 状态,占用 CPU 。

# 平均负载

:load average ,指一定时间范围内平均的活跃进程数。

  • 活跃进程包括:

    • 正在被 CPU 运行的进程(Running)
    • 等待被 CPU 运行的进程(Runnable)
    • 不可中断的进程(比如 iowait)
  • 这些活跃进程,使用的系统资源可能不同,主要分为几类:

    • CPU 密集型(CPU intensive)
      • :进程长时间使用 CPU 进行运算。因此平均负载高时,CPU 使用率也高。
    • IO 密集型(IO intensive)
      • :进程长时间等待磁盘 IO 或网络 IO 。因此平均负载高时,CPU 使用率不一定高。
      • 一个进程可能同时属于 CPU 密集型、IO 密集型,导致平均负载很高。也可能几乎不用资源,长时间 sleep 。
  • 如果只存在 CPU 密集型进程,则理想情况下,系统的平均负载数应该刚好等于 CPU 核数,使得每个 CPU 运行一个活跃进程,且没有 CPU 空闲。

    • 例:对于有 2 核 CPU 的系统,
      • 若平均负载为 1 ,说明 CPU 使用率为 50% 。
      • 若平均负载为 2.6 ,说明 CPU 超载了,有部分进程竞争不到 CPU 。
    • 实际上,除了 CPU 密集型进程,系统中经常存在一些 sleep 状态的进程,不会增加 CPU 使用率,但会导致平均负载看起来虚高。
      • 例如平均负载为 4 时,可能 CPU 使用率为 0% 。

# 相关命令

# uptime

uptime      # 显示系统运行时长、CPU 平均负载
  • 例:
    [root@CentOS ~]# uptime
    up 21 days, 41 min,  1 users,  load average: 0.52, 0.58, 0.59
    
    • up 21 days, 41 min :表示系统的运行时长。
    • 1 users :表示已登录的用户数。
    • load average: 0.52, 0.58, 0.59 :表示最近 1 分钟、5 分钟、15 分钟的平均负载。