资讯详情

程序放在哪儿?

1.写在前面

本博客参考操作系统实战 45 讲》

上一个博客介绍了如何在程序中转换地址,这个博客介绍程序放在哪里?

我们已经知道了 CPU 如何执行程序,也研究了程序的地址空间,在这里我们终于到了程序的存储位置-内存。

你知道什么是 Cache 是吗?在你心中,真正的内存是什么样子的?今天,让我们识它 Cache 和内存,这对我们有用 Cache 编写高性能的程序代码和管理操作系统的内存有很大的帮助。

通过本课程的内容,让我们来看看内存是什么,它的特点是什么。有了这种理解,你可以对我们看似熟悉的东西有更深的理解,找出为什么 Cache 是解决内存瓶颈的神来之笔。最后,我会带你去分析 x86 平台上的 Cache,规避 Cache 一致性问题,让你掌握获取内存视图的方法。

2.从一段“经典”代码看局部性原理

不知道,你还记得吗? C 用语言打印99乘法表的代码并不重要。让我把它贴出来。代码很短,很简单。即使你自己写一个,也不需要一分钟,如下所示。

#include <stdio.h> int main(){ 
           int i,j;   for(i=1; i<=9; i ){ 
             for(j=1; j<=i; j ){ 
               printf("%d*%d=- ", i, j, i*j);     }     printf("\n");   }   return 0; } 

当然,我们不研究代码本身。这个代码很简单。在这里,我们主要观察这个结构。代码的结构主要是,这三种结构可以写所有现存算法的程序。

我们通常写的代码大多是顺序和循环结构。上述代码有两个循环,内部循环的次数受外部循环变量的影响。

可以看出,这个代码大部分时间都在执行,一旦程序被编译并装载到内存中,它的地址就会确定。也就是说,CPU 大部分时间访问相同或相邻的地址,换句话说:CPU 大部分时间都在执行相同的指令或相邻的指令。这就是为什么 是名鼎鼎

3.内存

在理解了程序的局部原理后,让我们来看看内存。你可能会觉得这个跨度有点大,但只有理解了内存的结构和特性,你才能理解程序局部原理的应用场景及其重要性。

内存也可以称为主存储器。无论硬盘有多大,存储了多少程序和数据,只要程序运行或数据需要计算,它们都必须首先包含在内存中。让我们看看内存是什么样子的(你也可以在网上搜索),如下图所示。

在这里插入图片描述

从上图可以看到在 PCB 板上有内存颗粒芯片。SPD 芯片用于存储内存本身的容量、频率、制造商和其他信息。还有连接数据总线和地址总线、电源等最显眼的金手指。

事实上,从专业的角度来看,内存应该被称为 DRAM,即。存储颗粒芯片中的存储单元由电容器和相关元件制成,电容器中存储的电荷多少代表数字信号 0 和 1。

随着时间的推移,电容器漏电,导致。DRAM 结构简单,集成度高,通常用于制造内存条中的储存颗粒芯片。

虽然内存技术标准不断更新,但存储颗粒的内部结构并没有发生本质的变化,或者电容存储电荷,标准似乎更高,

比如 DDR SDRAM,即,它使用 2.5V 数据位宽为工作电压 64 核心频率最高为位 166MHz。下面简称 DDR 内存,它

后来的 DDR2、DDR3、DDR4 也提高了核心频率和预取位数。 DDR4 采用 1.2V 数据位宽为工作电压 64 位,预取 16 位数据。DDR4 取消双通道机制,内存为通道,工作频率最高 4266MHz,单根 DDR4 内存数据传输带宽最高 34GB/s。

事实上,我们不需要太关注内存硬件层面的技术规范和标准,重点是,,这样,你就可以意识到内存有多慢,是什么导致内存缓慢。

让我们解释一下图片,如下图所示。

