硬件结构

硬件结构

操作系统建立在计算机之上,依托于计算机的众多硬件运行,其中最主要的两个硬件就是 CPU 和内存,此外还有显卡,网卡,硬盘,键盘,鼠标等等

冯诺依曼模型将计算机基本结构定义为运算器控制器存储器输入设备输出设备 5 个部分

冯诺依曼模型.png (613×271) (xiaolincoding.com)

CPU

CPU 包括寄存器、控制单元和逻辑运算单元

为了知道新任务从哪加载,又从哪开始运行,操作系统需要事先帮 CPU 设置好 CPU 寄存器和程序计数器
CPU 寄存器是 CPU 内部一个容量小,但是速度极快的内存
程序计数器则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置

CPU 上下文切换就是先把前一个任务的 CPU 上下文(CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务

32 位 VS 64 位

硬件的 64 位和 32 位指的是 CPU 的位宽,软件的 64 位和 32 位指的是指令的位宽

32 位和 64 位 CPU 的区别

64 位/32 位软件表示其 CPU 指令是 64 位还是 32 位的
32 位指令可以在 64 位机器上执行,但 64 位指令不能在 32 位机器上执行,因为 32 位的寄存器存不下 64 位的指令

寄存器

CPU 中的寄存器主要作用是存储计算时的数据,相比于内存,寄存器就位于 CPU 中,因此速度更快
寄存器一般要求在半个 CPU 时钟周期内完成读写

常见寄存器种类:

CPU Cache

CPU-Cache.png (1325×881) (xiaolincoding.com)|625

CPU Cache 使用 SRAM(Static Random-Access Memory,静态随机存储器) 芯片

CPU Cache 通常可以分为 L1、L2、L3 三层高速缓存,也称为一级缓存、二级缓存、三级缓存

CPU Cache 由多个 Cache Line 组成
Cache Line 是 CPU 从内存读取数据的基本单位,由标志(Tag) + 数据块(Data Block)组成

直接映射 Cache

使用取模运算把内存块的地址始终映射在同一个 CPU Cache Line 中,通过 Tag 区分模相同的内存块,此外 Cache 中存在有效位(Valid Bit),当有效位为 0 时,CPU 跳过 Cache 直接从内存中加载数据

直接Cache映射.png (1209×827) (xiaolincoding.com)|650

如果内存中的数据已经在 CPU Cache 中了,那 CPU 访问一个内存地址的时候,会经历这 4 个步骤:

  1. 根据内存地址中索引信息,计算在 CPU Cache 中的索引,也就是找出对应的 CPU Cache Line 的地址
  2. 找到对应 CPU Cache Line 后,判断 CPU Cache Line 中的有效位,如果是无效的,CPU 就会直接访问内存,并重新加载数据
  3. 对比内存地址中和 CPU Cache Line 中的组标记,确认 CPU Cache Line 中的数据是我们要访问的内存数据,如果不是的话,CPU 就会直接访问内存,并重新加载数据
  4. 根据内存地址中偏移量信息,从 CPU Cache Line 的数据块中,读取对应的字

缓存一致性

写直达:把数据同时写入内存和 Cache 中
写回:当发生写操作时,新的数据仅仅被写入 Cache Block 里,只有当修改过的 Cache Block 被替换时才需要写到内存中

由于 L1 Cache 和 L2 Cache 通常不共有,采用写回策略时多核 CPU 存在缓存一致性问题

解决方法:

CPU 通过 MESI 协议实现了缓存一致性
MESI 是 4 个状态单词的开头字母缩写

MESI协议.png (1113×602) (xiaolincoding.com)

伪共享

多个线程同时读写同一个 Cache Line 中的不同变量而导致 CPU Cache 失效

解决方法:空间换时间,使竞争激烈的同一个 Cache Line 中的不同变量处于不同的 Cache Line 中

指令

指令的内容是一串二进制数字的机器码,每条指令都有对应的机器码,CPU 通过解析机器码来知道指令的内容

一条指令通常分为 4 个阶段,称为 4 级流水线,又称为指令周期(Instrution Cycle),CPU 的工作就是一个周期接着一个周期,周而复始
CPU指令周期.png (513×363) (xiaolincoding.com)
Fetch(取得指令)- CPU 通过程序计数器读取对应内存地址的指令
 Decode(指令译码)- CPU 对指令进行解码
 Execution(执行指令)- CPU 执行指令
 Store(数据回写)- CPU 将计算结果存回寄存器或者将寄存器的值存入内存

Todo

指令类型
指令执行速度

任务调度

Linux 内核中进程和线程都用 task_struct 结构体表示,因此 Linux 内核中的调度器针对 task_struct 进行调度

Linux 系统中,根据任务的优先级以及响应要求,主要分为两种,其中优先级的数值越小,优先级越高:

每个 CPU 都有自己的运行队列(Run Queue, rq),用于描述在此 CPU 上所运行的所有进程,包含三个运行队列:Deadline 运行队列 dl_rq、实时任务运行队列 rt_rq 和 CFS 运行队列 cfs_rq

调度类 调度器 调度策略 运行队列
Deadline Deadline调度器 SCHED_DEADLINE dl_rq
Realtime RT 调度器 SCHED_FIFO
SCHED_RR
rt_rq
Fair CFS 调度器 SCHED_NORMAL
SCHED_BATCH
cfs_rq

Deadline 和 Realtime 应用于实时任务

Fair 应用于普通任务

Linux 内核按照Deadline > Realtime > Fair 优先级顺序从运行队列中选择任务执行
因此,实时任务总是会比普通任务优先被执行

完全公平调度

Linux 中 CFS 调度器采用完全公平调度算法,通过每个任务的虚拟运行时间 vruntime 进行调度

`vruntime = min_vruntime + (exec_time * weight / priority)
min_vruntime 是一个常数,表示当前进程在调度队列中的最小虚拟运行时间
exec_time 表示进程在 CPU 上执行的时间
weight 是进程的权重,priority 是进程的优先级

cfs_rq 用红黑树存储,按 vruntime 大小排列

优先级调整

可以通过调整任务的 nice 值调整其优先级
nice 值范围为 -20~19priority = default_priority+nice

总线

总线用于 CPU 和内存以及其他设备之间的通信
总线分为 3 种:

CPU 要读写内存数据时:

  1. 首先通过地址总线指定内存地址
  2. 然后通过控制总线控制是读或写命令
  3. 最后通过数据总线传输数据

存储

内存

内存用于存储运行的程序和数据

数据段:存放程序运行时数据
正文段:存放指令

内存速度大概在 200~300 个时钟周期之间

硬盘

存储器的层次关系图.png (1007×485) (xiaolincoding.com)|675

设备控制

设备控制器

CPU 通过设备控制器与设备通信
设备控制器中有芯片和寄存器,CPU 通过读写设备控制器中的寄存器控制设备
设备控制器有三类寄存器:

输入输出设备可分为两大类 :

IO 控制方式

CPU 通过两种方式与设备通信:

设备通过中断与 CPU 通信,硬件通常有一个中断控制器,当设备完成任务后触发中断到中断控制器,CPU 中断当前进程转而处理中断

设备可通过DMA(Direct Memory Access) 功能自行把 IO 数据放入内存

具体流程:

CPU 仅在开始时向 DMA 发送指令,DMA 在数据拷贝完成后触发中断通知 CPU

驱动程序

设备控制器属于硬件,而设备驱动程序属于操作系统

不同的设备控制器功能不同,但设备驱动程序提供统一接口给操作系统,使不同设备以相同方式接入操作系统

驱动程序.png (842×812) (xiaolincoding.com)

设备驱动程序中包含该设备的中断处理函数,CPU 响应设备中断后调用该函数

通用块层

Linux 通过统一的通用块层来管理不同的块设备

通用块层是处于文件系统和磁盘驱动中间的一个块设备抽象层,主要有两个功能:

Linux 存在五种 IO 调度算法:

IO 软件分层

Linux 存储系统的 I/O 由上到下可以分为三个层次:

存储系统的 I/O 是整个系统最慢的一个环节,所以 Linux 提供了不少缓存机制来提高 I/O 的效率:

完整流程(键盘输入到屏幕显示)

  1. 用户输入了键盘字符,键盘控制器就会产生扫描码数据,并将其缓冲在键盘控制器的寄存器中
  2. 键盘控制器通过总线给 CPU 发送中断请求
  3. CPU 收到中断请求后,操作系统保存被中断进程的 CPU 上下文,然后调用键盘的中断处理程序(在键盘驱动程序初始化时注册)
  4. 键盘中断处理程序从键盘控制器的寄存器的缓冲区读取扫描码,再根据扫描码找到用户在键盘输入的字符,如果输入的字符是显示字符,那就会把扫描码翻译成对应显示字符的 ASCII 码
  5. 得到了显示字符的 ASCII 码后,把 ASCII 码放到读缓冲区队列
  6. 显示设备的驱动程序定时从读缓冲区队列读取数据放到写缓冲区队列,然后把写缓冲区队列的数据写入到显示设备控制器的寄存器中的数据缓冲区,最后将这些数据显示在屏幕里
  7. 显示出结果后,恢复被中断进程的上下文