不奢望岁月静好 只希望点滴积累

0%

如何配置ip地址

  1. 使用net-tools
    1
    2
    $ sudo ifconfig eth1 10.0.0.1/24
    $ sudo ifconfig eth1 up
  2. 使用iprote2
    1
    2
    $ sudo ip addr add 10.0.0.1/24 dev eth1
    $ sudo ip link set up eth1
    但: 是可以随意配置的吗 ?

    显然不是. 只要在网络上的包、都是完整的、可以有下层无上层、不能反之.
    若配置为不同网段, 发送请求包时、linux的默认逻辑是: 跨网段调用、不会直接将包发送到网络上、而是企图发送到网关.
    若配置同网段呢? – 会配置失败

动态主机配置协议(DHCP)

DHCP: 动态主机配置协议(Dynamic Host Configuration Protocol)
有了DHCP、网络管理员只需要配置一段共享的ip地址. 每一台新接入的机器会通过DHCP协议去共享的ip地址里申请、然后自动配置

若数据中心里的服务器、ip一旦配置好、基本不会改变、相当于自己买房、自己装修. DHCP的方式类似于租房、临时借用、用完归还即可.

解析DHCP的工作方式

刚加入网络的机器、暂无ip地址、处于DHCP Discover的状态.

它会使用ip地址 0.0.0.0 发送广播包、目的地址为: 255.255.255.255. 广播包封装了UDP、UDP封装了BOOTP. 若网络里配置了DHCP Server, Server会分配ip地址给该MAC地址、同时记录(不会分配给其它机器). 这个过程称为 DHCP Offer.

若收到多个DHCP Server回复、一般会选择接受第一个到达的、且向网络发送一个DHCP Request广播数据包, 包含Clineet的MAC地址、接受的ip地址、提供该ip的HDCP Server地址等, 告诉其它server它接受了哪一台server提供的ip、请求他们撤销提供的ip、以提供给下一个ip租用者.

DHCP Server 接收到Client的request后、会广播返回一个ACK消息.

client会在租期过去50%时、向为其提供ip地址的server发送request消息包, client 再根据接收到server回应的ack消息包中提供的新的租期及其他已更新的tcp/ip参数更新自己的配置.

预启动执行环境(PXE)

预启动执行环境:

ip地址是一个网卡在网络世界的通信地址、类似门牌号

32位ip地址分类.png

无类型域间选路(CIDR)

从上图可以看到、C类地址能包含的主机数只有 28 = 254个, 数量过少; 而B类地址能包含的最大主机数量又太多了、一般企业规模达不到65534、剩余的地址就是浪费.

于是产生了一个折中方案: CIDR. 打破ip类别设计、将32位ip一分为二、前边是网络号、后边是主机号.
eg. 10.100.122.2/24
/ 表示前24位代表网络号、后8位是主机号

伴随CIDR存在的还有广播地址子网掩码.
广播地址是: 10.100.122.255, 若发送这个地址、所有10.100.122 网络里的机器都可以收到
子网掩码: 255.255.255.0

将子网掩码 和 ip 进行 AND运算、可以得到网络号

公有ip地址 和 私有ip地址

私有ip地址范围.png

组播地址: 属于某个组的机器都能收到消息.
eg. 邮件组

在ip地址的后边、有个scope, 对eth0这个网卡来讲、是global, 说明这个网卡是可以对外的、可以接收来自各个地方的包. 对于lo来讲、是host, 说明这张网卡仅可以供本机相互通信.

lo 全程 loopback, 又称回环接口, 往往被分配打平 127.0.0.1 这个地址、经过内核处理后直接返回、不会在任何网络中出现.

MAC地址

MAC地址是一个网卡的物理地址、十六进制表示、6个byte、全局唯一. 但不可替代ip地址.
类似: 身份证号全网唯一、但若在问路时、被人知道的概率确很小、ip具有定位功能、类似省市区街道小区的概念、

MAC地址本身更像是身份证、是唯一标识. 设计唯一性是为了不同网卡放在同一网络时、可以不用担心冲突. 从硬件角度、确保不同网卡有不同标识.

MAC具有一定定位功能、但范围有限. 局限在同一子网. 跨子网的情况、就需要ip地址来查找了.

网络设备的状态标识

<BROADCAST,MULTICAST,UP,LOWER_UP> 是干什么的?这个叫作 net_device flags,网络设备的状态标识

UP 代表网卡处于启动的状态
BROADCAST 表示网卡有广播地址、可以发送广播
MULTICAST 表示网卡可以发送多播包
LOWER_UP 表示L1是启动的, 即: 网线插着呢

mtu 1500 呢 ? 代表最大传输MTU为1500, 这个是以太网的默认值
网络是层层封装的, MTU是二层MAC概念. MAC层有MAC层的头、以太网规定连MAC头带正文合起来不能超过1500个字节、正文里有ip、tcp、http的头,
若放不下、就需要分片来传输.

