APM – iOS OOM监控 XNU Jetsam

简介

在移动端设备中内存始终是严重资源,受限于电量和闪存性能,移动端并没有memory swap机制,而是运用compress机制。在可用内存不足的多种状况下,iOS中会运用Jetsam机制依照内存占用状况和优先级等信息停止一些app,用于释放部分内存。

在用户体验上,Jetsam和Crash都是app闪退,但是Jetsam与Crash机制不同,并不会像经过信号量一样捕获,而是把app相关停止的信息记录在体系日志中。

Jetsam Log

{
    "uuid" : "a02fb850-9725-4051-817a-8a5dc0950872",
    "states" : [
      "frontmost"
    ],
    "lifetimeMax" : 92802,
    "purgeable" : 0,
    "coalition" : 68,
    "rpages" : 92802,
    "reason" : "per-process-limit",
    "name" : "MyCoolApp"
}

首要字段

  • uuid

用于跟dSYM和app比对,确认app的版本

  • states

描绘app当前的内存运用状态,例如运用内存作为最前面的应用程序,或者挂起并且不主动运用内存

  • lifetimeMax

整个进程的生命周期中最高内存分配运用量

  • coalition

如果你的应用程序的进程是涉及其他体系进程代表你的应用程序进行工作的联盟的一部分,运用此信息来识别相关进程及其内存运用,因为你的应用程序能够影响其他联盟进程的内存运用

  • name

进程称号,确认是你的app,仍是其他app或者体系进程

原因类型

APM - iOS OOM监控 XNU Jetsam

  • per-process-limit

进程超过了体系对一切app在常驻内存上的约束,超过这个约束的进程能够被体系停止。如果是app中的app extension,相对于前台app内约束的内存更小。当你需求运用SpriteKit或者MKMapView在你的扩展中,需求更加留意内存的操控。这时分或许运用MKMapSnapshotter会比运用MKMapView消耗更少的内存,更适宜在这些extension的场景中运用。

  • vm-pageshortage

体系内存严重,为了当前前台的APP释放后台进程内存。

  • vnode-limit

整个体系中翻开了太多文件。体系内核vnode的数量是有限的,vnode是支持翻开文件的内存结构体,这些结构体即将竭尽。避免在vnode竭尽的时分停止最前台app,体系能够停止后台的app来释放vnode,即便被停止的app并没有运用很多vnode。

  • highwater

体系看护进程内存占用量超出预期。

  • fc-thrashing

当读取和写入内存映射文件的非次序部分过于频频时,进程在体系文件缓存上发生颤动。体系能够停止后台的app来释放文件缓存的空间,即便不是你的app导致文件缓存的颤动。

  • jettisoned

其他原因导致的进程被放弃。

类型枚举

switch (cause) {
    case kMemorystatusKilledHiwat:                                          jetsam_flags |= P_JETSAM_HIWAT; break;
    case kMemorystatusKilledVnodes:                                         jetsam_flags |= P_JETSAM_VNODE; break;
    case kMemorystatusKilledVMPageShortage:                         jetsam_flags |= P_JETSAM_VMPAGESHORTAGE; break;
    case kMemorystatusKilledVMCompressorThrashing:
    case kMemorystatusKilledVMCompressorSpaceShortage:      jetsam_flags |= P_JETSAM_VMTHRASHING; break;
    case kMemorystatusKilledFCThrashing:                            jetsam_flags |= P_JETSAM_FCTHRASHING; break;
    case kMemorystatusKilledPerProcessLimit:                        jetsam_flags |= P_JETSAM_PID; break;
    case kMemorystatusKilledIdleExit:                                       jetsam_flags |= P_JETSAM_IDLEEXIT; break;
    }

类型称号

/* For logging clarity */
static const char *memorystatus_kill_cause_name[] = {
    ""                              ,       /* kMemorystatusInvalid                         */
    "jettisoned"                    ,       /* kMemorystatusKilled                          */
    "highwater"                     ,       /* kMemorystatusKilledHiwat                     */
    "vnode-limit"                   ,       /* kMemorystatusKilledVnodes                    */
    "vm-pageshortage"               ,       /* kMemorystatusKilledVMPageShortage            */
    "proc-thrashing"                ,       /* kMemorystatusKilledProcThrashing             */
    "fc-thrashing"                  ,       /* kMemorystatusKilledFCThrashing               */
    "per-process-limit"             ,       /* kMemorystatusKilledPerProcessLimit           */
    "disk-space-shortage"           ,       /* kMemorystatusKilledDiskSpaceShortage         */
    "idle-exit"                     ,       /* kMemorystatusKilledIdleExit                  */
    "zone-map-exhaustion"           ,       /* kMemorystatusKilledZoneMapExhaustion         */
    "vm-compressor-thrashing"       ,       /* kMemorystatusKilledVMCompressorThrashing     */
    "vm-compressor-space-shortage"  ,       /* kMemorystatusKilledVMCompressorSpaceShortage */
};

源码解析

环境初始化和发动

arm_init()

machine_startup() 多个

kernel_bootstrap()

vm_mem_bootstrap()

kmem_init(…)

首要流程

Caller

bsd_init()

该办法担任在XNU内核内初始化BSD子体系,执行以下使命

  1. 初始化BSD数据结构,比方进程表,文件描绘符表和vnode(虚拟结点)数据结构
  2. 设置BSD调度器,担任办理体系进程的执行
  3. 初始化virtual memory子体系和memory management BSD部分的内核
  4. 设置BSD体系的调用表,用于映射体系调用从用户空间到符合的内核办法
  5. 初始化不同的kernel子体系,比方网络栈,文件体系和像pipes和socks一样的IPC通讯机制
  6. 发动init process(process ID 1),体系中第一个用户进程,也是其他一切用户的父进程

跟内存相关的如下

//初始化BSD内存Zone,根据Mach内核的Zone构建
    kmeminit();
//iOS中特性,内存和进程的休眠的常驻监控线程
#if CONFIG_FREEZE
#ifndef CONFIG_MEMORYSTATUS
    #error "CONFIG_FREEZE defined without matching CONFIG_MEMORYSTATUS"
#endif
    /* Initialise background freezing */
    bsd_init_kprintf("calling memorystatus_freeze_init\n");
    memorystatus_freeze_init();
#endif
//iOS中Jetsam相关,低内存事情的常驻监控线程
#if CONFIG_MEMORYSTATUS
    /* Initialize kernel memory status notifications */
    bsd_init_kprintf("calling memorystatus_init\n");
    memorystatus_init();
#endif /* CONFIG_MEMORYSTATUS */

其间后面两部代码都是调用kern_memorystatus.c中的接口,从内核中敞开2个最高优先级线程来监控整个体系内存状况,其间CONFIG_FREEZE会对进程进行冻结,而不是kill

进程优先级数据结构如下,内核对于一切进程都有一个优先级的分布,经过一个数组维护,这个数组的大小是JETSAM_PRIORITY_MAX + 1

