文章目录
- 1. 冯诺依曼模型由哪几部分构成?
- 2. 简要介绍一下内存、中央处理器、总线和输入输出设备
- 3. 64 位相比 32 位 CPU 的优势在哪吗?64 位 CPU 的计算性能一定比 32 位 CPU 高很多吗?
- 4. 软件的 32 位和 64 位之间的区别?32 位的操作系统可以运行在 64 位的电脑上吗?64 位的操作系统可以运行在 32 位的电脑上吗?如果不行,原因是什么?
- 5. 简述存储器的层次结构
- 6. 存储器的层次关系
- 7. 机械硬盘、固态硬盘、内存和CPU L1 Cache的访问速度相差多少倍?
- 8. 为什么有了内存,还需要CPU Cache?
- 9. CPU Cache的数据结构和读取过程是怎么样的?
- 10. 如何写出让CPU跑得更快的代码?
- 11. 写入数据的方法有哪些?
- 12. 现在CPU都是多核心的,由于L1/L2 Cache是多个核心独有的,那么如何保证缓存一致性的问题?
- 13. 写传播和事务串行化具体是用什么技术实现的?
- 14. 什么情况下会出现Cache伪共享?Cache伪共享是什么?避免伪共享的方法有哪些?
- 15. CPU是根据什么来选择当前要执行的线程?
- 16. 中断是什么?什么是软中断?系统中有哪些软中断?
- 17. 为什么负数要用补码表示?
- 18. 十进制小数怎么转换成二进制?
- 19. 计算机是如何存储小数的?
- 20. 0.1 + 0.2 = 0.3 吗?
1. 冯诺依曼模型由哪几部分构成?
冯诺依曼模型由运算器、控制器、存储器、输入设备、输出设备
5个部分构成。
2. 简要介绍一下内存、中央处理器、总线和输入输出设备
- 内存:我们的程序和数据都是存储于内存,存储数据的基本单位是
字节
,每一个字节对应一个内存地址,内存地址是从0开始编号的,并且内存的读写任何一个数据的速度都是一样的。 - 中央处理器:也就是我们常说的
CPU
32位和64位CPU的区别:32位CPU一次可以计算4个字节,64位CPU一次可以计算8个字节。这里的32位和64位,通常称为CPU的位宽
,代表CPU一次可以运算的数据量。
CPU内部包含寄存器、控制单元和逻辑运算单元等。控制单元负责控制CPU工作,逻辑运算单元负责计算,而寄存器主要用于存储计算时的数据。寄存器又分为通用寄存器、程序寄存器和指令寄存器。 - 总线:总线用于CPU和内存以及其他设备之间的通信,总线分为3种:地址总线、数据总线和控制总线。
当CPU要读写内存数据的时候,首先要通过地址总线来指点内存的地址,然后通过控制总线控制是读还是写的操作,最后通过数据总线来传输数据。 - 输入、输出设备:输入设备向计算机输入数据,计算机经过计算后,把数据输出到输出设备。
3. 64 位相比 32 位 CPU 的优势在哪吗?64 位 CPU 的计算性能一定比 32 位 CPU 高很多吗?
(1)64位相比32位CPU的优势主要体现在两个方面:
- 64位CPU可以一次计算超过32位的数字,而32位CPU一次最多只能32位的数字,如果要计算超过32位的数字,就要分多步骤进行计算,所以,
当运算超过32位的大数字的时候,64位CPU的计算效率更高
。 - 通常来说64位CPU的地址总线是48位,而32位CPU的地址总线是32位,所以
64位CPU可以寻址更大的物理内存空间
。32位CPU的最大寻址空间是4G,即使有8G的物理内存,也还是只能寻址4G大小的地址;64位CPU的最大寻址空间是248bit,寻址范围远远超过32位CPU。
(2)64位CPU的计算性能也不一定比32位CPU高很多,大部分应用程序很少会计算超过32位那么大的数字,所以只有运算大数字的时候,64位CPU的优势才能体现出来,否则和32位CPU的计算性能相差不大。
4. 软件的 32 位和 64 位之间的区别?32 位的操作系统可以运行在 64 位的电脑上吗?64 位的操作系统可以运行在 32 位的电脑上吗?如果不行,原因是什么?
- 软件的32位和64位实际代表指令是32位还是64位的。
- 如果32位的指令在64位机器上运行,只需要一套兼容机制,就可以做到兼容运行了;然而64位的指令不能在32位机器上运行,因为32位的寄存器存不下64位的指令。
操作系统其实也是一种程序,因此32位的操作系统可以运行在64位的电脑上,而64位的操作系统无法运行在32位的电脑上。
总之,硬件的64位和32位指的是CPU的位宽
,软件的64位和32位指的是指令的位宽
。
5. 简述存储器的层次结构
存储器通常分为以下几个层次:寄存器、CPU Cache、内存、SSD/HDD硬盘
。
寄存器:材料最贵、处理速度最快、容量最小。
寄存器的数量通常在几十到几百之间,32位CPU中的大多数寄存器可以存储4个字节,64位CPU中的大多数寄存器可以存储8个字节。
一般要求在半个CPU时钟周期内完成读写,CPU时钟周期为主频的倒数。
CPU Cache:是一种叫SRAM(静态随机存储器)的芯片,断电数据就会丢失。
CPU的高速缓存,通常分为L1、L2、L3三层高速缓存:
- L1高速缓存的访问速度只需要2~4个时钟周期,大小在几十KB到几百KB,L1高速缓存通常分为指令缓存和数据缓存;
- L2高速缓存的访问速度在10~20个时钟周期,大小在几百KB到几MB;
- L3高速缓存的访问速度在20~60个时钟周期,大小在几MB到几十MB。
每个CPU核心都独自拥有自己的L1高速缓存和L2高速缓存,L3高速缓存是多个核心共用的。
内存:是一种叫DRAM(动态随机存储器)的芯片,需要定时刷新电容,才能保证数据不会丢失。
内存的访问速度大概在200~300个时钟周期之间。
SSD/HDD 硬盘:断电后数据不会丢失。
内存的读写速度比SSD大概快10~1000倍,比HDD快10w倍左右。
6. 存储器的层次关系
存储器包括寄存器、CPU高速缓存、内存、SSD/HHD硬盘
。
存储空间越大的存储器设备,其访问速度越慢,所需成本也相对越低。
每个存储器只和相邻的一层存储器设备直接打交道,并且存储设备为了追求更快的速度,所需的成本必然也会更高,也正因为成本太高,所以CPU内部的寄存器、L1/L2/L3 Cache 只好用较小的容量,相反内存和硬盘则可以使用更大的容量,这就是我们今天所说的存储器结构层次。
7. 机械硬盘、固态硬盘、内存和CPU L1 Cache的访问速度相差多少倍?
CPU L1 Cache 比内存快100
倍左右,比SSD快15w
倍左右,比机械硬盘快1000w
倍左右。
8. 为什么有了内存,还需要CPU Cache?
根据摩尔定律,CPU的访问速度每年增长60%左右,而内存访问速度每年只增长7%左右,CPU与内存访问性能上的差距在不断拉大。到现在,CPU和内存访问速度已经相差200~300倍,为了弥补CPU和内存两者之间的性能差距,就在CPU内部引入了CPU Cache。
9. CPU Cache的数据结构和读取过程是怎么样的?
CPU Cache是由很多个Cache Line(缓存块)
组成的,Cache Line 是CPU从内存中读取数据的基本单位,而Cache Line 是由组标记(Tag)、有效位(Valid bit)和数据块
组成。
一个内存的访问地址包括组标记、CPU Cache Line索引、偏移量。CPU Cache的数据结构则是由索引、有效位、组标记和数据块
组成。
从CPU Cache中读取数据的过程:
- 根据内存地址中的索引信息,计算在CPU Cache中的
索引
,找出对应的CPU Cache Line; - 判断CPU Cache Line 中的
有效位
,确定CPU Cache Line 中的数据是否有效,如果是无效的,CPU直接访问内存,并重新加载数据,如果数据有效,则往下执行; - 对比内存地址中的组标记和CPU Cache Line 中的
组标记
,确定缓存块中的数据是否是我们要访问的内存数据,如果不是的话,CPU就会直接访问内存,并重新加载数据,如果是的话,则往下执行; - 根据内存地址中的
偏移量
信息,从CPU Cache Line 的数据块中,读取对应的字。
10. 如何写出让CPU跑得更快的代码?
提高访问数据的缓存命中率,缓存命中率越高的话,代码的性能越好,CPU也就跑的越快。
CPU L1 Cache 分为数据缓存和指令缓存,因而需要分别提高它们的缓存命中率:
- 对于
数据缓存
,在我们遍历数据的时候,应该按照内存布局的顺序操作,这是因为 CPU Cache 是根据 CPU Cache Line 从内存中批量加载数据的,所以顺序地操作连续内存数据时,可减少对内存空间的访问次数,提高数据缓存的命中率; - 对于
指令缓存
,有规律的条件分支语句能够让CPU的分支预测器发挥作用,可以提前把预测会执行的语句放在指令缓存中,这样CPU可以直接冲CPU Cache 中读取数据,代码的执行速度就会很快; - 对于
多核的CPU系统
,线程可能在不同CPU核心来回切换,这样各个核心的缓存命中率就会受到影响,于是要提高线程的缓存命中率,可以考虑将线程绑定在某一个CPU核心上。
11. 写入数据的方法有哪些?
有两种针对写入数据的方法:写直达和写回
(1)写直达
:将数据同时写入内存和Cache中
如果数据已经在Cache中,先将数据更新到Cache里面,再写入到内存中;
如果数据没有在Cache中,就直接把数据更新到内存中。
特点:直观简单,但是每次写操作都会写回到内存中,这样写操作将会花费大量的时间,写数据的性能不高。
(2)写回
:当发生写操作时,新的数据仅仅被写入Cache Block中,只有当修改过的Cache Block被替换时才需要写到内存中,这样可以减少数据写回内存的频率,提高系统的性能。
写回的具体实现:
- 当发生写操作时,如果数据已经存在于CPU Cache中,则把数据更新到CPU Cache里,同时标记CPU Cache 里的这个Cache Block 为
脏(Dirty)
的,这个脏的标记代表这时我们的 CPU Cache 中的这个Cache Block 的数据和内存中的数据不一致,此时不用将数据写回内存里; - 当发生写操作时,如果数据所对应的 Cache Block 里存放的是别的内存地址的数据的话,就需要检查这个Cache Block 里的数据有没有被标记为脏的:
- 如果被标记为脏的,我们需要把这个 Cache Block 中的数据写回到内存,然后再把当前要写入的数据先从内存读入到该Cache Block中,然后再把当前要写入的数据写入到 Cache Block,最后将该Cache Block标记为脏的;
- 如果不是脏的,先把当前要写入的数据从内存读入到Cache Block 中,接着将数据写入到这个Cache Block中,然后再把该Cache Block标记为脏的。
12. 现在CPU都是多核心的,由于L1/L2 Cache是多个核心独有的,那么如何保证缓存一致性的问题?
要保证多个核心的缓存一致性,就需要同步不同核心的缓存数据,要保证做到以下两点:
- 第一点,某个CPU核心里的 Cache 数据更新时,必须要传播到其他核心的 Cache,这个称为
写传播
; - 第二点,某个CPU核心里对数据的操作顺序,必须在其他核心看上去顺序是一样的,这个称为
事务的串行化
。
要实现事务的串行化,要做到以下两点:- CPU核心对于Cache中数据的操作,需要同步到其他的CPU核心;
- 要引入锁的概念,如果两个CPU核心里有相同数据的Cache,那么对于这个Cache数据的更新,只有拿到了锁,才能进行对应的数据更新。
13. 写传播和事务串行化具体是用什么技术实现的?
(1)写传播的实现方式:总线嗅探
CPU需要每时每刻监听总线上的一切活动,并检查是否有相同的数据在自己的Cache中。不管别的核心的Cache是否缓存相同的数据,都需要发出一个广播事件,这无疑加重了总线的负载。同时,总线嗅探无法保证事务的串行化。
(2)事务串行化的实现方式:MESI协议
M:Modified,已修改;E:Exclusive,独占;S:Shared,共享;I:Invalidated,已失效。
这四个状态标记了 Cache Line 四个不同的状态:
已修改
:代表该 Cache Block 里的数据已经被更新,同时还没有写入到内存中,该状态就是所谓的脏标记;已失效
:代表该 Cache Block 里的数据已经失效了,不可以读取该状态的数据;独占
:代表该 Cache Block 里的数据是干净的,数据只存储在一个CPU核心的Cache里,可以自由地向Cache中写入数据,而不需要通知其他CPU核心,此时只有你有该数据,不存在缓存一致性的问题;共享
:代表该 Cache Block 里的数据是干净的,相同的数据存储在多个CPU核心的Cache里,当对某个核心的共享数据进行修改时,需要先向所以的其他CPU核心广播一个请求,要求其他核心的Cache 中对应的 Cache Block 标记为无效状态,然后再更新当前Cache里面的数据。
基于总线嗅探机制的MESI协议是保证缓存一致性的协议。整个MESI状态的变更,则是根据来自本地CPU核心的请求,或者来自其他CPU核心通过总线传输过来的请求,从而构成一个流动的状态机。另外,对于已修改或者独占状态的 Cache Block,更新其数据不需要发送广播给其他CPU核心。
14. 什么情况下会出现Cache伪共享?Cache伪共享是什么?避免伪共享的方法有哪些?
- CPU从内存读取数据到Cache的单位是CPU Cache Line,比如L1 Cache 一次载入数据的大小是64字节,如果当不同的核心并行运行着不同的线程,当各自的线程读写的不同变量位于同一个Cache Line时,就会出现Cache失效,也就是出现Cache伪共享。
- 多个线程同时读写同一个Cache Line 的不同变量时,而导致 CPU Cache 失效的现象称为
伪共享
。 - 避免伪共享的方法:Cache Line大小字节对齐,以及字节填充等。
15. CPU是根据什么来选择当前要执行的线程?
Linux内核里的调度器,调度的对象是task_struct
,根据任务的优先级以及响应要求可将任务分为:
- 实时任务:对系统的响应时间要求很高,优先级在0~99的范围;
- 普通任务:响应时间没有很高的要求,优先级在100~139的范围;
优先级的数值越小,优先级越高。
16. 中断是什么?什么是软中断?系统中有哪些软中断?
- 在计算机中,中断是系统用来响应硬件设备请求的一种机制,操作系统受到硬件的中断请求,会打断当前正在执行进程,然后调用内核中的中断处理程序来响应请求。
- 中断是一种异步的事件处理机制,可以提高系统的并发处理能力。中断处理程序要尽可能快的执行完,这样可以减少对正常进程运行调度地影响。
- Linux系统为了解决中断处理程序执行过长和中断丢失的问题,将中断过程分成了两个阶段,分别是
上半部和下半部
。上半部用来快速处理中断
,一般会暂时关闭中断请求,主要负责处理跟硬件紧密相关或者时间敏感的事情;下半部用来延迟处理上半部未完成的工作
,一般以内核线程
的方式运行。
- 中断处理程序的上半部和下半部可以理解为:
- 上半部直接处理硬件请求,也就是硬中断,主要负责耗时短的工作,特点是快速执行;
- 下半部是由内核触发,也就是软中断,主要负责上半部未完成的工作,通常都是耗时比较长的事情,特点是延迟执行。软中断是以内核线程的方式执行,并且每一个CPU都对应一个软中断内核线程,名字通常为
ksoftirqd/CPU编号
。
- 软中断不只是包括硬件设备中断处理程序的下半部,一些内核自定义事件也属于软中断,比如内核调度、RCU锁等。可通过
cat /proc/softirqs
来查看软中断的运行情况。
17. 为什么负数要用补码表示?
如果负数不使用补码表示,则在做基本的加减法运算的时候,还需要进一步判断数字是否是负数,如果是负数,还得把加法反转为减法或者把减法反转为加法;而如果负数使用补码表示,对于负数的加减法操作,实际上是和正数加减法操作相同。
18. 十进制小数怎么转换成二进制?
十进制整数部分转二进制采用的是除2取余法,十进制小数部分转二进制采用的是乘2取整法。
将十进制数0.1转换成二进制的小数部分出现了无限循环0.00011001100110...
,由于计算机的资源是有限的,所以没办法用二进制精确的表示0.1,只能用近似值来表示,于是就会造成精度缺失的情况。
19. 计算机是如何存储小数的?
计算机是以浮点数的形式来存储小数的,大多数计算机的浮点数格式包含三个部分:
- 符号位:表示数字是正数还是负数,符号位为0表示正数,为1表示负数。
- 指数位:指定了小数点在数据中的位置,指数可以是负数,也可以是正数,为了减少不必要的麻烦,在实际存储指数的时候,需要将指数加上偏移量,转换成无符号整数。指数位的长度越长,则数值的表达范围就越大。
- 尾数位:小数点右侧的数字,也就是小数部分,而且尾数的长度决定了这个数的精度,因此如果要表示精度更高的小数,则需要提高尾数位的长度。
用32位表示的浮点数,称为单精度浮点数,也就是编程语言中的 float 变量,而用64位表示的浮点数,称为双精度浮点数,也就是 double 变量。
20. 0.1 + 0.2 = 0.3 吗?
不是的,0.1 和 0.2 这两个数字用二进制表示是一个循环的二进制数,对于计算机而已,0.1 和 0.2 无法精确表示,这是浮点数计算造成精度损失的根源。因此,只能用近似值来表示该二进制数,那么意味着计算机中存放的小数可能不是一个真实值。0.1 + 0.2 并不等于完整的0.3,这主要是因为这两个小数无法用完整的二进制数来表示,只能根据精度舍入,所以计算机里只能采用近似值的方式来保存,那两个近似值相加,得到的必然是一个近似值。