COMPANION · OHOS SIDE

OpenHarmony 系统级内存策略

memmgr 回收优先级、zswapd 压缩换出、purgeable 三通道、内存分级通知与 LMKD 查杀——resourceschedule_memmgr 源码剖析。
姊妹篇 · 主报告见导航
📊 主报告UIKit 详篇系统层详篇

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-1084UpdatePriorityByProcStatus:后台非 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_adjss << "/proc/" << pid << "/oom_score_adj")。

优先级应用入口 reclaim_priority_manager.cpp:1215-1230ApplyReclaimPriority):在 USE_HYPERHOLD_MEMORY 编译开关下把 (pid, uid, priority, action) 交给 ReclaimStrategyManager::NotifyAppStateChanged(memcg 侧动作),随后写 oom_score_adj。

memmgr 监听哪些应用状态事件

事件原因枚举 reclaim_priority_constants.h:64-86AppStateUpdateReason: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/BACKGROUNDsrc/event/app_state_observer.cpp:41-57;映射表 include/event/app_state_observer.h:62-66
AppMgrServiceOnProcessCreated/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

针对后台应用的具体内存动作

  1. oom_score_adj 降级(最核心):后台 = 400 起,被系统 LMKD 和内核 OOM 排在最前回收。
  2. memcg 分组回收(zram/eswap):新进程创建时按"多用户"加入 /dev/memcg/<userId> memcg,src/reclaim_strategy_manager/reclaim_strategy_manager.cpp:139-144HandleProcessCreate_MemcgMgr::AddProcToMemcg);src/reclaim_strategy_manager/memcg.cpp:329-342(写 cgroup.procs)。每个用户 memcg 写入回收比例参数 memcg.cpp:199-232memory.app_score + memory.zswapd_single_memcg_param:mem2zramRatio / zram2ufsRatio / refaultThreshold),内核 zswapd 线程据此对后台 memcg 做匿名页压缩/换出(见第 5 节)。切帐号时按帐号优先级整组调整 memcg score 与比例 reclaim_strategy_manager.cpp:181-205
  3. 用户态 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-124KillOneBundleByPrio:按优先级从高到低逐 bundle SIGKILL,创建 3 秒内的新应用受保护,见 :95 与常量 NOT_TO_KILL_DURING :28);实际杀进程在 common/src/kernel_interface.cpp:375-400kill(pid, SIGKILL) :392)。查杀表可由 /etc/memmgr/memmgr_config.xmlkillConfig 覆盖,见 profile/memmgr_config.xml:49-70
  4. 后台整组 SwapInmemcg.cpp:234-241SwapIn:写 memory.ub_ufs2zram_ratio=100 + memory.force_swapin,帐号回到前台时把 eswap 数据拉回 zram)。

机制解释

memmgr 是 OpenHarmony 的用户态内存管理服务(SA 1909,services/memmgrservice/memmgrservice.rcpost-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-84CalcSystemMemoryLevel:读当前 buffer(KernelInterface::GetCurrentBuffer),按 critical < low < moderate < purgeable 阈值比较得出等级)。

通知链 memory_level_manager.cpp:99-135NotifyMemoryLevel):

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-152UnloadAllIdleSystemAbility()

触发源:

  • PSI 观察者 src/event/memory_pressure_observer.cpp:epoll 监听 MEMORY_PRESSURE_FILEinclude/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_FILEinclude/event/kswapd_observer.h:27 定义为 /proc/kswapd_monitor),事件到达 HandleKswapdReport(:138-145)只通知 PurgeableMemManager。/proc/kswapd_monitor 由内核 kernel_linux_5.10/mm/memory_monitor.c:54proc_create("kswapd_monitor", ...))创建,kswapd 每次被唤醒时触发(kernel_linux_5.10/mm/vmscan.c:3958kswapd_monitor_wake_up_queue())。

