CACHE & TLB (May 14)

一.unix内核简单回顾(内存相关)

               1. 当使用传统的分时调度策略的时候,在用户级执行的进程总会被分到时间片内执行,从而让所有的进程公平的共享CPU,在内核态执行的进程却不会分入时间片内执行,只有当前的内核进程明确允许的情况下,才能切换到内核级执行另一个进程。

               2. 线程的开销比进程小主要体现在线程共享进程的地址空间。linux内核中没有线程的概念,使用的线程库,实现的功能是线程调度在内核进行,线程的管理在用户空间进行。从概念上看,每一个进程地址空间都分为用户地址空间和内核地址空间,linux中作进程切换的时候,如果是线程的切换,由于共用用户地址空间,所以用户空间的3G内存映射是不需要切换的,在内核中有对应的mm_struct对应,从而减少切换开销。

               3. 地址空间的分配,一般是3G用户空间,1G内核空间 , 用户空间由text/data/bss/stack这几个部分组成,分别是程序指令,初始化数据,未初始化数据和堆栈。另外一些类型的段,如共享库和共享存储,都包含在用户地址空间内。共享库包括附加的正文,数据和BSS段,它们用于常用的函数和服务。共享存储在后面说明。

               内核空间包括内核的正文和数据结构,当内核正在执行的时候,它可以访问整个地址空间,这样易于让内核在代表用户进程执行一次系统调用的时候可以在用户进程的地址空间中执行。一个内核进程实际上执行的都是内核的正文中的代码,有固定的映射,切换的时候重装task_struct等结构是必须的。

               可以从下图中有个更加清晰的概念:(这里是2G/2G的设计)

                 

               

               4. 地址空间映射

                    线程切换减少开销主要是因为共享用户地址空间,所以用户空间地址映射的物理地址对应关系可以保存下来,不需要切换,从而减少开销。

                    内核负责将一个进程的虚拟地址空间映射到物理地址空间上,由MMU执行。

                   

                   

                        

             每一个进程都有它自己的映射关系,这种映射关系与内存有关,而且作为进程的现场的一部分保存起来。在进程运行的时候,内核将进程映射关系的描述提供给MMU

           注意,不是所有的虚拟页面都需要映射,例如,上图中的虚拟页面158就没有被映射到任何物理页面上,它们可以表示进程的地址空间中没有使用的页面,也可以是当前没有驻留在内存中的页面,如果一个进程试图返回后一种类型的页面,发生缺页错误之后,内核会将相应的物理页面调入到内存中,并且把虚拟页面映射到新分配的物理页面上。

           同样,不是存储器中所有的物理页面都由一个进程使用。比如上图的248,当前正在执行的进程无法访问它们,这些物理页面可以属于系统中的其他进程,或者干脆没有使用。无论何种情况,当前在执行的进程是不允许访问它们的。

           虚拟页面映射到同一个物理页面上,内核就可以让多个进程共享特定的物理页面。

           大多数的MMU会给每一个映射关系关联一个访问权限的能力。比如将内核的正文页面(text page)映射为只读,同时允许对数据页面的读写访问。(ForkCOW

           Fork会根据需要进行拷贝物理页面,简单的就是cow(copy-on-write)

           

                

 

          

               

           上面4图能很好的说明fork时的地址映射关系。

                 使用exec时,原来程序的正文、数据、bss和堆栈以及诸如共享存储的其他存        储对象会被抛弃,而为新程序创建新的虚拟地址空间。新程序的正文和初始化数据从           指定文件中读入,而内核将地址空间内的空间分配给bss和堆栈,进程内单个线程的Pc被设定在新程序的起始地址。这个进程的执行实际上看的也是调度,内核中制造   的假象是该进程已经执行很长时间,重新参与调度。新程序可以访问exec调用之前 进程打开的文件,因为这些文件都是和进程相关联的,而不是和程序相关联的。

                 exit或者kill可以终止一个进程,androidzygote退出时不使用exit,而是自己给自己发送一个kill的信号。终止一个进程时,内核必须丢弃进程的地址空间,取消进程正在使用的内核服务,比如关闭进程留下的任何打开的文件。此时进程以一种僵 尸进程的形式存在,使的父进程有机会通过wait读取到子进程的退出状态之前保持进程间的父子关系,最后释放代表进程本身的内部的内核数据结构(如task_struct),一些结构的释放是在父进程中进行的。之后内核执行一次现场切换,选择另外一个要执行的进程。

                系统调用sbrk/brk都是供一个进程用来分配或者回收它的bss段空间。系统调用以BSS段的BReaK address得名。sbrk/brk能让进程改变它的断开地址,从而增长  或者缩小bss段的大小,增大通过页错误来实现,缩小BSS,那么在新老断开地址之   间地址范围的虚拟地址和物理地址都会被释放,访问权限也变了,进程再也不能访问 它。

               共享存储段位于进程BSS和堆栈段之间未用的虚拟存储区中,便于进程通信,它将一个或者多个物理页面映射到不同进程的不同虚拟地址空间中(不需要是相同的虚拟地址)。

                映射文件,从上面的内存分配和一些记录,能很好的理解映射文件的概念。因为存在物理地址到虚拟地址的映射,unix提供将文件映射到一个进程地址空间内的功能。一旦文件被映射到进程地址空间内,这个文件就可以作为地址空间内一段连续的字节区直接访问,一般映射到用户空间地址。

 

二. cache
            1.局部性

                时间局部性和空间局部性

            2.cache基本原理

                在高速缓存的设计中,要考虑的重要一点是用一个标记确定多少数据。例如,独立地标记告诉缓存中的每一个字节代价太大,因此,来自主存储器的一个或者更多连续的字被组织到一起形成一个高速缓冲行(line)或者块(block),并且给每一行关联一个标记,所以一个完整的高速缓冲行由一个标记和一段数据组成,高速缓冲行的大小是指数据部分的字节数。

                例如,Proc-arm926.S中有如下设置,

/*

 * thecache line size of the I and D cache

 */

#define CACHE_DLINESIZE       32

                 

                 标记部分还包括有效位(validbit),修改位(modified bit)以及其他依赖实现的信息,    比如key.

                 替换策略有LRU,伪LRU,以及随机替换。

                 写入策略有写直通(write-through)和写回策略(write-back),写直通不存在一致性问题,而写回策略需要注意一致性。Modified bit就是用于write-back策略的.

                 

           3.cache的组织方式--直接映射高速缓存

             

                     

                  由于许多不同的地址都会被散列到同一行上,一些程序可能导致高速缓存行在被再次命中之前被替换掉,从而导致高速缓存颠簸(cache thrashing),和进程颠簸类似.

                 

                4.cache的组织方式--双路组相联高速缓存

                   双路组相联高速缓存通过索引一组两行可能保存数据的高速缓冲行,来减少高速缓存的颠簸,但是不能彻底解决此问题.

                  

                              

                  5.cache的组织方式--N路组相联高速缓存     

                       组内的行数并没有理论上的限制,所以有4路或者更多录的组相联高速缓存,到最后就可以将组的大小增加到组内行的数目等于高速缓存内的全部行数,此时只有一组,称作全相联高速缓存.

                  6.cache的组织方式--全相联高速缓存            

                       全相联高速缓存能最大的减小高速缓存颠簸的现象,但是全相联高速缓存构建成本高,因为需要并行搜索高速缓存中的所有行.TLB在这方面比较实用,因为大多数程序都体现出局部引用特性,这意味着工作集的转换会被多次使用,             

                 7.高速缓存冲洗(flush)

                       高速缓存冲洗有两中形式,使主存储器有效(validating main memory)和使高速缓存无效(invalidating cache).

                       Validating mainmemory指采用write-back policycache将修改过的数据写回到主内存的做法,比如说flush_kern_cache_all函数,这种行为会在每隔一段时间自动完成,也可以由操作系统明确的控制.

                       Invalidating cache指直接将cache数据抛弃的做法,这个对于write-back policywrite-through policy均适用.

                  8.icache & dcache

                  

                     9.高速缓冲的性能

                                            

                       10.如何区分不同的高速缓存结构

                       高速缓存大小

                       行大小

                       组大小

                       写分配的使用

                       替换策略

                       按照虚拟或者物理地址查找

                       如何标记行(通过虚拟地址、物理地址还是其他信)

                       写直通或者写回策略.

 

四.虚拟高速缓存

            1.虚拟高速缓存的概念

                 

               

            2.别名和歧义的概念

           歧义:(ambiguity)

           在高速缓存建立映射表之后,如果虚拟地址和物理地址的映射关系发生变化,而从虚拟高速缓存来看,虚拟地址不变,所以依然会命中而直接返回,从而导致读取到的是旧的数据.

           别名:(alias)

           别名指的是不同的虚拟地址映射到同一个物理地址,在虚拟缓存中会有多份对应关系,在写操作之后,物理地址上的数据进行了更新,但是因为不同虚拟地址对应同一个物理地址,在缓存中命中之后获取到的有可能是旧的数据.

 

 

     

            3. 管理虚拟高速缓存

           context_switch

           虚拟缓存在进程切换的时候,是需要flush cache的,因为进程切换时前后进程中的虚拟地址有可能相同,却有不同的物理映射,不flush cache,则hit之后,获取的是前一个进程的数据。

           ARM926EJ-S的文档中有如下说明,说明是否由软件冲刷是处理器相关的

           page -- 4.1

The caches arevirtual index, virtual tag, addressed using the Modified Virtual

Address (MVA). Thisenables the avoidance of cache cleaning and/or invalidating

on context switch.      

           

           具体到代码中是cpu_do_switch_mm函数,可以根据不同的处理器看,后面分析

           

           fork

           采用COWfork,如果fork之后没发生切换,父进程继续执行,此时通过write-back将数据从cache写回到主存之后,由于COW,会建立一个副本作为父进程的页面,从而发生子进程和父进程数据不一致的情况。不带键值的cache设计中,在fork执行前作flush操作,保证数据一致性.

 

           execve

           exec丢弃当前进程的地址空间,但是由于虚拟地址的问题,新老进程可能使用了同样的虚拟地址,映射的是不同的物理地址,导致新程序会接收到一些老程序的数据,所以在获取数据前,要使cache无效,主存储器有效,在使用进程键的cache设计中,因为exec会完全丢弃老进程的地址空间,同时使用的是和原进程相同的进程键(减少查找进程键的时间),所以刷新操作必须执行.

           具体代码中比较多应用,比如copy_strings中,flush_kernel_dcache_page,flush_arg_page,具体到汇编的分析前面已举例。

           

           exit

           exit会丢弃一个进程的地址空间,内核应该确保丢弃任何高速缓存的数据,从而保证在下一个进程运行时不会出现歧义,因为exit的最后一步是执行现场切换,现场切换会进行冲洗告诉缓存的操作,所以在exit中不需要添加冲洗操作,是否需要flush和体系cache设计有关,在exit_mmap中,最后会调用到flush_cache_mm.

           

           brk /sbrk

           brk/sbrk用于增大和缩小进程的bss段,增大bss段不会带来高速缓存的问题,因为是新分配的虚拟存储空间。而缩小bss段,必须防止进程访问相应于刚刚释放掉的虚拟存储区的高速缓存数据,所以必须是缩小bss部分对应的高速缓存无效。

           具体到代码中,

           sbrk的操作也由brk实现,如果brk区域变小,则做对应的unmap操作,在unmap操作中有flush操作.

           /* Always allow shrinking brk. */

           if (brk <= mm->brk) {

                       if (!do_munmap(mm,newbrk, oldbrk-newbrk))

                                   gotoset_brk;

                       goto out;

           }

           共享存储器和映射文件

           对于不同进程使用不同的虚拟地址来共享相同的共享存储段,需要考虑带来高速缓存的别名问题,但是由于每次现场切换都已经进行冲洗高速缓存的操作,所以不需手动消除。

           但是如果是一个进程内不同的虚拟地址映射同意物理页面,则需要考虑别名问题,一方面内核可以通过只访问一次的方式去处理,也可以高速缓存的组织方式去解决。(比如每次附加的起始地址都索引到相同的高速缓冲行)

 

           输入输出

           使用DMA传输需要注意使高速缓存无效,所以在上面说到unmap时会有flush dcache的操作。

           

              

           用户-内核数据的歧义

           在内核模式和用户模式执行期间都会使用高速缓存,所以必须保证用户不能访问任何被高速缓存的内核数据,也必须保证任何被高速缓存的用户数据不会被误认为内核数据。比较简单的处理方式是,用户进程不能访问被高速缓存的内核数据(也不能修改它),但是在行替换期间可以写回到内核数据,这样就不用显式的高速缓存冲洗操作来防止用户-内核的歧义,否则需要显式冲洗数据。

           说明linux内核中已经有swapper守护进程来作页面的换入和换出工作,在完成这些工作之后一定会进行进程切换,如果是原来不带键值的cache设计中,则会在切换时flush,保证主存储器的内容最新,因此也可以安全的执行DMA,再带键值的进程在执行此操作时,则需要在交换页面之后进行flush的操作,查看代码,swap.cput_page有进行相关操作.

 

 

      4.带有键值的cache设计      

        

 

            

           

            context_switch

           ARM926EJ-S的文档中有如下说明

           page -- 4.1

The caches arevirtual index, virtual tag, addressed using the Modified Virtual

Address (MVA). Thisenables the avoidance of cache cleaning and/or invalidating

on context switch.      

           如何通过Modified Virtual Address (MVA)来避免context switch时的cache冲洗呢?事实上,之所以需要在进程切换的时候冲洗cache,原因就在于前后进程在同一个虚拟地址对应的可能是不同的物理页面,而cache是不感知的。

           在高速缓存中添加一个进程键唯一地确定一行高速缓存属于一个特殊的进程,这样就能减少必须冲洗操作的次数,理想情况下,给每一个进程分配一个唯一的键,则VA和键就能组成唯一的标识符,组成MVA,从而在进程切换时无须冲洗.

           这里进程键和pid不是同一个概念,进程键是和寄存器相关的,只要是每个进程的唯一标识就行,与pid无关.

           使用key的方法还是有一点问题的,切换进程之后,高速缓存中只包含老进程数据的虚拟地址,切换期间,MMU已经载入新进程的地址空间映射关系,所以不能转换老进程的地址。用写直通能消除此问题。如果使用将MVA传递给MMU的方式,则MMU需要管理多个地址空间的映射关系,这样就能使用写回高速缓存机制。

           ARM926EJ-S的手册中对MMUaddresstranslation描述如下,

The VA generated bythe CPU core is converted to a Modified Virtual Address (MVA)

by the FCSE usingthe value held in CP15 c13. The MMU translates MVAs into

physical addressesto access external memory, and also performs access permission

checking.

 

             fork

           在使用键值的情况下,如果是N组单行的设计cache颠簸是必然的,这个问题在前面有过说明.父子进程的键值不一样,在进程切换之后(使用键值不再刷新cache),单行必然会无法命中(因为键值不同,即使虚拟地址一致,并且COW),此时使用N组多行的设计就能避免此问题.
            
下图是一个双路组相联父子进程共享读取地址0x100000的例子,

            

 

           

           exec

           使用进程键同样要刷新cache,新老进程使用的是同一个进程键,由于execve丢弃所有的地址空间,所以flush之后不会产生歧义.

           

           exit

           在使用不带键值的cache时,因为任务切换会进行flush操作,所以不需要在exit的最后手动冲洗,而使用键值的设计,现场切换没有flush操作,需要相关操作(和体系有关)

           具体在exit_mmap中,最后会调用到flush_cache_mm.

 

           brk/sbrk

           带有键值的cache设计和不使用的一致,在sbrk时需要flush.

 

           共享存储和映射文件

           不带键值的cache设计在现场交换时会进行flush操作,而在带键值的cache上就显得比较麻烦,首先N路组相联必然是一个能稍微解决此问题的办法,和前面说的fork一致,其次使用内核只能访问一次的方式处理效率较低,而最好的方法是通过键值加虚地址的组合仍然能索引到相同的高速缓冲行的设计。但是这也无可避免的体现到,带键值的cache设计的缺点,共享的数据在高速缓存中不能共享.

           下图是通过键值加虚地址的组合仍然能索引到相同的高速缓冲行的示例.

            

         

              

           ashmem.c的代码来看,一方面使用N路组相联稍微解决cache flush问题,同时使用mutex_lock实现互斥操作,另外还提供了ioctl的操作,应用可以传递ASHMEM_CACHE_FLUSH_RANGE来实现flush操作.           

 

           输入输出

           带键值的cache这里和不带键值的cache设计保持一致.

 

           用户-内核数据的歧义

           和不带键值的设计基本保持一致.

 

            TLB

           TLB本身仅仅是一个虚拟高速缓存而已,通过使用虚拟高速缓存可以找到转换所用的入口地址,它也同样以这个地址来进行索引和标记。TLB所高速缓存的数据是物理页号和页访问权限。TLB内发生一次命中的时候,MMU能够从TLB的数据立刻计算出物理地址和有效的权限。在出现一次缺失的时候,有些实现会读取保存在主存储器内的页面,以获得相关的映射关系,然后将新的映射关系自动高速缓存到TLB中供以后使用.

              Tlb和cache/mmu的关系可以见下图(摘自wiki)

                

              

           cache类似,如果只使用虚拟地址来标记TLB数据,在context_swith时必须flush TLB,防止发生歧义,而一些处理器的TLB带键值设计的则不需要flush,只要在sbrk的时候或者另一个进程要重用键的时候.DMA传输时,是不需要作flushTLB.

           看看ARM926EJ-S中,TLB的说明,

The MMU contains asingle unified TLB used for both data accesses and instruction

fetches. The TLB isdivided into two parts:

• an eight-entryfully-associative part used exclusively for holding locked down

  TLB entries

• a set-associativepart for all other entries, 2 way x 32 entry.

When an entry hasbeen written into the lockdown part of the TLB, it can only be

removed by beingoverwritten explicitly, or by an MVA-based TLB invalidate

operation, wherethe MVA matches the locked down entry.

           从上面的说明来看,这是带键值的TLB,因为在进程切换时,不需要flush TLB

           SMP 和 TLB        

            在SMP体系下TLB的刷新设计到广播操作,详细可以见ipi_flush_tlb_kernel_range函数。

            从下图可以很容易明白为啥SMP下需要做同步的tlb flush操作

           

            5.带有物理标记的虚拟高速缓存

           另外一种不用key的增强方式是,高速缓存保存的是物理地址,暂不学习.

            

 

.物理高速缓存

           前面说到的都是虚拟高速缓存,一般就是我们平时说的L1 cache, L2 cache一般就是这里说的物理高速缓存.

           crash_note.c中,函数crash_notes_save_this_cpu有一个示例应用,

             

 

              

             下面是物理高速缓存的组织结构,

 

           这个设计的好处在于不会出现歧义也不会出现别名,共享数据的进程使用相同的物理页面,使得它们索引到相同的高速缓冲行或者组,并且会匹配标记从而发生一次命中,最后一般不需要冲洗高速缓存.

           物理高速缓存的在现场切换,fork,exec,exit,brk/sbrk,共享存储,映射文件,用户-内核数据的歧义等方面均不需要冲洗操作.

           输入输出和总线监听

 

            

 

                 

                高速缓存能看到所有的总线交易,所以它能监听所有IO设备执行的总线活动,从而可以检查交易中的物理地址是否驻留在高速缓存中,如果发生不一致就进行更新.不管是write-back还是write-through策略,都能通过总线监听的方式得到一致更新.

                带有次级物理高速缓存的主虚拟高速缓存组织结构如下,

                

               

                  

.高速缓存的管理

           一个高速缓存的整体性能是由3个因素决定的:高速缓存的物理设计,在系统上运行的程序的局部引用特性,以及操作系统管理高速缓存的效果。

           高速缓存的物理设计在定下来之后就无法改变,而对于应用程序的局部引用特性操作系统无法控制,而操作系统管理高速缓存的方式则比较复杂.

           操作系统管理高速缓存主要有三种技术,分别是地址空间布局,延迟高速缓存无效,和对齐高速缓存的数据结构.

           地址空间布局:

           地址空间的布局主要针对一个进程中的3个主要区域(text,data/bss,stack),一个比较好的布局是能减少cache冲洗次数,最好的办法是能动态地址绑定.

           静态地址布局如下,

            

 

               动态布局如下,

                

               

           linux代码中是如何实现的呢?

           比如有如下设计,

           config COMPAT_BRK

           bool "Disable heap randomization"

           default y

           help

            Randomizing heap placement makes heap exploits harder, but it

            also breaks ancient binaries (including anything libc5 based).

            This option changes the bootup default to heap randomization

            disabled, and can be overridden at runtime by setting

            /proc/sys/kernel/randomize_va_space to 2.

 

            On non-ancient distros (post-2000 ones) N is usually a safe choice.

           

           android的init.rc中,   write/proc/sys/kernel/randomize_va_space 2

           

           物理索引高速缓存(pagecolor)

           page color的理论比较复杂,目前基本的了解是在申请page时通过取模做编号,不同的编号对应不同的color的概念,这样在管理page时,能最大限度的利用到cache.(以后分析学习)

 

           延迟高速缓存无效:

           延迟高速缓存无效和推后执行有点类似,在不需要flush操作时,先进行其他操作,比如在带有key的虚拟高速缓存中,exit之后可以不用flush操作,因为由于进程键的关系,cache必然无法命中,在后面会重新flushcache.

           对齐高速缓存的数据结构:           

           在设计数据结构时最后能根据cacheline来进行数据结构的对齐,这样便于命中和也能减少flush的次数.


source code

ARM926EJS为例分析,

主要的代码位于arch/arm/mm/cache-arm926.sarch/arm/mm/proc-arm926.s

1. cache和tlb的定义

 

#ifdef MULTI_CACHE

 

ENTRY(\name\()_cache_fns)

            .long    \name\()_flush_icache_all

            .long    \name\()_flush_kern_cache_all

            .long    \name\()_flush_user_cache_all

            .long    \name\()_flush_user_cache_range

            .long    \name\()_coherent_kern_range

            .long    \name\()_coherent_user_range

            .long    \name\()_flush_kern_dcache_area

            .long    \name\()_dma_map_area

            .long    \name\()_dma_unmap_area

            .long    \name\()_dma_inv_range

            .long    \name\()_dma_clean_range

            .long    \name\()_dma_flush_range

            .size     \name\()_cache_fns, . - \name\()_cache_fns

.endm

 

 

/*

 * thecache line size of the I and D cache

 */

#define CACHE_DLINESIZE          32

 

/*

 *         MM Cache Management

 *         ===================

 *

 *         The arch/arm/mm/cache-*.S andarch/arm/mm/proc-*.S files

 *         implement these methods.

 *

 *         Start addresses are inclusive and endaddresses are exclusive;

 *         start addresses should be rounded down,end addresses up.

 *

 *         See Documentation/cachetlb.txt for moreinformation.

 *         Please note that the implementation ofthese, and the required

 *         effects are cache-type (VIVT/VIPT/PIPT)specific.

 *

 *         flush_icache_all()

 *

 *                     Unconditionally clean andinvalidate the entire icache.

 *                     Currently only needed forcache-v6.S and cache-v7.S, see

 *                     __flush_icache_all for thegeneric implementation.

 *

 *         flush_kern_all()

 *

 *                     Unconditionally clean andinvalidate the entire cache.

 *

 *         flush_user_all()

 *

 *                     Clean and invalidate all userspace cache entries

 *                     before a change of pagetables.

 *

 *         flush_user_range(start, end, flags)

 *

 *                     Clean and invalidate arange of cache entries in the

 *                     specified address spacebefore a change of page tables.

 *                     - start - user startaddress (inclusive, page aligned)

 *                     - end   - user end address   (exclusive, page aligned)

 *                     - flags - vma->vm_flagsfield

 *

 *         coherent_kern_range(start, end)

 *

 *                     Ensure coherency betweenthe Icache and the Dcache in the

 *                     region described by start, end.  If you have non-snooping

 *                     Harvard caches, you need toimplement this function.

 *                     - start  - virtual start address

 *                     - end    - virtual end address

 *

 *         coherent_user_range(start, end)

 *

 *                     Ensure coherency betweenthe Icache and the Dcache in the

 *                     region described by start,end.  If you have non-snooping

 *                     Harvard caches, you need toimplement this function.

 *                     - start  - virtual start address

 *                     - end    - virtual end address

 *

 *         flush_kern_dcache_area(kaddr, size)

 *

 *                     Ensure that the data heldin page is written back.

 *                     - kaddr  - page address

 *                     - size   - region size

 *

 *         DMA Cache Coherency

 *         ===================

 *

 *         dma_inv_range(start, end)

 *

 *                     Invalidate (discard) thespecified virtual address range.

 *                     May not write back anyentries.  If 'start' or 'end'

 *                     are not cache line aligned,those lines must be written

 *                     back.

 *                     - start  - virtual start address

 *                     - end    - virtual end address

 *

 *         dma_clean_range(start, end)

 *

 *                     Clean (write back) the specifiedvirtual address range.

 *                     - start  - virtual start address

 *                     - end    - virtual end address

 *

 *         dma_flush_range(start, end)

 *

 *                     Clean and invalidate thespecified virtual address range.

 *                     - start  - virtual start address

 *                     - end    - virtual end address

 */

struct cpu_cache_fns {

            void(*flush_icache_all)(void);

            void(*flush_kern_all)(void);

            void(*flush_user_all)(void);

            void(*flush_user_range)(unsigned long, unsigned long, unsigned int);

 

            void(*coherent_kern_range)(unsigned long, unsigned long);

            void(*coherent_user_range)(unsigned long, unsigned long);

            void(*flush_kern_dcache_area)(void *, size_t);

 

            void(*dma_map_area)(const void *, size_t, int);

            void(*dma_unmap_area)(const void *, size_t, int);

 

            void(*dma_inv_range)(const void *, const void *);

            void(*dma_clean_range)(const void *, const void *);

            void(*dma_flush_range)(const void *, const void *);

};

 

 

extern struct cpu_cache_fns cpu_cache;

 

#define __cpuc_flush_icache_all                    cpu_cache.flush_icache_all

#define __cpuc_flush_kern_all                       cpu_cache.flush_kern_all

#define __cpuc_flush_user_all                       cpu_cache.flush_user_all

#define __cpuc_flush_user_range                  cpu_cache.flush_user_range

#define __cpuc_coherent_kern_range            cpu_cache.coherent_kern_range

#define __cpuc_coherent_user_range            cpu_cache.coherent_user_range

#define __cpuc_flush_dcache_area                cpu_cache.flush_kern_dcache_area

 

/*

 *These are private to the dma-mapping API. Do not use directly.

 *Their sole purpose is to ensure that data held in the cache

 * is visibleto DMA, or data written by DMA to system memory is

 *visible to the CPU.

 */

#define dmac_map_area                                 cpu_cache.dma_map_area

#define dmac_unmap_area                             cpu_cache.dma_unmap_area

#define dmac_inv_range                                cpu_cache.dma_inv_range

#define dmac_clean_range                 cpu_cache.dma_clean_range

#define dmac_flush_range                 cpu_cache.dma_flush_range

 

 

#ifdef MULTI_TLB

struct cpu_tlb_fns cpu_tlb __read_mostly;

#endif

struct cpu_tlb_fns {

            void(*flush_user_range)(unsigned long, unsigned long, struct vm_area_struct *);

            void(*flush_kern_range)(unsigned long, unsigned long);

            unsignedlong tlb_flags;

};

 

/*

 *         TLB Management

 *         ==============

 *

 *         The arch/arm/mm/tlb-*.S files implementthese methods.

 *

 *         The TLB specific code is expected toperform whatever tests it

 *         needs to determine if it shouldinvalidate the TLB for each

 *         call. Start addresses are inclusive and end addresses are

 *         exclusive; it is safe to round theseaddresses down.

 *

 *         flush_tlb_all()

 *

 *                     Invalidate the entire TLB.

 *

 *         flush_tlb_mm(mm)

 *

 *                     Invalidate all TLB entriesin a particular address

 *                     space.

 *                     - mm    - mm_struct describing address space

 *

 *         flush_tlb_range(mm,start,end)

 *

 *                     Invalidate a range of TLBentries in the specified

 *                     address space.

 *                     - mm    - mm_struct describing address space

 *                     - start - start address(may not be aligned)

 *                     - end    - end address (exclusive, may not bealigned)

 *

 *         flush_tlb_page(vaddr,vma)

 *

 *                     Invalidate the specifiedpage in the specified address range.

 *                     - vaddr - virtual address(may not be aligned)

 *                     - vma   - vma_struct describing address range

 *

 *         flush_kern_tlb_page(kaddr)

 *

 *                     Invalidate the TLB entryfor the specified page.  The address

 *                     will be in the kernelsvirtual memory space.  Current uses

 *                     only require the D-TLB tobe invalidated.

 *                     - kaddr - Kernel virtualmemory address

 */

 

 

 

#ifdef MULTI_TLB

 

#define __cpu_flush_user_tlb_range  cpu_tlb.flush_user_range

#define __cpu_flush_kern_tlb_range cpu_tlb.flush_kern_range

#define __cpu_tlb_flags                                 cpu_tlb.tlb_flags

 

/*

 *Convert calls to our calling convention.

 */

#define local_flush_tlb_range(vma,start,end)            __cpu_flush_user_tlb_range(start,end,vma)

#define local_flush_tlb_kernel_range(s,e)                  __cpu_flush_kern_tlb_range(s,e)

 

2.体系结构指针的传递 

setup_processor函数中,体系相关的指针传递,通过 lookup_processor_type函数. 

static void __init setup_processor(void)

{

            structproc_info_list *list;

 

            /*

             * locate processor in the list of supportedprocessor

             * types. The linker builds this table for us from the

             * entries in arch/arm/mm/proc-*.S

             */

            list= lookup_processor_type(read_cpuid_id());

 

#ifdef MULTI_CPU

            processor= *list->proc;

#endif

#ifdef MULTI_TLB

            cpu_tlb= *list->tlb;

#endif

#ifdef MULTI_USER

            cpu_user= *list->user;

#endif

#ifdef MULTI_CACHE

            cpu_cache= *list->cache;

#endif

            ...

}

 

例如, ARM926EJS

 

            .type    __arm926_proc_info,#object

__arm926_proc_info:

            .long    0x41069260                            @ARM926EJ-S (v5TEJ)

            .long    0xff0ffff0

            .long   PMD_TYPE_SECT | \

                        PMD_SECT_BUFFERABLE| \

                        PMD_SECT_CACHEABLE| \

                        PMD_BIT4| \

                        PMD_SECT_AP_WRITE| \

                        PMD_SECT_AP_READ

            .long   PMD_TYPE_SECT | \

                        PMD_BIT4| \

                        PMD_SECT_AP_WRITE| \

                        PMD_SECT_AP_READ

            b          __arm926_setup

            .long    cpu_arch_name

            .long    cpu_elf_name

            .long       HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA

            .long    cpu_arm926_name

            .long    arm926_processor_functions

            .long    v4wbi_tlb_fns

            .long    v4wb_user_fns

            .long    arm926_cache_fns

            .size     __arm926_proc_info, . - __arm926_proc_info

 

 

3.cpu_tlb & cpu_cache

           

            focuson cpu_tlb & cpu_cache.

           

            a)cpu_cache

 

