小林图解系统硬件结构
1.操作系统之硬件结构
1.1 CPU是如何执行程序的
图灵机的原理:
图灵机主要功能就是读取纸带格子中的内容,然后交给控制单元识别字符是数字还是运算符指令,如果是数字则存入到图灵机状态中,如果是运算符,则通知运算符单元读取状态中的数值进行计算,计算结果最终返回给读写头,读写头把结果写入到纸带的格子中。
冯诺依曼模型:中央处理器(CPU)、内存、输入设备、输出设备、总线
- 内存:程序和数据都是存储在内存中,存储的区域是线性的。存储的单位是二进制位,最小的存储单位是字节。
- 中央处理器:32位(CPU的位宽)CPU一次可以计算四个字节,64位CPU可以计算8个字节。
- 总线:用于CPU和内存以及其他设备之间的通信,包括地址总线(指定CPU将要操作的内存地址),数据总线(用于读写内存的数据),控制总线(发送和接受中断,设备复位等信号)
- 输入输出设备:如果输入设备是键盘,是需要和CPU进行交互的,这时就要用到控制总线了
- 线路位宽和CPU位宽:低电压表示0,高电压表示1,线路位宽决定一次能访问多少内存地址,比如32条地址总线能访问2 ^ 32 = 4G的内存。
程序执行的基本过程:
CPU读取程序计数器的内存地址,从内存里把需要执行的指令读取到指令寄存器里面执行,然后根据指令长度自增,开始顺序读取下一条指令。
程序的CPU执行时间 = CPU时钟周期数 时钟周期时间 = 指令数 指令的平均时钟周期数 * 时钟周期时间
64和32位软件,实际上代表指令的64位的还是32位的,一般32位指令在64位机器执行,需要一套兼容机制,64位指令在32位机器上执行,就比较困难了。硬件的64位和32位指的是CPU的位宽,软件的64位和32位指的是指令的位宽。
1.2 存储器金字塔
断电后内存的数据会丢失,硬盘不会,因为其是持久化存储设备。
- 寄存器:最靠近CPU,速度最快,数量通常在几十到几百之间
- CPU Cache:用的是SRAM(有电数据就存在,断电数据就丢失了)静态随机存储器,分成L1,L2,L3三层高速缓存
- 内存:使用DRAM,动态随机存储器
- SSD/HDD硬盘:SSD结构和内存类似,但是相比内存的优点是断电后数据依然存在。SSD比HDD快10 ~ 1000倍
1.3 如何写出让CPU跑的更快的代码
CPU Cache缓存的结构如下:
CPU读取数据的顺序,先在Cache中找有没有,没有就到内存中去找,并把内存的数据读取到Cache中,CPU再从CPU Cache中读取数据。CPU Cache一次性加载64字节大小数据。
想要代码跑得快,就要提高CPU的缓存命中率。
- 比如二维数组,先遍历行,再遍历列的效率比反过来快很多,提高数据缓存命中率。
- 再比如数据先排序,后遍历的顺序会更快,因为遍历时数据都是排好序的,提高指令缓存命中率。
因为L1和L2Cache是每个核心独有的,当进程在多核CPU之间切换时,各个核心的缓存命中率就会收到影响,这时可以考虑把线程绑定到CPU某一个核心上。
1.4 CPU缓存一致性
数据写入的方法:写直达和写回
- 写直达:把数据同时写入内存和Cache中。无论数据在不在Cache里面,每次写操作都会写回到内存。如果数据在Cache里有,还要写到Cache Block里
- 写回:发生写操作时,新的数据仅仅被写入到Cache Block里,只有当Cache Block被替换时才需要写到内存中。可以理解为只有在缓存不命中,并且在Cache Block为脏的情况下,才将数据写到内存中,实际上就是如果想缓存数据到Cache(也就是缓存未命中时),发现上次Cache的数据还没有写到内存中(也就是脏的),就将数据写到Cache中
为了解决CPU多核的缓存一致性问题,需要以下两种机制:
- 写传播:某个CPU核心里的Cache数据更新时,必须要传播到其他核心的Cache
- 事务的串行化:某个 CPU 核心里对数据的操作顺序,必须在其他核心看起来顺序是一样的,需要将对Cache数据的操作同步给其他CPU核心,并且需要锁机制
MESI协议:
包括已修改(Modified),独占(Exclusive),共享(Shared)和已失效(Invalidated)四个状态
已修改指数据在Cache Block更新,但没写到内存,已失效指数据失效,不能读取该状态的数据
独占和共享指的是Cache的数据和内存的数据是一致的,独占指的是数据只存储在一个CPU核心的Cache里,共享指的是数据在多个CPU的Cache里都有
1.5 CPU是如何执行任务的
CPU读写数据:CPU 从内存中读取数据到 Cache 的时候,并不是一个字节一个字节读取,而是一块一块的方
式来读取数据的,这一块一块的数据被称为 CPU Line(缓存行),所以 CPU Line 是 CPU从内存读取数据到 Cache 的单位。Cache Line的大小一般是64个字节。因此访问数组时如果按照内存分布的地址顺序访问,能够充分利用Cache,提升程序性能。
Cache伪共存:多个线程同时读写同一个Cache Line的不同变量时,会重复4和5这两个步骤,Cache没有起到缓存的效果,虽然变量A和B没有关系,但他们同时归属于一个Cache Line,这个Cache Line的数据被修改后,都会相互影响。
Cache Line也就是将数据附近的一组数据都读入进来,因为下次读取他们的可能性比较大,这时候就可以直接从内存中获取数据了。
因为多个线程同时读写同⼀个 Cache Line 的不同变量时,而导致 CPU Cache 失效的现象称为伪共享(False Sharing) 。
Linux中用__cacheline_aligned_in_smp
宏定义,来解决伪共享的问题。 其功能是让在物理内存上连续的成员读到不同的Cache Line中,这样就避免了伪共存的问题。
CPU对任务的调度:
在Linux系统中,根据任务的优先级以及相应要求,主要分为实时任务和普通任务两种,其中优先级的数值越小,优先级越高。
完全公平调度:理念是分给每个任务的CPU时间是一样的。
1.6 软中断
在计算机中,中断是系统用来相应硬件设备请求的一种机制,操作系统收到硬件的中断请求,会打断正在执行的进程,然后调用内核的中断处理程序来相应请求。(有点像是外卖员给打电话,接到电话之前还会干自己的事)
但是中断应该尽快执行完,这样可以减少对正常进行调度的影响。在Linux中将中断分成了两个阶段,上半部用于快速处理中断,也就是硬中断;下半部由内核触发,用来延迟处理上半部未完成的工作,特点是延迟执行,异步处理上半部未完成的工作,也就是软中断。
以网卡为例:网卡收到数据包后,会通过硬件中断通知内核有新的数据到了,于是内核就会调用对应的中断处理程序来相应该事件,也就是将网卡的数据读到内存中,然后更新硬件寄存器的状态。接着,内核会触发软中断,主要是在内存中找到网络数据,然后按照网络协议栈,对网络数据进行逐层解析和处理,最后把数据发送给应用程序。
1.7 计算机中的数据存储
负数用补码表示:方法是其对应的正数取反再加一;
十进制小数转换为二进制:整数部分使用除2取余法,小数部分使用乘2取余法。
计算机存小数是用浮点数来存的,按照IEEE 754的标准,包括三个部分:
- 符号位:0为正数,1为负数
- 指数位:小数点在数据中的位置,指数位长度越长数的表示范围越大,为了避免负数,存的值是实际取值 + 127
- 尾数位:小数右侧的数字,一共23位
因为小数在保存时按照浮点数格式存,存的数据和真实数据可能存在微小的偏差。