qdisc pfifo_fast 全称queueing discipline, 即排队规则. 内核若需要通过某个网络接口发送数据包、就需要按照为这个接口配置的qdisc将数据包加入队列.
最简单的qdisc是 pfifo, 它不对进入的数据包做任何处理、数据包采用先入先出的方式通过队列. pfifo_fast 稍微复杂些、它的队列包括3个波段(band)、在每个波段里使用先进先出的规则. band 0的优先级最高, band 2的最低, 若band 0里有数据包、系统就不会处理band 1里的数据包.

数据包是按照服务类型(Type of Service, TOS)被分配到三个波段的、TOS是 IP 头里边的一个字段、代表了当前包的优先级.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
指令周期: Fetch -> Decode -> Execute

CPU周期: 从内存取出一条指令的最短时间(一个指令周期至少需要2个CPU周期)

通过D触发器实现存储功能
通过时钟信号实现计数器

一个时钟周期内确保完成一条最复杂的CPU指令 -> 单指令周期处理器
译码器 -> 从输入的多个位的信号中、根据一定的开关和电路组合、选择出自己想要的信号
优化1: CPU流水线设计(保障一个最复杂的流水线级在一个时钟周期内完成即可)
不能优化单条指令执行延时、可以提高CPU的吞吐率(可同时并行多条指令)

缺点: 流水线深度增加带来性能成本、每级流水线的输出需放到流水线寄存器、然后在下一个时钟周期、交给下一个流水线级处理、每增加一级就要多一次写入流水线寄存器的操作(20ps)
为了保证指令的响应时间这个指标、只有提升时钟周期、电路数量和晶体管数量都会增加、功耗变大

挑战: 冒险和分支预测

优化2: 结构冒险 -> 增加资源(eg. 数据内存和指令内存分开, 现代CPU只将CPU内部的缟素缓存分成指令缓存和数据缓存)

优化3: 数据冒险 -> 流水线停顿(插入NOP操作), 最差会退化成单指令周期CPU
优化4: 数据冒险 -> 操作数前推(操作数转发、节省了写入写入寄存器、从寄存器读出的时间)
优化5: 乱序执行 -> 将指令发送到保留站 -> 提交到不同的FU -> 重排缓存区重排(cpu按照取指顺序、对指令结果重排, 只有排在前边的都完成、才提交指令、完成指令运算) -> 指令提交
提高了CPU的吞吐量
优化6: 分支预测(缩短分支延迟、动态分支预测、静态分支预测)
优化7: 超标量(多发射, 让cpi>1, 并行取指、译码 -> 需要增加电路元件)
优化8: 超线程: 在一个CPU内部、有双份PC寄存器、指令寄存器、条件码寄存器、同时维护两个并行指令的状态(指令译码器、ALU都只有单份)
优化9: SIMD(单指令多数据流):一次加载多个整数、然后使用多个FU并行计算(MMX)

超线程

在一个物理CPU核心内部、有双份的PC寄存器、指令寄存器和条件码寄存器、这样这个CPU核心就可以维护两条并行的指令的状态、在外部看起来似乎有两个逻辑层面的CPU在同时运行. so 又称为同时多线程(SMT: Simulataneous Multi-Threading)、但其它功能组件依然是一份、因为超线程不是真的同时运行两个指令、而是在一个线程A的指令在流水线里停顿的时候、让空闲的CPU译码器和ALU去执行线程B的指令

SIMD 单指令多数据流

一种指令级并行的加速方案、在处理向量计算的情况下、同一个向量的不同维度直接的计算是相互独立的、而CPU里的寄存器、又可以放得下多条数据、所以可以一次性取出多条数据、交给CPU并行执行

SSD的IOPS可以达到2w、4w, 可CPU主频在2GHZ以上、每秒可以有2亿次操作, 若对于IO操作、都是由CPU发出对应的指令、然后等待IO设备完成操作之后返回、那CPU有大部分的时间在等待IO. 其实对于IO设备的大量操作、都只是把内存数据传输到IO设备而已、CPU是无效等待, 于是, 就有了 DMA(Direct Memory Access)直接内存访问 技术

理解DMA、一个协处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
本质上: DMA技术就是在主板上放一块独立的芯片, 在进行内存与IO设备的数据传输时, 不在通过CPU来控制
数据传输、而是通过`DMA控制器`(`DMAC`), 这块芯片可以认为就是一个`协处理器`

DMAC最有价值的地方体现在, 当要传输的数据特别大、速度特别快, 或者传输的数据特别小、速度特别慢时.
eg. 1. 用千兆网卡或者硬盘传输大量数据时、若都用CPU来搬运的话、肯定忙不过来、所以, 选择DMAC
2. 数据传输很慢时, DMAC可以等数据到齐了、再发送信号给CPU处理、而不是让CPU在那里忙等待.

DMAC在控制数据传输时、还是需要CPU的.

