• Cache与内存:程序放在哪儿?


    1)什么是局部性原理?

     
     #include <stdio.h>
     int main(){
         int i,j;
         for(i=1;i<=9;i++){        
             for(j=1;j<=i;j++){
                 printf("%d*%d=%2d ",i,j,i*j);
            }
             printf("\n");
        }
         return 0;
     }
    • 可以看到,这个代码大数时间在执行一个乘法计算和调用一个 printf 函数,而程序一旦编译装载进内存中,它的地址就确定了。也就是说,CPU 大多数时间在访问相同或者与此相邻的地址,换句话说就是:CPU 大多数时间在执行相同的指令或者与此相邻的指令。这就是大名鼎鼎的程序局部性原理。

    要想弄懂局部性原理,首先得先搞懂内存!!!

    2)内存条的解读?

    • 在 PCB 板上有内存颗粒芯片,主要是用来存放数据的。SPD 芯片用于存放内存自身的容量、频率、厂商等信息。还有最显眼的金手指,用于连接数据总线和地址总线,电源等。

    3)专业角度来讲,内存应该叫什么?

    • DRAM,即动态随机存储器

    4)内存储存颗粒芯片中的存储单元是由什么组成的?

    • 电容和相关元件。

    • 电容存储电荷的多、少代表数字信号 0 和 1

    5)随着时间的流逝,电容会出现什么问题?

    • 漏电,导致电荷不足,就会让存储单元的数据出错。

    6)为了处理漏电现象带来的数据差错问题,DRAM做了哪些努力?

    • 周期性刷新,以保持电荷状态。

    7)内存的速度还有逻辑上内存和系统的连接方式和结构是怎样的?

     

    • 控制内存刷新和内存读写的是内存控制器,而内存控制器集成在北桥芯片中

    • 传统方式下,北桥芯片存在于系统主板上。现在由于技术牛逼了北桥芯片被就放到 CPU 芯片中了,这大大提升了 CPU 访问内存的性能。

    8)作为软件开发人员我们从逻辑上应该怎样看待内存?

    • 把内存看成一个巨大的字节数组就可以,而内存地址就是这个数组的下标。

    9)CPU 要数据,内存一时给不了怎么办?

    • CPU 会让总线插入等待时钟周期,直到内存准备好。根据木桶原理,CPU 的性能多高都没用,内存才是决定系统整体性能的关键。

    10)有了上面的理论知识,让我们来看看什么是Cache?

    • CPU 大多数时间在访问相同或者与此相邻的地址。那么,我们立马就可以想到用一块小而快的储存器,放在 CPU 和内存之间,就可以利用程序的局部性原理来缓解 CPU 和内存之间的性能瓶颈。这块小而快的储存器就是 Cache,即高速缓存。

    11)Cache 中有哪些内容?

    • 存放了内存中的一部分数据。

    12)有了cache之后,CPU的工作流程是怎样的?

    • 在访问内存时要先访问 Cache,若 Cache 中有需要的数据就直接从 Cache 中取出,若没有则需要从内存中读取数据,并同时把这块数据放入 Cache 中。由于程序的局部性原理,在一段时间内,CPU 总是能从 Cache 中读取到自己想要的数据。

    13)cache的物理位置在哪里?

    • 可以集成在 CPU 内部,也可以做成独立的芯片放在总线上。

    • 现在 x86 CPU 和 ARM CPU 都是集成在 CPU 内部的。

       

    14)Cache主要由哪几个部分组成?

    • 高速的静态储存器

    • 地址转换模块

    • Cache 行替换模块

    15)Cache 的逻辑工作流程是怎样的?

    • 地址转换模块把CPU 发出的地址 切成 3 段:组号,行号,行内偏移。

    • 根据组号、行号查找高速静态储存器中对应的行。找到的话用行内偏移读取并返回数据给 CPU,否则就分配一个新行并访问内存,把内存中对应的数据加载到 这个新行并返回给 CPU。

    • 如果没有新行了,替换算法来覆盖不常用的行。

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

    16)凡事都有两面性,Cache 带来了什么问题?

    • 数据一致性问题。

    16.1)先来看看Cache 在硬件层面的结构是怎样的?

     

    • 这是一颗最简单的双核心 CPU,它有三级 Cache,第一级 Cache 是指令和数据分开的(指令是由操作码和操作数组成的,所以指令中也有数据),第二级 Cache 是独立于 CPU 核心的,第三级 Cache 是所有 CPU 核心共享的。

    16.2)Cache 的一致性问题,主要包括哪三个方面?

    • 一个 CPU 核心中的指令 Cache 和数据 Cache 的一致性问题。

    • 多个 CPU 核心各自的 2 级 Cache 的一致性问题。

    • CPU 的 3 级 Cache 与设备内存,如 DMA、网卡帧储存,显存之间的一致性问题。这里我们不需要关注这个问题。

    16.3)为什么CPU 核心中的指令 Cache 和数据 Cache 会有一致性问题?

    • 程序运行的时候,指令经过指令cache,指令中涉及到的数据则会经过数据 Cache。

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

    • 但是我们接下来去执行地址 A 处的指令的时候,指令 Cache 里面可能命中的是修改之前的指令。

    16.4)怎样解决上面的问题?

    • 需要把数据 Cache 中的数据写入到内存中,然后让指令 Cache 无效,重新加载内存中的数据。

    16.5)多个 CPU 核心各自的 2 级 Cache 为什么会有数据一致性问题?

    • 假如第一个 CPU 核心读取了一个 A 地址处的变量,第二个 CPU 也读取 A 地址处的变量。根据硬件设计,直接把第一个 CPU 核心的 A 地址处 Cache 内容直接复制到第二个 CPU 的第 2、1 级 Cache,这样两个 CPU 核心都得到了 A 地址的数据。

    • 如果这时第一个 CPU 核心改写了 A 地址处的数据,而第二个 CPU 核心的 2 级 Cache 里面还是原来的值,数据显然就不一致了。

    16.6)怎样解决2级缓存数据不一致问题?

    • 数据同步协议。

    16.6.1)典型的多核心 Cache 数据同步协议有哪些?

    • MESI 和 MOESI。

    16.7)MESI 协议是怎样的?

    • 定义了 4 种基本状态:M、E、S、I,即

    • 修改(Modified)

      • 一个缓存修改了值

    • 独占(Exclusive)

      • 只有一个有

    • 共享(Shared)

      • 两个都有

    • 无效(Invalid)

      • 前面三幅图 Cache 中没有数据的那些,都属于这个情况。

    16.8)Cache 硬件是怎样设计的?

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

    x86 CPU 上默认是关闭 Cache 的!!!

    17)前面讲了这么多细节,那如何开启 Cache呢?

    • CD=1 时表示 Cache 关闭,NW=1 时 CPU 不维护内存数据一致性。

    • 所以我们只需要将 CR0 寄存器中 CD、NW 位同时清 0 即可。

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

    18)如何获取内存视图?

    • 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;

        

  • 相关阅读:
    [原创]Windows 7 下成功添加网络共享HP打印机
    [原创]PDFCreator自动保存及文件名带空格、后缀名丢失的解决方法(Windows 7通过)
    [原创]U872客户端“system.net.sockets.socketexception”的解决方法
    [转载]Windows 7默认共享无法访问
    [转载]使Excel不显示0值的三招
    [原创]使用空密码远程桌面连接
    分布式架构理论
    ffmpeg重要函数和结构体整理
    es~存储部分字段
    es~text与keyword的选择
  • 原文地址:https://www.cnblogs.com/YXBLOGXYY/p/16009446.html
Copyright © 2020-2023  润新知