#define MEMSTAT_BUCKET_COUNT (JETSAM_PRIORITY_MAX + 1) 
typedef struct memstat_bucket {
 TAILQ_HEAD(, proc) list; 
 int count; 
} memstat_bucket_t; 
memstat_bucket_t memstat_bucket[MEMSTAT_BUCKET_COUNT];
  • 体系内核优先级 > 用户态进程优先级
  • 用户态前台进程优先级 > 后台进程优先级
  • SpringBoard是应用程序中优先级最高的程序

Init

memorystatus_init()

  • 初始化bucket存储同优先级列表
  • 装备Jetsam
  • 其他判别条件
  • 初始化Jetsam线程memorystatus_thread
__private_extern__ void
memorystatus_init(void)
{
    kern_return_t result;
    int i;
#if DEVELOPMENT || DEBUG
    if (kill_on_no_paging_space) {
        max_kill_priority = JETSAM_PRIORITY_MAX;
    }
#endif
    /* Init buckets */
    // 每个bucket都持有了一个同优先级进程列表
    for (i = 0; i < MEMSTAT_BUCKET_COUNT; i++) {
        TAILQ_INIT(&memstat_bucket[i].list);
        memstat_bucket[i].count = 0;
        memstat_bucket[i].relaunch_high_count = 0;
    }
    memorystatus_idle_demotion_call = thread_call_allocate((thread_call_func_t)memorystatus_perform_idle_demotion, NULL);
    nanoseconds_to_absolutetime((uint64_t)DEFERRED_IDLE_EXIT_TIME_SECS * NSEC_PER_SEC, &memorystatus_sysprocs_idle_delay_time);
    nanoseconds_to_absolutetime((uint64_t)DEFERRED_IDLE_EXIT_TIME_SECS * NSEC_PER_SEC, &memorystatus_apps_idle_delay_time);
#if CONFIG_JETSAM
    /* Apply overrides */
    // 获取一系列内核参数
    if (!PE_parse_boot_argn("kern.jetsam_delta", &delta_percentage, sizeof(delta_percentage))) {
        PE_get_default("kern.jetsam_delta", &delta_percentage, sizeof(delta_percentage));
    }
    if (delta_percentage == 0) {
        delta_percentage = 5;
    }
    if (max_mem > config_jetsam_large_memory_cutoff) {
        critical_threshold_percentage = critical_threshold_percentage_larger_devices;
        delta_percentage = delta_percentage_larger_devices;
    }
    assert(delta_percentage < 100);
    if (!PE_parse_boot_argn("kern.jetsam_critical_threshold", &critical_threshold_percentage, sizeof(critical_threshold_percentage))) {
        PE_get_default("kern.jetsam_critical_threshold", &critical_threshold_percentage, sizeof(critical_threshold_percentage));
    }
    assert(critical_threshold_percentage < 100);
    PE_get_default("kern.jetsam_idle_offset", &idle_offset_percentage, sizeof(idle_offset_percentage));
    assert(idle_offset_percentage < 100);
    PE_get_default("kern.jetsam_pressure_threshold", &pressure_threshold_percentage, sizeof(pressure_threshold_percentage));
    assert(pressure_threshold_percentage < 100);
    PE_get_default("kern.jetsam_freeze_threshold", &freeze_threshold_percentage, sizeof(freeze_threshold_percentage));
    assert(freeze_threshold_percentage < 100);
    if (!PE_parse_boot_argn("jetsam_aging_policy", &jetsam_aging_policy,
        sizeof(jetsam_aging_policy))) {
        if (!PE_get_default("kern.jetsam_aging_policy", &jetsam_aging_policy,
            sizeof(jetsam_aging_policy))) {
            jetsam_aging_policy = kJetsamAgingPolicySysProcsReclaimedFirst;
        }
    }
    if (jetsam_aging_policy > kJetsamAgingPolicyMax) {
        jetsam_aging_policy = kJetsamAgingPolicySysProcsReclaimedFirst;
    }
    switch (jetsam_aging_policy) {
    case kJetsamAgingPolicyNone:
        system_procs_aging_band = JETSAM_PRIORITY_IDLE;
        applications_aging_band = JETSAM_PRIORITY_IDLE;
        break;
    case kJetsamAgingPolicyLegacy:
        /*
         * Legacy behavior where some daemons get a 10s protection once
         * AND only before the first clean->dirty->clean transition before
         * going into IDLE band.
         */
        system_procs_aging_band = JETSAM_PRIORITY_AGING_BAND1;
        applications_aging_band = JETSAM_PRIORITY_IDLE;
        break;
    case kJetsamAgingPolicySysProcsReclaimedFirst:
        system_procs_aging_band = JETSAM_PRIORITY_AGING_BAND1;
        applications_aging_band = JETSAM_PRIORITY_AGING_BAND2;
        break;
    case kJetsamAgingPolicyAppsReclaimedFirst:
        system_procs_aging_band = JETSAM_PRIORITY_AGING_BAND2;
        applications_aging_band = JETSAM_PRIORITY_AGING_BAND1;
        break;
    default:
        break;
    }
    /*
     * The aging bands cannot overlap with the JETSAM_PRIORITY_ELEVATED_INACTIVE
     * band and must be below it in priority. This is so that we don't have to make
     * our 'aging' code worry about a mix of processes, some of which need to age
     * and some others that need to stay elevated in the jetsam bands.
     */
    assert(JETSAM_PRIORITY_ELEVATED_INACTIVE > system_procs_aging_band);
    assert(JETSAM_PRIORITY_ELEVATED_INACTIVE > applications_aging_band);
    /* Take snapshots for idle-exit kills by default? First check the boot-arg... */
    if (!PE_parse_boot_argn("jetsam_idle_snapshot", &memorystatus_idle_snapshot, sizeof(memorystatus_idle_snapshot))) {
        /* ...no boot-arg, so check the device tree */
        PE_get_default("kern.jetsam_idle_snapshot", &memorystatus_idle_snapshot, sizeof(memorystatus_idle_snapshot));
    }
    memorystatus_delta = (unsigned int) (delta_percentage * atop_64(max_mem) / 100);
    memorystatus_available_pages_critical_idle_offset = (unsigned int) (idle_offset_percentage * atop_64(max_mem) / 100);
    memorystatus_available_pages_critical_base = (unsigned int) ((critical_threshold_percentage / delta_percentage) * memorystatus_delta);
    memorystatus_policy_more_free_offset_pages = (unsigned int) ((policy_more_free_offset_percentage / delta_percentage) * memorystatus_delta);
    memorystatus_sysproc_aging_aggr_pages = (unsigned int) (sysproc_aging_aggr_threshold_percentage * atop_64(max_mem) / 100);
    /* Jetsam Loop Detection */
    if (max_mem <= (512 * 1024 * 1024)) {
        /* 512 MB devices */
        memorystatus_jld_eval_period_msecs = 8000;      /* 8000 msecs == 8 second window */
    } else {
        /* 1GB and larger devices */
        memorystatus_jld_eval_period_msecs = 6000;      /* 6000 msecs == 6 second window */
    }
    memorystatus_jld_enabled = TRUE;
    /* No contention at this point */
    memorystatus_update_levels_locked(FALSE);
#endif /* CONFIG_JETSAM */
#if __arm64__
    if (!PE_parse_boot_argn("entitled_max_task_pmem", &memorystatus_entitled_max_task_footprint_mb,
        sizeof(memorystatus_entitled_max_task_footprint_mb))) {
        if (!PE_get_default("kern.entitled_max_task_pmem", &memorystatus_entitled_max_task_footprint_mb,
            sizeof(memorystatus_entitled_max_task_footprint_mb))) {
            // entitled_max_task_pmem is not supported on this system.
            memorystatus_entitled_max_task_footprint_mb = 0;
        }
    }
    if (memorystatus_entitled_max_task_footprint_mb > max_mem / (1UL << 20) || memorystatus_entitled_max_task_footprint_mb < 0) {
        os_log_with_startup_serial(OS_LOG_DEFAULT, "Invalid value (%d) for entitled_max_task_pmem. Setting to 0",
            memorystatus_entitled_max_task_footprint_mb);
    }
#endif /* __arm64__ */
    memorystatus_jetsam_snapshot_max = maxproc;
    memorystatus_jetsam_snapshot_size = sizeof(memorystatus_jetsam_snapshot_t) +
        (sizeof(memorystatus_jetsam_snapshot_entry_t) * memorystatus_jetsam_snapshot_max);
    memorystatus_jetsam_snapshot = kalloc_flags(memorystatus_jetsam_snapshot_size, Z_WAITOK | Z_ZERO);
    if (!memorystatus_jetsam_snapshot) {
        panic("Could not allocate memorystatus_jetsam_snapshot");
    }
    memorystatus_jetsam_snapshot_copy = kalloc_flags(memorystatus_jetsam_snapshot_size, Z_WAITOK | Z_ZERO);
    if (!memorystatus_jetsam_snapshot_copy) {
        panic("Could not allocate memorystatus_jetsam_snapshot_copy");
    }
    nanoseconds_to_absolutetime((uint64_t)JETSAM_SNAPSHOT_TIMEOUT_SECS * NSEC_PER_SEC, &memorystatus_jetsam_snapshot_timeout);
    memset(&memorystatus_at_boot_snapshot, 0, sizeof(memorystatus_jetsam_snapshot_t));
    /* Check the boot-arg to see if fast jetsam is allowed */
    if (!PE_parse_boot_argn("fast_jetsam_enabled", &fast_jetsam_enabled, sizeof(fast_jetsam_enabled))) {
        fast_jetsam_enabled = 0;
    }
    /* Check the boot-arg to configure the maximum number of jetsam threads */
    if (!PE_parse_boot_argn("max_jetsam_threads", &max_jetsam_threads, sizeof(max_jetsam_threads))) {
        max_jetsam_threads = JETSAM_THREADS_LIMIT;
    }
    /* Restrict the maximum number of jetsam threads to JETSAM_THREADS_LIMIT */
    if (max_jetsam_threads > JETSAM_THREADS_LIMIT) {
        max_jetsam_threads = JETSAM_THREADS_LIMIT;
    }
    /* For low CPU systems disable fast jetsam mechanism */
    if (vm_pageout_state.vm_restricted_to_single_processor == TRUE) {
        max_jetsam_threads = 1;
        fast_jetsam_enabled = 0;
    }
    /* Initialize the jetsam_threads state array */
    jetsam_threads = zalloc_permanent(sizeof(struct jetsam_thread_state) *
        max_jetsam_threads, ZALIGN(struct jetsam_thread_state));
    /* Initialize all the jetsam threads */
    for (i = 0; i < max_jetsam_threads; i++) {
        jetsam_threads[i].inited = FALSE;
        jetsam_threads[i].index = i;
        result = kernel_thread_start_priority(memorystatus_thread, NULL, 95 /* MAXPRI_KERNEL */, &jetsam_threads[i].thread);
        if (result != KERN_SUCCESS) {
            panic("Could not create memorystatus_thread %d", i);
        }
        thread_deallocate(jetsam_threads[i].thread);
    }
}