总线上的设备包括`主设备``从设备`, 主动发起数据传输的、必须是主设备, eg.CPU;从设备只能接收数据.
所以: 只能是 CPU从IO设备读数据、或者是CPU向IO设备写数据.

IO设备不能向主设备发起请求吗?
可以. 但发送的不是实际的数据内容, 而是控制信号. IO设备可以告诉CPU, 有数据要传输、实际数据由CPU
拉取, 而不是IO设备主动推送给CPU.

包含DMAC的数据传输.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
DMAC既是一个主设备、有是一个从设备. 对CPU来说、它是从设备, 对IO设备来说、它是主设备.
使用DMAC进行数据传输的过程如下:
1. CPU作为一个主设备、向DMAC设备发起请求(就是在DMAC里修改配置寄存器).
2. CPU修改DMAC寄存器的时候, 会告诉DMAC几个信息:
1) 源地址的初始值及传输时的地址增减方式
源地址: 数据要从哪里传输过来. eg. 从内存写入硬盘时, 就是一个内存地址
从硬盘读取到内存时, 就是硬盘的IO接口的地址.
地址的增减方式: 是说数据从大的地址向小的地址传输, 还是从晓得地址往大的地址传输.
2) 目标地址初始值及传输时的地址增减方式
3) 要传输的数据长度
3. 设置完这些信息DMAC就变成一个空闲的状态idle
4. 若从硬盘 -> 内存加载数据, 硬盘会向DMAC发起一个数据传输请求,这个请求是通过额外的连线(不是总线)
5. DMAC再通过一个额外的连线响应该请求.
6. 于是, DMAC再向内存发起总线读的数据传输请求, 就从硬盘读到了DMAC控制器里.
7. 然后、DMAC再向我们的内存发起写的请求、把数据写入内存
8. DMAC会反复进行67 的操作, 直到DMAC的寄存器里边设置的数据长度传输完成.
9. 数据传输完成之后、DMAC重新回到第三步的空闲状态.

整个数据传输的过程中,我们不是通过 CPU 来搬运数据,而是由 DMAC 这个芯片来搬运数据.
但是 CPU 在这个过程中也是必不可少的. 因为传输什么数据,从哪里传输到哪里,其实还是 CPU 来设置的.
这也是为什么,DMAC 被叫作`协处理器`

早期的计算机里没有DMAC、所有数据都是CPU来搬运的、然后出现了主板上独立的DMAC控制器.
现在数据传输要求越来越复杂、加上显卡、网卡、硬盘等各个设备对数据传输的需求不同、各个设备
都有自己的DMAC芯片了

Kafka为什么这么快

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kafka 是一个用来处理实时数据的管道, 常用来做消息队列、或者收集和落地海量的日志. 
作为一个实时数据和日志的管道、瓶颈自然也在IO层面

kafka常见的两种海量数据传输: 1.从网络接收上游数据,落地到本地磁盘 2.从本地磁盘读取、发送到网络上.

先看场景2, 最直观的是用一个文件读操作, 从磁盘把数据读到内存、再通过一个socket、把数据发到网络上.

File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);

这样会有四次数据传输:(两次DMA、两次通过CPU控制的传输)
1. 从硬盘上、读到操作系统内核缓冲区(通过DMA搬运).
2. 将内核缓冲区的数据、复制到应用分配的内存里(通过CPU搬运)
3. 从应用的内存、写到操作系统Socket的缓冲区里(CPU搬运)
4. 从Socket的缓冲区、写到网卡的缓冲区(DMA)

本次磁盘到网络的数据传输过程.png

1
2
3
4
其实只需要搬运一份数据、却搬运了4次、且: 从内核的缓冲区传输到应用的内存里、再从应用的内存里
传输到socket的缓冲区里、其实是把一份数据在内存里搬来搬去, 所以kafka就调用Java NIO库、
FileCahnnel->transfer to 方法, 将数据直接通过Channel写入到对应网络设备, 且对于socket的操作、
也不是写到socket的buffer里、而是直接根据描述符写入到网卡的缓冲区, 省去了23, 只有两次数据传输.

kafka的数据传输模型.png

1
2
这样、同一份数据的传输次数从4次变成了2次、且没有通过CPU传输、都是DMA传输, 在这个方法里、
没有在内存层面copy数据, 也称为`零拷贝`(Zero-Copy)

FPG

CPU其实是一些简单的门电路搭建而成、从最简单的门电路、搭建成半加器、全加器, 然后再搭建完整功能的ALU. 这些功能有完成各种实际计算功能的组合逻辑电路、也有用来控制数据访问、创建出寄存器和内存的时序逻辑电路, 一个4核 i7的CPU、大约有20亿个晶体管.

思考:

如何保证连接20亿晶体管时不出错 ?若每验证一次bug就重新生成一块芯片、代价过高.