结合图片我们看到,**内存控制器集成在北桥芯片中,控制内存刷新和内存读写。**北方是传统的芯片存在于系统主板上,而现在由于芯片制造工艺的升级,芯片集成度越来越高,所以北桥芯片被就集成到 CPU 芯片中了,同时这也大大提升了 CPU访问内存的性能。

而作为软件开发人员,从逻辑上

4.CPU 到内存的性能瓶颈

尽管 CPU 和内存是同时代发展的,但 CPU 所使用技术工艺的材料和内存是不同的,侧重点也不同,价格也不同。如果内存使用 CPU 的工艺和材料制造,那内存条的昂贵程度会超乎想象,没有多少人能买得起。

由于这些不同,导致了 CPU 和内存条的数据吞吐量天差地别。尽管最新的 DDR4 内存条带宽高达 34GB/s,然而这相比 CPU 的数据吞吐量要慢上几个数量级。再加上多核心 CPU同时访问内存,会导致总线争用问题,数据吞吐量会进一步下降。

CPU 要数据,内存一时给不了怎么办?CPU 就得等,通常 ,直到内存准备好,到这里你就会发现,无论 CPU 的性能多高都没用,而。显然依靠目前的理论直接提升内存性能,达到 CPU 的同等水平,这是不可行的,得想其它的办法。

5.Cache

让我们重新回到前面的场景中,回到程序的局部性原理,它告诉我们:,放在CPU 和内存之间,就可以利用程序的局部性原理来缓解 CPU 和内存之间的性能瓶颈。这块小而快的储存器就是

Cache 中存放了内存中的,CPU ,并。但是由于程序的局部性原理,在一段时间内,CPU 总是能从 Cache 中 读取到自己想要的数据。

Cache 可以集成在 CPU 内部,也可以做成独立的芯片放在总线上,现在 x86 CPU 和ARM CPU 都是集成在 CPU 内部的。其逻辑结构如下图所示。

Cache 主要由高速的静态储存器、地址转换模块和 Cache 行替换模块组成。

Cache 会把,一行大小通常为 32 字节或者 64 字节。Cache 和内存交换数据的,为方便管理,在 Cache 内部的高速储存器中,多个行又会形成一组。

除了正常的数据空间外,Cache 行中还有一些标志位,如等,这些位会被 Cache 的替换模块所使用。

Cache 大致的逻辑工作流程如下。

  1. CPU 发出的地址由 Cache 的地址转换模块分成 3 段:
  2. Cache 会根据组号、行号查找高速静态储存器中对应的行。如果找到即,否则就。写入操作则比较直接,分为回写和直通写,
  3. 如果没有新行了,就要进入,即,替换行有相关的算法,替换算法是为了让替换的代价最小化。例如,,这样就不用把它其中的数据回写到内存中了,还有找出 ,因为它大概率不会再访问了。

以上这些逻辑都由 Cache 硬件独立实现,软件不用做任何工作,对软件是透明的。

6.Cache 带来的问题

Cache 虽然带来性能方面的提升,但同时也给和硬件和软件开发带来了问题,那就是数据一致性问题。

为了搞清楚这个问题,我们必须先搞清楚 Cache 在硬件层面的结构,下面我画了 x86 CPU的 Cache 结构图:

这是一颗最简单的双核心 CPU,它有三级 Cache,

下面来看看 Cache 的一致性问题,主要包括这三个方面.

  1. 一个 CPU 核心中的指令 Cache 和数据 Cache 的一致性问题。
  2. 多个 CPU 核心各自的 2 级 Cache 的一致性问题。
  3. CPU 的 3 级 Cache 与设备内存,如 DMA、网卡帧储存,显存之间的一致性问题。

我们先来看看 CPU 核心中的,对于程序代码运行而言,指令都是经过指令 Cache,而指令中涉及到的数据则会经过数据 Cache。

所以,对自修改的代码(即修改运行中代码指令数据,变成新的程序)而言,比如我们修改了内存地址 A 这个位置的代码(典型的情况是 Java 运行时编译器),这个时候我们是通过储存的方式去写的地址 A,所以新的指令会进入数据 Cache。