For example,

            unmap_single--- __cpuc_flush_dcache_area, 期间不需要刷新TLB.

           

           

            __cpuc_flush_dcache_area(ptr,size);-----  arm926_flush_kern_dcache_area

 

 

           

            ps:

            //设置Dcache的无效位,使得主存储有效,则缓冲行无效,会从主存中获取数据

            其中lr为返回寄存器,所以最后要返回时,mov pc,lr

            r0为参数ptr,r1为参数size

 

            Thesource code:

ENTRY(arm926_flush_kern_dcache_area)

            add      r1, r0, r1

1:         mcr      p15, 0, r0, c7, c14, 1               @ clean+invalidate D entry

            add      r0, r0, #CACHE_DLINESIZE

            cmp     r0, r1

            blo       1b

            mov     r0, #0

            mcr      p15, 0, r0, c7, c5, 0                 @ invalidate I cache

            mcr      p15, 0, r0, c7, c10, 4               @ drain WB

            mov     pc,lr                                      @return unmap_single !

 

 

 

            Ps:

            if set invalidate, it mean the phymemory will flush data to cache

            if set validate,it mean the cachewill flush data to phy memory when write-back

 

for example,

 

/*

 *         flush_kern_cache_all()

 *

 *         Cleanand invalidate the entire cache.

 */

