2019独角兽企业重金招聘Python工程师标准>>>

date: 2014-11-26 09:53

翻译自: http://community.arm.com/groups/processors/blog/2010/02/17/caches-and-self-modifying-code

Cache处在CPU核心与内存存储器之间,它给我们的感觉是,它具有“使之运行得更快”的魔力。当然,不同体系结构,其Cache也是千差万别。在编写代码时,常见的建议是,大脑中有一个通用的Cache的概念就可以了,这使得我们能编写出高效率的代码。比如内核代码中,某些数据结构其成员位置的“精心安排”,使得同时会被访问的成员尽量按cache line对齐。但在某些情况,为了保证我们想要的结果,我们必须考虑到cache的具体实现细节,自修改(Self-Modifying)代码就是一种典型的情况。

ARM架构有相互独立的数据cache和指令cache,分别称之为D-cache和I-cache。正因如此,ARM架构经常被当做Modified Harvard Architecture(意即有各自独立的数据总线和指令总线,可以在这两条总线上同时进行存取。与之相对的是von Neumann architecture,这种架构只有一条总线,无论是数据传输还是指令传输都要走这条总线,因此取指令和读(写)数据不能同时进行)。Modified Harvard Architecture架构有很多优点,为了便于后面的讨论,这里只强调一点:因为有两条总线的存在,CPU可以同时进行取指令和取数据的操作。

使用 Harvard-style memory interface自有它的优点,比如效率提升;但它也有自己的缺点。对纯 Harvard架构来说,一个典型的问题是:内存中的指令区(比如代码段)不能被当做数据来直接访问(这句话翻译的可能有问题,不过不影响后面的讨论。原话是:The typical drawback of a pure Harvard architecture is that instruction memory is not directly accessible from the same address space as data memory)。不过这种限制并没有实施到ARM架构上。在ARM架构下,你可以改写指令(比如当前指令之后的某条指令)并将新的指令(指令其实是一种特殊的数据)写到内存中,但是因为D-cache和I-cache不同步,新写的指令会被标记成“已经在I-cache中存在了(而不再从内存中读取)”,导致CPU最终执行的还是老的指令。(这段话比较难懂吧,看可问题描述你就明白了)。

1.问题描述

假定有这样一段“自修改代码”:其中包含及时编译器(JIT)在运行时要动态生成本地指令的“字节码”(不一定是java的字节码),该“字节码”要执行的操作是,将目标函数的地址加载到某个寄存中然后跳转过去。及时编译器(JIT compiler)已经将目标函数移到别处,因此需要更新指向它的指针(因此要修改“加载目标函数地址到寄存器”的指令)。这对及时编译器来说,是再平常不过的操作了,一来目标函数的地址在编译时不确定,二来为了对目标函数实施某些优化而可能将其重编译至别处。 在修改指令之前,CPU看到的指令和数据是这样的:

译者注:movw和movt指令的用法如下:

指令 作用
MOVW 把16 位立即数放到寄存器的低16 位,高16位清0
MOVT 把16 位立即数放到寄存器的高16 位,低16位不影响

上图中,I-Cache一开始就装载了旧版指令。这并不总是正确,如果指令不曾执行那它存在I-cache中的可能性比较低,但不排除这种可能,比如指令预取。为了方便讨论,我们假定I-cache已经装载旧版指令。

处理器只能从I-cache中执行指令,同时只能从D-cache中“看到”数据(内存存储器对它就是透明的),通常处理器不能直接访问内存。对我们而言,我们需要记住:处理器不能直接执行存在于D-cache的“指令”并且不能被安排来读写I-cache中的“数据”。因为CPU不能直接往I-Cache(或内存)中写(指令),因此,当我们改写指令后,CPU看到的指令和数据是这样的:

如果现在尝试去执行修改后的代码,处理器将会忽略它而简单的执行旧的版本,因为对处理器来说,(旧版本)代码仍然在I-cache中并且CPU不知道代码已经做了改动(没人通知CPU说I-cache已经失效)。这对使用自修改代码的Applications (such as JIT compilers)来说,的确是件讨厌的事。

2.问题解决