Thread

static void memorystatus_thread(..)

BSD层起了一个内核优先级最高的线程VM_memorystatus,这个线程会维护两个列表

  • 根据进程优先级的进程列表
  • 内存快照列表memorystatus_jetsam_snapshot,保存了每个进程消耗的内存页面

这个常驻线程接受从内核对于内存的看护程序pageout,经过内核调用给每个app进程发送的内存压力通知,在UI层面接受到全局内存警告和每个ViewController里面的didReceiveMemoryWaring

static void
memorystatus_thread(void *param __unused, wait_result_t wr __unused)
{
    boolean_t post_snapshot = FALSE;
    uint32_t errors = 0;
    uint32_t hwm_kill = 0;
    boolean_t sort_flag = TRUE;
    boolean_t corpse_list_purged = FALSE;
    int     jld_idle_kills = 0;
    struct jetsam_thread_state *jetsam_thread = jetsam_current_thread();
    uint64_t total_memory_reclaimed = 0;
    assert(jetsam_thread != NULL);
    if (jetsam_thread->inited == FALSE) {
        /*
         * It's the first time the thread has run, so just mark the thread as privileged and block.
         * This avoids a spurious pass with unset variables, as set out in <rdar://problem/9609402>.
         */
        char name[32];
        thread_wire(host_priv_self(), current_thread(), TRUE);
        snprintf(name, 32, "VM_memorystatus_%d", jetsam_thread->index + 1);
        /* Limit all but one thread to the lower jetsam bands, as that's where most of the victims are. */
        if (jetsam_thread->index == 0) {
            if (vm_pageout_state.vm_restricted_to_single_processor == TRUE) {
                thread_vm_bind_group_add();
            }
            jetsam_thread->limit_to_low_bands = FALSE;
        } else {
            jetsam_thread->limit_to_low_bands = TRUE;
        }
#if CONFIG_THREAD_GROUPS
        thread_group_vm_add();
#endif
        thread_set_thread_name(current_thread(), name);
        jetsam_thread->inited = TRUE;
        memorystatus_thread_block(0, memorystatus_thread);
    }
    KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_SCAN) | DBG_FUNC_START,
        MEMORYSTATUS_LOG_AVAILABLE_PAGES, memorystatus_jld_enabled, memorystatus_jld_eval_period_msecs, memorystatus_jld_eval_aggressive_count, 0);
    /*
     * Jetsam aware version.
     *
     * The VM pressure notification thread is working it's way through clients in parallel.
     *
     * So, while the pressure notification thread is targeting processes in order of
     * increasing jetsam priority, we can hopefully reduce / stop it's work by killing
     * any processes that have exceeded their highwater mark.
     *
     * If we run out of HWM processes and our available pages drops below the critical threshold, then,
     * we target the least recently used process in order of increasing jetsam priority (exception: the FG band).
     */
    while (memorystatus_action_needed()) {
        boolean_t killed;
        int32_t priority;
        uint32_t cause;
        uint64_t memory_reclaimed = 0;
        uint64_t jetsam_reason_code = JETSAM_REASON_INVALID;
        os_reason_t jetsam_reason = OS_REASON_NULL;
        cause = kill_under_pressure_cause;
        switch (cause) {
        case kMemorystatusKilledFCThrashing:
            jetsam_reason_code = JETSAM_REASON_MEMORY_FCTHRASHING;
            break;
        case kMemorystatusKilledVMCompressorThrashing:
            jetsam_reason_code = JETSAM_REASON_MEMORY_VMCOMPRESSOR_THRASHING;
            break;
        case kMemorystatusKilledVMCompressorSpaceShortage:
            jetsam_reason_code = JETSAM_REASON_MEMORY_VMCOMPRESSOR_SPACE_SHORTAGE;
            break;
        case kMemorystatusKilledZoneMapExhaustion:
            jetsam_reason_code = JETSAM_REASON_ZONE_MAP_EXHAUSTION;
            break;
        case kMemorystatusKilledVMPageShortage:
        /* falls through */
        default:
            jetsam_reason_code = JETSAM_REASON_MEMORY_VMPAGESHORTAGE;
            cause = kMemorystatusKilledVMPageShortage;
            break;
        }
        /* Highwater */
        boolean_t is_critical = TRUE;
        if (memorystatus_act_on_hiwat_processes(&errors, &hwm_kill, &post_snapshot, &is_critical, &memory_reclaimed)) {
            total_memory_reclaimed += memory_reclaimed;
            if (is_critical == FALSE) {
                /*
                 * For now, don't kill any other processes.
                 */
                break;
            } else {
                goto done;
            }
        }
        jetsam_reason = os_reason_create(OS_REASON_JETSAM, jetsam_reason_code);
        if (jetsam_reason == OS_REASON_NULL) {
            printf("memorystatus_thread: failed to allocate jetsam reason\n");
        }
        /* Only unlimited jetsam threads should act aggressive */
        if (!jetsam_thread->limit_to_low_bands &&
            memorystatus_act_aggressive(cause, jetsam_reason, &jld_idle_kills, &corpse_list_purged, &post_snapshot, &memory_reclaimed)) {
            total_memory_reclaimed += memory_reclaimed;
            goto done;
        }
        /*
         * memorystatus_kill_top_process() drops a reference,
         * so take another one so we can continue to use this exit reason
         * even after it returns
         */
        os_reason_ref(jetsam_reason);
        /* LRU */
        killed = memorystatus_kill_top_process(TRUE, sort_flag, cause, jetsam_reason, &priority, &errors, &memory_reclaimed);
        sort_flag = FALSE;
        if (killed) {
            total_memory_reclaimed += memory_reclaimed;
            if (memorystatus_post_snapshot(priority, cause) == TRUE) {
                post_snapshot = TRUE;
            }
            /* Jetsam Loop Detection */
            if (memorystatus_jld_enabled == TRUE) {
                if ((priority == JETSAM_PRIORITY_IDLE) || (priority == system_procs_aging_band) || (priority == applications_aging_band)) {
                    jld_idle_kills++;
                } else {
                    /*
                     * We've reached into bands beyond idle deferred.
                     * We make no attempt to monitor them
                     */
                }
            }
            /*
             * If we have jetsammed a process in or above JETSAM_PRIORITY_UI_SUPPORT
             * then we attempt to relieve pressure by purging corpse memory and notifying
             * anybody wanting to know this.
             */
            if (priority >= JETSAM_PRIORITY_UI_SUPPORT) {
                memorystatus_approaching_fg_band(&corpse_list_purged);
            }
            goto done;
        }
        if (memorystatus_avail_pages_below_critical()) {
            /*
             * Still under pressure and unable to kill a process - purge corpse memory
             * and get everything back from the pmap.
             */
            pmap_release_pages_fast();
            if (total_corpses_count() > 0) {
                task_purge_all_corpses();
                corpse_list_purged = TRUE;
            }
            if (!jetsam_thread->limit_to_low_bands && memorystatus_avail_pages_below_critical()) {
                /*
                 * Still under pressure and unable to kill a process - panic
                 */
                panic("memorystatus_jetsam_thread: no victim! available pages:%llu\n", (uint64_t)MEMORYSTATUS_LOG_AVAILABLE_PAGES);
            }
        }
done:
        /*
         * We do not want to over-kill when thrashing has been detected.
         * To avoid that, we reset the flag here and notify the
         * compressor.
         */
        if (is_reason_thrashing(kill_under_pressure_cause)) {
            kill_under_pressure_cause = 0;
#if CONFIG_JETSAM
            vm_thrashing_jetsam_done();
#endif /* CONFIG_JETSAM */
        } else if (is_reason_zone_map_exhaustion(kill_under_pressure_cause)) {
            kill_under_pressure_cause = 0;
        }
        os_reason_free(jetsam_reason);
    }
    kill_under_pressure_cause = 0;
    if (errors) {
        memorystatus_clear_errors();
    }
    if (post_snapshot) {
        proc_list_lock();
        size_t snapshot_size = sizeof(memorystatus_jetsam_snapshot_t) +
            sizeof(memorystatus_jetsam_snapshot_entry_t) * (memorystatus_jetsam_snapshot_count);
        uint64_t timestamp_now = mach_absolute_time();
        memorystatus_jetsam_snapshot->notification_time = timestamp_now;
        memorystatus_jetsam_snapshot->js_gencount++;
        if (memorystatus_jetsam_snapshot_count > 0 && (memorystatus_jetsam_snapshot_last_timestamp == 0 ||
            timestamp_now > memorystatus_jetsam_snapshot_last_timestamp + memorystatus_jetsam_snapshot_timeout)) {
            proc_list_unlock();
            int ret = memorystatus_send_note(kMemorystatusSnapshotNote, &snapshot_size, sizeof(snapshot_size));
            if (!ret) {
                proc_list_lock();
                memorystatus_jetsam_snapshot_last_timestamp = timestamp_now;
                proc_list_unlock();
            }
        } else {
            proc_list_unlock();
        }
    }
    KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_SCAN) | DBG_FUNC_END,
        MEMORYSTATUS_LOG_AVAILABLE_PAGES, total_memory_reclaimed, 0, 0, 0);
    memorystatus_thread_block(0, memorystatus_thread);
}