应用侧派发链(仓库 ability_ability_runtime,master):

  • memmgr → AppMgrService:services/appmgr/src/app_mgr_service_inner.cpp:4452-4473NotifyMemoryLevel:校验调用方必须是 memmgr 进程 MEMMGR_PROC_NAME,校验 level 范围)。
  • 广播到所有应用:services/appmgr/src/app_running_manager.cpp:1399-1411(遍历所有 AppRunningRecord 调 ScheduleMemoryLevel)。
  • 进程内派发:services/appmgr/src/app_running_record.cpp:676-681services/appmgr/src/app_lifecycle_deal.cpp:178-186(经 appThread IPC 到应用主线程)。
  • 主线程:frameworks/native/appkit/app/main_thread.cpp:616-627ScheduleMemoryLevel)→ main_thread.cpp:3204-3215HandleMemoryLevelapplication_->OnMemoryLevel(level))。
  • 应用对象:frameworks/native/appkit/app/ohos_application.cpp:352-381OHOSApplication::OnMemoryLevel:遍历所有 AbilityRecordGetAbilityThread()->NotifyMemoryLevel(level)(:365-370),遍历 AbilityStage(:373-377),再 abilityRuntimeContext_->DispatchMemoryLevel(level)(:379))。
  • Ability 侧:frameworks/native/ability/native/ability_impl.cpp:538-546NotifyMemoryLevelability_->OnMemoryLevel(level))。
  • ApplicationContext:frameworks/native/appkit/ability_runtime/context/application_context.cpp:681-690DispatchMemoryLevel 遍历 envCallbacks)。
  • JS/ArkTS 层:frameworks/native/appkit/ability_runtime/context/environment_callback.cpp:106-122JsEnvironmentCallback::OnMemoryLevel → 调用 JS 侧 "onMemoryLevel" 回调)。
  • ArkTS 接口声明:frameworks/ets/ets/@ohos.app.ability.EnvironmentCallback.ets:19-21onMemoryLevel(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-29frameworks/ets/ets/@ohos.app.ability.AbilityConstant.ets:78-86MEMORY_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-84cgroup_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-84GetSuspendStateByUid:校验 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/cpuctlcontroller: 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-95SetThreadSchedPolicyAddTidToCgroup 把 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)时统一驱动:

  1. purgeable heapMAP_PURGEABLE 匿名页 + 用户扩展页表 uxpte):内核在回收时直接丢弃物理页,进程再次访问时缺页,用户态通过 Builder 重建内容;
  2. purgeable ashmem:ashmem 驱动扩展(CONFIG_PURGEABLE_ASHMEM),未 pin 的区域作为内核 shrinker 注册,内存回收时被 FALLOC_FL_PUNCH_HOLE 直接打洞丢弃,应用用 ioctl PIN/UNPIN 控制可丢弃性;
  3. 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-37struct uxpte_t,present 位 + 引用计数,每页 512 项);mm/purgeable.c:228-251do_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-3697do_anonymous_pageif (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-62is_purgeable/purged 字段);:500-556(ashmem_shrink_scan:注释明确"our cache shrinker, called from mm/vmscan.c",按 LRU 把未 pin 区域标记 ASHMEM_WAS_PURGEDf->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-620TriggerByPsireclaimTargetKB = 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-40PATH_PURGE_HEAP=/proc/sys/kernel/purgeablePATH_PURGEABLE_ASHMEM=/proc/purgeable_ashmem_triggerFILE_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-513PurgAshmIdOneByOne:按 minPriority 降序(优先杀后台应用的 ashmem)逐个丢弃,IsPurgeWhiteApp(:500-502)校验进程名在 memmgr_config.xmlpurgeWhiteAppList 内(profile/memmgr_config.xml:78-82,默认仅 default))。
  • 订阅者接口 include/purgeable_mem_manager/iapp_state_subscriber.h:40-69OnConnected / 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.hPurgeableMem:匿名 mmap,MAP_PURGEABLE 标志,配套 UxPageTable)与 libpurgeablemem/cpp/include/purgeable_ashmem.hPurgeableAshMem:ashmem fd)。上限 1GB:cpp/include/purgeable_mem_base.h:19-21OHOS_MAXIMUM_PURGEABLE_MEMORY = 1G)。
  • 访问协议 cpp/src/purgeable_mem_base.cpp:54-106BeginRead:先 Pin(),若 IfNeedRebuild()(数据被清)则 BuildContent()PurgeableMemBuilder 重建(:200-209),最多重试 3 次;EndReadUnpin(),此后 OS 才可再次回收该内容)。
  • heap 路径 Pin/Unpin 即 uxpte 引用:cpp/src/purgeable_mem.cpp:90-106CreatePurgeableDatammap(..., UxpteIsEnabled() ? MAP_PURGEABLE : MAP_PRIVATE, ...));:109-123(PinpageTable_->GetUxpteUnpinPutUxpte)。
  • ashmem 路径 Pin/Unpin 即 ioctl:cpp/src/purgeable_ashmem.cpp:112-142CreatePurgeableDataAshmemCreate("PurgeableAshmem", size) + mmap + ioctl(ashmemFd_, ASHMEM_SET_PURGEABLE)(:136));:102-110(IsPurgedioctl(ashmemFd_, PURGEABLE_ASHMEM_IS_PURGED));:144-174(Pin/UnpinASHMEM_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(FreePixelMapPurgeableMem::PurgeableResourceManager::GetInstance().RemoveResource(purgeableMemPtr_),即 PixelMap purgeable 对象由 PurgeableResourceManager 以 LRU 统一管理)。
  • 编译开关门控:frameworks/innerkitsimpl/test/BUILD.gn:83-89memory_utils_purgeable_ashmem_enable && resourceschedule_memmgr_override 两部件同时存在才定义 IMAGE_PURGEABLE_PIXELMAP,并依赖 memmgr:libpurgeablemem_plugin + memory_utils:libpurgeablemem)。[推断] 产品镜像需同时启用 memmgr 的 purgeable 部件(memmgr/bundle.json:70memmgr_purgeable_memory)与 memory_utils 的 purgeable_ashmem 开关,PixelMap 的 purgeable 路径才会编入;默认 master 代码中 purgeable 相关实现均在该 ifdef 之内(image_source.cpp:4130-4136GetSourceSize 同样受控)。

