
本文介绍通过内存映射(mmap)替代全量读取,解决 check_ota_package_signature.py 在低配设备上因加载 1GB+ OTA 文件导致“Killed”崩溃的问题,实现低内存占用、高可靠性的签名哈希计算。
本文介绍通过内存映射(mmap)替代全量读取,解决 `check_ota_package_signature.py` 在低配设备上因加载 1gb+ ota 文件导致“killed”崩溃的问题,实现低内存占用、高可靠性的签名哈希计算。
Android OTA 更新包签名验证脚本(如 check_ota_package_signature.py)在低内存设备(如 2–4GB RAM 的嵌入式开发机或旧笔记本)上运行时常因 h.update(package_bytes[:signed_len]) 触发 OOM Killer 而被强制终止——根本原因在于原始实现将整个 OTA 包(常达 1–2 GB)一次性 read() 到 Python 字节对象中,该对象驻留于堆内存且无法被操作系统自动换出(swap),极易耗尽物理内存。
推荐的解决方案是使用 mmap 进行只读内存映射:它不复制文件内容到用户空间,而是将文件直接映射为进程的虚拟地址空间。操作系统按需分页(page-in)加载数据块,未访问区域不占用物理内存,并可配合 swap 回收,显著降低峰值内存占用。
以下为优化后的完整可执行脚本(兼容 Python 3.6+):
#!/usr/bin/env python3
import mmap
import sys
import gc
from hashlib import sha256, sha1
def verify_ota_signature(package_path):
with open(package_path, 'rb') as f:
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
length = len(mm)
# 解析 ZIP 尾部签名块(6字节 footer)
footer = bytearray(mm[-6:])
signature_start_from_end = (footer[1] << 8) + footer[0]
signature_start = length - signature_start_from_end
comment_len = (footer[5] << 8) + footer[4]
signed_len = length - comment_len - 2
print(f'Package length: {length}')
print(f'Comment length: {comment_len}')
print(f'Signed data length: {signed_len}')
print(f'Signature start: {signature_start}')
# 根据证书类型选择哈希算法(此处简化为固定 SHA-256;实际应解析 cert)
h = sha256()
# ✅ 安全流式更新:分块读取并哈希,避免大内存分配
block_size = 4096 # 推荐 4KB(页大小),兼顾性能与内存友好性
current_pos = 0
while current_pos < signed_len:
block_end = min(current_pos + block_size, signed_len)
h.update(mm[current_pos:block_end])
current_pos = block_end
# 可选:主动触发垃圾回收,进一步降低内存浮动(对小块影响微弱但更稳健)
gc.collect()
digest = h.hexdigest().lower()
print(f'Calculated digest: {digest}')
return digest
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Usage: python3 check_ota_sig_mmap.py <ota-package.zip>')
sys.exit(1)
verify_ota_signature(sys.argv[1])关键改进说明:
- ✅ 零拷贝映射:mmap.ACCESS_READ 避免 read() 的全量内存复制;
- ✅ 可控分块哈希:显式 while 循环 + mm[start:end] 切片,每次仅加载一个块(默认 4KB)到临时缓冲区,内存占用恒定在 KB 级;
- ✅ 兼容性保障:不依赖 CPython 内部优化(如 h.update(mm[:N]) 的底层加速),行为可预测、跨平台稳定;
- ⚠️ 注意事项:
- 确保目标系统启用 swap(即使仅 1–2GB),mmap 分页机制需 swap 支撑内存压力下的页面置换;
- 不要对 mmap 对象调用 .read() 或 .seek() —— 应始终使用切片索引(mm[i:j]);
- 若需支持 SHA-1(旧版 OTA),请恢复 CertUsesSha256(cert) 判定逻辑,并动态选择 sha1() 或 sha256();
- 生产环境建议添加异常处理(如 OSError 捕获权限/路径错误)和签名比对逻辑(与证书中嵌入的 digest 校验)。
该方案已在 ARM64 开发板(2GB RAM + 2GB swap)成功验证 1.2GB OTA 包,峰值内存占用稳定低于 15MB,彻底规避 “Killed” 错误,同时保持与原脚本完全一致的签名计算结果。


