从Thread处理的全体代码看下来,首要是while循环中优先判别Action_needed,之后判别和处理Highwater,之后判别和处理Aggressive,之后处理部分进程和Kill

Action_needed

memorystatus_action_needed()

进入memorystatus_action之前的条件在这个办法中判别,有3个首要条件

  • is_reason_thrashing(内存页面颤动,频频换进换出)
  • is_reason_zone_map_exhaustion(zone map耗尽)
  • memorystatus_available_pages <= memorystatus_available_pages_pressure(可用内存页面数小于压力值)
static boolean_t
memorystatus_action_needed(void)
{
#if CONFIG_JETSAM
    return is_reason_thrashing(kill_under_pressure_cause) ||
           is_reason_zone_map_exhaustion(kill_under_pressure_cause) ||
           memorystatus_available_pages <= memorystatus_available_pages_pressure;
#else /* CONFIG_JETSAM */
    return is_reason_thrashing(kill_under_pressure_cause) ||
           is_reason_zone_map_exhaustion(kill_under_pressure_cause);
#endif /* CONFIG_JETSAM */
}
static boolean_t is_reason_thrashing(…)
/* Does cause indicate vm or fc thrashing? */
static boolean_t
is_reason_thrashing(unsigned cause)
{
    switch (cause) {
    case kMemorystatusKilledFCThrashing:
    case kMemorystatusKilledVMCompressorThrashing:
    case kMemorystatusKilledVMCompressorSpaceShortage:
        return TRUE;
    default:
        return FALSE;
    }
}
static boolean_t is_reason_zone_map_exhaustion(…)
/* Is the zone map almost full? */
static boolean_t
is_reason_zone_map_exhaustion(unsigned cause)
{
    if (cause == kMemorystatusKilledZoneMapExhaustion) {
        return TRUE;
    }
    return FALSE;
}

