1. memmgr 的回收优先级机制(reclaim_priority_manager)
结论
memmgrservice 维护一张"应用状态 → 回收优先级(即 oom_score_adj)"映射表:前台 0、可见 50、挂起延迟 100、后台可感知 200、分布式连接 260、普通后台 400、冻结 600、挂起 800、空进程 900。每次应用状态变化(前台/后台、窗口可见性、长时/短时任务、extension 绑定等)都会重算进程优先级,并直接写入内核 /proc/<pid>/oom_score_adj。内存紧张时按优先级从高到低 SIGKILL 整个应用(userspace LMKD),并对后台应用做 memcg 分组 + zram/eswap 比例化回收。
关键代码
优先级表(仓库 resourceschedule_memmgr,master)services/memmgrservice/include/reclaim_priority_manager/reclaim_priority_constants.h:22-50:
constexpr int RECLAIM_PRIORITY_SYSTEM = -1000; // 系统应用
constexpr int RECLAIM_PRIORITY_KILLABLE_SYSTEM = -800; // 可杀的系统应用
constexpr int RECLAIM_PRIORITY_FOREGROUND = 0; // 前台
constexpr int RECLAIM_PRIORITY_VISIBLE = 50; // 可见
constexpr int RECLAIM_PRIORITY_BG_SUSPEND_DELAY = 100; // 短时任务挂起延迟
constexpr int RECLAIM_PRIORITY_FG_BIND_EXTENSION = 100;
constexpr int RECLAIM_PRIORITY_BG_PERCEIVED = 200; // 后台可感知(长时任务/事件)
constexpr int RECLAIM_PRIORITY_BG_BIND_EXTENSION = 240;
constexpr int RECLAIM_PRIORITY_BG_DIST_DEVICE = 260; // 分布式连接
constexpr int RECLAIM_PRIORITY_BACKGROUND = 400; // 普通后台
constexpr int RECLAIM_PRIORITY_NO_BIND_EXTENSION = 500;
constexpr int RECLAIM_PRIORITY_FROZEN = 600; // 冻结
constexpr int RECLAIM_PRIORITY_SUSPEND = 800; // 挂起
constexpr int RECLAIM_PRIORITY_EMPTY = 900;
constexpr int RECLAIM_PRIORITY_UNKNOWN = 1000;状态 → 优先级核心映射 services/memmgrservice/src/reclaim_priority_manager/reclaim_priority_manager.cpp:898-924:
int ReclaimPriorityManager::GetPriorityByProcStatus(const ProcessPriorityInfo &proc)
{
int priority = RECLAIM_PRIORITY_UNKNOWN;
if (!proc.isFreground && proc.hasUI_) {
priority = RECLAIM_PRIORITY_BACKGROUND; // 有 UI 的后台应用 = 400
}
if (proc.isFreground) {
priority = RECLAIM_PRIORITY_FOREGROUND; // 前台 = 0
} else if (proc.isVisible_) {
priority = RECLAIM_PRIORITY_VISIBLE; // 可见 = 50
} else if (proc.isSuspendDelay) {
priority = RECLAIM_PRIORITY_BG_SUSPEND_DELAY;
} else if (proc.isBackgroundRunning || proc.isEventStart) {
priority = RECLAIM_PRIORITY_BG_PERCEIVED; // 长时任务/事件中 = 200
} else if (proc.isDistDeviceConnected) {
priority = RECLAIM_PRIORITY_BG_DIST_DEVICE;
}
...
}增量降级逻辑(只降不升的取 min 风格)reclaim_priority_manager.cpp:1041-1084(UpdatePriorityByProcStatus:后台非 extension 进程直接设为 400,见 1054-1056;可见时若优先级 >50 则降到 50,见 1057-1061)。
应用挂起事件将整个 bundle 所有进程降为 800 reclaim_priority_manager.cpp:735-747:
bool ReclaimPriorityManager::HandleApplicationSuspend(std::shared_ptr<BundlePriorityInfo> bundle)
{
...
for (auto i = bundle->procs_.begin(); i != bundle->procs_.end(); ++i) {
i->second.priority_ = RECLAIM_PRIORITY_SUSPEND;
}
...
}优先级落地到内核 services/memmgrservice/src/reclaim_priority_manager/oom_score_adj_utils.cpp:34-44:逐进程写 /proc/<pid>/oom_score_adj(ss << "/proc/" << pid << "/oom_score_adj")。
优先级应用入口 reclaim_priority_manager.cpp:1215-1230(ApplyReclaimPriority):在 USE_HYPERHOLD_MEMORY 编译开关下把 (pid, uid, priority, action) 交给 ReclaimStrategyManager::NotifyAppStateChanged(memcg 侧动作),随后写 oom_score_adj。
memmgr 监听哪些应用状态事件
事件原因枚举 reclaim_priority_constants.h:64-86(AppStateUpdateReason:CREATE_PROCESS / FOREGROUND / BACKGROUND / SUSPEND_DELAY_START/END / BACKGROUND_RUNNING_START/END / EVENT_START/END / APPLICATION_SUSPEND / PROCESS_TERMINATED / DIST_DEVICE_CONNECTED/DISCONNECTED / BIND_EXTENSION/UNBIND_EXTENSION / VISIBLE/UN_VISIBLE / ABILITY_START 等)。
各 Observer 注册于 services/memmgrservice/src/event/mem_mgr_event_center.cpp:84-119,包括:
| 事件源 | 事件 | 文件:行号 |
|---|---|---|
| AppMgrService(应用生命周期) | OnAbilityStateChanged:READY/FOREGROUND/BACKGROUND | src/event/app_state_observer.cpp:41-57;映射表 include/event/app_state_observer.h:62-66 |
| AppMgrService | OnProcessCreated/OnProcessDied(含 render 进程) | src/event/app_state_observer.cpp:63-98 |
窗口管理器(经 MemMgrService::OnWindowVisibilityChanged IPC 进入) | VISIBLE / UN_VISIBLE(窗口级可见性) | src/event/window_visibility_observer.cpp:104-129;服务入口 src/mem_mgr_service.cpp:212-224 |
| 后台任务管理(BackgroundTaskMgr) | SUSPEND_DELAY_START/END(短时任务)、BACKGROUND_RUNNING_START/END(长时任务) | src/event/bg_task_observer.cpp:26-92 |
| 分布式连接(ExtensionConnectionObserver)、帐号切换、公共事件(开关机/充放电/亮灭屏) | BIND/UNBIND_EXTENSION、OS_ACCOUNT_CHANGED 等 | src/event/mem_mgr_event_center.cpp:84-119 |
针对后台应用的具体内存动作
- oom_score_adj 降级(最核心):后台 = 400 起,被系统 LMKD 和内核 OOM 排在最前回收。
- memcg 分组回收(zram/eswap):新进程创建时按"多用户"加入
/dev/memcg/<userId>memcg,src/reclaim_strategy_manager/reclaim_strategy_manager.cpp:139-144(HandleProcessCreate_→MemcgMgr::AddProcToMemcg);src/reclaim_strategy_manager/memcg.cpp:329-342(写cgroup.procs)。每个用户 memcg 写入回收比例参数memcg.cpp:199-232(memory.app_score+memory.zswapd_single_memcg_param:mem2zramRatio / zram2ufsRatio / refaultThreshold),内核 zswapd 线程据此对后台 memcg 做匿名页压缩/换出(见第 5 节)。切帐号时按帐号优先级整组调整 memcg score 与比例reclaim_strategy_manager.cpp:181-205。 - 用户态 LMKD 查杀:
src/kill_strategy_manager/low_memory_killer.cpp:46-52默认查杀表(100M/0、200M/100、300M/200、400M/300、500M/400,单位 KB buffer + 最低可杀优先级);low_memory_killer.cpp:78-124(KillOneBundleByPrio:按优先级从高到低逐 bundle SIGKILL,创建 3 秒内的新应用受保护,见 :95 与常量NOT_TO_KILL_DURING:28);实际杀进程在common/src/kernel_interface.cpp:375-400(kill(pid, SIGKILL):392)。查杀表可由/etc/memmgr/memmgr_config.xml的killConfig覆盖,见profile/memmgr_config.xml:49-70。 - 后台整组 SwapIn:
memcg.cpp:234-241(SwapIn:写memory.ub_ufs2zram_ratio=100+memory.force_swapin,帐号回到前台时把 eswap 数据拉回 zram)。
机制解释
memmgr 是 OpenHarmony 的用户态内存管理服务(SA 1909,services/memmgrservice/memmgrservice.rc 中 post-fs-data 启动;memmgrservice.cfg 的 init job 挂载 /dev/memcg cgroup)。它以 bundle(应用)为单位、进程为粒度维护优先级集合(totalBundlePrioSet_,按优先级升序的 std::set),任何应用状态事件都会触发重算并把结果写入内核 oom_score_adj;内核 OOM 与用户态 LMKD 都以该值为杀进程排序依据。后台应用(400)与被挂起应用(800)的区别只是"被杀的先后",memmgr 本身不冻结进程(见第 3 节)。zram/eswap 回收按 memcg(多用户分组)独立施加比例参数,帐号后台时整组 memcg 可以被更激进地换出。
2. 内存分级通知(memory_level_manager 与应用侧 onMemoryLevel)
结论
memmgr 按系统可用内存(buffer)把内存压力分为 4 级:PURGEABLE(默认 1024MB) / MODERATE(800MB) / LOW(700MB) / CRITICAL(600MB),由内核 PSI(/proc/pressure/memory)事件触发计算。MODERATE 及以上会通过 AppMgrService 通知所有应用,最终派发到应用内 ArkTS 的 EnvironmentCallback.onMemoryLevel / Ability.onMemoryLevel 回调(应用层 onMemoryLevel 的系统来源)。同时通知 SAMgr 卸载空闲系统服务、触发 PurgeableMemManager 回收 purgeable 内存。
关键代码
等级定义与默认阈值(仓库 resourceschedule_memmgr,master)services/memmgrservice/include/memory_level_manager/memory_level_constants.h:25-46:
constexpr unsigned int MEMORY_LEVEL_PURGEABLE_DEFAULT = 1024; /* 1024M */
constexpr unsigned int MEMORY_LEVEL_MODERATE_DEFAULT = 800; /* 800M */
constexpr unsigned int MEMORY_LEVEL_LOW_DEFAULT = 700; /* 700M */
constexpr unsigned int MEMORY_LEVEL_CRITICAL_DEFAULT = 600; /* 600M */
enum class SystemMemoryLevel { UNKNOWN=0, MEMORY_LEVEL_PURGEABLE=1, MEMORY_LEVEL_MODERATE=2,
MEMORY_LEVEL_LOW=3, MEMORY_LEVEL_CRITICAL=4 };
enum class MemorySource { UNKNOWN=0, PSI_MEMORY=1, KSWAPD=2, MANUAL_DUMP=3 };等级计算 services/memmgrservice/src/memory_level_manager/memory_level_manager.cpp:57-84(CalcSystemMemoryLevel:读当前 buffer(KernelInterface::GetCurrentBuffer),按 critical < low < moderate < purgeable 阈值比较得出等级)。
通知链 memory_level_manager.cpp:99-135(NotifyMemoryLevel):
case SystemMemoryLevel::MEMORY_LEVEL_MODERATE: {
isNotifyMemoryLevelToSaMgr = true;
appMgrClient_->NotifyMemoryLevel(AppExecFwk::MemoryLevel::MEMORY_LEVEL_MODERATE);
break; }
...
if (isNotifyMemoryLevelToSaMgr) { NotifyMemoryLevelToSystemAbilityManager(); } // 卸载空闲SA
PurgeableMemManager::GetInstance().NotifyMemoryLevel(info); // 触发purgeable回收memory_level_manager.cpp:137-152:UnloadAllIdleSystemAbility()。
触发源:
- PSI 观察者
src/event/memory_pressure_observer.cpp:epoll 监听MEMORY_PRESSURE_FILE(include/event/memory_pressure_observer.h:31定义为/proc/pressure/memory),事件到达后HandleLevelReport(:191-196)同时调用MemoryLevelManager::PsiHandler()与LowMemoryKiller::PsiHandler()。 - kswapd 观察者
src/event/kswapd_observer.cpp:epoll 监听KSWAPD_PRESSURE_FILE(include/event/kswapd_observer.h:27定义为/proc/kswapd_monitor),事件到达HandleKswapdReport(:138-145)只通知 PurgeableMemManager。/proc/kswapd_monitor由内核kernel_linux_5.10/mm/memory_monitor.c:54(proc_create("kswapd_monitor", ...))创建,kswapd 每次被唤醒时触发(kernel_linux_5.10/mm/vmscan.c:3958,kswapd_monitor_wake_up_queue())。
应用侧派发链(仓库 ability_ability_runtime,master):
- memmgr → AppMgrService:
services/appmgr/src/app_mgr_service_inner.cpp:4452-4473(NotifyMemoryLevel:校验调用方必须是 memmgr 进程MEMMGR_PROC_NAME,校验 level 范围)。 - 广播到所有应用:
services/appmgr/src/app_running_manager.cpp:1399-1411(遍历所有 AppRunningRecord 调ScheduleMemoryLevel)。 - 进程内派发:
services/appmgr/src/app_running_record.cpp:676-681→services/appmgr/src/app_lifecycle_deal.cpp:178-186(经 appThread IPC 到应用主线程)。 - 主线程:
frameworks/native/appkit/app/main_thread.cpp:616-627(ScheduleMemoryLevel)→main_thread.cpp:3204-3215(HandleMemoryLevel→application_->OnMemoryLevel(level))。 - 应用对象:
frameworks/native/appkit/app/ohos_application.cpp:352-381(OHOSApplication::OnMemoryLevel:遍历所有 AbilityRecord 调GetAbilityThread()->NotifyMemoryLevel(level)(:365-370),遍历 AbilityStage(:373-377),再abilityRuntimeContext_->DispatchMemoryLevel(level)(:379))。 - Ability 侧:
frameworks/native/ability/native/ability_impl.cpp:538-546(NotifyMemoryLevel→ability_->OnMemoryLevel(level))。 - ApplicationContext:
frameworks/native/appkit/ability_runtime/context/application_context.cpp:681-690(DispatchMemoryLevel遍历 envCallbacks)。 - JS/ArkTS 层:
frameworks/native/appkit/ability_runtime/context/environment_callback.cpp:106-122(JsEnvironmentCallback::OnMemoryLevel→ 调用 JS 侧"onMemoryLevel"回调)。 - ArkTS 接口声明:
frameworks/ets/ets/@ohos.app.ability.EnvironmentCallback.ets:19-21(onMemoryLevel(level: AbilityConstant.MemoryLevel): void);frameworks/ets/ets/@ohos.app.ability.Ability.ets:23(Ability 基类onMemoryLevel)。 - 应用侧 MemoryLevel 枚举:
interfaces/inner_api/app_manager/include/appmgr/app_mem_info.h:21-29与frameworks/ets/ets/@ohos.app.ability.AbilityConstant.ets:78-86:MEMORY_LEVEL_MODERATE=0, LOW=1, CRITICAL=2, UI_HIDDEN=3, BACKGROUND_MODERATE=4, BACKGROUND_LOW=5, BACKGROUND_CRITICAL=6(比 memmgr 内部枚举多了 UI_HIDDEN 和 BACKGROUND_* 三级)。
机制解释
通知是进程级的:memmgr 计算出系统级压力等级后通过 AppMgrService 一次性通知所有正在运行的应用进程;应用主线程再把它派发给当前进程内所有 Ability/AbilityStage/ApplicationContext 注册的回调。也就是说,无论应用在前台还是后台,只要进程活着就会收到 onMemoryLevel;系统不会区分"某个 Ability 页面是否可见"。memmgr 的通知只覆盖 MODERATE/LOW/CRITICAL(PURGEABLE 级不通知应用,只做 purgeable 回收)。应用侧枚举中的 MEMORY_LEVEL_UI_HIDDEN / MEMORY_LEVEL_BACKGROUND_* 在 memmgr master 的通知路径中未见产生来源 [推断:这两个等级可能由其他子系统(如后台感知)通过 NotifyProcMemoryLevel(app_running_manager.cpp:1413-1439,按 pid 精确通知)下发,本次未核实到生产者]。
3. 后台应用冻结/挂起(freeze / suspend)
结论
部分证实。内核具备标准 cgroup v2 freezer(kernel_linux_5.10/kernel/cgroup/freezer.c,含 cgroup.freeze 文件接口),resourceschedule_resource_schedule_service 中也存在 suspend_manager_base 模块(对外提供按 uid/pid 查询 isFrozen 状态与注册挂起观察者的 IPC)。但在本次核实的 memmgr / ability_ability_runtime / RSS cgroup_sched_plugin 源码中,未找到对后台应用执行 cgroup freezer 冻结(写 cgroup.freeze=1)的实现。memmgr 侧的"挂起"(APPLICATION_SUSPEND)只是把回收优先级降到 800 的内存优先级动作,不是进程冻结。
关键代码
内核 cgroup v2 freezer(仓库 kernel_linux_5.10,master)kernel/cgroup/freezer.c:52-84(cgroup_update_frozen:当 CGRP_FREEZE 位置位且所有任务冻结时置 CGRP_FROZEN,向 cgroup.events 发通知)——该文件为 Linux 5.10 标准实现,OpenHarmony 内核保留了它。
RSS 侧挂起状态查询(仓库 resourceschedule_resource_schedule_service,master)ressched/services/suspend_manager_base/src/suspend_manager_base_service.cpp:53-84(GetSuspendStateByUid:校验 ohos.permission.GET_SUSPEND_STATE 权限后,通过同步插件事件 SYNC_RES_TYPE_GET_SUSPEND_STATE_BY_UID 查询并返回 JSON 中的 isFrozen 布尔值);:86-116(按 pid 的 GetSuspendStateByPid);:118-129(RegisterSuspendObserver)。
memmgr 的"挂起"语义(仓库 resourceschedule_memmgr,master)services/memmgrservice/src/reclaim_priority_manager/reclaim_priority_manager.cpp:969-971(收到 APPLICATION_SUSPEND 原因 → HandleApplicationSuspend,即第 1 节引用的把整 bundle 优先级设为 800)。
RSS cgroup 调度插件只管理 cpu/cpuset(不涉及 freezer),ressched/plugins/cgroup_sched_plugin/profiles/cgroup_action_config.json:2-27(仅 controller: cpu /dev/cpuctl 与 controller: cpuset /dev/cpuset 两项);策略枚举 framework/process_group/include/sched_policy.h:33-37(SP_BACKGROUND=1/SP_FOREGROUND=2/SP_SYSTEM_BACKGROUND=3/SP_TOP_APP=4);写组实现 framework/process_group/src/cgroup_controller.cpp:82-95(SetThreadSchedPolicy → AddTidToCgroup 把 tid 写入对应 cgroup 的 tasks fd)。
机制解释
从源码可确认的事实链是:(a) 内核 freezer 存在且可用;(b) RSS 服务有"查询某 uid/pid 是否 isFrozen"的对外接口,说明系统中确实存在"应用冻结"这个概念,且冻结决策与状态维护在某个插件/服务里(ReportSyncEvent 的对端插件未在本次读取范围内);(c) memmgr 的 APPLICATION_SUSPEND 只降内存优先级。未找到:到底哪个组件写 cgroup.freeze(或 legacy freezer)实施冻结。[推断] 结合 suspend_manager_base 的命名与观察者接口,OpenHarmony 的后台冻结很可能由 RSS(资源调度服务)的 suspend 插件基于帐号/应用后台状态实施 cgroup freezer 冻结,并在冻结前后通知观察者;这需要进一步读取 ressched/plugins/ 下相关插件源码才能确认,本次研究范围内明确记为"冻结执行者未找到"。
4. Purgeable(可清除)内存
结论
OpenHarmony 的 purgeable 内存有三条实现路径,全部由 memmgr 的 PurgeableMemManager 在内存压力(buffer ≤ 1024MB 默认阈值,即 MEMORY_LEVEL_PURGEABLE)时统一驱动:
- purgeable heap(
MAP_PURGEABLE匿名页 + 用户扩展页表 uxpte):内核在回收时直接丢弃物理页,进程再次访问时缺页,用户态通过 Builder 重建内容; - purgeable ashmem:ashmem 驱动扩展(CONFIG_PURGEABLE_ASHMEM),未 pin 的区域作为内核 shrinker 注册,内存回收时被
FALLOC_FL_PUNCH_HOLE直接打洞丢弃,应用用 ioctl PIN/UNPIN 控制可丢弃性; - subscriber(订阅者强制回收):系统服务(如图像服务)向 memmgr 注册 IAppStateSubscriber,压力不够时收到 OnTrim/ForceReclaim 回调自行释放。
系统侧不存在独立的"PurgeableCache 服务"仓库—— PurgeableMemManager 就在 memmgr 仓库内(services/memmgrservice/src/purgeable_mem_manager/),内核侧驱动在 kernel_linux_5.10,用户态库在 commonlibrary_memory_utils。
关键代码
内核侧(仓库 kernel_linux_5.10,master):
- uxpte(用户扩展页表,CONFIG_MEM_PURGEABLE):
mm/purgeable.c:16-37(struct uxpte_t,present 位 + 引用计数,每页 512 项);mm/purgeable.c:228-251(do_uxpte_page_fault:purgeable 页被丢弃后再次访问时,从 uxpgd radix tree 找回/重建映射);include/linux/mm_purgeable.h:9-38(对 mm 子系统导出的接口:mm_purg_pages_info/purg_pages_info统计进程/系统的 purgeable 总页数与 pin 页数)。 - 匿名页缺页路径挂钩:
mm/memory.c:3688-3697(do_anonymous_page中if (vma->vm_flags & VM_USEREXPTE) { if (do_uxpte_page_fault(vmf, &entry)) ... goto got_page; })。 - purgeable ashmem 触发接口:
mm/purgeable_ashmem_trigger.c:43-70(写/proc/purgeable_ashmem_trigger:仅允许 memmgr/root uid;写"0 0"触发ashmem_shrinkall(),写"<ashmem_id> <create_time>"触发ashmem_shrink_by_id());:72-109(读该文件列出所有进程的 purgeable ashmem 明细,含 oom_score_adj、大小、pin 数、是否已 purged);:126-134(proc 文件创建)。 - ashmem 驱动扩展(CONFIG_PURGEABLE_ASHMEM):
drivers/staging/android/ashmem.c:60-62(is_purgeable/purged字段);:500-556(ashmem_shrink_scan:注释明确"our cache shrinker, called from mm/vmscan.c",按 LRU 把未 pin 区域标记ASHMEM_WAS_PURGED并f->f_op->fallocate(f, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, ...)(:541-544)直接释放页面——不杀进程、不通知应用);:884-892(ashmem_shrinkall);:895-941(ashmem_shrink_by_id:按 id+创建时间精确丢弃);:680-699(purgeable ashmem 的 PIN 用引用计数实现,pin 计数 >0 期间不可被回收)。
memmgr 系统侧(仓库 resourceschedule_memmgr,master):
- 回收入口
services/memmgrservice/src/purgeable_mem_manager/purgeable_mem_manager.cpp:578-620(TriggerByPsi:reclaimTargetKB = purgeable阈值(1024MB) - currentBuffer(:596),按 HEAP → ASHMEM → SUBSCRIBER 顺序回收(:601-603),仍不够则TrimAllSubscribers(:619,触发订阅者 OnTrim));触发节流 10 秒(include/purgeable_mem_manager/purgeable_mem_constants.h:35)。 - 触发源:PSI(第 2 节)+ kswapd(
src/event/kswapd_observer.cpp:138-145,KSWAPD 源直接进TriggerByPsi)+ hidumper 手动(purgeable_mem_manager.cpp:622-650)。 - 内核接口封装
src/purgeable_mem_manager/purgeable_mem_utils.cpp:33-40(PATH_PURGE_HEAP=/proc/sys/kernel/purgeable、PATH_PURGEABLE_ASHMEM=/proc/purgeable_ashmem_trigger、FILE_PURGE_MEMCG_HEAP=memory.force_shrink_purgeable_bysize;heap 统计来自 /proc/meminfo 的Active(purg)/Inactive(purg)/Pined(purg)行);:138(PurgeHeapAll:echo 1 到 /proc/sys/kernel/purgeable);:142-146(按 memcg 路径 + 目标 KB 精确 shrink heap);:175/:181(ashmem 全量/按 id 丢弃)。 - ashmem 精确回收按优先级排序 + 白名单:
purgeable_mem_manager.cpp:481-513(PurgAshmIdOneByOne:按 minPriority 降序(优先杀后台应用的 ashmem)逐个丢弃,IsPurgeWhiteApp(:500-502)校验进程名在memmgr_config.xml的purgeWhiteAppList内(profile/memmgr_config.xml:78-82,默认仅default))。 - 订阅者接口
include/purgeable_mem_manager/iapp_state_subscriber.h:40-69(OnConnected / OnAppStateChanged(pid,uid,state) / ForceReclaim(pid,uid) / OnTrim(level)),公开 SDK 头interface/innerkits/include/app_state_subscriber.h:59-74。内存分级变化时订阅者也会收到 OnTrim(purgeable_mem_manager.cpp:296-305)。
用户态库(仓库 commonlibrary_memory_utils,master,libpurgeablemem):
- 两种数据结构:
libpurgeablemem/cpp/include/purgeable_mem.h(PurgeableMem:匿名 mmap,MAP_PURGEABLE标志,配套UxPageTable)与libpurgeablemem/cpp/include/purgeable_ashmem.h(PurgeableAshMem:ashmem fd)。上限 1GB:cpp/include/purgeable_mem_base.h:19-21(OHOS_MAXIMUM_PURGEABLE_MEMORY = 1G)。 - 访问协议
cpp/src/purgeable_mem_base.cpp:54-106(BeginRead:先Pin(),若IfNeedRebuild()(数据被清)则BuildContent()用PurgeableMemBuilder重建(:200-209),最多重试 3 次;EndRead→Unpin(),此后 OS 才可再次回收该内容)。 - heap 路径 Pin/Unpin 即 uxpte 引用:
cpp/src/purgeable_mem.cpp:90-106(CreatePurgeableData:mmap(..., UxpteIsEnabled() ? MAP_PURGEABLE : MAP_PRIVATE, ...));:109-123(Pin→pageTable_->GetUxpte;Unpin→PutUxpte)。 - ashmem 路径 Pin/Unpin 即 ioctl:
cpp/src/purgeable_ashmem.cpp:112-142(CreatePurgeableData:AshmemCreate("PurgeableAshmem", size)+ mmap +ioctl(ashmemFd_, ASHMEM_SET_PURGEABLE)(:136));:102-110(IsPurged:ioctl(ashmemFd_, PURGEABLE_ASHMEM_IS_PURGED));:144-174(Pin/Unpin:ASHMEM_PIN/ASHMEM_UNPIN)。
图像框架如何使用(仓库 multimedia_image_framework,master):
- PixelMap 析构时把自身从 purgeable LRU 摘除:
frameworks/innerkitsimpl/common/src/pixel_map.cpp:63-64(#ifdef IMAGE_PURGEABLE_PIXELMAP包含purgeable_resource_manager.h);:171-176(FreePixelMap:PurgeableMem::PurgeableResourceManager::GetInstance().RemoveResource(purgeableMemPtr_),即 PixelMap purgeable 对象由 PurgeableResourceManager 以 LRU 统一管理)。 - 编译开关门控:
frameworks/innerkitsimpl/test/BUILD.gn:83-89(memory_utils_purgeable_ashmem_enable && resourceschedule_memmgr_override两部件同时存在才定义IMAGE_PURGEABLE_PIXELMAP,并依赖memmgr:libpurgeablemem_plugin+memory_utils:libpurgeablemem)。[推断] 产品镜像需同时启用 memmgr 的 purgeable 部件(memmgr/bundle.json:70的memmgr_purgeable_memory)与 memory_utils 的 purgeable_ashmem 开关,PixelMap 的 purgeable 路径才会编入;默认 master 代码中 purgeable 相关实现均在该 ifdef 之内(image_source.cpp:4130-4136的GetSourceSize同样受控)。
机制解释
工作原理一句话:把"可重建的数据"(解码后的位图等)放进可丢弃内存,pin 期间内核保证不回收,unpin 后内核在内存压力下可直接丢页(shrinker 路径或 zswapd 触发 memmgr 写 /proc/purgeable_ashmem_trigger 路径),进程不死、不换出、零 IO;下次访问(BeginRead)发现数据被清就用 Builder 原地重建。 heap 路径的"被清"由 uxpte present 位表达(缺页进入 do_uxpte_page_fault 重建映射);ashmem 路径的"被清"由 ashmem 驱动的 purged 标志表达(PUNCH_HOLE 打洞后访问得到重建流程)。对商品详情页这类"图片多、页面被覆盖"的场景,这是系统提供的最直接受益机制(见最后一节)。
5. zram / swap(ESwap)配置
结论
OpenHarmony 用自研 zswapd 内核线程 + ESwap(扩展交换,zram → 存储设备换出)替代/补充 kswapd 管理匿名页回收。memmgr 通过 cgroup 文件向内核下发三组参数:(1) buffer 水位(availBuffer/minAvailBuffer/highAvailBuffer/swapReserve,默认 800/750/850/200 MB,xml 中 100/50/150/200);(2) 每个 memcg 的回收比例(mem2zramRatio=60%、zram2ufsRatio=10%、refaultThreshold=50,root memcg 40%/0%/0);(3) 帐号优先级 → memcg app_score。zram 设备本身的大小与压缩算法配置不在 memmgr 仓库(由产品 init/内核配置决定,本次未在已读仓库中找到,zram 驱动位于 kernel_linux_5.10 drivers/block/zram/,含 OHOS 扩展的 zram_group 按组写回机制)。
关键代码
memmgr 侧(仓库 resourceschedule_memmgr,master):
- 水位默认值
services/memmgrservice/include/reclaim_strategy_manager/reclaim_strategy_constants.h:22-35:
constexpr unsigned int AVAIL_BUFFER = 800; // MB
constexpr unsigned int MIN_AVAIL_BUFFER = 750;
constexpr unsigned int HIGH_AVAIL_BUFFER = 850;
constexpr unsigned int SWAP_RESERVE = 200;
constexpr int MEMCG_MEM_2_ZRAM_RATIO = 60; // 60%
constexpr int MEMCG_ZRAM_2_UFS_RATIO = 10; // 10%
constexpr int MEMCG_REFAULT_THRESHOLD = 50;
constexpr int ROOT_MEMCG_MEM_2_ZRAM_RATIO = 40;
constexpr int ROOT_MEMCG_ZRAM_2_UFS_RATIO = 0;
constexpr int ROOT_MEMCG_REFAULT_THRESHOLD = 0;
constexpr int APP_SCORE = 300;- 水位下发
services/memmgrservice/src/reclaim_strategy_manager/avail_buffer_manager.cpp:106-111(把 4 个值写/dev/memcg/memory.avail_buffers);:121-129(InitAvailBuffer:先读 /proc/meminfo 的 SwapTotal 判断 zram 是否启用,未启用则写 0 关闭 zswapd);产品配置profile/memmgr_config.xml:17-22(100/50/150/200)。 - memcg 比例下发
services/memmgrservice/src/reclaim_strategy_manager/memcg.cpp:199-232(SetScoreAndReclaimRatiosToKernel:写memory.app_score+memory.zswapd_single_memcg_param,回读校验);root memcg 初始化memcg_mgr.cpp:53-65(score=300,比例 40/0/0)。 - 比例与优先级挂钩:
src/reclaim_strategy_manager/reclaim_strategy_manager.cpp:207-221(GetReclaimRatiosByScore_:按 memmgr_config.xml 的ZswapdParam(minScore~maxScore 区间)选比例);xml 样例与字段说明见README_zh.md:130-200。
内核侧(仓库 kernel_linux_5.10,master):
- zswapd 主循环
mm/zswapd.c:760-824:buffer 低于 min_avail_buffers 被唤醒(wakeup_zswapd:436-465,:453-455 判定),先做匿名页 LRU 回收(zswapd_shrink_anon:587-639,逐 memcg 检查(zram+eswap)/(anon+zram+eswap)比例是否达到该 memcg 的ub_mem2zram_ratio(:613-618),达到则跳过),再把 zram 数据换出到 ESwap(swapout→ 按组写回设备 :104-141,受ub_zram2ufs_ratio约束),回收目标为 high_avail_buffers(__calc_nr_to_reclaim:641-659)。 - buffer 计算口径:
mm/zswapd.c:176-192(calc_sys_cur_avail_buffers= free + inactive_file×比例 + active_file×比例)。 - 压力上报:
mm/zswapd.c:786,810-820(回收后仍低于 avail_buffers 时按严重程度zswapd_pressure_report(LEVEL_LOW/MEDIUM/CRITICAL));mm/zswapd_control.c:296-305(zswapd_pressure_report通过 eventfd 通知用户态)。 - 参数入口:
mm/zswapd_control.c:46-62(avail_buffers/min/high/max_reclaim_size/zram_wm_ratio(默认 37,zswapd.c:22)/compress_ratio 等 atomic 变量);:140-171(avail_buffers_params_write解析 "buffers min high swap_reserve" 四元组并wake_all_zswapd())。 - memmgr 读取当前 buffer 的文件:
common/src/kernel_interface.cpp:45(HYPERHOLD 打开时/dev/memcg/memory.zswapd_pressure_show)与 :48(否则回退 /proc/meminfo 的 MemAvailable)。 - zram 分组写回(ESwap 底座):
drivers/block/zram/zram_group/zram_group.c(OHOS 扩展:zram 对象按组管理,CONFIG_ZRAM_GROUP_WRITEBACK支持组级写出到存储,为 per-memcg 的 eswap 提供按组统计与写回,:16-27 的对象表结构、:70-81 的 group swap 设备注册接口)。 - NAND 寿命管控(限制 swapout):memmgr
services/memmgrservice/src/nandlife_controller/nandlife_controller.cpp:66-96(读 xmlnandLifeConfig的每日/总量换出配额,超限则CloseSwapOutTemporarily;xml 默认 0/0 表示不限,profile/memmgr_config.xml:71-74)。
机制解释
回收顺序是"先压缩后换出":匿名页先按 memcg 比例压进 zram(内存内压缩,占用仍算物理内存但变小),zram 占比过高或 buffer 不足时再按比例把 zram 内容换出到存储(ESwap)。refault 阈值用于识别"换出后很快又被读回"的 memcg 并暂停对它的换出(zswapd.c:251-285,refault 比例超阈值则 skip,:603-606)。[推断] zram 磁盘大小与压缩算法(lzo/rz 等)由各产品的内核 defconfig + init 脚本配置(如 swapon zram 设备),不在 memmgr/RSS 仓库管辖范围内,故本笔记不给出具体值。
6. 其他相关机制
6.1 RSS cgroup 调度分组(间接影响后台内存)
结论:resourceschedule_resource_schedule_service 的 cgroup_sched_plugin 把进程按调度策略分入 /dev/cpuctl(cpu controller)与 /dev/cpuset 的 background/foreground/system-background/top-app 子组,限制后台应用 CPU 与大核使用。它不直接回收内存,但会显著降低后台应用的 CPU 活动,从而抑制其内存增长速度。
代码:配置 ressched/plugins/cgroup_sched_plugin/profiles/cgroup_action_config.json(全文 28 行,仅 cpu/cpuset 两个 controller);策略枚举 framework/process_group/include/sched_policy.h:33-37;写组 framework/process_group/src/cgroup_controller.cpp:82-95(SetThreadSchedPolicy → AddTidToCgroup 把 tid 写入对应 cgroup 的 tasks fd)。[推断] 该插件由 RSS 的事件(如应用前后台切换)驱动 SetThreadSchedPolicy,具体触发链在 framework/sched_controller/sched_controller.cpp,本次未展开读取。
6.2 availbuffer 模块与 zswapd 联动
已在第 5 节覆盖:memmgr AvailBufferManager 是 OHOS "availbuffer" 特性的用户态半边,内核 memory.avail_buffers 是 zswapd 的唤醒水位。要点补充:avail_buffer_manager.cpp:69-80(CheckAvailBuffer 强制 min ≤ avail ≤ high ≤ memTotal)。
6.3 内核 RSS 阈值监控(rss_threshold)
结论:kernel_linux_5.10 增加 per-mm 的 RSS 阈值监控接口,RSS 超阈值时内核打印错误日志(不杀不回收),用于内存问题定位。
代码:mm/rss_threshold.c:26-49(listen_rss_threshold:total_rss > mm->rss_threshold 时 pr_err("rss_threshold monitor:Pid:%d [%s] rss size:%lu KB is out of range:%lu KB"));:52-117(proc 写接口设置阈值)。
6.4 多用户 memcg(帐号级内存隔离)
结论:每个 OS 帐号一个 /dev/memcg/<userId> memcg,帐号被切到后台时整组 memcg 的 score/比例被调得更激进(可被整组换出),切回前台可整组 SwapIn。代码:memcg_mgr.cpp:76-130(AddUserMemcg/AddProcToMemcg)、reclaim_strategy_manager.cpp:176-205(HandleAccountDied_/HandleAccountPriorityChanged_)。单用户设备(手机)上主要 beneficiary 是"整个用户 0 的 app 都在同一 memcg",此时 memcg 分组对单个应用的差异化回收作用有限 [推断]。
7. 对商品详情页场景的意义(前台 App 内不可见页面)
场景:用户在一个前台 App 内用 ArkUI router 嵌套打开多个商品详情页,被覆盖的页面不可见(ArkUI 框架层问题由搭档分析)。系统层机制对该场景的作用分两类:
只作用于"整个后台进程"、对本场景几乎无效的机制
- 回收优先级 / oom_score_adj:该 App 处于前台(FOREGROUND=0,至少也是 VISIBLE=50),嵌套打开多少个页面都不改变进程优先级——窗口可见性观察者(
window_visibility_observer.cpp:104-129)以窗口为单位,同一 App 内 router 页面栈共用窗口,页面级不可见不产生 UN_VISIBLE 事件 [推断:router 页面不创建新窗口,窗口可见性对 App 内页面栈透明]。 - 用户态 LMKD 查杀与内核 OOM:只按 oom_score_adj 杀后台进程,前台 App 内的不可见页面不会因此被回收。
- cgroup 调度分组(cpu/cpuset):作用于进程 CPU 限额,前台进程不受 background 组限制。
- 多用户 memcg 差异化回收:单用户设备上同一 memcg,无差异化。
- APPLICATION_SUSPEND 降级(→800)与(未完全证实的)cgroup freezer 冻结:只针对退到后台的应用,不针对前台进程内的不可见页面。
能惠及前台 App 内不可见页面的机制
- zram/ESwap 匿名页回收(最普惠):zswapd 按 buffer 水位对所有 memcg(含前台进程)的匿名页做压缩(mem2zramRatio=60%)与换出。被覆盖页面持有的匿名内存(未在显示的位图数据、JS 对象等)冷下来后同样会被压进 zram,物理占用直接缩小;用户返回该页面时通过缺页 + zram 解压恢复(少量 CPU,无磁盘 IO,除非已下沉 ESwap)。无需应用做任何事。 refault 阈值机制保证"用户很快会回看"的页面不会被过度换出。
- Purgeable 内存(最对症,但需要应用/框架配合):商品详情页的大头通常是图片。若图片以 purgeable PixelMap(IMAGE_PURGEABLE_PIXELMAP 编入)或 PurgeableMem/PurgeableAshMem 承载,那么不可见页面的位图在
EndRead/Unpin之后随时可被内核直接丢页;系统 buffer ≤ 1024MB(MEMORY_LEVEL_PURGEABLE)时 memmgr 按"后台优先(minPriority 降序)+ 白名单"逐个丢弃 ashmem(purgeable_mem_manager.cpp:481-513),前台 App 的不可见页面位图同样是可丢弃对象 [推断:白名单机制意味着应用需被列入 purgeWhiteAppList 才会被 ashmem 精确丢弃;未被列入的应用主要靠内核 shrinker 的 LRU 路径(ashmem.c:500-556,任何 unpin 的 purgeable ashmem 都在 LRU 上)]。丢弃不杀进程、零 IO;回看时 BeginRead 触发 Builder 重建(重新解码)。这正是"不可见页面图片内存"的理想归宿:把内存占用换成回看时的一次解码。 - onMemoryLevel 主动释放(需要应用实现):系统在 MODERATE(800MB)/LOW(700MB)/CRITICAL(600MB) 时通知所有应用进程(含前台),回调会派发到进程内每个 Ability 与 EnvironmentCallback(
ohos_application.cpp:352-381)。应用可在 onMemoryLevel 中释放不可见 router 页面的缓存(商品图、预加载数据),或把页面数据转为 purgeable。这是应用层把"页面级可见性"翻译成内存动作的官方挂钩点。 - PurgeableMemManager 订阅者(系统服务间):图像/图形类系统服务可注册 IAppStateSubscriber 收到 OnTrim/ForceReclaim,按进程精确释放——[推断] PurgeableResourceManager 的 LRU(
pixel_map.cpp:171-176)在收到 ForceReclaim(pid) 时可对该进程的 purgeable 资源做定向处理,此路径对前台进程同样有效。
一句话总结
系统级策略里,kill/冻结/优先级降级都是"进程粒度"的,帮不到前台 App 内的不可见页面;真正能惠及它们的是匿名页 zram 压缩(自动、无感)与 purgeable 丢页-重建(需把位图放进 purgeable 通道),而 onMemoryLevel 是应用主动清理不可见页面缓存的系统信号来源。页面栈本身的内存(ArkUI 组件树、JS 堆)只能靠 zram 压缩缓解,无法被 purgeable 覆盖——这部分需要框架层(搭档分析方向)的页面栈优化配合。