精华内容
下载资源
问答
  • CPU访问内存(DRAM)较慢,基于数据的空间局部性(该数据存储位置附近的数据可能很快被访问)和时间局部性(当前正在访问的数据可能很快被再访问)而引入了高速缓存(SRAM)。高速缓存位于分页单元和内存控制器...

    目录

    一、高速缓存与地址映射

    二、MESI缓存一致性协议和伪共享

    三、指令重排序和内存屏障

    四、Java  volatile和final关键字


    一、高速缓存与地址映射

           CPU访问内存(DRAM)较慢,基于数据的空间局部性(该数据存储位置附近的数据可能很快被访问)和时间局部性(当前正在访问的数据可能很快被再访问)而引入了高速缓存(SRAM)。高速缓存位于分页单元和内存控制器之间,即虚拟地址通过分页单元转换成物理地址后,首先在高速缓存中查找是否存在对应的数据,如果存在则返回缓存中的数据,否则请求内存控制器读取内存,把目标数据附近的一个缓存行大小(通常是64byte)的数据读到高速缓存中,具体的读取规则取决于高速缓存同内存之间的地址映射方式。缓存行是高速缓存同内存交换数据的最低单位,是替换高速缓存中的数据的最低单位,也是高速缓存同内存地址映射的最低单位。高速缓存的命中率直接影响程序的性能,Linux下可以通过perf命令查看缓存的命中率。

          高速缓存同内存之间的地址映射方式有三种:全相联映射、直接相联映射和组相联映射。全相联映射是指按缓存行大小将高速缓存分成N块,内存分成M块,内存的第m块可以映射到高速缓存中的任意一块n,通过映射表记录m和n之间对应关系,缺点是映射表实现电路复杂,需要比较的记录多,优点是高速缓存的利用率和命中率都高,适用于小容量的高速缓存。直接相联映射是指按缓存行大小将高速缓存分成N块,内存分成M块,将M块进一步分成S组,每组N块,M=S*N,内存的第s组下的第n块映射至高速缓存的第n块,通过映射表记录n和s的对应关系,缺点是因为需要频繁替换缓存行导致高速缓存利用率和命中率较低,优点是映射表实现简单。组相联映射按缓存行大小将高速缓存分成N块,S组,每组n块,N=S*n,将内存分成M块,T组,每组S块,即M=S*T,内存的第s组下第a块映射至高速缓存的第a组下的n块中的任意一块,通过映射表记录s和n之间的对应关系,即内存和高速缓存的组间采用直连相联映射,组内采用全相联映射。组相联映射按照每组n块分为n路全相联映射,n通常是2,4,8,16。组相联映射是前面两者间的折中,兼顾两者的优缺点,是普遍采用的地址映射方案。综上分析,从内存读取目标数据附近一个缓存行大小的数据时,目标数据可能位于缓存行中的任意位置,即不是从目标物理地址往后读取一个缓存行数据。

         现代CPU的高速缓存包含L1,L2,L3三级高速缓存,访问速度逐渐下降,缓存容量逐渐上升,上级缓存的内容来源于下级,其中L1,L2是CPU独享的,L3是多核CPU共享的,但是对操作系统而言高速缓存只有一个,由底层硬件维护三级缓存的缓存内容读写和彼此间的缓存一致性。

         参考: 主存与Cache的地址映射

                     CPU缓存

                    CPU体系结构之cache小结

    二、MESI缓存一致性协议和伪共享

           CPU将修改后的数据写回高速缓存和内存有两种策略:通写(write through),每次CPU修改了高速缓存中的内容就立即同步更新到内存,写内存较慢,且频繁更新内存会导致总线资源竞争;回写(write back),每次CPU修改了cache中的数据,不会立即更新到内存,而是等到cache line在某一个必须或合适的时机才会更新到内存中,具体的时机根据缓存一致性协议确定。物理页是否采用高速缓存和写回策略由页表项中的PCD和PTW标志指定,Linux下默认是启用高速缓存和采用回写策略。

          多核处理器系统每个核都可能保存了内存中同一份数据的副本,为了确保各副本之间数据一致性而引入了MESI缓存一致性协议,该协议由高速缓存控制器实现,操作系统不需要关心。MESI协议定义了缓存行的四种状态,M表示已修改,E表示独享,S表示共享,I表示无效,高速缓存控制器会监听所有CPU核对同一内容的缓存行的读取和修改操作,然后根据协议规则改变缓存行的状态,进而触发将缓存行刷新至内存或者从新从内存读取缓存行。以两个线程并发修改变量x为例,CPU A先将变量x所属的缓存行XA从内存读入高速缓存,该缓存行XA状态为独享E,接着CPU B读取变量x,此时L3高速缓存有该缓存行,从L3读取至B自己的L1,记为XB,XB的状态是共享S,这时会通知XA的高速缓存控制器将其置为共享S。假如CPU A先计算完成,将结果写回到XA,XA的状态变成M,然后通知XB的高速缓存控制器将其置为I。接着CPU B计算完成,将结果写回到XB,这时会通知XA的高速缓存控制器将XA写回到内存,并把XA的状态置为I,然后重新从内存读取XB并把状态变成M。CPU A再次读取x时,发现XA是I,会重新从内存读取缓存行,XB监听到XA的读取动作会提前把XB刷新到内存,保证CPU A读取到的是最新的,然后XB的状态变成S。

           综上分析,缓存一致性协议会导致更新丢失,即第二次更新的结果会直接覆盖掉第一次更新的结果,但是会保证读到的数据是最新修改的。另外,在多线程并发修改同一个变量的时候会导致该变量频繁从高速缓存刷新至内存,接着又从内存读取到高速缓存,如果多线程并发修改的多个变量位于同一个缓存行,这个问题更加严重,并且理论上对多个变量的并行修改会变成串行修改,称之为伪共享问题。以两个线程并发修改X,Y变量,X,Y变量都位于同一个缓存行M为例,CPU A和CPU B的缓存行的初始状态为共享S,接着CPU A修改变量X,写回到缓存行MA,MA的状态变成修改M,MB的状态变成无效I,接着CPU B修改变量Y,写回到缓存MB,这时会通知并等待MA写回到内存并将其状态改成无效I,然后从内存从新读取MB并修改,将状态变成M,即事实上修改Y变量需要等待对X变量的修改刷新到内存后才能执行。

          解决规避伪共享的问题的核心就是区分经常变动和不变的全局变量,不变的全局变量尽量放在同一个缓存行,经常变动的全局变量放在不同的缓存行中,后者的实现逻辑是添加不需要修改的长整型变量,确保这些长整型变量和某个容易变动的全局变量在同一个缓存行中。JDK8中新增了一个注解@sun.misc.Contended,来使各个变量在Cache line中分隔开,但是jvm需要添加参数-XX:-RestrictContended才能开启此功能。

          参考:【并发编程】CPU cache结构和缓存一致性(MESI协议)

                      Java专家系列:CPU Cache与高性能编程

                      Java8使用@sun.misc.Contended避免伪共享

    三、指令重排序和内存屏障

          多核处理器下CPU修改或者读取数据后会导致缓存行状态的改变,需要跟其他CPU的高速缓存控制器通信来协调同一缓存行的状态改变,通信期间CPU是阻塞的,为了提高CPU的利用率,引入了MOB(Memory Ordering Buffers)来缓存同高速缓存交互的指令,MOB位于CPU和L1缓存之间,对其他CPU不可见,如下图所示:

    MOB由一个64长度的load buffer和36长度的store buffer组成,CPU读取数据的指令会先放入load buffer,修改数据的指令先放入store buffer,两个buffer都是FIFO队列结构,存入buffer后CPU立即执行其他的指令,由高速缓存控制器逐一执行buffer中指令,执行完成由高速缓存控制器通知CPU。除此之外,为了提高invalid消息的应答效率,引入了invalid buffer队列,即当高速缓存控制器接收到其他控制器发送的invalid消息时会立即发送消息答复,并将对应的invalid操作放入invalid buffer中,然后异步执行该buffer中的invalid操作。注意store buffer对CPU是可见的,即CPU可以读取store buffer中未写入到高速缓存中的数据,但是invalid buffer对CPU是不可见的,即除非invalid buffer中的invalid操作对具体的缓存行生效了,CPU会继续读取已经事实上失效的数据。MOB通过异步执行读写缓存指令提高了CPU利用率,但是引入了两个新的问题,第一,修改数据的指令在buffer中还未执行或者invalid buffer中的invalid操作未执行时,其他CPU同一缓存行的状态还未改成无效I,此时读取的数据就不是最新的;第二,不同高速缓存控制器执行指令的顺序不一定是CPU写入指令的顺序,比如CPU A 写入load A指令,然后CPU B写入load B指令,实际上可能是load B指令先于load A指令执行,这种执行顺序上的变化称之为指令重排序,在并发的情形下可能会导致异常。

        指令重排序除上述情形外,在编译器优化代码,CPU并行执行指令时都可能发生,这两种情形下的指令重排序都是为了提供代码的运行效率。所有的指令重排序都遵循as-if-serial语义原则,表示重排序后的指令执行效果同单线程下未重排序的指令的执行效果一样,这要求编译器和CPU不能对存在数据依赖性,重排序会改变执行结果的的指令做重排序。Java从JDK5中引入了happens-before概念,如果一个操作happens-before(之前发生)另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前,但是保证结果一致的前提下允许重排序。JMM规范中制定了一系列的重排序的规则,用于规范编译器优化代码时的重排序行为,其中与程序员密切相关的规则如下:

          为了解决并发环境下指令重排序可能导致的问题和引入MOB导致的数据未及时更新的问题,CPU提供了内存屏障(memory barrier)指令,不同架构的CPU对应的内存屏障指令不同,通常包含以下四种类型的指令:

    屏障类型说明(load指令指读取数据的一类指令,store指令指修改数据的一类指令)
    Load Barriers(x86下为ifence指令强制所有在load屏障指令之后的load指令,都在load buffer 和 invaild buffer被高速缓存控制器执行完之后才执行,即确保load屏障指令前的load指令都先于load屏障之后的指令执行,且load屏障之后的指令可以看到其他CPU对同一缓存行的修改。
    Store Barriers(x86下为sfence指令强制所有在store屏障指令之前的store指令,都在该store屏障指令执行之前被执行,并把store buffer的数据都刷到高速缓存,即确保数据修改对其它CPU可见
    Full Barriers(x86下为mfence指令复合了Load Barries和Store Barriers的功能

               参考:内存屏障

                          指令重排序

    四、Java  volatile和final关键字

    Java volatile变量在执行写操作之后由JVM插入一个store屏障,在执行读操作之前插入一个load屏障,从而确保程序读取到的volatile变量始终是最新的,对该变量的修改立即对其他线程可见。一个类的final字段会在初始化后插入一个store屏障,来确保final字段在构造函数初始化完成并可被使用时可见。

         volatile关键字可以参考如下测试用例:

        private static volatile boolean isOver = false;
        public static void main(String[] args) throws Exception {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!isOver) ;
                    System.out.println("thread exit");
                }
            });
            thread.start();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            isOver = true;
            System.out.println("main update");
            thread.join();
            System.out.println("main exit");
        }

            上述用例在isOver加了volatile关键字以后,子线程立即读取到更新后的isOver并很快退出,没有加volatile关键字,则子线程一直运行,始终没有读取到修改后的值。理论上没有加volatile关键字,子线程不能立即读取到更新,但是应该延后一段时间也能读取到更新,为啥这里没有读取到更新了?答案是编译器优化后CPU从内存读取了一遍isOver后没有再读取,如下用例就可以读取到isOver更新后的取值。因此对于多线程共享的变量都要加上volatile关键字。

    private static boolean isOver = false;
        public static void main(String[] args) throws Exception {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("isOver-->"+isOver);
                    try {
                        Thread.sleep(501);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("isOver-->"+isOver);
                    System.out.println("thread exit");
                }
            });
            thread.start();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            isOver = true;
            System.out.println("main update");
            thread.join();
            System.out.println("main exit");
        }

         参考:让你彻底理解volatile

    展开全文
  • 为了缩小cpu和ram之间的速度不匹配,引入了硬件高速缓存内存。它基于著名的局部性原理。 Cache的分类: a. 统一的。指令和数据用同一个Cache。 b. 指令和数据分开的。有时这种组织方式也被称为改进的哈佛结构。   ...

    这篇主要是讲cache的基础知识,从网上也可以搜到相关的。

    这篇主要是讲cache的基础知识,从网上也可以搜到相关的。

    Cache的作用:

    为了缩小cpu和ram之间的速度不匹配,引入了硬件高速缓存内存。它基于著名的局部性原理。

    Cache的分类:

    a. 统一的。指令和数据用同一个Cache。

    b. 指令和数据分开的。有时这种组织方式也被称为改进的哈佛结构。

     

    L1\L2\L3 cache的解释:

    http://www.360doc.com/content/11/1013/01/1317564_155627198.shtml

    自己看吧

    Cache与主存的关系:

    a.充分映射方式(fully associative)

    主存中的任意一个行可以存放高速缓存中的任意位置。

     

    b.直接映射方式(direct-mapped)

    主存中的每个地址都对应Cache存储器中惟一的一行。由于主存的容量远远大于Cache存储器,所以在主存中很多地址被映射到同一个Cache行。

     

    c.N-路组相关联映射方式(N-way set associative)

    充分映射方式要进行搜索,所以比较慢。

    直接映射方式虽然快,但是它是主存多行映射到一个行。假设是x\y\z三行映射到cache的第n行。我们程序是从访问从x->y->z顺序,那么假设读y,此时cache中是x。那么只能读内存,再把y复制到cache中。访问x\z时同样。这样cache就不起作用了,这个问题称为Cache抖动(Cache Thrash)

    根据上面的原因,大部分选用了一个折中的方法:相关联映射方式

    主存中的任意一个行可以存放在高速缓存N行中的任意一行中。

     

    Cache的结构:

    Cache的基本存储单元为Cache行(Cache line)。存储系统把Cache和主存储器都划分为相同大小的行。Cache与主存储器交换数据是以行为基本单位进行的。

    高速缓存单元插在分页单元和主内存之间。有一个高速缓存内存和一个高速缓存控制器。高速缓存内存中存放真正的行。高速缓存控制器存放一个表项数组,每个表现对应高速缓存内存的一个行。每个表项有一个标签(tag)和描述高速缓存行状态的几个标签(flag)。

     

    flag标签:Dirty位和Invalidate位

     

    Cache行的大小通常是2L字节。通常情况下是16字节(4个字)和32字节(8个字)。如果Cache行的大小为2L字节,那么对主存的访问通常是 2L字节对齐的。

    如果为充分映射方式和直接映射方式,对于一个虚拟地址来说,它的bit[31∶L]位,是Cache行的一个标识。当CPU发出的虚拟地址的bit[31∶L]和 Cache中的某行bit[31∶L]相同,那么Cache中包含CPU要访问的数据,即成为一次Cache命中。bit[31:L]中有标签(Tag和flag)。bit[L-1:0]是Offset

     

    如果为N-路组相关联映射方式,一个cache组中包涵2S个行。那么bit[31:L+S]为标签,bit[L+S-1:L]为组标识(set), bit[L-1:0]是Offset

     

    Cache的写策略:

    通写(write-through):

    控制器总是即写ram也写高速缓存行,为了提高写操作的效率关闭高速缓存。

     

    回写(write-back)

    只更新高速缓存行,不改变Ram的内容,提供了更快的功效。当cpu执行一条要求刷新高速缓存表项指令时,或者当一个FLUSH硬件信号产生时(通常在高速缓存不命中之后),高速缓存控制器才把高速缓存行写回RAM中。

     

    比较了直写法和回写法的优缺点:

    ·  可靠性。直写法要优于回写法。这是因为直写法始终保证Cache是主存的正确副本。当Cache发生错误时,可以从主存中纠正。

    ·  与主存的通信量。一般情况下,回写法少于直写法。这是因为,一方面,Cache的命中率很高,对于回写法来说,CPU绝大多数操作只需要写 Cache,不必写主存。另一方面,当Cache失效时,要将Cache中的行替换到主存,而直写法每次只写一个字到主存。总的来说,由于直写法在每次写Cache时,同时写主存,从而增加了写操作的开销。而回写法是把与主存的数据交换集中到一次主存操作,可能要一次性的进行多个字的操作。

    ·  控制的复杂性。直写法必回写法简单。直写法在Cache的行状态表中不需要修改位。同时,直写法的纠错技术相对简单。

    ·  硬件代价。回写法比直写法好。因为直写法中,每次写操作都要写主存,因此为了节省写主存所花费的时间,通常要采用一个高速小容量的缓存存储器,把要写的数据和地址写到这个缓存中。在每次读主存时,也要首先判断所读的数据是否在这个缓存中。而回写法不需要上述操作,相对硬件代价要小。

     

    写缓冲器:

    看到上面用黑体字写的那句话了吧!这个就是写缓冲器

    写缓存器是一个非常小的先进先出(FIFO)存储器,位于处理器核与主存之间。使用写缓 存的目的是,将处理器核和Cache从较慢的主存写操作中解脱出来。当CPU向主存储器做写入操作时,它先将数据写入到写缓存区中,由于写缓存器的速度很高,这种写入操作的速度也将很高。写缓存区在CPU空闲时,以较低的速度将数据写入到主存储器中相应的位置。

     

    smp上的cache:

    多cpu时每个cpu都有自己的本地cache,那么一个cache更新,所有的都有更新。这是由硬件上一个叫“高速缓存侦听”(cachesnooping)的单元来处理的。

     

    Cache的替换策略:

    在Cache访问过程中,发现查找的Cache行已经失效,则需要从主存中调入新的行到Cache中。当所有的对应行都已经装满时,就要使用Cache替换算法,从这些组中找出一个Cache,把它调回到主存中原来存放它的地方,腾出新行来存放新调入的行。如何选择一个行就是Cache的替换策略。在ARM常用的替换算法有两种:

     

    轮转算法:

    轮转算法又叫循环法,这种算法维护一个逻辑计数器,每进行一次替换,计算器加1,当计算器达到最大值时,就被复位成预先定义好的一个基值。这种算法容易预测最坏情况下的Cache性能。但它一个明显缺点就是,在程序发生很小变化时,可能造成Cache性能急剧下降。实时系统中会十分注重这个算法的可预测性。

     

    随机替换算法:

    随机算法从特定的位置上随机地选出一行替换出去。它通过一个随机发生器来完成上述操作。当每次需要替换Cache行时,随机发生器将产生一个随机数,用新 行将编号为该随机数的行替换出去。这种算法与轮转算法最大的区别在于它在每次产生替换行时,增加的是一个非连续值,这个值是由控制器随机产生的。同样,当丢弃计算器达到最大值时,会被复位成预先定义好的一个基值。

     

    转换后援缓冲器(TLB):

    TLB主要是为了加快线性地址的转换。它也是一种高速缓存。

    当一个线性地址被第一次使用时,通过慢速访问RAM中的页表计算出相应的物理地址。同时,物理地址被存放在衣蛾TLB表项中,以便以后对同一个线性地址的引用可以快速地得到转换。

    对应smp,不需要向cache一样同步。因为运行在现有cpu上的进程可以使同一线性地址与不同的物理地址发生关系。

     

    总结:

    主要的高速缓存都说完了,用一个表来总结一下。


    在网络上,还有一个ARP Cache表概念,在此就不再讨论了。


    展开全文
  • CPU如何通过硬件高速缓存和TLB得到虚拟地址所对应的数据

    硬件高速缓存和TLB原理

    基本概念

    1. 硬件高速缓存的引入是为了缩小CPU和RAM之间的速度不匹配,高速缓存单元插在分页单元和主内存之间,它包含一个硬件高速缓存内存和一个高速缓存控制器。高速缓存内存存放内存中真正的行。高速缓存控制器存放一个表项数组,每个表项对应高速缓存内存中的一个行,如下图:

      高速缓存

    2. 除了通用硬件高速缓存,80x86处理器还包含了另一个称为转换后援缓冲器或TLB(Translation Lookaside Buffer)的高速缓存用于加快线性地址的转换。TLB是一个小的、虚拟寻址的缓存,其中的每一行都保存着一个由单个页表条目组成的块。

    原理

    下面通过一个具体的地址翻译示例来说明缓存和TLB的原理(注意:这是简化版的示例,实际过程可能复杂些,不过原理相同),地址翻译基于以下设定:

    • 存储器是按字节寻址的
    • 存储器访问是针对1字节的字的
    • 虚拟地址(线性地址)是14位长的
    • 物理地址是12位长的
    • 页面大小是64字节
    • TLB是四路组相连的,总共有16个条目
    • L1 Cache是物理寻址、直接映射的,行大小为4字节,总共有16个组

    下面给出小存储系统的一个快照,包括TLB、页表的一部分和L1高速缓存

    虚拟地址(TLBI为索引):

    虚拟地址

    TLB:四组,16个条目,四路组相连(PPN为物理页号):

    TLB

    页表:只展示前16个页表条目(PTE)

    页表

    物理地址(CO为块偏移):

    物理地址

    高速缓存:16个组,4字节的块,直接映射

    高速缓存

    基于以上的设定,我们来看下当CPU执行一条读地址0x3d4(虚拟地址)处字节的加载指令时发生了什么:

    • 从0x3d4中取出如下几个字段:
      1. TLBT: 0x03
      2. TLBI: 0x03
      3. VPN: 0x0F
      4. VPO: 0x14
    • 首先,MMU从虚拟地址中取出以上字段,然后检查TLB,看它是否因为前面的某个存储器的引用而缓存了PTE0x0F的一个拷贝。TLB从VPN中抽取出TLB索引(TLBI:0x03)和TLB标记(TLBT:0x03),由上面的TLB表格可知,组0x3的第二个条目中有效匹配,所以命中,将缓存的PPN(0x0D)返回给MMU。
    • 将上述的PPN(0x0D)和来自虚拟地址的VPO(0x14)连接起来,得到物理地址(0x354)。
    • 接下来,MMU发送物理地址给缓存,缓存从物理地址中取出缓存偏移CO(0x0)、缓存组索引CI(0x5)以及缓存标记CT(0x0D)。
    • 从上面高速缓存表格中可得,组0x5中的标记与CT相匹配,所以缓存检测到一个命中,读出在偏移量CO处的数据字节(0x36),并将它返回给MMU,随后MMU将它传递回CPU。

    以上只分析命中的情况。

    展开全文
  • 高速缓存(page cache)是从实际物理内存中开辟出来一部分内存空间,用作操作系统的磁盘读写缓存。比如客户端写入的数据并不直接写入磁盘,而是写入到这一段物理内存中即代表已经写完,这样由内存本身的高速读写性能...

    描述

    页高速缓存(page cache)是从实际物理内存中开辟出来一部分内存空间,用作操作系统的磁盘读写缓存。比如客户端写入的数据并不直接写入磁盘,而是写入到这一段物理内存中即代表已经写完,这样由内存本身的高速读写性能是能够提升系统整体io性能。
    基本管理单位:页

    原理

    页高速缓存的淘汰原理是根据:时间局部原理,空间局部原理;即最长时间未被访问(时间局部性)或者被进程引用次数最少(空间局部性)的页面最先被淘汰

    关于页高速缓存存在操作系统的哪个层次可以参考如下图:
    在这里插入图片描述

    作用

    我们用户使用系统调用读写(read/write)时,页高速缓存的基本工作原理如下图:
    在这里插入图片描述
    即写请求先写入page cache,再由page cache落盘
    读请求同样先从page cache 中读,入无法读到,则由page cache从磁盘读出

    落盘方式:
    写请求在数据写入到page cahe中后会直接返回客户端写入完毕,但是数据本身并未完全写入到磁盘,而是等到page cache达到操作系统刷新缓存的比例之后才会将缓存中经过淘汰算法计算的页脏数据同步到磁盘。

    测试

    命令测试

    dd if=/dev/zero of=./test.dat bs=1M count=10 #向文件中写入数据
    cat /proc/meminfo |grep Dirty  #查看内存脏页情况
    sync #将内存中的脏页同步到磁盘
    cat /proc/meminfo |grep Dirty #再次查看内存脏也情况
    

    代码测试

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <strings.h>
    #include <fcntl.h>
    
    #define NAME "testfile"
    
    int main()
    {
    	char buf[100];
    	memset(buf,0,100);
    
    	//带有SYNC标记得打开文件
    	int fd=open(NAME,O_CREAT|O_RDWR|O_SYNC, 0666);
    	
    	fgets(buf,100,stdin);
    	int result = write(fd,buf,sizeof(buf));
    	if ( -1 == result) {
    		printf("write failed\n");
    		_exit(-1);
    	}
    	printf("write size is %d\n",result);
    	fsync(fd);
    	printf("sync the page cache\n");
    	return 0;
    }
    

    实现

    每当内核开始执行一个页IO操作时,就先到高速缓存中找。这样就可以大大减少磁盘操作。
    页高速缓存的实现中起主要作用的一个对象为address_space数据结构
    3.10.0-957.5.1.el7.x86_64/include/linux/fs.h

    struct address_space {
    		/*通常情况下,会与一个索引节点(inode)关联,这时host域就会指向该索引节点
    		如果关联对象不是一个索引节点的话,比如address_space和swapper关联时,这时host域会被置为NULL*/
            struct inode            *host;              /* owning inode */
            struct radix_tree_root  page_tree;          /* radix tree of all pages */
            spinlock_t              tree_lock;          /* page_tree lock */
            unsigned int            i_mmap_writable;    /* VM_SHARED ma count */
    		
    		/*i_mmap字段是一个优先搜索树,它的搜索范围包含了在address_sapce中私有的和共享的页面*/
            struct prio_tree_root   i_mmap;             /* list of all mappings */
            struct list_head        i_mmap_nonlinear;   /* VM_NONLINEAR ma list */
            spinlock_t              i_mmap_lock;        /* i_mmap lock */
            atomic_t                truncate_count;     /* truncate re count */
    		
    		/*nrpages反应了address_space空间的大小*/
            unsigned long           nrpages;            /* total number of pages */
            pgoff_t                 writeback_index;    /* writeback start offset */
    
    		/*a_ops域指向地址空间对象中的操作函数表*/
            struct address_space_operations   *a_ops;   /* operations table */
            unsigned long           flags;              /* gfp_mask and error flags */
            struct backing_dev_info *backing_dev_info;  /* read-ahead information */
            spinlock_t              private_lock;       /* private lock */
            struct list_head        private_list;       /* private list */
            struct address_space    *assoc_mapping;     /* associated buffers */
    };
    

    操作函数列表address_space_operations定义如下,其中主要的为writepagereadpage

    • readpage根据file即address_spaces的mapping尝试从page cache中读缓存页,如果搜索页不在搜索树(基数树radix tree)管理的缓存页面中,则内核会重新创建一个缓存也加入到搜索树中。
    • writepage 则根据当前内存页page是否有回写标记,即是否为脏页;如果是则将当前内存页的数据写入到磁盘,并从搜素树中删除该页 mm/filemap.c
    struct address_space_operations {
            int (*writepage)(struct page *, struct writeback_control *);
            int (*readpage) (struct file *, struct page *);
            int (*sync_page) (struct page *);
            int (*writepages) (struct address_space *, struct writeback_control *);
            int (*set_page_dirty) (struct page *);
            int (*readpages) (struct file *, struct address_space *,struct list_head *, unsigned);
            int (*prepare_write) (struct file *, struct page *, unsigned, unsigned);
            int (*commit_write) (struct file *, struct page *, unsigned, unsigned);
            sector_t (*bmap)(struct address_space *, sector_t);
            int (*invalidatepage) (struct page *, unsigned long);
            int (*releasepage) (struct page *, int);
            int (*direct_IO) (int, struct kiocb *, const struct iovec *,loff_t, unsigned long);
    };
    

    page cache的页高速缓存管理如下:
    在这里插入图片描述
    根据上图我们知道

    • 读文件流程如下
      1. 数据结构关联:inode -> i_mapping 指向address_space对象,address_space->host指向inode
      2. 数据结构关联:page->mapping 指向页缓存owneraddress_space
      3. 系统调用传参:文件描述符+文件偏移地址
      4. 操作系统找到文件address_space,根据偏移量到页缓存中查找page
      5. 若查找到,返回数据到用户空间
      6. 否则,内核新建一个page并加入到页缓存,数据从磁盘载入该项
      7. 调用readpage方法将数据返回给用户空间
    • 写文件流程如下:
      1. 数据结构关联:inode -> i_mapping 指向address_space对象,address_space->host指向inode
      2. 数据结构关联:page->mapping 指向页缓存owneraddress_space
      3. 系统调用传参:文件描述符+文件偏移地址
      4. 操作系统找到文件address_space,根据偏移量到页缓存中查找page
      5. 若查找到,将数据写入到该缓存中,该页成为脏页
      6. 若没有查找到,向缓存的计数树管理的页面中添加一个新的页面,并将用户空间的数据写入到该页面
      7. 当数据满足页缓存的时间或空间原理时,使用pdflush后台回写例程来将脏页数据会写到磁盘

      pdfush的实现如下:
      pdflush线程在系统中的空闲内存低于一个特定的阈值时,将脏页刷新回磁盘;
      该后台回写例程的目的在于在可用物理内存过低时,释放脏页以重新获得内存

      其中控制内存阈值的系统调用设置为:dirty_background_ratio,一旦页高速缓存中搜索树管理的内存页占总内存的比例超过这个数值时,内核便会调用函数wakeup_bdflush() 唤醒一个pdflush线程,随后pdflush线程进一步调用函数background_writeout()开始将脏页写会到磁盘,函数background_writeout()需要一个长整型参数,该参数指定试图写回的页面数目。直到满足以下两个条件:
      1.已经有指定的最小数目的页被写回到磁盘。
      2.已使用内存页已经减少,小于阈值dirty_background_ratio

    缓存控制

    关于控制操作系统页高速缓存占用内存比例可以控制如下两个参数:dirty_ratiodirty_background_ratio
    关于dirty_background_ratio已经讲过,即脏页比例达到内存总空间的dirty_background_ratio阈值时,pdflush开始刷缓存,同时页高速缓存也可以接收io
    关于dirty_ratio,即脏页比例达到内存总空间的dirty_ratio时,页高速缓存不接受客户端io,仅由pdflush刷数据

    • 获取参数配置 sysctl -a | grep dirty
    • 设置参数
      修改配置文件/etc/sysctl.conf,增加参数
      vm.dirty_background_ratio = 5
      vm.dirty_ratio = 10
      执行sysctl -p即可生效
    展开全文
  • 高速缓存

    2014-07-16 23:31:58
    L i n u x系统使用了几种涉及到高速缓存内存管理方法。 1 缓冲区高速缓存 缓冲区高速缓存中保存着块设备驱动程序所用到的数据缓冲区。 这些缓冲区的大小固定,一般包括从块设备中读入的和将要写入到块设备中的信息...
  • Linux高速缓存概述

    千次阅读 2012-12-16 13:42:28
    [数据缓冲区高速缓存] 内核通过保存一个称为数据缓冲区高速缓存的内部数据缓冲区池来试图减小对磁盘的存取频率。高速缓冲含有最近被使用过的磁盘块的数据 在Linux内核0.11中,它默认最多支持16M的物理内存。对系统...
  • 虚拟内存做缓存 高速缓存存储器 (Cache Memory) In memory hierarchy, there is an additional level of memory which is Cache. It is high-speed storage and much faster than the main storage. It is much ...
  • 为解决这些问题,大量研究为探索自转扭矩RAM(STT-RAM),相变存储器(PCM) ,电阻RAM(RRAM)和域壁内存(DWM)新型新型插入性存储器(非易失性存储器,NVM)架构缓存的方法,对比了其与传统存储器件的物理特性,...
  • LINUX使用的缓存,缓冲区高速缓存

    千次阅读 2013-10-18 21:37:12
    使用了多种和内存管理相关的高速缓存。 1.缓冲区高速缓存:  缓冲区高速缓存中包含了由块设备使用的数据缓冲区。这些缓冲区中包含了从设备中读取的数据块或写入设备的数据块。缓冲区高速缓存由设备标识号和块...
  • Linux页高速缓存和页回写

    千次阅读 2018-03-19 10:11:26
    是通过把磁盘中的数据缓存到 物理内存中,把对磁盘的访问变为对物理内存的访问。 磁盘高速缓存有两个重要因素:第一,访问磁盘的速度要远低于访问内存的速度,若从处理器L1和L2高速缓存访问则速度更快。第二,数据...
  • 高速缓存(Cache memory...为了缩小CPU和DDR两者之间速度上的不匹配造成的等待问题,硬件上引入高速缓存,这利用了局部行原理,简单来说,该原理的想表达的就是:CPU运行所需要的内存一般都在当前访问地址的附近,最...
  • 缓冲区和高速缓存

    千次阅读 2015-05-13 15:17:37
    例如,正在运行的进程的指令既存储在磁盘上,也存储在物理内存上,也被复制到CPU的二级和一级高速缓存中。 不过,磁盘高速缓存技术不同于通常意义下的介于CPU与内存之间的小容量高速存储器,而是指利用内存中的存储...
  • 磁盘高速缓存

    2013-06-23 12:02:11
    一 概述: (1)磁盘高速缓存:利用内存中的存储空间暂存从磁盘中读出的一系列磁盘盘块的信息。高速缓存是一组在逻辑上属于磁盘,物理上驻留在内存中的盘块(并非如内存和CPU之间增设的一个小容量高速存储器)。
  • 高速缓存和页回写

    千次阅读 2013-09-25 17:42:47
    具体地讲,是通过把磁盘中的数据缓存到物理内存中,把对磁盘的访问变为对物理内存的访问。  磁盘高速缓存之所以在任何现代操作系统中尤为重要源自两个因素:第一,访问磁盘的速度要远远低于访问内存的速度,因此,...
  • 关于高速缓存和I/O高速缓冲的讲解

    千次阅读 2015-09-13 10:25:09
    例如,正在运行的进程的指令既存储在磁盘上,也存储在物理内存上,也被复制到CPU的二级和一级高速缓存中。 不过,磁盘高速缓存技术不同于通常意义下的介于CPU与内存之间的小容量高速存储器,而是指利用内存中的...
  • Linux磁盘高速缓存

    2018-05-02 09:56:56
    磁盘高速缓存是一种软件机制,允许系统把通常存放的磁盘上的一些数据保留在RAM中。例如,目录项高速缓存(dentry cache),加速从文件路径名到最后一个路径分量的索引节点转换过程。Linux还有其他磁盘高速缓存,如页...
  • hibernate之高速缓存基本原理(Hibernate高速缓存架构)
  •   早期的计算机系统只有物理内存的概念,所谓的物理内存就是真实的内存直观一点就是咱们计算机的内存条,他的大小决定了内存的大小。但是直接使用物理内存会存在很多的问题,首先就是直接使用物理内存可能会产生很...
  • 高速缓存与缓冲区

    2015-11-24 21:53:54
    1. 磁盘高速缓存(Disk Cache) ...例如,正在运行的进程的指令既存储在磁盘上,也存储在物理内存上,也被复制到CPU的二级和一级高速缓存中。 不过,磁盘高速缓存技术不同于通常意义下的介于
  • 高速缓存(英语:cache,英语发音:/kæʃ/ kash [1][2][3],简称缓存),其原始意义是指访问速度比一般随机存取存储器(RAM)快的一种RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。...
  • 《深入理解计算机系统》笔记(二)内存高速缓存的原理 (本篇) 《深入理解计算机系统》笔记(三)链接知识 《深入理解计算机系统》笔记(四)虚拟存储器,malloc,垃圾回收 《深入理解计算机...
  • linux内核虚拟内存之物理内存

    千次阅读 2017-09-15 11:41:58
    linux虚拟内存之物理内存描述
  • 硬件高速缓存&TLB

    2015-10-15 17:22:33
    硬件高速缓存是通过高速缓存行寻址的。L1_CACHE_BYTES宏产生以字节为单位的...当为一大数组数据分配空间时,内核师徒把他们放在内存中,以便所有高速缓存行按同一方式使用。 TLB是一个内存管理单元用于改进虚
  • 磁盘缓存内存缓存的区别

    万次阅读 多人点赞 2015-08-23 22:49:50
    内存缓存高速缓存(英语:cache,英语发音:/kæʃ/ kash [1][2][3],简称缓存),其原始意义是指访问速度比一般随机存取存储器(RAM)快的一种RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM...
  • Linux与Windows高速缓存处理机制

    千次阅读 2010-02-09 17:09:00
    一般的高速缓存分为两级,cpu和内存之间一般会设置高速缓存,但是 这一般是硬件实现的,还有一个高速缓存存在于内存和磁盘之间,这个一般由操作系统实现,当然为了给应用程序提供灵活的策略自定义机制,在某些情况下...
  • 该博客为第6单元的学习笔记,这一单元的主要内容是存储设备层次结构、缓存、高速缓存的通用组织、虚拟内存等。内容来自《深入理解计算机系统》的第六章的部分内容。对应ssd6课程的lecture7。 存储器系
  • 缓存分为内存缓存和硬盘缓存
  • 摘要:CPU内置少量的高速缓存的重要性不言而喻,在体积、成本、效率等因素下产生了当今用到的计算机的存储结构。 介绍 cpu缓存的结构 缓存的存取与一致 代码设计的考量 最后 CPU频率太快,其处理速度远快于存储...
  • 高速缓存与一致性

    2020-06-05 15:11:09
    首先是介绍高速缓存的基本原理,硬件是如何缓存和查找数据,这是个基础入门。 smcdef:Cache的基本原理​zhuanlan.zhihu.com 针对高速缓存基本原理中引入的问题,在下篇文章中解答。从代码的角度考虑高速缓存是如何...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 53,961
精华内容 21,584
关键字:

物理内存高速缓存