Highwater

static boolean_t memorystatus_act_on_hiwat_processes(…)

进入action之后,首要判别和处理Highwater

static boolean_t
memorystatus_act_on_hiwat_processes(uint32_t *errors, uint32_t *hwm_kill, boolean_t *post_snapshot, __unused boolean_t *is_critical, uint64_t *memory_reclaimed)
{
    boolean_t purged = FALSE, killed = FALSE;
    *memory_reclaimed = 0;
    killed = memorystatus_kill_hiwat_proc(errors, &purged, memory_reclaimed);
    if (killed) {
        *hwm_kill = *hwm_kill + 1;
        *post_snapshot = TRUE;
        return TRUE;
    } else {
        if (purged == FALSE) {
            /* couldn't purge and couldn't kill */
            memorystatus_hwm_candidates = FALSE;
        }
    }
#if CONFIG_JETSAM
    /* No highwater processes to kill. Continue or stop for now? */
    if (!is_reason_thrashing(kill_under_pressure_cause) &&
        !is_reason_zone_map_exhaustion(kill_under_pressure_cause) &&
        (memorystatus_available_pages > memorystatus_available_pages_critical)) {
        /*
         * We are _not_ out of pressure but we are above the critical threshold and there's:
         * - no compressor thrashing
         * - enough zone memory
         * - no more HWM processes left.
         * For now, don't kill any other processes.
         */
        if (*hwm_kill == 0) {
            memorystatus_thread_wasted_wakeup++;
        }
        *is_critical = FALSE;
        return TRUE;
    }
#endif /* CONFIG_JETSAM */
    return FALSE;
}
static boolean_t memorystatus_kill_hiwat_proc(…)
static boolean_t
memorystatus_kill_hiwat_proc(uint32_t *errors, boolean_t *purged, uint64_t *memory_reclaimed)
{
    pid_t aPid = 0;
    proc_t p = PROC_NULL, next_p = PROC_NULL;
    boolean_t new_snapshot = FALSE, killed = FALSE, freed_mem = FALSE;
    unsigned int i = 0;
    uint32_t aPid_ep;
    os_reason_t jetsam_reason = OS_REASON_NULL;
    KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_JETSAM_HIWAT) | DBG_FUNC_START,
        MEMORYSTATUS_LOG_AVAILABLE_PAGES, 0, 0, 0, 0);
    jetsam_reason = os_reason_create(OS_REASON_JETSAM, JETSAM_REASON_MEMORY_HIGHWATER);
    if (jetsam_reason == OS_REASON_NULL) {
        printf("memorystatus_kill_hiwat_proc: failed to allocate exit reason\n");
    }
    proc_list_lock();
    next_p = memorystatus_get_first_proc_locked(&i, TRUE);
    while (next_p) {
        uint64_t footprint_in_bytes = 0;
        uint64_t memlimit_in_bytes  = 0;
        boolean_t skip = 0;
        p = next_p;
        next_p = memorystatus_get_next_proc_locked(&i, p, TRUE);
        aPid = p->p_pid;
        aPid_ep = p->p_memstat_effectivepriority;
        if (p->p_memstat_state  & (P_MEMSTAT_ERROR | P_MEMSTAT_TERMINATED)) {
            continue;
        }
        /* skip if no limit set */
        if (p->p_memstat_memlimit <= 0) {
            continue;
        }
        footprint_in_bytes = get_task_phys_footprint(p->task);
        memlimit_in_bytes  = (((uint64_t)p->p_memstat_memlimit) * 1024ULL * 1024ULL);   /* convert MB to bytes */
        skip = (footprint_in_bytes <= memlimit_in_bytes);
        if (skip) {
            continue;
        } else {
            if (memorystatus_jetsam_snapshot_count == 0) {
                memorystatus_init_jetsam_snapshot_locked(NULL, 0);
                new_snapshot = TRUE;
            }
            if (proc_ref_locked(p) == p) {
                /*
                 * Mark as terminated so that if exit1() indicates success, but the process (for example)
                 * is blocked in task_exception_notify(), it'll be skipped if encountered again - see
                 * <rdar://problem/13553476>. This is cheaper than examining P_LEXIT, which requires the
                 * acquisition of the proc lock.
                 */
                p->p_memstat_state |= P_MEMSTAT_TERMINATED;
                proc_list_unlock();
            } else {
                /*
                 * We need to restart the search again because
                 * proc_ref_locked _can_ drop the proc_list lock
                 * and we could have lost our stored next_p via
                 * an exit() on another core.
                 */
                i = 0;
                next_p = memorystatus_get_first_proc_locked(&i, TRUE);
                continue;
            }
            footprint_in_bytes = 0;
            freed_mem = memorystatus_kill_proc(p, kMemorystatusKilledHiwat, jetsam_reason, &killed, &footprint_in_bytes); /* purged and/or killed 'p' */
            /* Success? */
            if (freed_mem) {
                if (killed == FALSE) {
                    /* purged 'p'..don't reset HWM candidate count */
                    *purged = TRUE;
                    proc_list_lock();
                    p->p_memstat_state &= ~P_MEMSTAT_TERMINATED;
                    proc_list_unlock();
                } else {
                    *memory_reclaimed = footprint_in_bytes;
                }
                proc_rele(p);
                goto exit;
            }
            /*
             * Failure - first unwind the state,
             * then fall through to restart the search.
             */
            proc_list_lock();
            proc_rele_locked(p);
            p->p_memstat_state &= ~P_MEMSTAT_TERMINATED;
            p->p_memstat_state |= P_MEMSTAT_ERROR;
            *errors += 1;
            i = 0;
            next_p = memorystatus_get_first_proc_locked(&i, TRUE);
        }
    }
    proc_list_unlock();
exit:
    os_reason_free(jetsam_reason);
    if (!killed) {
        *memory_reclaimed = 0;
        /* Clear snapshot if freshly captured and no target was found */
        if (new_snapshot) {
            proc_list_lock();
            memorystatus_jetsam_snapshot->entry_count = memorystatus_jetsam_snapshot_count = 0;
            proc_list_unlock();
        }
    }
    KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_JETSAM_HIWAT) | DBG_FUNC_END,
        MEMORYSTATUS_LOG_AVAILABLE_PAGES, killed ? aPid : 0, killed, *memory_reclaimed, 0);
    return killed;
}
static proc_t memorystatus_get_first_proc_locked(….)