但是我们接下来去

再来看看多个 CPU 核心各自的 2 级 Cache 的一致性问题。从上图中可以发现,。比如第一个 CPU 核心读取了一个 A 地址处的变量,第二个CPU 也读取 A 地址处的变量,那么第二个 CPU 核心是不是需要从内存里面经过第 3、2、1 级 Cache 再读一遍,这个显然是没有必要的。

在硬件上 Cache 相关的控制单元,可以把第一个 CPU 核心的 A 地址处 Cache 内容直接复制到第二个 CPU 的第 2、1 级 Cache,这样两个 CPU 核心都得到了 A 地址的数据。不过如果这时第一个 CPU 核心改写了 A 地址处的数据,而第二个 CPU 核心的 2 级 Cache 里面还是原来的值,数据显然就不一致了。

为了解决这些问题,硬件工程师们开发了多种协议,典型的多核心 Cache 数据同步协议有MESI 和 MOESI。MOESI 和 MESI 大同小异,下面我们就去研究一下 MESI 协议。

7.Cache 的 MESI 协议

MESI 协议定义了 4 种基本状态:M、E、S、I,即修改(Modified)、独占(Exclusive)、共享(Shared)和无效(Invalid)。下面我结合示意图,给你解释一下这四种状态。

  1. M 修改(Modified):当前 Cache 的内容有效,数据已经被修改而且与内存中的数据不一致,数据只在当前 Cache 里存在。比如说,内存里面 X=5,而 CPU 核心 1 的 Cache中 X=2,Cache 与内存不一致,CPU 核心 2 中没有 X。

  2. E 独占(Exclusive):当前 Cache 中的内容有效,数据与内存中的数据一致,数据只在当前 Cache 里存在;类似 RAM 里面 X=5,同样 CPU 核心 1 的 Cache 中X=5(Cache 和内存中的数据一致),CPU 核心 2 中没有 X。

  3. S 共享(Shared):当前 Cache 中的内容有效,Cache 中的数据与内存中的数据一致,数据在多个 CPU 核心中的 Cache 里面存在。例如在 CPU 核心 1、CPU 核心 2 里面 Cache 中的 X=5,而内存中也是 X=5 保持一致。

  4. 无效(Invalid):当前 Cache 无效。前面三幅图 Cache 中没有数据的那些,都属于这个情况。

最后还要说一下 Cache 硬件,它会监控所有 CPU 上 Cache 的操作,根据相应的操作使得Cache 里的数据行在上面这些状态之间切换。Cache 硬件通过这些状态的变化,就能安全地控制各 Cache 间、各 Cache 与内存之间的数据一致性了。

这里不再深入探讨 MESI 协议了,感兴趣的话你可以自行拓展学习。这里只是为了让你明白,有了 Cache 虽然提升了系统性能,却也带来了很多问题,好在这些问题都由硬件自动完成,对软件而言是透明的。

不过看似对软件透明,这却是有代价的,因为硬件需要耗费时间来处理这些问题。如果我们编程的时候不注意,不能很好地规避这些问题,就会引起硬件去维护大量的 Cache 数据同步,这就会使程序运行的效能大大下降。

8.开启 Cache

前面我们研究了大量的 Cache 底层细节和问题,就是为了使用 Cache,目前 Cache 已经成为了现代计算机的标配,但是 x86 CPU 上默认是关闭 Cache 的,需要在 CPU 初始化时将其开启。

在 x86 CPU 上开启 Cache 非常简单,只需要将 CR0 寄存器中 CD、NW 位同时清 0 即可。CD=1 时表示 Cache 关闭,NW=1 时 CPU 不维护内存数据一致性。所以 CD=0、NW=0 的组合才是开启 Cache 的正确方法。

开启 Cache 只需要用四行汇编代码,代码如下:

mov eax, cr0
;开启 CACHE
btr eax,29 ;CR0.NW=0
btr eax,30 ;CR0.CD=0
mov cr0, eax

