关于内存,总是少不了TLB, cache,buffer, 最近有了点粗浅的概念理解。
- TLB
处理器为每个processor都分配了虚拟地址,具体的map方式可以通过/proc/${PID}/map 查看进程的具体映射,这是通过MMU(内存管理单元)实现的。Linux虚拟地址到物理地址的映射通过多级分页方式(具体分级和体系结构有关),如果页表是存在于主存中的,那么我们针对这各级的页表,都需要逐次访问内存,这很慢,实际上,即使是把他们缓存在cache中,也仍然很慢,因为各级之间的访问并不能并行,必须要得到前面的访问结果之后才能进行下一个访问,加上可能存在的cache miss,缓存带宽将会被消耗。所以针对这种缓存条目目录的方式,如果能针对虚拟地址一次性得到物理地址将会极大提升性能,实际上,也是这么做的。TLB(旁路转换缓存)就是MMU改进虚拟地址到物理地址转换的方法,TLB实质上也是一种cache,它会将地址转换中最频繁出现的地址,保存到TLB的高速CPU缓存中,这样就可以直接从TLB中直接取得地址数据。
当CPU执行读操作时,需要将虚拟地址转换为物理地址,在这里需要访问TLB,TLB保存着虚拟地址前20位到页框号的映射关系,之后根据得到的页框号,再加上后面12位的物理偏移地址,就可以迅速定位到物理地址。如果TLB中虚拟地址不存在,就出现TLB丢失,需要到页表中查找页表项(如果找到,需要将找到的页表项替换TLB中的一个条目), 如果在页表中也不存在,则需要到磁盘中去取。
具体页表的建立在这里不做描述,目前Linux是支持Huge TLB的,因为随着可用内存的加大,继续使用4KB的分页,将会导致大量的TLB miss 和 缺页中断。
在上下文切换时,都需要重新刷新TLB,但是存在两种情况避免刷新TLB,实际上就我自己理解来说,需要的寄存器数据等都改变了,自然需要刷新TLB,但是如果你需要的内容没有被改变呢?
- 使用相同页表的进程切换
- 普通进程切换到内核进程(lazy-tlb模式)
- cache
cache是为了解决高速CPU和慢速内存设备之间的速率差异出现的,存在这L1 cache(L1D,L1I), L2 cache 和 L3 cache,出现较多的情况是L1 cache做在CPU内,为CPU私有,其指令和CPU一样, L2 cache是SRAM,存在位置有在CPU外的,也有在CPU内的,速率略低于CPU指令周期;L3 cache是逻辑概念,为所有L2 cache的集合。
cache line 是 cache 和 DRAM 同步的最小单位,一般一个 cache line 为 32字节或者是 64 字节,在第一次访问一块区域时,会将内容同时复制到cache中,之后读写访问就可以通过cache 实现了。
对于通过cache超找到地址对应的数据的过程如下:
针对需要读写的地址,分成三部分,高t位用来检验是否是CPU需要访问的单元,s位用来计算该内存单元映射到哪一个组,而最低的b位表示了该单元在cache line中的偏移量
一个cahce分成S个组,每个组有E个cache line,每个cahe line中存在B个内存单元(1字节),对应内存单元,很容易看出S = 2 ^ s, B = 2 ^ b。
一条 cache entry包含以下部分:
- cache line, 即上图中的per cache block,包含B个字节(32位体系为4字节,64位8字节)
- tag, 标记cache line对应内存的高t位,因为可能有多个内存地址对应到同一个cache line(一个cache line有B个单位), 所以这个位用来校验是否是CPU需要访问的内存单元
- valid, 标记该cache line是否是有效的,如果该cache line并没有内存映射,就是无效的
通过以上方法,如果cache hit,就直接得到cache line,如果cache miss,则会将内存中cache line的数据load 进处理的同时 load 进cache中。
- 缓存对齐
比如现在有个变量int x, 32位机上占用4个字节,如果它的起始地址是0x1234567F;那么它占用的内存范围就在0x1234567F-0x12345682之间。如果现在Cache Line长度为32个字节,那么每次内存同Cache进行数据交换时,都必须取起始地址时32(0x20,起始地址后5位为0)倍数的内存位置开始的一段长度为32的内存同 Cache Line进行交换。如0x1234567F落在范围0x12345660~0x1234567F上,但是0x12345680~0x12345682落在范围 0x12345680~0x1234569F上,也就是说,为了将4个字节的整数变量0x1234567F~0x12345682装入Cache,我们必须调入两条Cache Line的数据。但是如果int x的起始地址按4的倍数对齐,比如是0x1234567C~0x1234567F,那么必然会落在一条Cache Line上,所以每次访问变量x就最多只需要装入一条Cache Line的数据了。比如现在一般的malloc()函数,返回的内存地址已经是8字节对齐的,这个就是为了能够让大部分程序有更好的性能。