机制解释

工作原理一句话:把"可重建的数据"(解码后的位图等)放进可丢弃内存,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-232SetScoreAndReclaimRatiosToKernel:写 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-221GetReclaimRatiosByScore_:按 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-192calc_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-305zswapd_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(读 xml nandLifeConfig 的每日/总量换出配额,超限则 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-95SetThreadSchedPolicyAddTidToCgroup 把 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-80CheckAvailBuffer 强制 min ≤ avail ≤ high ≤ memTotal)。

6.3 内核 RSS 阈值监控(rss_threshold)

结论:kernel_linux_5.10 增加 per-mm 的 RSS 阈值监控接口,RSS 超阈值时内核打印错误日志(不杀不回收),用于内存问题定位。

代码:mm/rss_threshold.c:26-49listen_rss_thresholdtotal_rss > mm->rss_thresholdpr_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-205HandleAccountDied_/HandleAccountPriorityChanged_)。单用户设备(手机)上主要 beneficiary 是"整个用户 0 的 app 都在同一 memcg",此时 memcg 分组对单个应用的差异化回收作用有限 [推断]。


7. 对商品详情页场景的意义(前台 App 内不可见页面)

场景:用户在一个前台 App 内用 ArkUI router 嵌套打开多个商品详情页,被覆盖的页面不可见(ArkUI 框架层问题由搭档分析)。系统层机制对该场景的作用分两类:

只作用于"整个后台进程"、对本场景几乎无效的机制

  1. 回收优先级 / oom_score_adj:该 App 处于前台(FOREGROUND=0,至少也是 VISIBLE=50),嵌套打开多少个页面都不改变进程优先级——窗口可见性观察者(window_visibility_observer.cpp:104-129)以窗口为单位,同一 App 内 router 页面栈共用窗口,页面级不可见不产生 UN_VISIBLE 事件 [推断:router 页面不创建新窗口,窗口可见性对 App 内页面栈透明]。
  2. 用户态 LMKD 查杀与内核 OOM:只按 oom_score_adj 杀后台进程,前台 App 内的不可见页面不会因此被回收。
  3. cgroup 调度分组(cpu/cpuset):作用于进程 CPU 限额,前台进程不受 background 组限制。
  4. 多用户 memcg 差异化回收:单用户设备上同一 memcg,无差异化。
  5. APPLICATION_SUSPEND 降级(→800)与(未完全证实的)cgroup freezer 冻结:只针对退到后台的应用,不针对前台进程内的不可见页面。

能惠及前台 App 内不可见页面的机制

  1. zram/ESwap 匿名页回收(最普惠):zswapd 按 buffer 水位对所有 memcg(含前台进程)的匿名页做压缩(mem2zramRatio=60%)与换出。被覆盖页面持有的匿名内存(未在显示的位图数据、JS 对象等)冷下来后同样会被压进 zram,物理占用直接缩小;用户返回该页面时通过缺页 + zram 解压恢复(少量 CPU,无磁盘 IO,除非已下沉 ESwap)。无需应用做任何事。 refault 阈值机制保证"用户很快会回看"的页面不会被过度换出。
  2. 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 重建(重新解码)。这正是"不可见页面图片内存"的理想归宿:把内存占用换成回看时的一次解码。
  3. onMemoryLevel 主动释放(需要应用实现):系统在 MODERATE(800MB)/LOW(700MB)/CRITICAL(600MB) 时通知所有应用进程(含前台),回调会派发到进程内每个 Ability 与 EnvironmentCallback(ohos_application.cpp:352-381)。应用可在 onMemoryLevel 中释放不可见 router 页面的缓存(商品图、预加载数据),或把页面数据转为 purgeable。这是应用层把"页面级可见性"翻译成内存动作的官方挂钩点。
  4. PurgeableMemManager 订阅者(系统服务间):图像/图形类系统服务可注册 IAppStateSubscriber 收到 OnTrim/ForceReclaim,按进程精确释放——[推断] PurgeableResourceManager 的 LRU(pixel_map.cpp:171-176)在收到 ForceReclaim(pid) 时可对该进程的 purgeable 资源做定向处理,此路径对前台进程同样有效。

一句话总结

系统级策略里,kill/冻结/优先级降级都是"进程粒度"的,帮不到前台 App 内的不可见页面;真正能惠及它们的是匿名页 zram 压缩(自动、无感)与 purgeable 丢页-重建(需把位图放进 purgeable 通道),而 onMemoryLevel 是应用主动清理不可见页面缓存的系统信号来源。页面栈本身的内存(ArkUI 组件树、JS 堆)只能靠 zram 压缩缓解,无法被 purgeable 覆盖——这部分需要框架层(搭档分析方向)的页面栈优化配合。