很明显,我们需要将数据(其实是指令)从D-cache中“转移”到I-Cache中。从上图我们知道,这只有一条路:将D-Cache中数据写到内存中,然后从内存中将指令装载到I-Cache中。 在将来的某个时间点,CPU可能会将D-cache中的数据写到内存中,并从内存中重写装载指令到I-Cache中,但具体在何时我们不得而知,因此无法将希望寄托在CPU不确定的行为身上,我们要立刻、现在就解决它。现在,D-cache中的数据为新的,与内存中的内容已经不一致了,因而是脏数据。毫无疑问,为了将数据写到内存中,我们只需clean它,并等待回写完成。此时,结果如下:

为了执行修改后的代码,我们需要通知处理器,I-cache中的指令已经“过时”,需要从内存中重现装载。我们通过使I-cache失效(invalidating)来达到此目的。此时结果如下:

现在,如果我们再去尝试执行修改后的指令,取指操作将遭遇I-cache miss(未命中),于是就从内存中重新装载,正如我们所料,这次执行的将是修改后的代码。 然而,这并不是事实的全部,还有一些其他的事情需要我们去做。如果处理器自带分支预测(branch prediction),我们还得清除跳转目标缓冲器(branch target buffer,BTB)。通常,处理器会将写内存的操作放在一个缓冲队列中缓冲起来。所以在清(clean)D-cache前,必须完成这些写内存的操作。当然,这些操作是与具体处理器架构相关的。你也可以用一个库函数来干这些“琐事”。如果你只是为了写自修改代码,那么理解你的库函数都干了些啥以及为啥要这样干就可以了。至于具体CPU架构的底层细节,就无需关注了。

最后,你可能想过利用PLI指令来给处理器一个提示,让他重新装载指令到I-Cache中。这可能会给你带来可观的效率提升, as it will not have to stall on memory when you eventually branch to it(这句不懂)。当然,既然是提示,处理器可能会忽视它而不起作用,但在某些实现上它还是有益的。

译者注:PLI 预取指令,这是服务于cache 系统的一条 hint 指令。

3.代码

通常,执行这些任务的相关指令为CP15 (System Control Coprocessor) 操作,不能在非特权模式下执行。这意味着必须借助操作系统(内核)来完成这些操作(系统调用陷入内核后,CPU即处在特权模式)。

在linxu系统中,如果用gcc编译,可以调用 __clear_cache()函数,而在Windwos CE系统中可以调用FlushInstructionCache()函数。

对Android操作系统来说,libc库提供了cacheflush()函数,我们来看看该函数的实现(这部分为译者添加,如果不想了解细节可以跳过)。

原型为:

    /* A special syscall that is only available on the ARM, not x86 function. */int cacheflush(long start, long end, long flags);

其对应的实现在cacheflush.s中

    ENTRY(cacheflush).save   {r4, r7}stmfd   sp!, {r4, r7}ldr     r7, =__NR_ARM_cacheflushswi     #0ldmfd   sp!, {r4, r7}movs    r0, r0bxpl    lrb       __set_syscall_errnoEND(cacheflush)

cacheflush通过swi #0陷入内核,其系统调用号为__NR_ARM_cacheflush。

在内核端,__NR_ARM_cacheflush的定义在<kernel/arch/arm/include/asm/unistd.h>中:

    #define __NR_SYSCALL_BASE   0/** The following SWIs are ARM private.*/#define __ARM_NR_BASE                (__NR_SYSCALL_BASE+0x0f0000)#define __ARM_NR_cacheflush              (__ARM_NR_BASE+2)

可见系统调用号__ARM_NR_cacheflush为0x0f0002。

再来看内核的实现(定义在<kernel/arch/arm/kernel/traps.c>文件中):

    #define NR(x) ((__ARM_NR_##x) - __ARM_NR_BASE)asmlinkage int arm_syscall(int no, struct pt_regs *regs){.../** Flush a region from virtual address 'r0' to virtual address 'r1'* _exclusive_.  There is no alignment requirement on either address;* user space does not need to know the hardware cache layout.** r2 contains flags.  It should ALWAYS be passed as ZERO until it* is defined to be something else.  For now we ignore it, but may* the fires of hell burn in your belly if you break this rule. ;)** (at a later date, we may want to allow this call to not flush* various aspects of the cache.  Passing '0' will guarantee that* everything necessary gets flushed to maintain consistency in* the specified region).*/case NR(cacheflush):do_cache_op(regs->ARM_r0, regs->ARM_r1, regs->ARM_r2);return 0;...}