9.获取内存视图

作为系统软件开发人员,与其了解内存内部构造原理,不如了解系统内存有多大。这个作用更大。

根据前面课程所讲,给出一个物理地址并不能准确地定位到内存空间,内存空间只是映射物理地址空间中的一个子集,物理地址空间中可能有空洞,有 ROM,有内存,有显存,有I/O 寄存器,所以获取内存有多大没用,关键是要获取哪些物理地址空间是可以读写的内存。

物理地址空间是由北桥芯片控制管理的,那我们是不是要找北桥要内存的地址空间呢?当然不是,在 x86 平台上还有更方便简单的办法,那就是 BIOS 提供的实模式下中断服务,就是 int 指令后面跟着一个常数的形式。

由于 PC 机上电后由 BIOS 执行硬件初始化,中断向量表是 BIOS 设置的,所以执行中断自然执行 BIOS 服务。这个中断服务是 int 15h,但是它需要一些参数,就是在执行 int 15h之前,对特定寄存器设置一些值,代码如下。

_getmemmap:
	xor ebx,ebx ;ebx设为0
	mov edi,E80MAP_ADR ;edi设为存放输出结果的1MB内的物理内存地址
loop:
	mov eax,0e820h ;eax必须为0e820h
	mov ecx,20 ;输出结果数据项的大小为20字节:8字节内存基地址,8字节内存长度,4字节内存类型
	mov edx,0534d4150h ;edx必须为0534d4150h
	int 15h ;执行中断
	jc error ;如果flags寄存器的C位置1,则表示出错
	add edi,20;更新下一次输出结果的地址
	cmp ebx,0 ;如ebx为0,则表示循环迭代结束
	jne loop ;还有结果项,继续迭代
		ret
error:;出错处理

上面的代码是在迭代中执行中断,每次中断都输出一个 20 字节大小数据项,最后会形成一个该数据项(结构体)的数组,可以用 C 语言结构表示,如下。

#define RAM_USABLE 1 //可用内存
#define RAM_RESERV 2 //保留内存不可使用
#define RAM_ACPIREC 3 //ACPI表相关的
#define RAM_ACPINVS 4 //ACPI NVS空间
#define RAM_AREACON 5 //包含坏内存
typedef struct s_e820{ 
        
	u64_t saddr; /* 内存开始地址 */
	u64_t lsize; /* 内存大小 */
	u32_t type; /* 内存类型 */
}e820map_t;

10.总结

今天我们主要讲了四部分内容,局部性原理、内存结构特性、Cache 工作原理和 x86 上的应用。我们一起来回顾一下这节课的重点。

首先从一个场景开始,我们了解了程序通常的结构。通过观察这种结构,我们发现 CPU 大多数时间在访问相同或者与此相邻的地址,执行相同的指令或者与此相邻的指令。这种现象就是

然后,我们研究了内存的结构和特性。了解它的工艺标准和内部原理,知道内存容量相对可以做得较大,程序和数据都要放在其中才能被 CPU 执行和处理。但是内存的速度却远远赶不上 CPU 的速度。

因为内存和 CPU 之间性能瓶颈和程序局部性原理,所以才开发出了 Cache(即高速缓存),它由高速静态储存器和相应的控制逻辑组成。

Cache 容量比内存小,速度却比内存高,它在 CPU 和内存之间,CPU 访问内存首先会访问 Cache,如果访问命中则会大大提升性能,然而它却带来了问题,那就是,为了解决这个问题,工程师又开发了 Cache。这个协议由 Cache 硬件执行,对软件透明。

最后,我们掌握了 x86 平台上开启 Cache 和获取物理内存视图的方法。

因为这节课也是我们硬件模块的最后一节,可以说没有硬件平台知识,写操作系统就如同空中建楼,通过这个部分的学习,就算是为写操作系统打好了地基。为了让你更系统地认识这个模块,我给你整理了的知识导图。

标签: 同等容量的电容器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台