精华内容
下载资源
问答
  • TLB
    千次阅读
    2022-04-14 10:49:49

    MMU和TLB

    MMU

    虚拟存储器的基本思想是程序,数据,堆栈的总的大小可以超过物理存储器的大小,操作系统把当前使用的部分保留在内存中,而把其他未被使用的部分保存在磁盘上。

    • 比如对一个16MB的程序和一个内存只有4MB的机器,OS通过选择,可以决定各个时刻将哪4M的内容保留在内存中,并在需要时在内存和磁盘间交换程序片段,这样就可以把这个16M的程序运行在一个只具有4M内存机器上了。
    • 而这个16M的程序在运行前不必由程序员进行分割。
    • 一个物理存储块(通常为一个页框)被多个逻辑页映射。

    伴随着这种技术的出现,“virtual address,即VA”和“physical address, 即PA”也就出现了。一般来说,CPU看到的地址是VA,VA是有地址线来决定的。

    • 比如,s3c2410是32位的SoC,那么它的寻址空间为 2^32=4GB,那么VA空间也就是4GB。

    既然PA没有VA那么大,而且CPU只能看到 VA,那么CPU如何找到PA呢?**这也正是MMU的基本作用之一,就是提供VA到PA的转换机制,除了硬件的支持外,软件上实际就是维护一张表,表中的内 容是VA到PA的转换法则。**由于有了MMU,那么就可以实现利用VA找到实际物理内存区域。具体的转换方法将在以后介绍。

    总而言之,CPU认为的内存空间是由其地址线决定的,但是我们真实的内存空间可能会发生与认为的不匹配,所以我们就引入了虚拟内存,虚拟内存就需要MMU来进行映射。

    什么是MMU

    MMU是 Memory Management Unit 的缩写即,内存管理单元. 针对各种CPU, MMU是个可选的配件. MMU负责的是虚拟地址与物理地址的转换. 提供硬件机制的内存访问授权.(现代 CPU 的应用中,基本上都选择了使用 MMU)

    现代的多用户多进程操作系统, 需要MMU, 才能达到每个用户进程都拥有自己的独立的地址空间的目标. 使用MMU, OS划分出一段地址区域,在这块地址区域中, 每个进程看到的内容都不一定一样.

    例如MICROSOFT WINDOWS操作系统, 地址4M-2G处划分为用户地址空间. 进程A在地址 0X400000映射了可执行文件. 进程B同样在地址 0X400000映射了可执行文件. 如果A进程读地址0X400000, 读到的是A的可执行文件映射到RAM的内容. 而进程B读取地址0X400000时则读到的是B的可执行文件映射到RAM的内容.

    MMU 作用

    MMU 的作用:

    1. 将虚拟地址翻译成为物理地址,然后访问实际的物理地址
    2. 访问权限控制

    MMU 工作过程

    参考了这篇

    一些概念

    • MMU 进行虚拟地址转换成为物理地址的过程是 MMU 工作的核心
    • 大多数使用虚拟存储器的系统都使用一种称为分页(paging)
    • 虚拟地址空间划分成称为**页(page)**的单位;
    • 而相应的物理地址空间也被进行划分,单位是页框-也可以说是块(frame),这里的块就是计组里面的内存划分块了。
    • 页和页框的大小必须相同。

    工作过程

    我们已经知道,大多数使用虚拟存储器的系统都使用一种称为分页(paging)的技术。

    虚拟地址空间被分成大小相同的一组页,每个页有一个用来标示它的页号(这个页号一般是它在该组中的索引,这点和C/C++中的数组相似)。

    分配页号:我们可以假设04K的页号为0,48K的页号为1,8~12K的页号为2,以此类推。

    而虚拟地址(注意:是一个确定的地址,不是一个空间)被MMU分为2个部分

    • 第一部分是页号索引(page Index)
    • 第二部分则是相对该页首地址的偏移量(offset)

    使用虚拟地址中的虚页号查询页表得到对应的物理页号,然后与虚拟地址中的页内位移组成物理地址。

    内存保护

    MMU除了具有地址翻译的功能外,还提供了内存保护功能。

    采用页式内存管理时可以提供页粒度级别的保护,允许对单一内存页设置某一类用户的读、写、执行权限.

    比如:一个页中存储代码,并且该代码不允许在用户模式下执行,那么可以设置该页的保护属性,这样当处理器在用户模式下要求执行该页的代码时,MMU会检测到并触发异常,从而实现对代码的保护。

    TLB

    参考

    页表以数据结构的方式存放在DDR(DDR是一个内存名称,意思即双倍速率同步动态随机存储器,是内存的其中一种)中,假如cpu每做一次地址访问,都要去DDR里去取出1级描述符,然后解析1级描述符,再去查找2级描述符,解析2级描述符,加上偏移得到物理地址,那真是太慢了。

    MMU的cache-TLB

    TLB的全称是Translation Lookaside Buffer,我们知道,处理器在取指或者执行访问memory指令的时候都需要进行地址翻译,即把虚拟地址翻译成物理地址。而地址翻译是一个漫长的过程,需要遍历几个level的Translation table,从而产生严重的开销。为了提高性能,我们会在MMU中增加一个TLB的单元,把地址翻译关系保存在这个高速缓存中,从而省略了对内存中页表的访问

    TLB也是一种cache,有cache也就意味着数据有多个copy,因此存在一致性(coherence)问题。和数据、指令cache或者unified cache不同的是,硬件并不维护TLB的coherence,一旦软件修改了page table,那么软件也需要进行TLB invalidate操作,从而维护了TLB一致性。

    快速上下文切换FSCE

    对于MMU如何识别进程的切换,几乎所有的参考资料上(包括ARM体系结构)均讲解的是快速上下文切换的方法,但其实操作系统并没有使用

    **快速上下文切换扩展FCSE(Fast Context Switch Extension),是MMU中的一个附加硬件,用于提高ARM嵌入式系统的系统性能。FCSE使得多个独立的任务可以运行在一个固定的重叠存储空间中,而上下文切换时又不需要清理或清除cache,或TLB。**如果没有FCSE,则从一个任务切换到另一个任务需要改变虚拟存储映射。

    如果涉及两个有重叠地址的任务,则保存在cache和TLB中的信息将变为无效,这样系统就必须清除cache和TLB中的无效数据。清除这些模块的过程使任务切换增加了很多时间,因为内核不仅要清除cache和TLB中的无效数据,还要从主存中装载新的数据到cache和TLB。

    使用FCSE,虚拟存储管理增加了一次地址转换。FCSE在虚拟地址到达cache和TLB前,使用一个特殊的,包含进程ID值的重定位寄存器来修改虚拟地址。把修改之前的虚存地址称为VA(Virtual Address),把第一次转换之后的地址称为修改后虚地址MVA(Modified Virtual Address)

    这样,任务间的切换就不用涉及到改变页表,只需简单的将新任务的进程ID写到位于CP15的FCSE进程ID寄存器。为了利用FCSE,编译链接所有的任务,使他们都运行在虚存的第一个32MB块空间,为每个任务分配一个进程ID;然后通过下边的公式,将每个任务放置在修改后虚存的不同32MB分区中:MVA = VA +(0x2000000 * 进程ID)

    FSCE的一个主要限制是您最多有128个进程,并且每个进程的大小被限制为2^25或32MB才能利用它。它还消耗大量的虚拟地址空间。这就是为什么它从未被引入Linux主线的原因。尽管许多嵌入式Linux设备可能受益,但它不像ASID那样通用,ASID支持256个进程,没有大小限制,也不消耗虚拟地址空间。

    Global TLB和non-global

    现代OS都将地址空间分为内核空间和用户空间。内核空间特权态访问,用户空间用户态访问。

    内核空间内容基本各个进程(包括内核线程)都差不多,内核地址空间是一样的,因此对于这部分地址翻译,无论进程如何切换,内核地址空间转换到物理地址的关系是永远不变的,在进程的时候,不需要清掉。

    对于用户空间,各个进程的内容都不太一样,保留只会造成混乱,需要清掉。在这种思路引导下,**CPU在切换进程的时候,只会清掉不带Global标志的用户空间页表TLB,而不会动带有global标志的内核页表项。**一个新的进程会开始一个半新的TLB,效能提高不少。

    TLB失效

    TLB的原理就是将映射到物理页框的虚拟页面的映射关系缓存在MMU中的硬件缓冲当中,每次MMU拿到一个虚拟地址后,都根据地址分析出虚拟页面号,然后查找TLB中有没有该页面的项目,有的话则取出其中的物理页框号,加上页内地址获得实际的物理地址送到地址总线上去访问内存,没有该页面的项目的话,即TLB失效的情况下,有两种方式去处理:

    1.硬件处理TLB失效

    即发生虚拟页号在TLB中找不到的情况,有两个原因:

    1. 该页面没有被加载到内存:这种情况就是缺页错误,MMU到进程的页表上查找发现页面没有在内存中,需要引发缺页中断,然后调用操作系统的一个汇编函数,由操作系统根据寄存器中的地址或者最后一条运行的指令拿到发生缺页错误的虚拟地址,根据虚拟页面号从磁盘中将页面加载到内存(当中可能涉及页面交换,由于内存空余页面不足,需要将内存中不常访问使用的页面换出到磁盘上的交换空间),然后更新发生缺页的进程的页表,将该虚拟页号和物理页框号的映射写到页表中,接着返回到CPU上再次执行最后一条指令,此时TLB上仍然没有该虚拟页面的表项,因此,由MMU到进程的页表中查找,找到该页面号的映射关系,取出该映射关系,加入到TLB中(如果TLB的表项中没有空余的空间,则牺牲一条已有表项)中,最后取出表项中的物理页框号,组成物理地址访问物理内存。
    2. 页面已被加载到内存,只是TLB中没有对应的映射关系:这种情况大部分是由于该页面的映射关系在TLB之前新增表项的时候被牺牲了,因此只要MMU在去进程的页面中重新加载该映射关系既可。

    2.软件处理TLB失效

    软件处理TLB失效和硬件处理方式雷同,最大的区别在于查找进程页表中的虚拟页面的映射关系以及添加映射关系到TLB表的操作由操作系统来完成。

    更多相关内容
  • tlb使用说明_TlB_VB_

    2021-10-02 12:20:02
    tlb全名叫“Type Library”,是COM技术中所谓的“类型库”,它的作用在于以一种和具体语言无关的方式说明COM组件中接口的定义。类型库文件以".tlb"为扩展名,使用tlb之前必须先注册。
  • TLB注册工具

    2020-10-20 04:30:14
    com组件开发在开发过程中可能会出现问题,除了对com组件使用regsvr32 进行注册外,还需要对tlb进行注册,tlb相比之下又是一项比较繁琐的过程 此工具可以对com组件tlb文件方便的注册
  • Win32Api.tlb是一个静态库文件,集合了很多API函数,以后VB不用声明API函数了,向内部函数一样使用API函数
  • tlb文件注册工具

    2019-05-25 02:32:16
    NULL 博文链接:https://strawhat.iteye.com/blog/473164
  • com组件开发在开发过程中可能会出现问题,除了对com组件使用regsvr32 进行注册外,还需要对tlb进行注册,tlb相比之下又是一项比较繁琐的过程 此工具可以对com组件tlb文件方便的注册
  • Accessible.tlb

    2021-01-14 23:24:01
    Accessible.tlb
  • 内存层次模拟器 模拟多级缓存、TLB、页表、主存、磁盘不同写策略的操作,保证一致性。 LRU 是替换算法。
  • 进程切换是一个复杂的过程,本文不准备详细描述整个进程切换的方方面面,而是关注进程切换中一个小小的知识点:TLB的处理。为了能够讲清楚这个问题,我们在第二章描述在单CPU场景下一些和TLB相关的细节,第三章推进...
  • Sample code on the Delphi for FingerPrint device with USB port connection
  • 2007每次打开excel都出现一个stdole32.tlb的解决方法。。。
  • TLB原理

    2022-05-04 22:17:34
    Cache为了更快的访问main memory中的数据和指令,而TLB是为了更快的进行地址翻译而将部分的页表内容缓存到了Translation lookasid buffer中,避免了从main memory访问页表的过程。 假如不做任何的处理,那么在进程A...

    二、单核场景的工作原理

    1、block diagram

    我们先看看在单核场景下,和进程切换相关的逻辑block示意图:

    CPU上运行了若干的用户空间的进程和内核线程,为了加快性能,CPU中往往设计了TLB和Cache这样的HW block。Cache为了更快的访问main memory中的数据和指令,而TLB是为了更快的进行地址翻译而将部分的页表内容缓存到了Translation lookasid buffer中,避免了从main memory访问页表的过程。

    假如不做任何的处理,那么在进程A切换到进程B的时候,TLB和Cache中同时存在了A和B进程的数据。对于kernel space其实无所谓,因为所有的进程都是共享的,但是对于A和B进程,它们各种有自己的独立的用户地址空间,也就是说,同样的一个虚拟地址X,在A的地址空间中可以被翻译成Pa,而在B地址空间中会被翻译成Pb,如果在地址翻译过程中,TLB中同时存在A和B进程的数据,那么旧的A地址空间的缓存项会影响B进程地址空间的翻译,因此,在进程切换的时候,需要有tlb的操作,以便清除旧进程的影响,具体怎样做呢?我们下面一一讨论。

    2、绝对没有问题,但是性能不佳的方案

    当系统发生进程切换,从进程A切换到进程B,从而导致地址空间也从A切换到B,这时候,我们可以认为在A进程执行过程中,所有TLB和Cache的数据都是for A进程的,一旦切换到B,整个地址空间都不一样了,因此需要全部flush掉(注意:我这里使用了linux内核的术语,flush就是意味着将TLB或者cache中的条目设置为无效,对于一个ARM平台上的嵌入式工程师,一般我们会更习惯使用invalidate这个术语,不管怎样,在本文中,flush等于invalidate)。

    这种方案当然没有问题,当进程B被切入执行的时候,其面对的CPU是一个干干净净,从头开始的硬件环境,TLB和Cache中不会有任何的残留的A进程的数据来影响当前B进程的执行。当然,稍微有一点遗憾的就是在B进程开始执行的时候,TLB和Cache都是冰冷的(空空如也),因此,B进程刚开始执行的时候,TLB miss和Cache miss都非常严重,从而导致了性能的下降。

    3、如何提高TLB的性能?

    对一个模块的优化往往需要对该模块的特性进行更细致的分析、归类,上一节,我们采用进程地址空间这样的术语,其实它可以被进一步细分为内核地址空间和用户地址空间。对于所有的进程(包括内核线程),内核地址空间是一样的,因此对于这部分地址翻译,无论进程如何切换,内核地址空间转换到物理地址的关系是永远不变的,其实在进程A切换到B的时候,不需要flush掉,因为B进程也可以继续使用这部分的TLB内容(上图中,橘色的block)。对于用户地址空间,各个进程都有自己独立的地址空间,在进程A切换到B的时候,TLB中的和A进程相关的entry(上图中,青色的block)对于B是完全没有任何意义的,需要flush掉。

    在这样的思路指导下,我们其实需要区分global和local(其实就是process-specific的意思)这两种类型的地址翻译,因此,在页表描述符中往往有一个bit来标识该地址翻译是global还是local的,同样的,在TLB中,这个标识global还是local的flag也会被缓存起来。有了这样的设计之后,我们可以根据不同的场景而flush all或者只是flush local tlb entry。

    4、特殊情况的考量

    我们考虑下面的场景:进程A切换到内核线程K之后,其实地址空间根本没有必要切换,线程K能访问的就是内核空间的那些地址,而这些地址也是和进程A共享的。既然没有切换地址空间,那么也就不需要flush 那些进程特定的tlb entry了,当从K切换会A进程后,那么所有TLB的数据都是有效的,从大大降低了tlb miss。此外,对于多线程环境,切换可能发生在一个进程中的两个线程,这时候,线程在同样的地址空间,也根本不需要flush tlb。

    4、进一步提升TLB的性能

    还有可能进一步提升TLB的性能吗?有没有可能根本不flush TLB?

    当然可以,不过这需要我们在设计TLB block的时候需要识别process specific的tlb entry,也就是说,TLB block需要感知到各个进程的地址空间。为了完成这样的设计,我们需要标识不同的address space,这里有一个术语叫做ASID(address space ID)。原来TLB查找是通过虚拟地址VA来判断是否TLB hit。有了ASID的支持后,TLB hit的判断标准修改为(虚拟地址+ASID),ASID是每一个进程分配一个,标识自己的进程地址空间。TLB block如何知道一个tlb entry的ASID呢?一般会来自CPU的系统寄存器(对于ARM64平台,它来自TTBRx_EL1寄存器),这样在TLB block在缓存(VA-PA-Global flag)的同时,也就把当前的ASID缓存在了对应的TLB entry中,这样一个TLB entry中包括了(VA-PA-Global flag-ASID)。

    有了ASID的支持后,A进程切换到B进程再也不需要flush tlb了,因为A进程执行时候缓存在TLB中的残留A地址空间相关的entry不会影响到B进程,虽然A和B可能有相同的VA,但是ASID保证了硬件可以区分A和B进程地址空间。

    三、多核的TLB操作

    1、block diagram

    完成单核场景下的分析之后,我们一起来看看多核的情况。进程切换相关的TLB逻辑block示意图如下:

    在多核系统中,进程切换的时候,TLB的操作要复杂一些,主要原因有两点:其一是各个cpu core有各自的TLB,因此TLB的操作可以分成两类,一类是flush all,即将所有cpu core上的tlb flush掉,还有一类操作是flush local tlb,即仅仅flush本cpu core的tlb。另外一个原因是进程可以调度到任何一个cpu core上执行(当然具体和cpu affinity的设定相关),从而导致task处处留情(在各个cpu上留有残余的tlb entry)。

    2、TLB操作的基本思考

    根据上一节的描述,我们了解到地址翻译有global(各个进程共享)和local(进程特定的)的概念,因而tlb entry也有global和local的区分。如果不区分这两个概念,那么进程切换的时候,直接flush该cpu上的所有残余。这样,当进程A切出的时候,留给下一个进程B一个清爽的tlb,而当进程A在其他cpu上再次调度的时候,它面临的也是一个全空的TLB(其他cpu的tlb不会影响)。当然,如果区分global 和local,那么tlb操作也基本类似,只不过进程切换的时候,不是flush该cpu上的所有tlb entry,而是flush所有的tlb local entry就OK了。

    对local tlb entry还可以进一步细分,那就是了ASID(address space ID)或者PCID(process context ID)的概念了(global tlb entry不区分ASID)。如果支持ASID(或者PCID)的话,tlb操作变得简单一些,或者说我们没有必要执行tlb操作了,因为在TLB搜索的时候已经可以区分各个task上下文了,这样,各个cpu中残留的tlb不会影响其他任务的执行。在单核系统中,这样的操作可以获取很好的性能。比如A---B--->A这样的场景中,如果TLB足够大,可以容纳2个task的tlb entry(现代cpu一般也可以做到这一点),那么A再次切回的时候,TLB是hot的,大大提升了性能。

    不过,对于多核系统,这种情况有一点点的麻烦,其实也就是传说中的TLB shootdown带来的性能问题。在多核系统中,如果cpu支持PCID并且在进程切换的时候不flush tlb,那么系统中各个cpu中的tlb entry则保留各种task的tlb entry,当在某个cpu上,一个进程被销毁,或者修改了自己的页表(也就是修改了VA PA映射关系)的时候,我们必须将该task的相关tlb entry从系统中清除出去。这时候,你不仅仅需要flush本cpu上对应的TLB entry,还需要shootdown其他cpu上的和该task相关的tlb残余。而这个动作一般是通过IPI实现(例如X86),从而引入了开销。此外PCID的分配和管理也会带来额外的开销,因此,OS是否支持PCID(或者ASID)是由各个arch代码自己决定(对于linux而言,x86不支持,而ARM平台是支持的)。

    四、进程切换中的tlb操作代码分析

    1、tlb lazy mode

    在context_switch中有这样的一段代码:

    if (!mm) {
        next->active_mm = oldmm;
        atomic_inc(&oldmm->mm_count);
        enter_lazy_tlb(oldmm, next);
    } else
        switch_mm(oldmm, mm, next);

    这段代码的意思就是如果要切入的next task是一个内核线程(next->mm == NULL )的话,那么可以通过enter_lazy_tlb函数标记本cpu上的next task进入lazy TLB mode。由于ARM64平台上的enter_lazy_tlb函数是空函数,因此我们采用X86来描述lazy TLB mode。

    当然,我们需要一些准备工作,毕竟对于熟悉ARM平台的嵌入式工程师而言,x86多少有点陌生。

    到目前,我们还都是从逻辑角度来描述TLB操作,但是在实际中,进程切换中的tlb操作是HW完成还是SW完成呢?不同的处理器思路是不一样的(具体原因未知),有的处理器是HW完成,例如X86,在加载cr3寄存器进行地址空间切换的时候,hw会自动操作tlb。而有的处理是需要软件参与完成tlb操作,例如ARM系列的处理器,在切换TTBR寄存器的时候,HW没有tlb动作,需要SW完成tlb操作。因此,x86平台上,在进程切换的时候,软件不需要显示的调用tlb flush函数,在switch_mm函数中会用next task中的mm->pgd加载CR3寄存器,这时候load cr3的动作会导致本cpu中的local tlb entry被全部flush掉。

    在x86支持PCID(X86术语,相当与ARM的ASID)的情况下会怎样呢?也会在load cr3的时候flush掉所有的本地CPU上的 local tlb entry吗?其实在linux中,由于TLB shootdown,普通的linux并不支持PCID(KVM中会使用,但是不在本文考虑范围内),因此,对于x86的进程地址空间切换,它就是会有flush local tlb entry这样的side effect。

    另外有一点是ARM64和x86不同的地方:ARM64支持在一个cpu core执行tlb flush的指令,例如tlbi vmalle1is,将inner shareablity domain中的所有cpu core的tlb全部flush掉。而x86不能,如果想要flush掉系统中多有cpu core的tlb,只能是通过IPI通知到其他cpu进行处理。

    好的,至此,所有预备知识都已经ready了,我们进入tlb lazy mode这个主题。虽然进程切换伴随tlb flush操作,但是某些场景亦可避免。在下面的场景,我们可以不flush tlb(我们仍然采用A--->B task的场景来描述):

    (1)如果要切入的next task B是内核线程,那么我们也暂时不需要flush TLB,因为内核线程不会访问usersapce,而那些进程A残留的TLB entry也不会影响内核线程的执行,毕竟B没有自己的用户地址空间,而且和A共享内核地址空间。

    (2)如果A和B在一个地址空间中(一个进程中的两个线程),那么我们也暂时不需要flush TLB。

    除了进程切换,还有其他的TLB flush场景。我们先看一个通用的TLB flush场景,如下图所示:

    一个4核系统中,A0 A1和A2 task属于同一个进程地址空间,CPU_0和CPU_2上分别运行了A0和A2 task,CPU_1有点特殊,它正在运行一个内核线程,但是该内核线程正在借用A1 task的地址空间,CPU_3上运行不相关的B task。

    当A0 task修改了自己的地址翻译,那么它不能只是flush CPU_0的tlb,还需要通知到CPU_1和CPU_2,因为这两个CPU上当前active的地址空间和CPU_0是一样的。由于A1 task的修改,CPU_1和CPU_2上的这些缓存的TLB entry已经失效了,需要flush。同理,可以推广到更多的CPU上,也就是说,在某个CPUx上运行的task修改了地址映射关系,那么tlb flush需要传递到所有相关的CPU中(当前的mm等于CPUx的current mm)。在多核系统中,这样的通过IPI来传递TLB flush的消息会随着cpu core的增加而增加,有没有办法减少那些没有必要的TLB flush呢?当然有,也就是上图中的A1 task场景,这也就是传说中的lazy tlb mode。

    我先回头看看代码。在代码中,如果next task是内核线程,我们并不会执行switch_mm(该函数会引起tlb flush的动作),而是调用enter_lazy_tlb进入lazy tlb mode。在x86架构下,代码如下:

    static inline void enter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk)
    {
    #ifdef CONFIG_SMP
        if (this_cpu_read(cpu_tlbstate.state) == TLBSTATE_OK)
            this_cpu_write(cpu_tlbstate.state, TLBSTATE_LAZY);
    #endif
    }

    在x86架构下,进入lazy tlb mode也就是在该cpu的cpu_tlbstate变量中设定TLBSTATE_LAZY的状态就OK了。因此,进入lazy mode的时候,也就不需要调用switch_mm来切换进程地址空间,也就不会执行flush tlb这样毫无意义的动作了。 enter_lazy_tlb并不操作硬件,只要记录该cpu的软件状态就OK了。

    切换之后,内核线程进入执行状态,CPU_1的TLB残留进程A的entry,这对于内核线程的执行没有影响,但是当其他CPU发送IPI要求flush TLB的时候呢?按理说应该立刻flush tlb,但是在lazy tlb mode下,我们可以不执行flush tlb操作。这样问题来了:什么时候flush掉残留的A进程的tlb entry呢?答案是在下一次进程切换中。因为一旦内核线程被schedule out,并且切入一个新的进程C,那么在switch_mm,切入到C进程地址空间的时候,所有之前的残留都会被清除掉(因为有load cr3的动作)。因此,在执行内核线程的时候,我们可以推迟tlb invalidate的请求。也就是说,当收到ipi中断要求进行该mm的tlb invalidate的动作的时候,我们暂时没有必要执行了,只需要记录状态就OK了。

    2、ARM64中如何管理ASID?

    和x86不同的是:ARM64支持了ASID(类似x86的PCID),难道ARM64解决了TLB Shootdown的问题?其实我也在思考这个问题,但是还没有想明白。很显然,在ARM64中,我们不需要通过IPI来进行所有cpu core的TLB flush动作,ARM64在指令集层面支持shareable domain中所有PEs上的TLB flush动作,也许是这样的指令让TLB flush的开销也没有那么大,那么就可以选择支持ASID,在进程切换的时候不需要进行任何的TLB操作,同时,由于不需要IPI来传递TLB flush,那么也就没有特别的处理lazy tlb mode了。

    既然linux中,ARM64选择支持ASID,那么它就要直面ASID的分配和管理问题了。硬件支持的ASID有一定限制,它的编址空间是8个或者16个bit,最大256或者65535个ID。当ASID溢出之后如何处理呢?这就需要一些软件的控制来协调处理。 我们用硬件支持上限为256个ASID的情景来描述这个基本的思路:当系统中各个cpu的TLB中的asid合起来不大于256个的时候,系统正常运行,一旦超过256的上限后,我们将全部TLB flush掉,并重新分配ASID,每达到256上限,都需要flush tlb并重新分配HW ASID。具体分配ASID代码如下:

    static u64 new_context(struct mm_struct *mm, unsigned int cpu)
    {
        static u32 cur_idx = 1;
        u64 asid = atomic64_read(&mm->context.id);
        u64 generation = atomic64_read(&asid_generation);

        if (asid != 0) {-------------------------(1)
            u64 newasid = generation | (asid & ~ASID_MASK); 
            if (check_update_reserved_asid(asid, newasid))
                return newasid; 
            asid &= ~ASID_MASK;
            if (!__test_and_set_bit(asid, asid_map))
                return newasid;
        }


        asid = find_next_zero_bit(asid_map, NUM_USER_ASIDS, cur_idx);---(2)
        if (asid != NUM_USER_ASIDS)
            goto set_asid;

        generation = atomic64_add_return_relaxed(ASID_FIRST_VERSION,----(3)
                             &asid_generation);
        flush_context(cpu);

        asid = find_next_zero_bit(asid_map, NUM_USER_ASIDS, 1); ------(4)

    set_asid:
        __set_bit(asid, asid_map);
        cur_idx = asid;
        return asid | generation;
    }

    (1)在创建新的进程的时候会分配一个新的mm,其software asid(mm->context.id)初始化为0。如果asid不等于0那么说明这个mm之前就已经分配过software asid(generation+hw asid)了,那么new context不过就是将software asid中的旧的generation更新为当前的generation而已。

    (2)如果asid等于0,说明我们的确是需要分配一个新的HW asid,这时候首先要找一个空闲的HW asid,如果能够找到(jump to set_asid),那么直接返回software asid(当前generation+新分配的hw asid)。

    (3)如果找不到一个空闲的HW asid,说明HW asid已经用光了,这是只能提升generation了。这时候,多有cpu上的所有的old generation需要被flush掉,因为系统已经准备进入new generation了。顺便一提的是这里generation变量已经被赋值为new generation了。

    (4)在flush_context函数中,控制HW asid的asid_map已经被全部清零了,因此,这里进行的是new generation中HW asid的分配。

    3、进程切换过程中ARM64的tlb操作以及ASID的处理

    代码位于arch/arm64/mm/context.c中的check_and_switch_context:

    void check_and_switch_context(struct mm_struct *mm, unsigned int cpu)
    {
        unsigned long flags;
        u64 asid;

        asid = atomic64_read(&mm->context.id); -------------(1)

        if (!((asid ^ atomic64_read(&asid_generation)) >> asid_bits) ------(2)
            && atomic64_xchg_relaxed(&per_cpu(active_asids, cpu), asid))
            goto switch_mm_fastpath;

        raw_spin_lock_irqsave(&cpu_asid_lock, flags); 
        asid = atomic64_read(&mm->context.id);
        if ((asid ^ atomic64_read(&asid_generation)) >> asid_bits) { ------(3)
            asid = new_context(mm, cpu);
            atomic64_set(&mm->context.id, asid);
        }

        if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending)) ------(4)
            local_flush_tlb_all();

        atomic64_set(&per_cpu(active_asids, cpu), asid);
        raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);

    switch_mm_fastpath:
        cpu_switch_mm(mm->pgd, mm);
    }

    看到这些代码的时候,你一定很抓狂:本来期望支持ASID的情况下,进程切换不需要TLB flush的操作了吗?怎么会有那么多代码?呵呵~~实际上理想很美好,现实很骨干,代码中嵌入太多管理asid的内容了。

    (1)现在准备切入mm变量指向的地址空间,首先通过内存描述符获取该地址空间的ID(software asid)。需要说明的是这个ID并不是HW asid,实际上mm->context.id是64个bit,其中低16 bit对应HW 的ASID(ARM64支持8bit或者16bit的ASID,但是这里假设当前系统的ASID是16bit)。其余的bit都是软件扩展的,我们称之generation。

    (2)arm64支持ASID的概念,理论上进程切换不需要TLB的操作,不过由于HW asid的编址空间有限,因此我们扩展了64 bit的software asid,其中一部分对应HW asid,另外一部分被称为asid generation。asid generation从ASID_FIRST_VERSION开始,每当HW asid溢出后,asid generation会累加。asid_bits就是硬件支持的ASID的bit数目,8或者16,通过ID_AA64MMFR0_EL1寄存器可以获得该具体的bit数目。

    当要切入的mm的software asid仍然处于当前这一批次(generation)的ASID的时候,切换中不需要任何的TLB操作,可以直接调用cpu_switch_mm进行地址空间的切换,当然,也会顺便设定active_asids这个percpu变量。

    (3)如果要切入的进程和当前的asid generation不一致,那么说明该地址空间需要一个新的software asid了,更准确的说是需要推进到new generation了。因此这里调用new_context分配一个新的context ID,并设定到mm->context.id中。

    (4)各个cpu在切入新一代的asid空间的时候会调用local_flush_tlb_all将本地tlb flush掉。

    展开全文
  • 龙芯2F系统在TLB替换处理上开销较大,现有软TLB技术从减少TLB重载入异常处理时间方面考虑,利用软TLB来缓存TLB表项,提高替换处理中Cached命中率,减少了重载入异常处理时间。为进一步提高TLB效率,设计采用多页技术...
  • CAPICOM_TLB_delphi_

    2021-10-03 16:08:53
    Create regisro diario
  • Linux缓存之TLB

    2022-07-13 20:56:57
    Linux缓存之TLB

    1. MMU

    CPU 的内存管理单元叫 MMU ,MMU的作用是把虚拟地址转换成物理地址。而 MMU 包括 TLB (TranslationLookaside Buffer ,快速翻译查找表)和一系列 CP0 寄存器。 TLB 负责从虚拟地址到物理地址的转换,是内存中页表的一个子集。

    MMU工作原理如图所示:
    在这里插入图片描述

    2. 页表与 TLB 结构

    在介绍 TLB 结构之前,先介绍页表结构。最简单的页表结构如图所示。
    在这里插入图片描述
    这是一种单级页表格式,虚拟页号一般称为 VPNVirtualPage Number ),而物理页号一般称为 PFN Page Frame Number )或者PPN Physical Page Number ),虚拟页和物理页的页内偏移是相等的,因此页表实际上是负责 VPN PFN 的转换。

    然而单级页表是有缺点的,以32 位系统为例:虚拟地址空间有 4 GB ,考虑到每个进程都有一个页表(内核自身还有一个页表),假设页面大小为 4KB ,每个页表项占用 4B,那么每个进程需要 4MB 的内存来存放页表( 4GB/4KB=1M ,每个进程有 1M 个页表项,每项 4B ,总共占用 4MB );而系统中有成百上千个进程,使用单级页表将会占用大量的内存。

    为了解决这个问题,我们引入了多级页表,下图是两级页表的结构。
    在这里插入图片描述
    它把虚拟页号划分成页目录表索引和页表索引,这样一个进程的“总页表 ”实际上就包括了一个页目录表和若干个页表。还以 32 位系统和4KB 页面为例:如果页目录表索引和页表索引各 10 位,那么页目录表自身只需占用 4KB ,而页目录表中每个有效表项对应一个 4KB 的页表。由于进程实际上不会用到 4GB 的物理内存(也就是说存在很大的虚拟地址空间),因此页目录表里面大部分都是无效项。一个无效的页目录表项不需要与之对应的页表,因此总页表并不会占用太多的内存(就算一个进程占用了 2GB 物理内存,页目录表里也有一半的无效项,因此总页表也大概只需要 2MB )。

    虚拟地址和物理地址的映射关系存储在页表中,而现在页表又是分级的。64位系统一般都是3~5级。常见的配置是4级页表,就以4级页表为例说明。分别是PGDPUDPMDPTE四级页表。在硬件上会有一个叫做页表基地址寄存器,它存储PGD页表的首地址。
    在这里插入图片描述

    MMU就是根据页表基地址寄存器从PGD页表一路查到PTE,最终找到物理地址(PTE页表中存储物理地址)。这就像在地图上显示一个景区,为了找到这个景区的地址,先确定这个景区在中国,再确定是某个省,继续往下某个市,最后找到这个景区是一样的原理。一级一级找下去。

    如果第一次查到这个景区的具体位置,如果记下来这个景区的名字和地址。下次查找时,是不是只需要景区的姓名是什么,就直接能够说出这个景区的地址,而不需要一级一级查找。四级页表查找过程需要四次内存访问。延时可想而知,非常影响性能。页表查找过程的示例如下图所示。以后有机会详细展开,这里了解下即可。
    在这里插入图片描述

    3. TLB本质

    TLB其实就是一块高速缓存。数据cache缓存地址(虚拟地址或者物理地址)和数据。TLB缓存虚拟地址和其映射的物理地址。TLB根据虚拟地址查找cache,它没得选,只能根据虚拟地址查找。所以TLB是一个虚拟高速缓存。

    硬件存在TLB后,虚拟地址到物理地址的转换过程发生了变化。虚拟地址首先发往TLB确认是否命中cache,如果cache hit直接可以得到物理地址。否则,一级一级查找页表获取物理地址。并将虚拟地址和物理地址的映射关系缓存到TLB中。既然TLB是虚拟高速缓存(VIVT),是否存在别名和歧义问题呢?如果存在,软件和硬件是如何配合解决这些问题呢?

    龙芯的 TLB 也是一个表,龙芯架构下 TLB 分为两个部分,一个是所有表项的页大小相同的单一页大小 TLB (SingularPage-Size TLB, 简称 STLB),另一个是支持不同表项的页大小可以不同的多重页大小 TLB (Multiple- Page-SizeTLB,简称 MTLB)。

    页大小与 STLB 所配置的页大小相同的页表项能否进入 MTLB,由实现决定,架构规范中不做限制。

    在虚实地址转换过程中,STLBMTLB 同时查找。相应地,软件需保证不会出现 MTLBSTLB同时命中的情況,否则处理器行为将不可知 MTLB 采用全相联查找表的组织形式,STLB 采用多路组相联的组织形式。对于 STLB, 如果其有 2NDEX组,且配置的页大小为20节,那么硬件查询STLB 的过程中,是将虚地址的[PS+INDEx:PS]位作为素引值来访问各路信息的。

    4. TLB表项

    STLBMTLB 的表项格式基本一致,区别仅在于 MTLB 每个表项均包含页大小信息,而STLB因为是同一页大小所以 TLB 表项中不再需要重复存放页大小信息。对于STLB 来说,其存放的页表项的页大小是由系统软件配置在 CSR.STLBPS 寄存器的PS域。

    每一个TLB 表项的格式如图所示,包含两个部分:比较部分和物理转换部分。
    在这里插入图片描述

    5. TLB的特殊

    虚拟地址映射物理地址的最小单位是4KB。所以TLB其实不需要存储虚拟地址和物理地址的低12位(因为低12位是一样的,根本没必要存储)。另外,我们如果命中cache,肯定是一次性从cache中拿出整个数据。所以虚拟地址不需要offset域。index域是否需要呢?这取决于cache的组织形式。

    如果是全相连高速缓存。那么就不需要index。如果使用多路组相连高速缓存,依然需要index。下图就是一个四路组相连TLB的例子。现如今64位CPU寻址范围并没有扩大到64位。64位地址空间很大,现如今还用不到那么大。因此硬件为了设计简单或者解决成本,实际虚拟地址位数只使用了一部分。这里以48位地址总线为了例说明。
    在这里插入图片描述

    6. TLB的别名问题

    我先来思考第一个问题,别名是否存在。我们知道PIPT的数据cache不存在别名问题。物理地址是唯一的,一个物理地址一定对应一个数据。但是不同的物理地址可能存储相同的数据。也就是说,物理地址对应数据是一对一关系,反过来是多对一关系。

    由于TLB的特殊性,存储的是虚拟地址和物理地址的对应关系。因此,对于单个进程来说,同一时间一个虚拟地址对应一个物理地址,一个物理地址可以被多个虚拟地址映射。将PIPT数据cache类比TLB,我们可以知道TLB不存在别名问题。而VIVT Cache存在别名问题,原因是VA需要转换成PA,PA里面才存储着数据。中间多经传一手,所以引入了些问题。

    7. TLB的歧义问题

    我们知道不同的进程之间看到的虚拟地址范围是一样的,所以多个进程下,不同进程的相同的虚拟地址可以映射不同的物理地址。这就会造成歧义问题。例如,进程A将地址0x2000映射物理地址0x4000。进程B将地址0x2000映射物理地址0x5000。当进程A执行的时候将0x2000对应0x4000的映射关系缓存到TLB中。当切换B进程的时候,B进程访问0x2000的数据,会由于命中TLB从物理地址0x4000取数据。这就造成了歧义。

    如何消除这种歧义,我们可以借鉴VIVT数据cache的处理方式,在进程切换时将整个TLB无效。切换后的进程都不会命中TLB,但是会导致性能损失。

    8. 如何尽可能的避免flush TLB

    首先需要说明的是,这里的flush理解成使无效的意思。我们知道进程切换的时候,为了避免歧义,我们需要主动flush整个TLB。如果我们能够区分不同的进程的TLB表项就可以避免flush TLB。我们知道Linux如何区分不同的进程?每个进程拥有一个独一无二的进程ID。如果TLB在判断是否命中的时候,除了比较tag以外,再额外比较进程ID该多好呢!这样就可以区分不同进程的TLB表项。进程A和B虽然虚拟地址一样,但是进程ID不一样,自然就不会发生进程B命中进程A的TLB表项。所以,TLB添加一项ASID(Address Space ID)的匹配。ASID就类似进程ID一样,用来区分不同进程的TLB表项。这样在进程切换的时候就不需要flush TLB。但是仍然需要软件管理和分配ASID

    在这里插入图片描述

    9. 如何管理ASID

    ASID和进程ID肯定是不一样的,别混淆二者。进程ID取值范围很大。但是ASID一般是8或16 bit。所以只能区分25665536个进程。我们的例子就以8位ASID说明。所以我们不可能将进程IDASID一一对应,我们必须为每个进程分配一个ASID,进程ID和每个进程的ASID一般是不相等的。每创建一个新进程,就为之分配一个新的ASID。当ASID分配完后,flush所有TLB,重新分配ASID。所以,如果想完全避免flush TLB的话,理想情况下,运行的进程数目必须小于等于256。然而事实并非如此,因此管理ASID上需要软硬结合。Linux kernel为了管理每个进程会有个task_struct结构体,我们可以把分配给当前进程的ASID存储在这里。页表基地址寄存器有空闲位也可以用来存储ASID。当进程切换时,可以将页表基地址和ASID(可以从task_struct获得)共同存储在页表基地址寄存器中。当查找TLB时,硬件可以对比tag以及ASID是否相等(对比页表基地址寄存器存储的ASID和TLB表项存储的ASID)。如果都相等,代表TLB hit。否则TLB miss。当TLB miss时,需要多级遍历页表,查找物理地址。然后缓存到TLB中,同时缓存当前的ASID

    10. 多个进程共享

    我们知道内核空间和用户空间是分开的,并且内核空间是所有进程共享。既然内核空间是共享的,进程A切换进程B的时候,如果进程B访问的地址位于内核空间,完全可以使用进程A缓存的TLB。但是现在由于ASID不一样,导致TLB miss

    我们针对内核空间这种全局共享的映射关系称之为global映射。针对每个进程的映射称之为non-global映射。所以,我们在最后一级页表中引入一个bit(non-global (nG) bit)代表是不是global映射。

    当虚拟地址映射物理地址关系缓存到TLB时,将nG bit也存储下来。当判断是否命中TLB时,当比较tag相等时,再判断是不是global映射,如果是的话,直接判断TLB hit,无需比较ASID。当不是global映射时,最后比较ASID判断是否TLB hit
    在这里插入图片描述

    11. 什么时候应该flush TLB

    1. ASID分配完的时候,需要flush全部TLB。ASID的管理可以使用bitmap管理,flush TLB后clear整个bitmap
    2. 当我们建立页表映射的时候,就需要flush虚拟地址对应的TLB表项。第一印象可能是修改页表映射的时候才需要flush TLB,但是实际情况是只要建立映射就需要flush TLB。原因是,建立映射时你并不知道之前是否存在映射。例如,建立虚拟地址A到物理地址B的映射,我们并不知道之前是否存在虚拟地址A到物理地址C的映射情况。所以就统一在建立映射关系的时候flush TLB。

    12. TLB初始化

    龙芯架构允许不实现 TLB 的硬件初始化,让启动阶段的软件通过执行INVTLB r0,r0来完成这一功能

    refer to

    1. 夯实基本功,深入理解 TLB 原理
    展开全文
  • MSTSCLib_TLB.pas

    2020-05-26 11:42:24
    delphi7开发mstsc的远程程序,解决不能使用“仅允许网络级别的身份验证的远程桌面的计算机连接”的问题
  • TLB表现转换

    2022-04-12 09:26:46
    进程切换相关的TLB逻辑block示意图如下 在多核系统中,进程切换的时候,TLB的操作要复杂一些,主要原因有两点:其一是各个cpu core有各自的TLB,因此TLB的操作可以分成两类,一类是flush all,即将所有cpu core上的...

    前一章节,我们学习了分页机制的硬件原理,从虚拟内存地址到物理内存地址的转换,我们通过页表来处理。为了节约页表的内存存储空间,我们会使用多级页表。但是,多级页表虽然节约了我们的存储空间,但是却存在问题:

    原本我们对于只需要进行一次地址转换,只需要访问一次内存就能找到对应的物理页号了,算出物理地址
    现在我们需要多次访问内存,才能找到对应的物理页号。
    最终带来了时间上的开销,变成了一个“以时间换空间”的策略,极大的限制了内存访问性能问题。所以为了解决这种问题导致处理器性能下降的问题,计算机工程师们专门在 CPU 里放了一块缓存芯片,这块缓存芯片我们称之为TLB,全称是地址变换高速缓冲(Translation-Lookaside Buffer)

    1. TLB介绍
    TLB是Translation Lookaside Buffer的简称,可翻译为“地址转换后援缓冲器”,也可简称为“快表”。简单地说,TLB就是页表的Cache,属于MMU的一部分,其中存储了当前最可能被访问到的页表项,其内容是部分页表项的一个副本。处理器在取指或者执行访问memory指令的时候都需要进行地址翻译,即把虚拟地址翻译成物理地址。而地址翻译是一个漫长的过程,需要遍历几个level的Translation table,从而产生严重的开销。为了提高性能,我们会在MMU中增加一个TLB的单元,把地址翻译关系保存在这个高速缓存中,从而省略了对内存中页表的访问。

    在这里插入图片描述

     

    TLB存放了之前已经进行过地址转换的查询结果。这样,当同样的虚拟地址需要进行地址转换的时候,我们可以直接在 TLB 里面查询结果,而不需要多次访问内存来完成一次转换。

    TLB其实本质上也是一种cache,既然是一种cache,其目的就是为了提供更高的performance。而与我们知道的指令cache和数据cache又又什么不同呢?

    1.指令cache:解决cpu获取main memory中的指令数据的速度比较慢的问题而设立
    2.数据cache:解决cpu获取main memory中的数据的速度比较慢的问题而设立

    Cache为了更快的访问main memory中的数据和指令,而TLB是为了更快的进行地址翻译而将部分的页表内容缓存到了Translation lookasid buffer中,避免了从main memory访问页表的过程。

    2. TLB的转换过程
    TLB中的项由两部分组成:

    标识区:存放的是虚地址的一部
    数据区:存放物理页号、存储保护信息以及其他一些辅助信息
    对于数据区的辅助信息包括以下内容:

    有效位(Valid):对于操作系统,所有的数据都不会加载进内存,当数据不在内存的时候,就需要到硬盘查找并加载到内存。当为1时,表示在内存上,为0时,该页不在内存,就需要到硬盘查找。
    引用位(reference):由于TLB中的项数是一定的,所以当有新的TLB项需要进来但是又满了的话,如果根据LRU算法,就将最近最少使用的项替换成新的项。故需要引用位。同时要注意的是,页表中也有引用位。
    脏位(dirty):当内存上的某个块需要被新的块替换时,它需要根据脏位判断这个块之前有没有被修改过,如果被修改过,先把这个块更新到硬盘再替换,否则就直接替换。

    在这里插入图片描述

     

    下面我们来看一下,当存在TLB的访问流程:

    当CPU收到应用程序发来的虚拟地址后,首先去TLB中根据标志Tag寻找页表数据,假如TLB中正好存放所需的页表并且有效位是1,说明TLB命中了,那么直接就可以从TLB中获取该虚拟页号对应的物理页号。
    假如有效位是0,说明该页不在内存中,这时候就发生缺页异常,CPU需要先去外存中将该页调入内存并将页表和TLB更新
    假如在TLB中没有找到,就通过上一章节的方法,通过分页机制来实现虚拟地址到物理地址的查找。
    如果TLB已经满了,那么还要设计替换算法来决定让哪一个TLB entry失效,从而加载新的页表项。
    引用位、脏位何时更新?
    1. 如果是TLB命中,那么引用位就会被置1,当TLB或页表满时,就会根据该引用位选择适合的替换位置
    2. 如果TLB命中且这个访存操作是个写操作,那么脏位就会被置1,表明该页被修改过,当该页要从内存中移除时会先执行将该页写会外存的操作,保证数据被正确修改。
    1
    2
    3
    3. 如何确定TLB match
    我们选择Cortex-A72 processor来描述ARMv8的TLB的组成结构以及维护TLB的指令

    在这里插入图片描述

     

    A72实现了2个level的TLB,

    绿色是L1 TLB,包括L1 instruction TLB(48-entry fully-associative)和L1 data TLB(32-entry fully-associative)。
    黄色block是L2 unified TLB,它要大一些,可以容纳1024个entry,是4-way set-associative的。当L1 TLB发生TLB miss的时候,L2 TLB是它们坚强的后盾
    通过上图,我们还可以看出:对于多核CPU,每个processor core都有自己的TLB。

    在这里插入图片描述

     

    假如不做任何的处理,那么在进程A切换到进程B的时候,TLB和Cache中同时存在了A和B进程的数据。

    对于kernel space其实无所谓,因为所有的进程都是共享的
    对于A和B进程,它们各种有自己的独立的用户地址空间,也就是说,同样的一个虚拟地址X,在A的地址空间中可以被翻译成Pa,而在B地址空间中会被翻译成Pb,如果在地址翻译过程中,TLB中同时存在A和B进程的数据,那么旧的A地址空间的缓存项会影响B进程地址空间的翻译
    因此,在进程切换的时候,需要有tlb的操作,以便清除旧进程的影响,具体怎样做呢?

    当系统发生进程切换,从进程A切换到进程B,从而导致地址空间也从A切换到B,这时候,我们可以认为在A进程执行过程中,所有TLB和Cache的数据都是for A进程的,一旦切换到B,整个地址空间都不一样了,因此需要全部flush掉

    这种方案当然没有问题,当进程B被切入执行的时候,其面对的CPU是一个干干净净,从头开始的硬件环境,TLB和Cache中不会有任何的残留的A进程的数据来影响当前B进程的执行。当然,稍微有一点遗憾的就是在B进程开始执行的时候,TLB和Cache都是冰冷的(空空如也),因此,B进程刚开始执行的时候,TLB miss和Cache miss都非常严重,从而导致了性能的下降。我们管这种空TLB叫做cold TLB,它需要随着进程的运行warm up起来才能慢慢发挥起来效果,而在这个时候有可能又会有新的进程被调度了,而造成TLB的颠簸效应。

    我们采用进程地址空间这样的术语,其实它可以被进一步细分为内核地址空间和用户地址空间。对于所有的进程(包括内核线程),内核地址空间是一样的,因此对于这部分地址翻译,无论进程如何切换,内核地址空间转换到物理地址的关系是永远不变的,其实在进程A切换到B的时候,不需要flush掉,因为B进程也可以继续使用这部分的TLB内容(上图中,橘色的block)。对于用户地址空间,各个进程都有自己独立的地址空间,在进程A切换到B的时候,TLB中的和A进程相关的entry(上图中,青色的block)对于B是完全没有任何意义的,需要flush掉。

    ​ 在这样的思路指导下,我们其实需要区分global和local(其实就是process-specific的意思)这两种类型的地址翻译,因此,在页表描述符中往往有一个bit来标识该地址翻译是global还是local的,同样的,在TLB中,这个标识global还是local的flag也会被缓存起来。有了这样的设计之后,我们可以根据不同的场景而flush all或者只是flush local tlb entry。

    4. 多核的TLB操作
    完成单核场景下的分析之后,我们一起来看看多核的情况。进程切换相关的TLB逻辑block示意图如下

    在这里插入图片描述

     

    在多核系统中,进程切换的时候,TLB的操作要复杂一些,主要原因有两点:其一是各个cpu core有各自的TLB,因此TLB的操作可以分成两类,一类是flush all,即将所有cpu core上的tlb flush掉,还有一类操作是flush local tlb,即仅仅flush本cpu core的tlb。另外一个原因是进程可以调度到任何一个cpu core上执行(当然具体和cpu affinity的设定相关),从而导致task处处留情(在各个cpu上留有残余的tlb entry)。

    我们了解到地址翻译有global(各个进程共享)和local(进程特定的)的概念,因而tlb entry也有global和local的区分。如果不区分这两个概念,那么进程切换的时候,直接flush该cpu上的所有残余。这样,当进程A切出的时候,留给下一个进程B一个清爽的tlb,而当进程A在其他cpu上再次调度的时候,它面临的也是一个全空的TLB(其他cpu的tlb不会影响)。当然,如果区分global 和local,那么tlb操作也基本类似,只不过进程切换的时候,不是flush该cpu上的所有tlb entry,而是flush所有的tlb local entry就OK了。

    5. PCID
    按照这种思路走下去,那就要思考,有没有别的办法能够不刷新TLB呢?有办法的,那就是PCID。

    PCID(进程上下文标识符)是在Westmere架构引入的新特性。简单来说,在此之前,TLB是单纯的VA到PA的转换表,进程1和进程2的VA对应的PA不同,不能放在一起。加上PCID后,转换变成VA + 进程上下文ID到PA的转换表,放在一起完全没有问题了。这样进程1和进程2的页表可以和谐的在TLB中共处,进程在它们之前切换完全不需要预热了!

    所以新的加载CR3的过程变成了:如果CR4的PCID=1,加载CR3就不需要Flush TLB。

    6. TLB shootdown
    一切看起来很美好,PCID这个在多年前就有了的技术,现在已经在每个Intel CPU中生根了,那么是不是已经被广泛使用了呢?而实际的情况是Linux在2017年底才在4.15版中真正全面使用了PCID(尽管在4.14中开始部分引入PCID,见参考资料1),这是为什么呢?

    PCID这么好的技术也有副作用。在它之前的日子里,Linux在多核CPU上调度进程时候,因为每次进程调度都会刷掉进程用户空间的TLB,并没有什么问题。如果支持PCID的话,TLB操作变得很简单,或者说我们没有必要去执行TLB的操作,因为在TLB的搜索的时候已经区分各个进程,这样TLB不会影响其他任务的执行。

    在单核系统中,这样的操作确实能够获得很好的性能,例如场景为A—>B—>A,如果TLB足够大,TLB再两个进程中反复切换,极大的提升了性能。

    但是在多核系统重,如果CPU支持PCID,并且在进程切换的时候不flush tlb,那么系统中各个CPU中的TLB entry则保留各个进程的TLB entry,当在某个CPU上,一个进程被销毁了,或者该进程修改了自己的页表的时候,就必须将该进程的TLB从系统中请出去。这时候,不仅仅需要flush本CPU上对应的TLB entry,还需要flush其他CPU上和该进程相关的残余。而这个动作就需要通过IPI实现,从而引起了系统开销,此外PCID的分配和管理也会带来额外的开销。再加上PCID里面的上下文ID长度有限,只能够放得下4096个进程ID,这就需要一定的管理以便申请和放弃。如此种种,导致Linux系统在应用PCID上并不积极,直到不得不这样做。

    7. 结论
    TLB的引入解决了分页机制的性能问题,但是如何提高TLB的性能问题,但是如何提高TLB的命中确成为一个新的技术难题,对于X86提供了PCID的方式,而ARM采用的ASID技术,但是对于现在日益复杂的应用场景,这些都未能彻底的解决这些问题。
     

    展开全文
  • 减少TLB失效开销提高64位Linux系统性能的方法.pdf
  • stdole32.tlb丢失或损坏怎么办.docx
  • 微软tts5.4语音识别与合成库文件前提是要安装microsoft tts 5,4 sdk
  • 去掉了TTF160_TLB控件,用memo1代替,省去了到处找老旧控件的麻烦
  • 虚拟内存管理器 使用 Pthreads 建模虚拟内存管理器。 通过将逻辑地址映射到物理地址,可以通过对页表或 TLB 进行散列以找到正确的帧来检索数据。
  • 为了加快微处理器中线性地址向物理地址转换的速度,提出了一种高速TLB结构。结构采用全定制的CAM阵列和SRAM阵列,并根据CAM和SRAM单元的输出特点设计了精巧的读出放大逻辑,有效提高了TLB的读出速度。经流片测试,...
  • 深入理解TLB原理

    2022-05-17 01:16:19
    原文:https://zhuanlan.zhihu.com/p/108425561更新:极客重生最近在极客星球群里讨论内存缺页中断问题,讨论到了...TLB是translation lookaside buffer的简称。首先,我们知道MMU的作用是把虚拟地址转换成物理地址...
  • 完整的导出代码,另外WPS引用的文件和常量值得记录一下。要注意新版的WPS要在关键字前加上K,ET要变成KET。
  • 【MMU篇】初见MMU和TLB

    2022-04-27 16:05:36
    TLB(D5.9) TLB全名是转址旁路缓存,是一种缓存映射表条目的技术。 为什么要设计TLB? 在没有TLB之前,虚拟地址的映射需要从内存中取映射表,这就导致了频繁的内存访问,反而会降低系统映射性能。TLB的设计初衷...
  • TLB的作用及工作过程 页表一般都很大,并且存放在内存中,所以处理器引入MMU后,读取指令、数据需要访问两次内存:首先通过查询页表得到物理地址,然后访问该物理地址读取指令、数据。为了减少因为MMU导致的...
  • Stieve Nisson's '三线突破' 策略的诠释
  • TLB 为了加速虚拟地址转换物理地址过程,CPU内部一般都集成TLB硬件单元,通过缓存存取虚拟地址与物理地址映射关系,避免再通过MMU 通过多级查表引入多次内存开销,直接将映射关系存储到硬件单元中,本质上TLB是一种...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 46,114
精华内容 18,445
关键字:

TLB

友情链接: Deque.rar