1
2
3
4
5
现场可编程门阵列(Filed-Programmable Gate array)解决了这个问题.
P: Programmable, 可通过编程来控制的硬件
G: Gate, 代码芯片里的门电路, 各种编程功能的实现、依赖的就是一个个的门电路
A: Array, 阵列, 是说在一块`FPGA`上、布满了大量的门电路
F: 一块`FPGA`的板子、可以多次进行编程、不像`PAL`(Programming Array Logic, 可编程阵列逻辑)这样古老的硬件设备、只能编程一次.

思考:

如何对硬件进行编程呢 ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CPU其实是通过晶体管、来实现各种组合逻辑或者时序逻辑、如何去`编程`这些线路呢 ?
一、用存储功能实现组合逻辑
在实现CPU时、需要完成各种各样的电路逻辑、在FPGA里、这些基本的电路逻辑、不是采用布线连接的方式进行的、
而是预先根据软件里设计的逻辑电路、算出对应的真值表、然后直接存到`LUT(Look-Up Table, 查找表)`的电路中
(其实就是一块存储空间、存储了输入特定信号时、对应输出是0还是1)
如图(Look-Up Table)示:
这个查表的方法、其实就是FPGA通过LUT来实现各种组合逻辑的方法

二、对于需要实现的时序逻辑电路、可以在FPGA里直接放上`D触发器`、作为`寄存器`.
和CPU里的触发器本质相同、会把多个LUT的电路和寄存器组合在一起(称为`逻辑簇`).
在FPGA里、组合了多个LUT和寄存器的设备叫 `CLB`(Configurable Logic Block, 可配置逻辑块)
通过配置CLB实现的功能类似全加器、是基础电路上的组合、可提供更复杂的功能、更复杂的芯片可以从CLB组合搭建、不用从基本门电路

三、FPGA是通过可编程逻辑布线来连接各个不同的CLB、最终实现我们想要的功能芯片.
可类比为铁路网、整个铁路系统已经铺好、单设计了很多岔道、通过控制岔道来确定不同列车的线路.
在可编程逻辑布线里、编程做的、就是拨动像岔道一样的各个电路开关、最终实现CLB之间的连接、完成我们想要的功能

Look-Up Table.png

image.png

ASIC

虽然在手机或者录音笔上塞上一个Intel的CPU可以实现拍照、录音等功能、但, 显然比较浪费、于是: 考虑为专门用途的场景、设计单独芯片, 称为:
ASIC(Application-Specifed Integrated Circuit) -> 专用集成电路, 电路比较精简、制造成本也比CPU低、功耗也较小

思考:

可以FPGA实现ASIC的事情吗?

1
2
3
4
5
6
7
8
9
10
可以、而且成本和功耗上要优于做通用计算的CPU和GPU、

那: 为什么不直接使用FPGA替代ASIC呢 ?
硬件上的浪费. 每一个门电路都是一个小小浪费、一个LUT电路设计出来、既可以实现`与`门、
又可以实现`或`门、用到的晶体管是比单一功能要多的、单品FPGA的生成制造成本也较高
FPGA的优点是: 无硬件研发成本、ASIC的电路设计、需要仿真、验证、还要经过六篇、变成一个印刷的电路板、最终变成芯片、
从研发到上式、最低花费也要几万美元、高的话、可能在几千万到数亿美元、还要失败的可能,
若设计的芯片最终只制造几千片、那还是FPGA的成本较低

实际上: 到底采用FPGA这样的可编程通用硬件、还是ASIC这样的专用芯片, 核心决定因素还是成本(不止是单个芯片的制造成本、还包括总体拥有成本)

FPGA 本质上是一个可以通过编程来控制硬件电路的芯片、通过LUT这样的存储设备来代替硬连线的电路、有了可编程逻辑门、然后把LUT和寄存器放在一起、变成一个更复杂的电路CLB, 单号通过可编程布线中的开关、设计成芯片、FPGA常用来进行芯片的设计和验证、也可直接当成专用芯片、替代CPU或者GPU
相比FPGA, ASIC设计出来的芯片针对特殊场景、研发成本高、制造成本和能耗较低、针对大量需求、适合ASIC

GPU的历史进程

1
2
3
4
5
6
GPU是随着需要在计算机里渲染三维图形、而发展起来的设备

90年代中期、随个人电脑的性能越来越好、开始有了3D显卡的需求、那个时代之前的3D游戏、都是伪3D
从不同视角看到的是8副不同的贴图、并不是通过图形学渲染出来的多边形

为什么CPU的性能已经大幅度提升、还需要单独的GPU呢 ?

图形渲染的流程

1
2
3
4
5
6
7
8
9
10
现在电脑显示的3D画面、其实是通过多边形组合出来的. 现在各种游戏人物的脸、不是相机或者摄像头拍出来的、而是通过多边形建模创建出来的

实际这些人物在画面里的移动、动作、乃至根据光线发生的变化、都是通过计算机根据图形学的各种计算、实时渲染出来的