从行列里取出最低优先级进程

static proc_t memorystatus_get_first_proc_locked(unsigned int *bucket_index, boolean_t search) {
    memstat_bucket_t *current_bucket;
    proc_t next_p;
    if ((*bucket_index) >= MEMSTAT_BUCKET_COUNT) {
        return NULL;
    }
    current_bucket = &memstat_bucket[*bucket_index];
    next_p = TAILQ_FIRST(&current_bucket->list);
    if (!next_p && search) {
        while (!next_p && (++(*bucket_index) < MEMSTAT_BUCKET_COUNT)) {
            current_bucket = &memstat_bucket[*bucket_index];
            next_p = TAILQ_FIRST(&current_bucket->list);
        }
    }
    return next_p;
}

Aggressive

static boolean_t memorystatus_act_aggressive(…)

急进的判别是看在急进循环里呈现的次数,这个次数决议了需求杀死的最大优先级带

在memorystatus_init()中的config_Jetsam代码段里,有对Jetsam Loop Detection做装备。在物理内存有512MB的设备装备了8秒的时刻,512MB以下的设备有6秒的时刻

static boolean_t
memorystatus_act_aggressive(uint32_t cause, os_reason_t jetsam_reason, int *jld_idle_kills, boolean_t *corpse_list_purged, boolean_t *post_snapshot, uint64_t *memory_reclaimed)
{
    boolean_t aggressive_jetsam_needed = false;
    boolean_t killed;
    uint32_t errors = 0;
    uint64_t footprint_of_killed_proc = 0;
    int elevated_bucket_count = 0;
    int total_candidates = 0;
    *memory_reclaimed = 0;
    /*
     * The aggressive jetsam logic looks at the number of times it has been in the
     * aggressive loop to determine the max priority band it should kill upto. The
     * static variables below are used to track that property.
     *
     * To reset those values, the implementation checks if it has been
     * memorystatus_jld_eval_period_msecs since the parameters were reset.
     */
    static int       jld_eval_aggressive_count = 0;
    static int32_t   jld_priority_band_max = JETSAM_PRIORITY_UI_SUPPORT;
    static uint64_t  jld_timestamp_msecs = 0;
    static int       jld_idle_kill_candidates = 0;
    if (memorystatus_jld_enabled == FALSE) {
        /* If aggressive jetsam is disabled, nothing to do here */
        return FALSE;
    }
    /* Get current timestamp (msecs only) */
    struct timeval  jld_now_tstamp = {0, 0};
    uint64_t        jld_now_msecs = 0;
    microuptime(&jld_now_tstamp);
    jld_now_msecs = (jld_now_tstamp.tv_sec * 1000);
    /*
     * The aggressive jetsam logic looks at the number of candidates and their
     * properties to decide if aggressive jetsam should be engaged.
     */
    if (jetsam_aging_policy == kJetsamAgingPolicySysProcsReclaimedFirst) {
        /*
         * For the kJetsamAgingPolicySysProcsReclaimedFirst aging policy, the logic looks at the number of
         * candidates in the idle and deferred band and how many out of them are marked as high relaunch
         * probability.
         */
        aggressive_jetsam_needed = memorystatus_aggressive_jetsam_needed_sysproc_aging(jld_eval_aggressive_count,
            jld_idle_kills, jld_idle_kill_candidates, &total_candidates, &elevated_bucket_count);
    } else {
        /*
         * The other aging policies look at number of candidate processes over a specific time window and
         * evaluate if the system is in a jetsam loop. If yes, aggressive jetsam is triggered.
         */
        aggressive_jetsam_needed = memorystatus_aggressive_jetsam_needed_default(jld_eval_aggressive_count,
            jld_idle_kills, jld_idle_kill_candidates, &total_candidates, &elevated_bucket_count);
    }
    /*
     * Check if its been really long since the aggressive jetsam evaluation
     * parameters have been refreshed. This logic also resets the jld_eval_aggressive_count
     * counter to make sure we reset the aggressive jetsam severity.
     */
    boolean_t param_reval = false;
    if ((total_candidates == 0) ||
        (jld_now_msecs > (jld_timestamp_msecs + memorystatus_jld_eval_period_msecs))) {
        jld_timestamp_msecs      = jld_now_msecs;
        jld_idle_kill_candidates = total_candidates;
        *jld_idle_kills          = 0;
        jld_eval_aggressive_count = 0;
        jld_priority_band_max   = JETSAM_PRIORITY_UI_SUPPORT;
        param_reval = true;
    }
    /*
     * If the parameters have been updated, re-evaluate the aggressive_jetsam_needed condition for
     * the non kJetsamAgingPolicySysProcsReclaimedFirst policy since its based on jld_idle_kill_candidates etc.
     */
    if ((param_reval == true) && (jetsam_aging_policy != kJetsamAgingPolicySysProcsReclaimedFirst)) {
        aggressive_jetsam_needed = (*jld_idle_kills > jld_idle_kill_candidates);
    }
    /*
     * It is also possible that the system is down to a very small number of processes in the candidate
     * bands. In that case, the decisions made by the memorystatus_aggressive_jetsam_needed_* routines
     * would not be useful. In that case, do not trigger aggressive jetsam.
     */
    if (total_candidates < kJetsamMinCandidatesThreshold) {
#if DEVELOPMENT || DEBUG
        printf("memorystatus: aggressive: [FAILED] Low Candidate Count (current: %d, threshold: %d)\n", total_candidates, kJetsamMinCandidatesThreshold);
#endif /* DEVELOPMENT || DEBUG */
        aggressive_jetsam_needed = false;
    }
    if (aggressive_jetsam_needed == false) {
        /* Either the aging policy or the candidate count decided that aggressive jetsam is not needed. Nothing more to do here. */
        return FALSE;
    }
    /* Looks like aggressive jetsam is needed */
    jld_eval_aggressive_count++;
    if (jld_eval_aggressive_count == memorystatus_jld_eval_aggressive_count) {
        memorystatus_approaching_fg_band(corpse_list_purged);
    } else if (jld_eval_aggressive_count > memorystatus_jld_eval_aggressive_count) {
        /*
         * Bump up the jetsam priority limit (eg: the bucket index)
         * Enforce bucket index sanity.
         */
        if ((memorystatus_jld_eval_aggressive_priority_band_max < 0) ||
            (memorystatus_jld_eval_aggressive_priority_band_max >= MEMSTAT_BUCKET_COUNT)) {
            /*
             * Do nothing.  Stick with the default level.
             */
        } else {
            jld_priority_band_max = memorystatus_jld_eval_aggressive_priority_band_max;
        }
    }

Kill

kill的调用有多处,终究都是调用memorystatus_do_kill和其间的exit_with_reason函数

memorystatus_kill_proc

static boolean_t
memorystatus_kill_proc(proc_t p, uint32_t cause, os_reason_t jetsam_reason, boolean_t *killed)
{
    pid_t aPid = 0;
    uint32_t aPid_ep = 0;
    uint64_t    killtime = 0;
        clock_sec_t     tv_sec;
        clock_usec_t    tv_usec;
        uint32_t        tv_msec;
    boolean_t   retval = FALSE;
    uint64_t    num_pages_purged = 0;
    aPid = p->p_pid;
    aPid_ep = p->p_memstat_effectivepriority;
    if (cause != kMemorystatusKilledVnodes && cause != kMemorystatusKilledZoneMapExhaustion) {
        /*
         * Genuine memory pressure and not other (vnode/zone) resource exhaustion.
         */
        boolean_t success = FALSE;
        networking_memstatus_callout(p, cause);
        num_pages_purged = vm_purgeable_purge_task_owned(p->task);
        if (num_pages_purged) {
            /*
             * We actually purged something and so let's
             * check if we need to continue with the kill.
             */
            if (cause == kMemorystatusKilledHiwat) {
                uint64_t footprint_in_bytes = get_task_phys_footprint(p->task);
                uint64_t memlimit_in_bytes  = (((uint64_t)p->p_memstat_memlimit) * 1024ULL * 1024ULL);  /* convert MB to bytes */
                success = (footprint_in_bytes <= memlimit_in_bytes);
            } else {
                success = (memorystatus_avail_pages_below_pressure() == FALSE);
            }
            if (success) {
                memorystatus_purge_before_jetsam_success++;
                os_log_with_startup_serial(OS_LOG_DEFAULT, "memorystatus: purged %llu pages from pid %d [%s] and avoided %s\n",
                num_pages_purged, aPid, (*p->p_name ? p->p_name : "unknown"),  memorystatus_kill_cause_name[cause]);
                *killed = FALSE;
                return TRUE;
            }
        }
    }
#if CONFIG_JETSAM && (DEVELOPMENT || DEBUG)
    MEMORYSTATUS_DEBUG(1, "jetsam: %s pid %d [%s] - %lld Mb > 1 (%d Mb)\n",
               (memorystatus_jetsam_policy & kPolicyDiagnoseActive) ? "suspending": "killing",
               aPid, (*p->p_name ? p->p_name : "unknown"),
               (footprint_in_bytes / (1024ULL * 1024ULL)),  /* converted bytes to MB */
               p->p_memstat_memlimit);
#endif /* CONFIG_JETSAM && (DEVELOPMENT || DEBUG) */
    killtime = mach_absolute_time();
    absolutetime_to_microtime(killtime, &tv_sec, &tv_usec);
    tv_msec = tv_usec / 1000;
#if CONFIG_JETSAM && (DEVELOPMENT || DEBUG)
    if (memorystatus_jetsam_policy & kPolicyDiagnoseActive) {
        if (cause == kMemorystatusKilledHiwat) {
            MEMORYSTATUS_DEBUG(1, "jetsam: suspending pid %d [%s] for diagnosis - memorystatus_available_pages: %d\n",
                aPid, (*p->p_name ? p->p_name: "(unknown)"), memorystatus_available_pages);
        } else {
            int activeProcess = p->p_memstat_state & P_MEMSTAT_FOREGROUND;
            if (activeProcess) {
                MEMORYSTATUS_DEBUG(1, "jetsam: suspending pid %d [%s] (active) for diagnosis - memorystatus_available_pages: %d\n",
                    aPid, (*p->p_name ? p->p_name: "(unknown)"), memorystatus_available_pages);
                    if (memorystatus_jetsam_policy & kPolicyDiagnoseFirst) {
                        jetsam_diagnostic_suspended_one_active_proc = 1;
                        printf("jetsam: returning after suspending first active proc - %d\n", aPid);
                    }
            }
        }
        proc_list_lock();
        /* This diagnostic code is going away soon. Ignore the kMemorystatusInvalid cause here. */
        memorystatus_update_jetsam_snapshot_entry_locked(p, kMemorystatusInvalid, killtime);
        proc_list_unlock();
        p->p_memstat_state |= P_MEMSTAT_DIAG_SUSPENDED;
        if (p) {
            task_suspend(p->task);
            *killed = TRUE;
        }
    } else
#endif /* CONFIG_JETSAM && (DEVELOPMENT || DEBUG) */
    {
        proc_list_lock();
        memorystatus_update_jetsam_snapshot_entry_locked(p, cause, killtime);
        proc_list_unlock();
        char kill_reason_string[128];
        if (cause == kMemorystatusKilledHiwat) {
            strlcpy(kill_reason_string, "killing_highwater_process", 128);
        } else {
            if (aPid_ep == JETSAM_PRIORITY_IDLE) {
                strlcpy(kill_reason_string, "killing_idle_process", 128);
            } else {
                strlcpy(kill_reason_string, "killing_top_process", 128);
            }
        }
        os_log_with_startup_serial(OS_LOG_DEFAULT, "%lu.%03d memorystatus: %s pid %d [%s] (%s %d) - memorystatus_available_pages: %llu\n",
               (unsigned long)tv_sec, tv_msec, kill_reason_string,
               aPid, (*p->p_name ? p->p_name : "unknown"),
               memorystatus_kill_cause_name[cause], aPid_ep, (uint64_t)memorystatus_available_pages);
        /*
         * memorystatus_do_kill drops a reference, so take another one so we can
         * continue to use this exit reason even after memorystatus_do_kill()
         * returns
         */
        os_reason_ref(jetsam_reason);
        retval = memorystatus_do_kill(p, cause, jetsam_reason);
        *killed = retval;
    }
    return retval;
}

static boolean_t memorystatus_do_kill(…)

执行杀掉进程

/*
 * Wrapper for processes exiting with memorystatus details
 */
static boolean_t
memorystatus_do_kill(proc_t p, uint32_t cause, os_reason_t jetsam_reason, uint64_t *footprint_of_killed_proc)
{
    int error = 0;
    __unused pid_t victim_pid = p->p_pid;
    uint64_t footprint = get_task_phys_footprint(p->task);
#if (KDEBUG_LEVEL >= KDEBUG_LEVEL_STANDARD)
    int32_t memstat_effectivepriority = p->p_memstat_effectivepriority;
#endif /* (KDEBUG_LEVEL >= KDEBUG_LEVEL_STANDARD) */
    KERNEL_DEBUG_CONSTANT((BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_DO_KILL)) | DBG_FUNC_START,
        victim_pid, cause, vm_page_free_count, footprint, 0);
    DTRACE_MEMORYSTATUS4(memorystatus_do_kill, proc_t, p, os_reason_t, jetsam_reason, uint32_t, cause, uint64_t, footprint);
#if CONFIG_JETSAM && (DEVELOPMENT || DEBUG)
    if (memorystatus_jetsam_panic_debug & (1 << cause)) {
        panic("memorystatus_do_kill(): jetsam debug panic (cause: %d)", cause);
    }
#else
#pragma unused(cause)
#endif
    if (p->p_memstat_effectivepriority >= JETSAM_PRIORITY_FOREGROUND) {
        printf("memorystatus: killing process %d [%s] in high band %s (%d) - memorystatus_available_pages: %llu\n", p->p_pid,
            (*p->p_name ? p->p_name : "unknown"),
            memorystatus_priority_band_name(p->p_memstat_effectivepriority), p->p_memstat_effectivepriority,
            (uint64_t)MEMORYSTATUS_LOG_AVAILABLE_PAGES);
    }
    /*
     * The jetsam_reason (os_reason_t) has enough information about the kill cause.
     * We don't really need jetsam_flags anymore, so it's okay that not all possible kill causes have been mapped.
     */
    int jetsam_flags = P_LTERM_JETSAM;
    switch (cause) {
    case kMemorystatusKilledHiwat:                                          jetsam_flags |= P_JETSAM_HIWAT; break;
    case kMemorystatusKilledVnodes:                                         jetsam_flags |= P_JETSAM_VNODE; break;
    case kMemorystatusKilledVMPageShortage:                         jetsam_flags |= P_JETSAM_VMPAGESHORTAGE; break;
    case kMemorystatusKilledVMCompressorThrashing:
    case kMemorystatusKilledVMCompressorSpaceShortage:      jetsam_flags |= P_JETSAM_VMTHRASHING; break;
    case kMemorystatusKilledFCThrashing:                            jetsam_flags |= P_JETSAM_FCTHRASHING; break;
    case kMemorystatusKilledPerProcessLimit:                        jetsam_flags |= P_JETSAM_PID; break;
    case kMemorystatusKilledIdleExit:                                       jetsam_flags |= P_JETSAM_IDLEEXIT; break;
    }
    /* jetsam_do_kill drops a reference. */
    os_reason_ref(jetsam_reason);
    error = jetsam_do_kill(p, jetsam_flags, jetsam_reason);
    *footprint_of_killed_proc = ((error == 0) ? footprint : 0);
    KERNEL_DEBUG_CONSTANT((BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_DO_KILL)) | DBG_FUNC_END,
        victim_pid, memstat_effectivepriority, vm_page_free_count, error, 0);
    KERNEL_DEBUG_CONSTANT((BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_COMPACTOR_RUN)) | DBG_FUNC_START,
        victim_pid, cause, vm_page_free_count, *footprint_of_killed_proc, 0);
    if (jetsam_reason->osr_code == JETSAM_REASON_VNODE) {
        /*
         * vnode jetsams are syncronous and not caused by memory pressure.
         * Running the compactor on this thread adds significant latency to the filesystem operation
         * that triggered this jetsam.
         * Kick of compactor thread asyncronously instead.
         */
        vm_wake_compactor_swapper();
    } else {
        vm_run_compactor();
    }
    KERNEL_DEBUG_CONSTANT((BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_COMPACTOR_RUN)) | DBG_FUNC_END,
        victim_pid, cause, vm_page_free_count, 0, 0);
    os_reason_free(jetsam_reason);
    return error == 0;
}