可见,最终调用do_cache_op(),该函数的实现也在本文件中:

    static inline voiddo_cache_op(unsigned long start, unsigned long end, int flags){struct mm_struct *mm = current->active_mm;struct vm_area_struct *vma;if (end < start || flags)return;down_read(&mm->mmap_sem);vma = find_vma(mm, start);if (vma && vma->vm_start < end) {if (start < vma->vm_start)start = vma->vm_start;if (end > vma->vm_end)end = vma->vm_end;up_read(&mm->mmap_sem);flush_cache_user_range(start, end);return;}up_read(&mm->mmap_sem);

vma即是给定地址区间[start, end)(前闭后开区间)对应的虚存区间,内核用vm_area_struct 结构来管理虚存空间,cacheflush()传进来的地址区间必须是有效的。进行必要的检查后,do_cache_op()调用 flush_cache_user_range() 执行核心操作。

flush_cache_user_range 是一个宏,其定义在<kernel/arch/arm/include/asm/cacheflush.h>:

    /** flush_cache_user_range is used when we want to ensure that the* Harvard caches are synchronised for the user space address range.* This is used for the ARM private sys_cacheflush system call.*/#define flush_cache_user_range(start,end) \__cpuc_coherent_user_range((start) & PAGE_MASK, PAGE_ALIGN(end))

__cpuc_coherent_user_range()是一个与CPU相关的函数,对ARMv7来说,其定义在<kernel/arch/arm/mm/cache-v7.s>中,要读懂这些代码需要了解ARM的技术手册。这里我们只关注'@'符号引导的注释,正如前文所说,这里干了三件事:

  • clean D-cache
  • invalidate I-cache
  • invalidate BTB

代码如下:

    /** v7_coherent_user_range(start,end)**  Ensure that the I and D caches are coherent within specified*  region.  This is typically used when code has been written to*  a memory region, and will be executed.**  - start   - virtual start address of region*  - end     - virtual end address of region**  It is assumed that:*  - the Icache does not read data from the write buffer*/ENTRY(v7_coherent_user_range)UNWIND(.fnstart     )dcache_line_size r2, r3sub r3, r2, #1bic   r12, r0, r3#ifdef CONFIG_ARM_ERRATA_764369ALT_SMP(W(dsb))ALT_UP(W(nop))#endif1:USER(    mcr p15, 0, r12, c7, c11, 1 )   @ clean D line to the point of unificationadd  r12, r12, r2cmp r12, r1blo  1bdsbicache_line_size r2, r3sub r3, r2, #1bic   r12, r0, r32:USER(  mcr p15, 0, r12, c7, c5, 1  )   @ invalidate I lineadd r12, r12, r2cmp r12, r1blo  2b3:mov r0, #0ALT_SMP(mcr   p15, 0, r0, c7, c1, 6)  @ invalidate BTB Inner ShareableALT_UP(mcr p15, 0, r0, c7, c5, 6)  @ invalidate BTBdsbisbmov  pc, lr/** Fault handling for the cache operation above. If the virtual address in r0* isn't mapped, just try the next page.*/9001:mov  r12, r12, lsr #12mov    r12, r12, lsl #12add    r12, r12, #4096b    3bUNWIND(.fnend     )ENDPROC(v7_coherent_user_range)

转载于:https://my.oschina.net/u/3857782/blog/1857567

译:Self-Modifying cod 和cacheflush相关推荐

  1. CockroachDB学习笔记——[译]在CockroachDB中如何让在线模式更改成为可能

    原文链接:https://www.cockroachlabs.com/blog/how-online-schema-changes-are-possible-in-cockroachdb/ 原作者: ...

  2. 【译】nginx关于location部分

    译: Syntax: location [ = | ~ | ~* | ^~ ] uri { ... }``location @name { ... } Default: - Context: serv ...

  3. Learning Hammerspoon中英文互译

    Learning Hammerspoon中英文互译 本书主要介绍mac的脚本软件Hammerspoon的基础使用,仅提供可阅读部分翻译. Unleash the power of automation ...

  4. [译]Core Animation 3D介绍(第2部分)

    尊重原创 转自:http://codingobjc.com/blog/2013/06/24/core-animation-3djie-shao-di-2bu-fen/ 在上一篇教程中,我们已经学习了C ...

  5. java程序a-z b-y_有一行电文,以按下面规律译成密码: A---Z a---z B---Y b---Y C---X c---x …… 即第1个字母编程第26个字...

    有一行电文,以按下面规律译成密码: A--->Z a--->z B--->Y b--->Y C--->X c--->x -- 即第1个字母编程第26个字母,第i个字 ...

  6. [译] ASP.NET 生命周期 – ASP.NET 上下文对象(六)

    使用 HttpApplication 对象 ASP.NET 框架中的许多类都提供了许多很方便的属性可以直接映射到 HttpContext 类中定义的属性.这种交叠有一个很好的例子就是 HttpAppl ...

  7. 【译】Monolith first —— Martin Fowler 对于微服务架构的看法

    转载文章,文章经 LiteCodes 授权,转载至本博客. 原文地址:[译]Monolith first -- Martin Fowler 对于微服务架构的看法 整体架构先行(Monolith fir ...

  8. TWAIN Specification Chapter 4 “Advanced Application Implementation”译——应用程序端的高级实现...

    本文是对TWAIN规范的第四章<应用程序端的高级实现>的翻译.因工作需要了解TWAIN,所以顺便译了一下.这是私人工作,您可以参考,但本人不保证不存在翻译的差错或不合宜.如果您发现有不妥的 ...

  9. (C++)第一个字母变成第26个字母,第i个字母变成第(26-i+1)个字母,非字母字符不变。要求根据密码译回原文,并输出。

    题目描述 有一行电文,已按如下规律译成密码: A–>Z a–>z B–>Y b–>y C–>X c–>x - - 即第一个字母变成第26个字母,第i个字母变成第(2 ...

  10. Express4.x API (四):Router (译)

    Express4.x API 译文 系列文章 Express4.x API (一):application (译) -- 完成 Express4.x API (二):request (译) -- 完成 ...

最新文章

  1. BAT都在悄悄“拆”中台,“碎片化中台” 时代已来!
  2. 和lock一起学beego 博客系统开发为例(六)
  3. 【Java 虚拟机原理】Dalvik 虚拟机 ( 简介 | CPU 指令集 | Dalvik 虚拟机内存 )
  4. 一道关于Java并发的面试题
  5. python启动mysql_Python操作MySQL
  6. BZOJ 2668: [cqoi2012]交换棋子
  7. ActivityGroup 实现分页和自定义标签(内有GridView的点击背景样式的改变方法)
  8. kotlin键值对数组_Kotlin程序以升序对数组进行排序
  9. java中Decimaformat_Java中 DecimalFormat 用法详解
  10. Python面试题【315+道题】
  11. vue 打开html流_【报Bug】“纯nvue”模式下,web-view无法打开本地html
  12. C#添加二维码带加密带logo
  13. Linux虚拟化之IOMMU
  14. python计算机视觉_Python计算机视觉编程
  15. telnet登陆入门
  16. 计算机网络的软件系统包括哪几部分,系统软件由哪几部分组成?
  17. linux终端联网网速慢,解决ubuntu 上网速度慢的问题
  18. Emacs之魂(七):变量捕获与卫生宏
  19. 基于Spring Aop及log4j2的MDC实现全链路调用跟踪(traceid)
  20. 计算机屏幕发蓝,电脑的颜色突然变成蓝色了,屏幕,什么颜色都和以

热门文章

  1. 实现无缝滑屏怎么实现_无缝扩展人工智能以实现分布式大数据
  2. 合并重叠数据combine_first
  3. 动态规划: 数字三角形
  4. 五脏六腑在脸上的反射区图片_“阳光运动场,亲子共成长”——赣县区白鹭乡中心幼儿园迎新年亲子趣味运动会...
  5. 深度系统文件服务器,深度系统镜像文件
  6. pre和code的区别
  7. SpringBoot结合ActiveMQ(同时支持Queue和Topic)
  8. 存储过程中SELECT INTO的使用
  9. Hibernate 验证版本不兼容问题
  10. ionic创建应用的三个模版