图像实时渲染、可拆解为:
1. 定点处理 (Vertex Processing)
2. 图元处理 (Primitive Processing)
3. 栅格化 (Rasteerization)
4. 片段处理 (Fragement Processing)
5. 像素操作 (Pixel Operations)

解放图像渲染的GPU

1
2
3
4
5
6
7
若使用CPU渲染、需要多少资源来处理 ?
上世纪90年代、屏幕分辨率大概: 640*480, 约30w, 为了眼睛看到的画面不眩晕、希望画面有60帧、即:
每秒重新渲染60次(1800万次单个像素的渲染), 从栅格化开始、每个像素有3个流水线步骤、假设每个步骤只有1个指令、也需要5400w条指令

90年代的CPU性能是多少呢 ? 93年第一代Pentium处理器、主频60MHZ、后续逐步推出了 66MHZ、75MHZ、100MHZ的处理器、以这个性能来看、用CPU来渲染3D图形、就基本上把CPU的性能耗光了、因为实际的每一个渲染步骤不可能只包含一个指令、所以、CPU跑不动3D图形渲染

既然图形渲染的流程是固定的、直接使用硬件来处理、不用CPU计算是不是可以呢 ? Voodoo FX这样的图形加速器出现了、显然、硬件会比制造通用计算性能的cpu要便宜的多、因为整个计算过程是固定的、不需要流水线停顿、乱序执行等各类导致CPU计算变的复杂的问题、也不需要可编程能力、只让硬件按照谢浩的逻辑进行运算即可

现代GPU的三个核心创意

1
2
3
4
现代CPU里的晶体管越来越多、越来越复杂、其实已经不是用来实现`计算`这个核心功能、而是拿来实现乱序执行、分支预测及存储器高速缓存

而 对于GPU、这些电路就很多余了、GPU的整个处理过程是一个流式处理的过程(Stream Processing),
因为没有太多分支条件或者复杂的依赖关系、可以把GPU的对应电路都省掉、只保留取指令、指令译码、ALU以及执行这些计算需要的寄存器和缓存、一般会抽象为如下图三部分: 取指令、指令译码、ALU和执行上下文(乱序执行、分支预测、高速缓存... 都被省掉)

image.png

SIMT和多核并行

1
2
3
4
5
6
7
8
9
10
这样一来、GPU的电路就比CPU简化很多了、于是可以在一个GPU里、塞很多并行的GPU来实现计算、好像CPU里的多核CPU一样、和CPU不同的是、不需要单独实现多线程的计算、GPU的计算是天然并行的

上节提到:
无论是对多边形里的顶点进行处理还是对屏幕里的每一个像素、每个点的计算都是独立的、所以:
简单的添加多核的GGPU、就能做到并行加速、
另外: CPU的SIMD技术: 在向量计算的时候、要执行的指令是一样的、只是同一个指令的数据有所不同、在GPU的渲染管里、无论是顶点线性变换还是屏幕上临近像素点的光照和上色、都是在用相同指令流程进行计算、GPU就借鉴了SIMD、用来一种叫SIMT的技术,

SIMT比SIMD更加灵活、在SIMD里、CPU一次性取出了多个固定长度的数据、放在寄存器里、用同一个指令去执行、而SIMT可以将多条数据、交给不同的线程来处理

各个线程里执行的指令流程是相同的、但可能根据数据的不同、走到不同的条件分支、这样相同的代码和相同的流程、可能执行不同的具体的指令、于是GPU可以进一步简化、取指的时候可以交给后面多个不同的ALU并行计算、这样一个GPU的核里就可以放下更多的ALU、同时进行更多的并行运算了

多核并行.png

SIMT并行-多个ALU.png

GPU里的超线程

1
2
3
4
GPU里的指令、可能会遇到和CPU类似的流水线停顿问题、是不是可以想到优化方案`超线程` ?
在GPU里一样可以有类似的优化. 即: 遇到流水线停顿时、调度一些别的计算任务给当前的ALU

和超线程一样、既然要调度一个不同的任务执行、就需要针对这个任务、提供更多的执行上下文、so. 一个Core里的执行上下文的数量、需要比 ALU 多

image.png

GPU在深度学习上的性能差异

1
2
3
4
通过芯片瘦身、SIMT和更多的执行上下文、GPU更擅长并行进行暴力运算、恰好适合深度学习的应用场景

一方面: GPU是一个可以进行通用计算的框架、可以通过编程、在GPU上实现不同的算法、
另一方面: 现在的深度学习计算、都是超大的向量和矩阵、海量的训练样本的计算、整个过程没有复杂的逻辑和分支、非常适合GPU这样并行、计算能力强的架构

接口和设备: 经典的适配器模式