static int jetsam_do_kill(…)

/*
 * The jetsam no frills kill call
 *      Return: 0 on success
 *      error code on failure (EINVAL...)
 */
static int
jetsam_do_kill(proc_t p, int jetsam_flags, os_reason_t jetsam_reason)
{
    int error = 0;
    error = exit_with_reason(p, W_EXITCODE(0, SIGKILL), (int *)NULL, FALSE, FALSE, jetsam_flags, jetsam_reason);
    return error;
}
int exit_with_reason(…)

进入停止推出逻辑,并带一个SIGKILL信号

/*
 * NOTE: exit_with_reason drops a reference on the passed exit_reason
 */
int
exit_with_reason(proc_t p, int rv, int *retval, boolean_t thread_can_terminate, boolean_t perf_notify,
    int jetsam_flags, struct os_reason *exit_reason)
{
    thread_t self = current_thread();
    struct task *task = p->task;
    struct uthread *ut;
    int error = 0;
    /*
     * If a thread in this task has already
     * called exit(), then halt any others
     * right here.
     */
    ut = get_bsdthread_info(self);
    if ((p == current_proc()) &&
        (ut->uu_flag & UT_VFORK)) {
        os_reason_free(exit_reason);
        if (!thread_can_terminate) {
            return EINVAL;
        }
        vfork_exit(p, rv);
        vfork_return(p, retval, p->p_pid);
        unix_syscall_return(0);
        /* NOT REACHED */
    }
    /*
     * The parameter list of audit_syscall_exit() was augmented to
     * take the Darwin syscall number as the first parameter,
     * which is currently required by mac_audit_postselect().
     */
    /*
     * The BSM token contains two components: an exit status as passed
     * to exit(), and a return value to indicate what sort of exit it
     * was.  The exit status is WEXITSTATUS(rv), but it's not clear
     * what the return value is.
     */
    AUDIT_ARG(exit, WEXITSTATUS(rv), 0);
    /*
     * TODO: what to audit here when jetsam calls exit and the uthread,
     * 'ut' does not belong to the proc, 'p'.
     */
    AUDIT_SYSCALL_EXIT(SYS_exit, p, ut, 0); /* Exit is always successfull */
    DTRACE_PROC1(exit, int, CLD_EXITED);
    /* mark process is going to exit and pull out of DBG/disk throttle */
    /* TODO: This should be done after becoming exit thread */
    proc_set_task_policy(p->task, TASK_POLICY_ATTRIBUTE,
        TASK_POLICY_TERMINATED, TASK_POLICY_ENABLE);
    proc_lock(p);
    error = proc_transstart(p, 1, (jetsam_flags ? 1 : 0));
    if (error == EDEADLK) {
        /*
         * If proc_transstart() returns EDEADLK, then another thread
         * is either exec'ing or exiting. Return an error and allow
         * the other thread to continue.
         */
        proc_unlock(p);
        os_reason_free(exit_reason);
        if (current_proc() == p) {
            if (p->exit_thread == self) {
                panic("exit_thread failed to exit");
            }
            if (thread_can_terminate) {
                thread_exception_return();
            }
        }
        return error;
    }
    while (p->exit_thread != self) {
        if (sig_try_locked(p) <= 0) {
            proc_transend(p, 1);
            os_reason_free(exit_reason);
            if (get_threadtask(self) != task) {
                proc_unlock(p);
                return 0;
            }
            proc_unlock(p);
            thread_terminate(self);
            if (!thread_can_terminate) {
                return 0;
            }
            thread_exception_return();
            /* NOTREACHED */
        }
        sig_lock_to_exit(p);
    }
    if (exit_reason != OS_REASON_NULL) {
        KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_PROC, BSD_PROC_EXITREASON_COMMIT) | DBG_FUNC_NONE,
            p->p_pid, exit_reason->osr_namespace,
            exit_reason->osr_code, 0, 0);
    }
    assert(p->p_exit_reason == OS_REASON_NULL);
    p->p_exit_reason = exit_reason;
    p->p_lflag |= P_LEXIT;
    p->p_xstat = rv;
    p->p_lflag |= jetsam_flags;
    proc_transend(p, 1);
    proc_unlock(p);
    proc_prepareexit(p, rv, perf_notify);
    /* Last thread to terminate will call proc_exit() */
    task_terminate_internal(task);
    return 0;
}

引证

Identifying high-memory use with jetsam event reports

Acquiring crash reports and diagnostic logs