arm memset crash
文章目录
设备信息
|
|
lspci显示存在3个bar space,物理地址分别在0xa1000000, 0xa2000000, 0x4000000000
|
|
查看/proc/iomem,存在对应物理地址
|
|
normal memory和device memory
normal memory:normal memory就是我们平常所说的内存,对该种memory访问时无副作用(side effect),即第n次访问与第n+1次访问没有任何差别(对比device memory的side effect特性,更容易理解一些)。
进一步地,通过memory attribute可以对normal memory进行细分,一段vma(virtual memory address)的memory attribute定义在页表的描述符中。
-
是否可共享:
- shareable:可以被所有PE(Processing Element,处理元素)访问,包括inner shareable和outer shareable。
- non-shareable:只能被唯一的PE访问。
-
是否支持缓存:
- write-through cacheable:同时写入cache与内存,可以理解为写穿;
- write-back cacheable:只写入cache,不更新内存,内存更新操作放到之后的适当时机;
- non-cacheable:无cache,直接读写内存。
device memory:The Device memory type attributes define memory locations where an access to the location can cause side-effects, or where the value returned for a load can vary depending on the number of loads performed. Typically, the Device memory attributes are used for memory-mapped peripherals and similar locations.
简言之,device memory是外设对应的物理地址空间,对该部分memory访问时,可能存在副作用(side effect),比如
- 某些状态寄存器可能read clear;
- 某些寄存器有写入顺序(否则写入不成功);
- 设备fifo地址固定不变,但是每次访问,内部的移位寄存器就会将下一个数据移出来[1],因此访问同一地址第n次访问与第n+1次访问结果是不同的。
进一步地,通过memory attribute可以对device memory进行细分
- 是否可合并访问
- non-Gathering(nG):处理器必须严格按照代码中内存访问来进行,不能把多次访问合并成一次。例如,代码中有两次对同样一个地址的读访问,此时处理器必须严格进行两次read操作。
- Gathering(G):处理器可以对内存访问进行合并。
- 是否可乱序
- non re-ordering(nR):处理器不允许对内存访问指令进行重排,必须严格执行program order;
- re-ordering(R):处理器允许对内存访问指令进行重排。
- 写入是否可中途返回(Early Write Acknowledgement–EWA)(E or nE)
PE对内存的访问是有问有答的(专业术语叫transaction),对于写入操作,PE需要收到write ack才能确定完成了一个transaction。为了加快写的速度,系统的中间环节可能会设定一些write buffer(比如cache),nE代表写操作的write ack必须来自最终目的地,而不是中间的write buffer。
综上:设备的bar0,bar1主要是寄存器,属于device memory,bar2是hbm内存,可以认为是normal memory
io memory
在系统运行时,外设的I/O内存资源的物理地址是已知的,这是由硬件设计决定的。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。
根据计算机平台和所使用总线的不同,I/O 内存可能是,也可能不是通过页表访问的,通过页表访问的是统一编址(PowerPC),否则是独立编址(Intel)。如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动 程序可见(这通常意味着在进行任何 I/O 之前必须先调用 ioremap)。如果访问无需页表,那么 I/O 内存区域就很象 I/O 端口,可以使 用适当形式的函数读写它们。
ioremap函数组共有五个接口,根据函数实现可以分为三类:
- ioremap & ioremap_nocache实现相同,使用场景为映射device memory类型内存;
- ioremap_cached,使用场景为映射normal memory类型内存,且映射后的虚拟内存支持cache;
- ioremap_wc & ioremap_wt实现相同,使用场景为映射normal memory类型内训,且映射后的虚拟内存不支持cache
在将I/O内存资源的物理地址映射成核心虚地址后,理论上讲我们就可以象读写RAM那样直接读写I/O内存资源了。**为了保证驱动程序的跨平台的可移植性,我们应该使用Linux中特定的函数来访问I/O内存资源,而不应该通过指向核心虚地址的指针来访问。**如在x86平台上,读写I/O的函数如下所示:
|
|
这些io函数在不同的平台,实现是不一样的,与内存的memcpy,memset相比,考虑了对齐,依赖io read/write函数读写数据。这也是为什么跨平台代码推荐使用io函数操作io类型内存的原因。
X86:
|
|
arm:
|
|
write combine
对于现代cpu而言,性能瓶颈则是对于内存的访问。cpu的速度往往都比主存的高至少两个数量级。因此cpu都引入了L1_cache与L2_cache,更加高端的cpu还加入了L3_cache.很显然,这个技术引起了下一个问题:
如果一个cpu在执行的时候需要访问的内存都不在cache中,cpu必须要通过内存总线到主存中取,那么在数据返回到cpu这段时间内(这段时间大致为cpu执行成百上千条指令的时间,至少两个数据量级)干什么呢? 答案是cpu会继续执行其他的符合条件的指令。比如cpu有一个指令序列 指令1 指令2 指令3 …, 在指令1时需要访问主存,在数据返回前cpu会继续后续的和指令1在逻辑关系上没有依赖的”独立指令”,cpu一般是依赖指令间的内存引用关系来判断的指令间的”独立关系”,具体细节可参见各cpu的文档。这也是导致cpu乱序执行指令的根源之一。
以上方案是cpu对于读取数据延迟所做的性能补救的办法。对于写数据则会显得更加复杂一点:
当cpu执行存储指令时,它会首先试图将数据写到离cpu最近的L1_cache, 如果此时cpu出现L1未命中,则会访问下一级缓存。速度上L1_cache基本能和cpu持平,其他的均明显低于cpu,L2_cache的速度大约比cpu慢20-30倍,而且还存在L2_cache不命中的情况,又需要更多的周期去主存读取。其实在L1_cache未命中以后,cpu就会使用一个另外的缓冲区,叫做合并写存储缓冲区。这一技术称为合并写入技术。在请求L2_cache缓存行的所有权尚未完成时,cpu会把待写入的数据写入到合并写存储缓冲区,该缓冲区大小和一个cache line大小,一般都是64字节。这个缓冲区允许cpu在写入或者读取该缓冲区数据的同时继续执行其他指令,这就缓解了cpu写数据时cache miss时的性能影响。
当后续的写操作需要修改相同的缓存行时,这些缓冲区变得非常有趣。在将后续的写操作提交到L2缓存之前,可以进行缓冲区写合并。 这些64字节的缓冲区维护了一个64位的字段,每更新一个字节就会设置对应的位,来表示将缓冲区交换到外部缓存时哪些数据是有效的。当然,如果程序读取已被写入到该缓冲区的某些数据,那么在读取缓存数据之前会先去读取本缓冲区的。
经过上述步骤后,缓冲区的数据还是会在某个延时的时刻更新到外部的缓存(L2_cache).如果我们能在缓冲区传输到缓存之前将其尽可能填满,这样的效果就会提高各级传输总线的效率,以提高程序性能。
|
|
测试结果:
|
|
原理:上面提到的合并写存入缓冲区离cpu很近,容量为64字节,很小了,估计很贵。数量也是有限的,我这款cpu它的个数为4。个数时依赖cpu模型的,intel的cpu在同一时刻只能拿到4个。
因此,run_one_case_for_8函数中连续写入8个不同位置的内存,那么当4个数据写满了合并写缓冲时,cpu就要等待合并写缓冲区更新到L2cache中,因此cpu就被强制暂停了。然而在run_two_case_for_4函数中是每次写入4个不同位置的内存,可以很好的利用合并写缓冲区,因合并写缓冲区满到引起的cpu暂停的次数会大大减少,当然如果每次写入的内存位置数目小于4,也是一样的。虽然多了一次循环的i++操作(实际上你可能会问,i++也是会写入内存的啊,其实i这个变量保存在了寄存器上), 但是它们之间的性能差距依然非常大。
**开启write combine以后,相当于写地址都是对齐的,所以在arm上即使不用io专用函数,依然可以正常运行memset。**
ARM memset问题
arm对于对齐data的读写明显快于不对齐的,所以它的memset实现,无论kernel还是umd,都是考虑对齐优化的
首先看kernel mode,kernel里面对io内存的memset,如果不使用io专用函数操作,memset直接访问必定因为对齐因素出现crash。
|
|
对umd,mmap出来的虚地址指针(ioremap方式映射),直接memset访问的话,由于汇编指令里面用到了DC ZVA指令,遇到device memory就会报对齐错误,这也是为什么会出现sigbus错误的原因。
|
|
解决办法是设置缺页属性为pgprot_writecombine。详见https://qiita.com/ikwzm/items/3216f907ff8fc41866c4
arm memset分析:https://www.reexpound.com/2021/05/26/arm性能优化三-memset-优化/
|
|
文章作者 carter2005
上次更新 2021-09-20