APM - iOS OOM监控 XNU Jetsam

lxf2023-05-13 00:56:44

简介

在移动端设备中内存始终是紧张资源,受限于电量和闪存性能,移动端并没有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

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!