操作系统(一)硬件结构

目录

硬件结构

CPU 

存储器

层次结构&&关系 

内存映射到 Cache 的方案

CPU Cache 读取过程&&数据结构 

如何优化语句,让 CPU 跑得更快? 

CPU 缓存一致性

Cache 数据写入

保证缓存一致性? 

总线嗅探

MESI 协议

伪共享

中断 

什么是中断?

什么是软中断? 

数字的二进制 

为什么负数要用补码表示? 

十进制小数如何转二进制? 

计算机如何存小数? 

0.1 + 0.2 == 0.3 吗? 


硬件结构

CPU 

存储器

层次结构&&关系 

离 CPU 越近越快;每个存储器只与相邻一层存储器打交道;速度越快成本越高。

寄存器:

  • 通常在几十 ~ 几百个之间
  • 每个寄存器一般存储 4Byte(32 位) 或者 8Byte(64 位) 数据
  • 一般半个时钟周期(1 / 主频)内读写完成

CPU Cache:使用静态随机存储器(SARM Static Random-Access Memory)

  • L1 高速缓存:2 ~ 4 个时钟周期几十 KB ~ 几百 KB
  • L2 高速缓存:10 ~ 20 个时钟周期几百 KB ~ 几 MB
  • L3 高速缓存:20 ~ 60 个时钟周期几 MB ~ 几十 MB

内存:使用动态随机存储器(DRAM Dynamic Random Access Memory)

  • 200 ~ 300 个时钟周期

SSD/HDD 硬盘:

  • SSD(固态硬盘):比内存慢 10 ~ 1000 倍
  • HDD(机械硬盘):比内存慢 10W 倍

内存映射到 Cache 的方案

直接映射:

优点:

  • 硬件简单,成本低,地址计算速度快
  • 不涉及替换算法

缺点:

  • 不灵活,每个内存块只有固定位置
  • Cache 空间得不到充分利用
  • 容易发生冲突,Cache 效率降低
  • 只适合大容量 Cache
  • 如 0 和 16 都映射到第 0 块,其实其他块空闲也不能占用,这两块会来回替换,降级命中率

 全相联映射:

优点:

  • 灵活,可以映射到任意块
  • Cache 利用率高
  • 冲突率低

缺点:

  • Cache 比较电路设计和实现比较困难
  • 只适合小容量 Cache

 组相联映射: 为前两种的折中方法,组间使用直接映射,组内使用全相联映射

  • 内存一个组内块数与 Cache 分组数相同;存放那一组固定,组内哪一块灵活
  • 内存第 0 块和第 8 块都直接映射到 Cache 第 0 组(直接映射)
  • 组内第 0 块和第 1 块都能被映射(全相联映射)
  • Cache 组内有多少块称为多少路

CPU Cache 读取过程&&数据结构 

读取过程:

  • 先从 L1 缓存读取,不存在则往 L2 缓存找;依次往下一级找(远离 CPU)
  • 从内存读数据,并非按单个变量读取,每次都连续读一小块(Cache Line,一般为 64 字节);如 int 数组,一次读取 16 个元素,下次访问直接从 Cache Line 读取,大大提高 CPU 读数据性能

数据结构: 

  • ⭐从内存中读取的这一块数据,称为内存块(Block);读取时,需要拿到数据所在内存块的地址
  • ⭐先会判断 Cache 中是否存在需要的数据,并且一般只会读取一个片段(字 word);对于直接映射,内存地址由 组标记 + 索引 + 偏移量 组成
  1. ⭐索引:用于找到指定 Cache Line,判断有效位,无效则去内存取
  2. ⭐组标记:用于判断 Cache Line 存储的,是否为所需的内存块
  3. ⭐偏移量:用于得到指定的数据片段(字 word)

如何优化语句,让 CPU 跑得更快? 

CPU 跑得快,其实就是读写快,那就得提高缓存命中率 


1. 提高数据缓存命中率:使 CPU 读到的数据在内存中是连续分布的,即按照数据在内存中的布局顺序访问

  • 如:遍历数组,尽量按照内存布局顺序访问
int a[5][5]

cache line0: a[0][0] a[0][1] a[0][2] a[0][3] a[0][4] ..... 
cache line1: a[3][1] a[3][2] a[3][3] a[3][4] a[4][0] a[4][1] ......

// 顺序读取
// 对于前 16 个元素(第一行),只需要读内存一次
// 第二行,需要再读内存一次
// 共 2 次
for(int i = 0; i <= 4; i++){
    for(int j = 0; j <= 4; j++){
        a[i][j]
    }
}

// 跳跃读取
// 第一行,读一次内存
// 当 j == 4 时,数据位于第二行,又需要读取一次内存
// 后续还存在读取内存的情况,并且数组越大,越容易出现这种情况
for(int i = 0; i <= 4; i++){
    for(int j = 0; j <= 4; j++){
        a[j][i]
    }
}

2. 提高指令缓存命中率:有规律的条件分支语句能够让 CPU 的分支预测器发挥作用(提前将指令放入缓存)

  • 如:对数组元素判断之后进行操作,可以先排序,这样可以充分发挥指令缓存的作用

3. 提高多核 CPU 缓存命中率:将线程绑定到某一个 CPU 核心,避免不同核切换可能降低命中率

CPU 缓存一致性

Cache 数据写入

写直达(Writer Through)

  • 数据同时写入到 cache(数据在 cache 中)和内存。
  • 每次都写入内存,影响性能