ENTRY(arm926_flush_kern_cache_all)

            mov     r2,#VM_EXEC

            mov     ip,#0

__flush_whole_cache:

#ifdefCONFIG_CPU_DCACHE_WRITETHROUGH

            mcr      p15,0, ip, c7, c6, 0                  @invalidate D cache

#else

1:         mrc      p15,0, r15, c7, c14, 3             @test,clean,invalidate

            bne      1b

#endif

            tst        r2,#VM_EXEC

            mcrne  p15, 0, ip, c7, c5, 0                  @invalidate I cache

            mcrne  p15, 0, ip, c7, c10, 4                @drain WB

            mov     pc,lr

            b)cpu_tlb

            这里使用的tlb  v4wbi的,具体的可以见ARM926EJ-s的说明

for example,   

            #definelocal_flush_tlb_kernel_range(s,e)      __cpu_flush_kern_tlb_range(s,e)

static inline voidipi_flush_tlb_kernel_range(void *arg)

{

            struct tlb_args *ta = (structtlb_args *)arg;

 

            local_flush_tlb_kernel_range(ta->ta_start,ta->ta_end);

}

            lr=ipi_flush_tlb_kernel_range

            r0=ta->ta_start

            r1=ta->ta_end

           

            local_flush_tlb_kernel_page is justa special case of  local_flush_tlb_kernel_range.

            The end=start+pagesize

 

            个人观点:

            From the source code ,we can seethat the flush_kernel_tlb_range is the same as flush_dcache_area (just set theinvalidate I TLB flag & D TLB flag), what is more , when we flush kerneltlb range ,we should write back the data to avoid the TLB can't hit successfully.

            The function is important whencontext_swith & SMP arch.

 

            The source code:

ENTRY(v4wbi_flush_kern_tlb_range)

            mov     r3,#0

            mcr      p15,0, r3, c7, c10, 4               @ drain WB

            bic       r0,r0, #0x0ff

            bic       r0,r0, #0xf00

1:         mcr      p15,0, r0, c8, c5, 1                 @invalidate I TLB entry

            mcr      p15,0, r0, c8, c6, 1                 @invalidate D TLB entry

            add      r0,r0, #PAGE_SZ

            cmp     r0,r1

            blo       1b

            mov     pc,lr

 

 

 4.    什么时候需要刷新tlb呢?

           个人观点:

            1.First, flush_tlb whencontext_switch

            2.SMP arch flush_tlb_kernel_range(if need boardcast)

            3.dma_remap/__dma_free_remap

            4.unmap_area_sections it hasrelative with !CONFIG_SMP

 

#if!defined(CONFIG_SMP) && !defined(CONFIG_ARM_LPAE)

/*

 * Section support is unsafe on SMP - If youiounmap and ioremap a region,

 * the other CPUs will not see this changeuntil their next context switch.

 * Meanwhile, (eg) if an interrupt comes in onone of those other CPUs

 * which requires the new ioremap'd region tobe referenced, the CPU will

 * reference the _old_ region.

 *

 * Note that get_vm_area_caller() allocates aguard 4K page, so we need to

 * mask the size back to 1MB aligned or we willoverflow in the loop below.

 */

static voidunmap_area_sections(unsigned long virt, unsigned long size)

           

            5. unmap_kernel_range

/**

 * unmap_kernel_range - unmap kernel VM areaand flush cache and TLB

 * @addr: start of the VM area to unmap

 * @size: size of the VM area to unmap

 *

 * Similar to unmap_kernel_range_noflush() butflushes vcache before

 * the unmapping and tlb after.

 */

voidunmap_kernel_range(unsigned long addr, unsigned long size)


  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值