ARM64 Linux下JIT编译指令缓存问题及技术问询
解答:ARM64 Linux下JIT代码追加执行的缓存一致性问题
核心问题1:ARM64 Linux下真的无法刷新指令缓存吗?
不是。mprotect调用的flush_cache_range确实在ARM64下对「已执行页内追加代码」的场景无效,但ARM64 Linux提供了可靠的指令缓存刷新方式:
- GCC内置函数
__builtin___clear_cache(void *start, void *end):该函数专为JIT场景设计,会自动处理ARM64架构下的数据缓存与指令缓存的同步,包括执行必要的缓存维护操作(如数据缓存清理、指令缓存失效)和同步屏障,确保修改后的代码能被CPU正确读取。 - 注意:不要手动执行特权级缓存指令(如
ic ivau),用户态无法直接执行这类指令,会触发异常。
核心问题2:是否有可靠方法确定可安全写入指令的地址偏移以避免缓存问题?
结论:依赖地址偏移完全不可靠,正确做法是显式刷新缓存
关于预取器的疑问:
- CPU预取缓存行的数量没有统一上限:不同ARM64 CPU(如树莓派5搭载的Cortex-A76)的预取器行为是硬件相关的,会根据代码执行模式、分支预测结果动态调整预取范围,无法通过固定偏移规避。
- 预取器不会严格在页边界停止:部分ARM64 CPU的预取器会跨页预取指令,你测试中相邻页不崩溃只是巧合,不能作为可靠方案。
修正后的JIT代码追加执行流程
将原流程调整为:
- 使用
mmap分配内存页(建议初始设置PROT_READ | PROT_WRITE权限) - 用
memcpy复制代码到该页 - 调用
__builtin___clear_cache(code_start, code_end)刷新缓存(end参数为代码末地址+1,因函数采用排他范围) - 用
mprotect设置为PROT_READ | PROT_EXEC - 执行代码
- 用
mprotect重新设置为PROT_READ | PROT_WRITE - 用
memcpy追加新代码到该页 - 调用
__builtin___clear_cache(new_code_start, new_code_end)刷新新写入区域的缓存 - 用
mprotect再次设置为PROT_READ | PROT_EXEC - 执行新复制的指令
额外注意事项
- 若系统允许,可在
mmap时直接设置PROT_READ | PROT_WRITE | PROT_EXEC权限(需确保系统未限制可执行匿名内存,可通过调整/proc/sys/vm/mmap_min_addr或使用MAP_ANONYMOUS | MAP_PRIVATE标志),减少频繁切换权限的开销,仅需在每次写入后调用缓存刷新函数即可。
内容的提问来源于stack exchange,提问作者Random Citizen