写回(Writer Back): 只有当前行需要被换出时,才写回内存;命中率高时,性能更好

  • 假如缓存命中(数据在该 Cache Line 中),则直接更新到 cache,并将该 Cache Line 标记为脏。
  • 假如缓存未命中(该 Cache Line 存放着其他内存地址的数据);若 Cache Line 为脏数据,则将其写回内存;从内存读出该数据到 Cache Line,将数据写入(未标记为脏则直接写入),并标记为脏。
  • 减少数据写回内存频率,提高性能

保证缓存一致性? 

 写传播(Write Propagation):某个 CPU 核心里的 Cache 数据更新,必须要传播到其他 CPU 核心的 Cache

事务的串行化(Transaction Serialization):一个或多个 CPU 核心对 Cache 统一数据的修改,在其他核心看起来的顺序必须要与实际顺序一致

  1. CPU 核心对 Cache 数据的操作,同步给其他核心
  2. 对 Cache 数据的更新,需要拿到

总线嗅探

  • CPU 每时每刻都监听总线上的广播事件
  • 不管其他核心上是否缓存相同数据,更新时,都发送广播
  • 只保证更新被其他核心知道,不保证事务的串行化

MESI 协议

特点:

  • 基于总线嗅探机制实现了事务的串行化
  • 状态机机制降低了总线带宽压力
  • 做到了缓存一致性

Cache Line 四种状态:

  • Modified:已修改
  • Exclusive:独占
  • Shared:共享
  • Invalidated:已失效

过程:

  1. 核心 A 读取数据到 Cache Line,此时其他核心未读取该数据,状态为 E(独占)
  2. 核心 B 也读取该数据到 Cache Line,会通知其他核心,由于核心 A 也读取了该数据,所以状态都变为 S(共享)
  3. 核心 A 要修改该数据,向其他核心广播一个请求,要求它们将对应的 Cache Line 修改为 I(无效);然后更新自己的 Cache Line,并标记为 M(已修改)
  4. 核心 A 若还要继续修改该数据,因为已经是 M(已修改),则无需向其他核心发送信息,直接修改。
  5. 若核心 A Cache Line 需要被替换 || 其他核心读取或者修改该数据,都会使核心 A 该数据写回内存。


  • [独占] 和 [共享],缓存和内存数据一致
  • [已修改] 和 [独占],更新不需要通知其他核心,缓解总线压力

伪共享

过程: 

  1. 普通变量 a,b 在内存中相邻。
  2. 核心 A 需要使用 a,核心 B 需要使用 b
  3. 由于从内存中读取数据是连续的;核心 A 读取 a 时,会将 b 也读入;核心 B 读取 b 时,会将 a 也读入;两核心读入相同 Cache Line
  4. 当核心 A 修改 a 时,会使核心 B Cache Line 失效。
  5. 核心 B 此时修改 b,需要核心 A 将数据写入内存,然后核心 B 再读取到 Cache Line,最后修改 Cache Line。
  6. 只要核心 A B 有修改操作,则缓存相当于已经失效。

解决方法:空间换时间

  • 大小字节对齐(另外的变量放到新 Cache Line)
  • 字节填充(使用不会被读写的变量,填充 Cache Line 空位)

总结:

  • 伪共享(False Share):就是多个线程同时读写同一 Cache Line 不同变量,导致缓存失效。
  • 多线程热点数据,应该避免在同一个 Cache Line。

中断 

什么是中断?

在计算机中,中断是系统用来响应硬件设备请求的一种机制,操作系统收到硬件的中断请求,会打断正在执行的进程,然后调用内核中的中断处理程序来响应请求;中断是异步处理机制,能提高系统并发但是需要尽快完成,避免影响正常程序运行

  • 如:我正在看书,妈妈打电话叫我开门,我需要先停止看书,去开门;我不需要一直询问妈妈是否回来了,只需要等电话通知

什么是软中断? 

中断丢失:同一时刻系统中只能响应一个中断,其他中断可能会因此关闭


 Linux 为了解决中断处理程序执行过长,将中断分为上下两部分:

  • 上半部:对应硬中断;用来快速处理中断,一般暂时关闭中断,主要处理与硬件相关或事件敏感的事情
  • 下半部:对应软中断;由内核(每个 CPU 核心对应一个内核线程)触发中断,处理上半部未完成的工作,将耗时的工作都交给软中断处理

Linux 中软中断事件:网络收发、定时、调度、RCU 锁

数字的二进制 

为什么负数要用补码表示? 

取补码步骤: 除符号位全部取反;最后 + 1

  • 若直接将符号位置为 1 来表示负数,则 -2 + 1 结果会变成 -3
  • 非补码直接相加不行,需要将加法变成减法,增加运算步骤,影响性能
  • 采取补码,可以直接相加,且结果正确

十进制小数如何转二进制? 

  • 整数部分:除 2 取余
  • 小数部分:乘 2 取整

计算机如何存小数? 

  • 符号位:表示数字是正数还是负数,为 0 表示正数,为 1 表示负数
  • 指数位:指定了小数点在数据中的位置,指数可以是负数,也可以是正数,指数位的长度越长则数值 的表达范围就越大;需要 +127(偏移量)再转成二进制
  • 尾数位:小数点右侧的数字,也就是小数部分,比如二进制 1.0011 x 2^(-2),尾数部分就是 0011, 而且尾数的长度决定了这个数的精度,因此如果要表示精度更高的小数,则就要提高尾数位的长度

0.1 + 0.2 == 0.3 吗? 

不相等0.1 与 0.2 二进制表示,是无限循环的,无法精确表示,采取近似值表示;两数相加必然不相等。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值