1
2
3
4
5
6
7
8
9
10
实际上, 输出输入设备、并不只是一个设备、大部分的输入输出设备、都有两个组成部分.
接口和实际的IO设备. 硬件设备不是直接接入到总线上和CPU通信的、而是通过接口、用接口连接到总线上
再通过总线和CPU通信的. 平时听说的并行接口(Parallel Interface), 串行接口(Serial Inteerface)
USB接口都是计算机主板上内置的各个接口, 实际的硬件设备 eg. 使用并口的打印机、使用串口的老式鼠标
或者使用USB接口的U盘、都要插入到这些接口上、才能和CPU工作及通信的.

接口本身就是一块电路板、CPU其实不是和实际的硬件设备打交道、而是和这个接口电路板打交道.
常说的设备里有三类寄存器、都在设备的接口电路上、而不是实际的设备上.

三类寄存器: 状态寄存器(Status Register)、命令寄存器(Command Register)和数据寄存器(Data Register)

CPU是如何控制IO设备的

1
2
3
4
无论是内置在主板上的接口、还是集成在设备上的接口、除了3类寄存器之外、还有对应的控制电路,
正是通过这个控制电路、CPU才能向这个接口电路板传输信号、来控制实际的硬件

设备上的寄存器有什么用呢 ?

打印机.png

1
2
3
4
5
6
7
8
9
1. 数据寄存器. CPU向IO设备写入需要传输的数据、eg. 要打印`GeekTime`, 会先发送一个G给对应IO设备
2. 命令寄存器. CPU发送一个命令、告诉打印机、要打印. 此时打印机里的控制电路会做两个动作.
1) 设置状态寄存器里的状态为 not-ready
2) 实际操作打印机进行打印
3. 状态寄存器. 告诉CPU、设备已经在工作了、此时再发送数据或者命令都是无效的
直到前边的动作已完成、状态寄存器重新变成ready状态、CPU才发送下一个字符和命令

在实际情况中、打印机通常不止有数据寄存器还有数据缓冲区、CPU也不是真的一个字符一个字符交给打印机处理的
而是一次性将整个文档传输至打印机的内存或者数据缓冲区一起打印的

信号和地址: 发挥总线的价值

1
2
3
4
5
6
7
8
9
10
11
CPU到底要往总线上发送一个什么样的命令、才能和IO接口上的设备通信呢 ?

CPU和IO设备的通信、一样是通过CPU支持的机器指令来执行的.
和访问主内存一样、使用内存地址来和IO设备通信. 为了让已经足够复杂的CPU尽可能简单、
计算机会把IO设备的各个寄存器及IO设备内部的内存地址、都映射到主内存地址空间来、
主内存的地址空间里、会给不同的IO设备预留一段一段的内存地址. CPU想和IO设备通信的时候、
就往这些地址发送数据、这些地址信息就是通过地址总线老来发送的、对应的数据信息就是通过数据总线发送的

而IO设备、会监控地址线、且在CPU往自己的地址发送数据的时候、把对应的数据线里边传输过来的数据、
接入到对应的设备里边的寄存器和内存里. CPU无论是向IO设备发送命令、查询状态还是传输数据都可以
通过这种方式. 称为内存映射IO(Memory-Mapped IO, MMIO)

image.png

那么、MMIO是不是唯一一种CPU和设备通信的方式呢 ?

1
2
3
4
5
6
7
8
9
10
11
不是的. 精简指令集MIPS的CPU特别简单、所以这里只有MMIO. 而有2000多个指令的Intel
而我们有2000多个指令的Intel X86架构的计算机、有专门的和IO设备通信的指令, 即: inout 指令.
Intel CPU虽然也支持MMIO、不过还可以通过特定的指令、来支持端口映射IO(Port-Mapped IO, 简称PMIO)
也可以叫独立输入输出(Isolated IO)

其实PMIO的通信方式和MMIO差不多、核心区别在于: PMIO里访问的设备地址、不再是在内存地址空间里、
而是一个专门的端口, 这个端口不是硬件杀昂的插口、而是和CPU通信的一个抽象概念

无论是PMIO还是MMIO、CPU都会传送一条二进制的数据、给到IO设备的对应地址. 设备自己本身的接口电路、
再去解码这个数据、解码之后的数据会变成设备支持的一条指令、再去通过控制电路操作实际的硬件设备
CPU来说、它不关心设备本身可以支持哪些操作、只是在总线上传输一条条数据就好了.

CISC: Complex Insturction Set Computing, 即: 复杂指令集

RISC: Reduced Instruction Set Computing, 即: 精简指令集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
早期、所有的CPU都是 CISC、并无 CISC 和 RISC 之分 
CPU指令集的设计、需要考虑硬件限制、为了性能考虑、很多功能直接通过硬件电路完成、为了减少内存、指令的长度也是可变的, 常用指令的长度短一些、不常用指令长度可以长一些

随历史发展、计算机性能和存储都在发展、当时 大卫*帕特森教授发现: 实际在CPU运行的程序里、80%都是在使用 20% 的简单指令、提出 RISC的理念

RISC的CPU想法其实很直观、既然80%的时间都在使用20%的简单指令、那只使用20%的简单指令呢? - 因为指令数量很多、计算机科学家在软硬件两方面都受到了很大的挑战

在硬件层面、想支持更多的复杂指令、CPU里的电路就要越复杂、在散热和功耗上也就会带来更大的挑战、
在软件层面、支持更多的复杂指令、编译器的优化就更难

于是在RISC架构里、CPU选择将指令精简到20%的简单指令、而原先的复杂指令 则用简单指令组合起来实现、让软件实现硬件的功能、这样CPU的硬件设计就更简单了、性能提升也会变的容易

RISC的CPU里完成指令的电路变简单了、就腾出了更多空间、这个空间、常被拿来放通用寄存器、因为RISC完成同样的功能、执行的指令数要比CISC多、所以、若需要反复从内存读取指令或者数据到寄存器、很多时间就会花在访问内存上、于是, RISC架构的CPU往往有更多的通用寄存器、

除了寄存器这样的存储空间、RISC的CPU也可以把更多的晶体管、用来实现更好的分支预测等相关功能、进一步提升CPU的实际利用率

`CPU执行时间` = `指令数` * `CPI` * `Clock Cycle Time`

CISC的架构其实就是通过优化指令数、来减少CPU的执行时间、而RISC的架构、其实是在优化CPI
指令比较简单、需要的时钟周期就很少

微指令架构

1
2
3
4
5
6
7
8
9
10
在微指令架构的CPU里、编译器编译出来的机器码和汇编代码没发生变化、但: 在指令译码阶段、指令译码器翻译出来的、不再是某一条CPU指令、译码器会把一条机器码、翻译成好几条微指令、这一条条的微指令就变成了固定长度的RISC风格的了

这些RISC风格的微指令、会被放在一个微指令缓冲区里、然后再从缓冲区里边、分发给后边的超标量、且乱序执行的流水线架构里(精简指令)、在这个架构里、指令译码器相当于变成了`适配器`, 填平; CISC和RISC之间的指令差异

但: 这样一个可以把CISC的指令译码成RISC指令的指令译码器、比原来的译码器要复杂、就意味着更复杂的电路和更长的译码时间, 本来以为可以通过RISC提升的性能、又有一部分浪费在了指令译码上、怎么解决呢?

由于80%运行的代码、只包含20%的常用指令、这个很强的局部性、可以使用缓存来解决.
Intel在CPU里加了一层 L0 Cache、这个Cache保存的就是指令译码器把CISC的指令翻译RISC的微指令的结果、于是大部分情况、CPU可以从Cache拿到译码结果、而不用实际译码, 不仅优化了性能、因为译码器的开关动作变少了、还减少了功耗

由于Intel本身在CPU层上做过大量优化: 分支预测、乱序执行等、X86的CPU始终在功耗上还是要超过RISC架构的ARM、所以最终在智能手机崛起替代PC的时代、落在了ARM后面

CPU的现在和未来

1
2
3
现在、CISC和RISC架构的分界已经不明显了、Intel和AMD的CPU也都是采用译码成RISC风格的微指令来运行、而ARM的芯片、一条指令通用需要多个时钟周期、有乱序执行和多发射

未来的CPU、多半会像Linux一样、逐渐成为一个业界的主流选择、若想打造一个属于自己的CPU、可以关注下 RISC-V开源项目的发展

SSD的读写原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
下边性能对比图显示: SSD的耐用性很差、若需要频繁的写入/删除数据、则机械硬盘比SSD性价比要高很多.

之前我们知道、CPU Cahe 是SRAM 用一个电容来存放一个比特的数据、对于SSD、也可先简单认为,
是一个电容+一个电压计、记录一个或多个比特

`SLC`、`MLC`、`TLC`、`QLC`
给电容充电时、电压就是1; 放电后,里边没电、就是0. 采用这种方式存储的SSD硬盘、称`使用了SLC的颗粒`,
全称`Single-Level Cell`, 即: 一个存储单元只有一位数据.
但: 这样就会遇到与CPU Cache相同的问题: 相同面积下、由于存放的元器件有限、存储容量上不去.
于是, 有了`MLC`(Multi-Level Cell)、`TLC`(Triple-Level Cell)及`QLC`(Quad-Level Cell),
即: 一个电容可存23乃至4个bit
同时, 由于对精度的要求更高、QLC的读写速度比SLC要慢好几倍

P/E擦写问题
SSD同其它IO设备、有对应接口和控制电路、控制电路中一个很重要的模块叫`TFL`(Flush Transaction Layer)
闪存转换层. SSD磁盘性能的好坏、很大程度上取决于FTL算法的好坏.

实际的IO设备和机械硬盘很像、有很多裸片叠在一起;
一张裸片上可放多个平面(Plane), 一个平面的存储容量大概在GB级别;
一个平面上会划分成很多块(Block), 一般一个块的存储大小在几百KB到几MB;
一个块、还会分为很多页(Page), 大小通常是4KB.

SSD读取和写入的基本单位是`页`, 擦除单位是`块`.
SSD的使用寿命、其实就是每一个块的擦除次数.
`SLC`的擦除次数大概在10w次、`MLC`就1w次左右、`TLC`和`QLC`只有几千次.

SSD特别适合读多、写少的应用、在日常应用里、系统盘适合使用SSD.
但若使用SSD做下载盘、一直下载各种影音数据、就不好了、特别是现在QLC的SSD, 只有几千次的擦写寿命.

SSD和HDD性能对比.png

SSD物理结构.png

如何最大化利用SSD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
`磨损均衡`: 实现的核心办法、类似虚拟内存、添加一个间接层`FTL`, 像通过一个页表映射虚拟内存和物理页
一样、在FLT里存放了逻辑块地址(Logic Block Address, LBA)到物理块地址(Physical Block Address,
PBA)的映射.

操作系统所有对SSD的读写请求、都要经过FLTFTL里又有逻辑块对应的物理块、这样FTL可以记录每个物理块
被擦写的次数. 若一个物理块被擦写较多、FTL就将它挪到一个擦写少的物理块上,逻辑块不动、操作系统无感.

`TRIM`指令支持
操作系统不关心底层硬件是什么、在SSD的使用上也会带来一个问题:
操作系统的逻辑层和SSD的逻辑层里的块状态是不匹配的.

在操作系统里删除一个文件、其实并没有真正在物理层操作删除、只在文件系统里把对应的inode元信息清理掉,
这个inode还可以继续使用, 写入新的数据, 在物理层面对应的存储空间、在操作系统里被标示可写入.
所以: 日常文件删除、只是操作系统层面的逻辑删除、不小心误删文件时、还可以通过恢复软件、恢复出来,
想彻底删除数据、需要使用文件粉碎功能.

这个删除的逻辑在机械硬盘上可行、后续的写入可直接覆写该位置. 但在SSD上不行.

eg. 在操作系统里删除一个刚下载的文件 a.txt, 在操作系统里、对应的inode里、就没有文件的元信息,
SSD的逻辑块层面、并不知道这个事情, a.txt 依然占用了空间、对应的物理页、也是被占用的.
此时: 若要对SSD进行垃圾回收、a.txt 对应的物理页、让要被搬运到其它Block中去、只有当操作系统再
在刚才的inode写入数据时、才会知道原来的数据已无用, 才会标记删除这种现象导致, 为了磨损均衡、
可能搬运了很多已删除的数据、导致很多不必要的数据读写和擦除, 损耗SSD性能.

为了解决这个问题、现在的操作系统和SSD的主控芯片都支持`TRIM`命令、可在文件删除时、让操作系统通知
SSD, 标记对应逻辑块为已删除.

`写入放大`:
TRIM 命令的发明,也反应了一个使用 SSD 硬盘的问题,那就是,SSD 硬盘容易越用越慢.
SSD存储空间被越占越多时、每次数据写入可能都没有足够空间, 不得不进行垃圾回收、合并块里的页、
然后擦除一些页. 此时, 从操作系统层看、可能只是写入4k或者4M的数据、通过FTL后、可能要搬运8MB16MB甚至更多的数据

`实际的闪存写入的数据量 / 系统通过 FTL 写入的数据量 = 写入放大`
写入放大越多、SSD的性能也就越差.
解决写入放大、需要在后台定时进行垃圾回收.在磁盘较闲时、把搬运数据、擦除数据、留出空白的工作做完.
实际数据写入时、就不会有这么多的性能损耗了.

image.png

AeroSpike: 如何最大化SSD的使用效率

1
2
3
4
5
6
7
8
9
10
11
12
1.AeroSpike 操作SSD硬盘、未通过操作系统的文件系统、而是直接操作SSD里的块和页.
2.AeroSpike 在读写数据时有2个优化.
1) 写数据时、尽可能写一个较大的块, 而不是频繁的写很多小的数据块, 这样磁盘就不太容易出现磁盘碎片.
也更好的理由磁盘的顺序写入优势.
2) 读取时、可以读取小数据, 因为SSD的随机读取性能很好、也不像写入数据一样有擦除寿命问题.
且一次性读较大数据、需要在网络间传输时、可能导致带宽不够.

另: 由于AeroSpike是一个对响应时间要求很高的实时KV数据库、若出现严重的写放大效应、会导致数据写入
响应时长大幅度变长. 所以做了下边的优化:
1. 持续进行磁盘碎片整理. 使用高水位算法, 其实就是: 一旦一个物理块的数据碎片超过50%, 就将其搬运
压缩、然后进行数据擦除、确保始终有足够空间写入.
2.为保障数据库性能,建议只用SSD硬盘容量一半, 即: 人为预留SSD50%空间、确保SSD的写放大效应尽可能小.