kern_memorystatus_notify.c [plain text]
#include <sys/kern_event.h>
#include <kern/sched_prim.h>
#include <kern/assert.h>
#include <kern/debug.h>
#include <kern/locks.h>
#include <kern/task.h>
#include <kern/thread.h>
#include <kern/host.h>
#include <kern/policy_internal.h>
#include <kern/thread_group.h>
#include <IOKit/IOBSD.h>
#include <libkern/libkern.h>
#include <mach/coalition.h>
#include <mach/mach_time.h>
#include <mach/task.h>
#include <mach/host_priv.h>
#include <mach/mach_host.h>
#include <os/log.h>
#include <pexpert/pexpert.h>
#include <sys/coalition.h>
#include <sys/kern_event.h>
#include <sys/proc.h>
#include <sys/proc_info.h>
#include <sys/reason.h>
#include <sys/signal.h>
#include <sys/signalvar.h>
#include <sys/sysctl.h>
#include <sys/sysproto.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/tree.h>
#include <sys/priv.h>
#include <vm/vm_pageout.h>
#include <vm/vm_protos.h>
#include <mach/machine/sdt.h>
#include <libkern/section_keywords.h>
#include <stdatomic.h>
#if CONFIG_FREEZE
#include <vm/vm_map.h>
#endif
#include <sys/kern_memorystatus.h>
#include <sys/kern_memorystatus_notify.h>
struct klist memorystatus_klist;
static lck_mtx_t memorystatus_klist_mutex;
static void memorystatus_klist_lock(void);
static void memorystatus_klist_unlock(void);
static int filt_memorystatusattach(struct knote *kn, struct kevent_qos_s *kev);
static void filt_memorystatusdetach(struct knote *kn);
static int filt_memorystatus(struct knote *kn, long hint);
static int filt_memorystatustouch(struct knote *kn, struct kevent_qos_s *kev);
static int filt_memorystatusprocess(struct knote *kn, struct kevent_qos_s *kev);
SECURITY_READ_ONLY_EARLY(struct filterops) memorystatus_filtops = {
.f_attach = filt_memorystatusattach,
.f_detach = filt_memorystatusdetach,
.f_event = filt_memorystatus,
.f_touch = filt_memorystatustouch,
.f_process = filt_memorystatusprocess,
};
enum {
kMemorystatusNoPressure = 0x1,
kMemorystatusPressure = 0x2,
kMemorystatusLowSwap = 0x4,
kMemorystatusProcLimitWarn = 0x8,
kMemorystatusProcLimitCritical = 0x10
};
#define INTER_NOTIFICATION_DELAY (250000)
#define VM_PRESSURE_DECREASED_SMOOTHING_PERIOD 5000
#define WARNING_NOTIFICATION_RESTING_PERIOD 25
#define CRITICAL_NOTIFICATION_RESTING_PERIOD 25
static vm_pressure_level_t convert_internal_pressure_level_to_dispatch_level(vm_pressure_level_t);
static boolean_t is_knote_registered_modify_task_pressure_bits(struct knote*, int, task_t, vm_pressure_level_t, vm_pressure_level_t);
static void memorystatus_klist_reset_all_for_level(vm_pressure_level_t pressure_level_to_clear);
static struct knote *vm_pressure_select_optimal_candidate_to_notify(struct klist *candidate_list, int level, boolean_t target_foreground_process);
static void vm_dispatch_memory_pressure(void);
kern_return_t memorystatus_update_vm_pressure(boolean_t target_foreground_process);
#if VM_PRESSURE_EVENTS
#if XNU_TARGET_OS_OSX
#define VM_PRESSURE_MINIMUM_RSIZE 10
#else
#define VM_PRESSURE_MINIMUM_RSIZE 6
#endif
static uint32_t vm_pressure_task_footprint_min = VM_PRESSURE_MINIMUM_RSIZE;
#if DEVELOPMENT || DEBUG
SYSCTL_UINT(_kern, OID_AUTO, memorystatus_vm_pressure_task_footprint_min, CTLFLAG_RW | CTLFLAG_LOCKED, &vm_pressure_task_footprint_min, 0, "");
#endif
vm_pressure_level_t memorystatus_vm_pressure_level = kVMPressureNormal;
boolean_t memorystatus_hwm_candidates = 0;
#endif
#if CONFIG_JETSAM
extern unsigned int memorystatus_available_pages;
extern unsigned int memorystatus_available_pages_pressure;
extern unsigned int memorystatus_available_pages_critical;
extern unsigned int memorystatus_available_pages_critical_base;
extern unsigned int memorystatus_available_pages_critical_idle_offset;
#else
extern uint64_t memorystatus_available_pages;
extern uint64_t memorystatus_available_pages_pressure;
extern uint64_t memorystatus_available_pages_critical;
#endif
extern lck_mtx_t memorystatus_jetsam_fg_band_lock;
uint32_t memorystatus_jetsam_fg_band_waiters = 0;
static uint64_t memorystatus_jetsam_fg_band_timestamp_ns = 0;
static uint64_t memorystatus_jetsam_fg_band_delay_ns = 5ull * 1000 * 1000 * 1000;
extern boolean_t(*volatile consider_buffer_cache_collect)(int);
#if DEVELOPMENT || DEBUG
SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_jetsam_fg_band_delay_ns, CTLFLAG_RW | CTLFLAG_LOCKED,
&memorystatus_jetsam_fg_band_delay_ns, "");
#endif
static int
filt_memorystatusattach(struct knote *kn, __unused struct kevent_qos_s *kev)
{
int error;
kn->kn_flags |= EV_CLEAR;
kn->kn_sdata = 0;
error = memorystatus_knote_register(kn);
if (error) {
knote_set_error(kn, error);
}
return 0;
}
static void
filt_memorystatusdetach(struct knote *kn)
{
memorystatus_knote_unregister(kn);
}
static int
filt_memorystatus(struct knote *kn __unused, long hint)
{
if (hint) {
switch (hint) {
case kMemorystatusNoPressure:
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PRESSURE_NORMAL) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PRESSURE_NORMAL;
}
break;
case kMemorystatusPressure:
if (memorystatus_vm_pressure_level == kVMPressureWarning || memorystatus_vm_pressure_level == kVMPressureUrgent) {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PRESSURE_WARN) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PRESSURE_WARN;
}
} else if (memorystatus_vm_pressure_level == kVMPressureCritical) {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PRESSURE_CRITICAL) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PRESSURE_CRITICAL;
}
}
break;
case kMemorystatusLowSwap:
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_LOW_SWAP) {
kn->kn_fflags = NOTE_MEMORYSTATUS_LOW_SWAP;
}
break;
case kMemorystatusProcLimitWarn:
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PROC_LIMIT_WARN;
}
break;
case kMemorystatusProcLimitCritical:
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL;
}
break;
default:
break;
}
}
#if 0
if (kn->kn_fflags != 0) {
proc_t knote_proc = knote_get_kq(kn)->kq_p;
pid_t knote_pid = knote_proc->p_pid;
printf("filt_memorystatus: sending kn 0x%lx (event 0x%x) for pid (%d)\n",
(unsigned long)kn, kn->kn_fflags, knote_pid);
}
#endif
return kn->kn_fflags != 0;
}
static int
filt_memorystatustouch(struct knote *kn, struct kevent_qos_s *kev)
{
int res;
int prev_kn_sfflags = 0;
memorystatus_klist_lock();
prev_kn_sfflags = kn->kn_sfflags;
kn->kn_sfflags = (kev->fflags & EVFILT_MEMORYSTATUS_ALL_MASK);
#if XNU_TARGET_OS_OSX
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN) {
if (prev_kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN) {
if (prev_kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_ACTIVE) {
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_ACTIVE;
}
if (prev_kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_INACTIVE) {
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_INACTIVE;
}
} else {
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_ACTIVE;
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_INACTIVE;
}
}
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL) {
if (prev_kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL) {
if (prev_kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_ACTIVE) {
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_ACTIVE;
}
if (prev_kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_INACTIVE) {
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_INACTIVE;
}
} else {
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_ACTIVE;
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_INACTIVE;
}
}
#endif
res = (kn->kn_fflags != 0);
memorystatus_klist_unlock();
return res;
}
static int
filt_memorystatusprocess(struct knote *kn, struct kevent_qos_s *kev)
{
int res = 0;
memorystatus_klist_lock();
if (kn->kn_fflags) {
knote_fill_kevent(kn, kev, 0);
res = 1;
}
memorystatus_klist_unlock();
return res;
}
static void
memorystatus_klist_lock(void)
{
lck_mtx_lock(&memorystatus_klist_mutex);
}
static void
memorystatus_klist_unlock(void)
{
lck_mtx_unlock(&memorystatus_klist_mutex);
}
void
memorystatus_kevent_init(lck_grp_t *grp, lck_attr_t *attr)
{
lck_mtx_init(&memorystatus_klist_mutex, grp, attr);
klist_init(&memorystatus_klist);
}
int
memorystatus_knote_register(struct knote *kn)
{
int error = 0;
memorystatus_klist_lock();
if ((kn->kn_sfflags & EVFILT_MEMORYSTATUS_ALL_MASK) == (unsigned int) kn->kn_sfflags) {
#if XNU_TARGET_OS_OSX
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN) {
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_ACTIVE;
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_INACTIVE;
}
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL) {
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_ACTIVE;
kn->kn_sfflags |= NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_INACTIVE;
}
#endif
KNOTE_ATTACH(&memorystatus_klist, kn);
} else {
error = ENOTSUP;
}
memorystatus_klist_unlock();
return error;
}
void
memorystatus_knote_unregister(struct knote *kn __unused)
{
memorystatus_klist_lock();
KNOTE_DETACH(&memorystatus_klist, kn);
memorystatus_klist_unlock();
}
#if VM_PRESSURE_EVENTS
#if CONFIG_MEMORYSTATUS
static inline int
memorystatus_send_note_internal(int event_code, int subclass, void *data, uint32_t data_length)
{
int ret;
struct kev_msg ev_msg;
ev_msg.vendor_code = KEV_VENDOR_APPLE;
ev_msg.kev_class = KEV_SYSTEM_CLASS;
ev_msg.kev_subclass = subclass;
ev_msg.event_code = event_code;
ev_msg.dv[0].data_length = data_length;
ev_msg.dv[0].data_ptr = data;
ev_msg.dv[1].data_length = 0;
ret = kev_post_msg(&ev_msg);
if (ret) {
printf("%s: kev_post_msg() failed, err %d\n", __func__, ret);
}
return ret;
}
int
memorystatus_send_note(int event_code, void *data, uint32_t data_length)
{
return memorystatus_send_note_internal(event_code, KEV_MEMORYSTATUS_SUBCLASS, data, data_length);
}
int
memorystatus_send_dirty_status_change_note(void *data, uint32_t data_length)
{
return memorystatus_send_note_internal(kDirtyStatusChangeNote, KEV_DIRTYSTATUS_SUBCLASS, data, data_length);
}
boolean_t
memorystatus_warn_process(const proc_t p, __unused boolean_t is_active, __unused boolean_t is_fatal, boolean_t limit_exceeded)
{
assert(p == current_proc());
pid_t pid = p->p_pid;
boolean_t ret = FALSE;
boolean_t found_knote = FALSE;
struct knote *kn = NULL;
int send_knote_count = 0;
uint32_t platform;
platform = proc_platform(p);
memorystatus_klist_lock();
SLIST_FOREACH(kn, &memorystatus_klist, kn_selnext) {
proc_t knote_proc = knote_get_kq(kn)->kq_p;
pid_t knote_pid = knote_proc->p_pid;
if (knote_pid == pid) {
if (platform == PLATFORM_MACOS || platform == PLATFORM_MACCATALYST || platform == PLATFORM_DRIVERKIT) {
if (!limit_exceeded) {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN) {
found_knote = TRUE;
if (!is_fatal) {
if (is_active) {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_ACTIVE) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PROC_LIMIT_WARN;
kn->kn_sfflags &= ~NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_ACTIVE;
send_knote_count++;
}
} else {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_INACTIVE) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PROC_LIMIT_WARN;
kn->kn_sfflags &= ~NOTE_MEMORYSTATUS_PROC_LIMIT_WARN_INACTIVE;
send_knote_count++;
}
}
} else {
kn->kn_fflags = NOTE_MEMORYSTATUS_PROC_LIMIT_WARN;
send_knote_count++;
}
}
} else {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL) {
found_knote = TRUE;
if (!is_fatal) {
if (is_active) {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_ACTIVE) {
kn->kn_sfflags &= ~NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_ACTIVE;
kn->kn_fflags = NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL;
send_knote_count++;
}
} else {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_INACTIVE) {
kn->kn_sfflags &= ~NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL_INACTIVE;
kn->kn_fflags = NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL;
send_knote_count++;
}
}
} else {
panic("Caught sending pid %d a critical warning for a fatal limit.\n", pid);
}
}
}
} else {
if (!limit_exceeded) {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_WARN) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PROC_LIMIT_WARN;
found_knote = TRUE;
send_knote_count++;
} else if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PRESSURE_CRITICAL) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PRESSURE_CRITICAL;
found_knote = TRUE;
send_knote_count++;
} else if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PRESSURE_WARN) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PRESSURE_WARN;
found_knote = TRUE;
send_knote_count++;
}
} else {
if (kn->kn_sfflags & NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL) {
kn->kn_fflags = NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL;
found_knote = TRUE;
send_knote_count++;
}
}
}
}
}
if (found_knote) {
if (send_knote_count > 0) {
KNOTE(&memorystatus_klist, 0);
}
ret = TRUE;
}
memorystatus_klist_unlock();
return ret;
}
int
memorystatus_low_mem_privileged_listener(uint32_t op_flags)
{
boolean_t set_privilege = FALSE;
if (op_flags == MEMORYSTATUS_CMD_PRIVILEGED_LISTENER_ENABLE) {
set_privilege = TRUE;
} else if (op_flags == MEMORYSTATUS_CMD_PRIVILEGED_LISTENER_DISABLE) {
set_privilege = FALSE;
} else {
return EINVAL;
}
return task_low_mem_privileged_listener(current_task(), set_privilege, NULL);
}
int
memorystatus_send_pressure_note(pid_t pid)
{
MEMORYSTATUS_DEBUG(1, "memorystatus_send_pressure_note(): pid %d\n", pid);
return memorystatus_send_note(kMemorystatusPressureNote, &pid, sizeof(pid));
}
boolean_t
memorystatus_is_foreground_locked(proc_t p)
{
return (p->p_memstat_effectivepriority == JETSAM_PRIORITY_FOREGROUND) ||
(p->p_memstat_effectivepriority == JETSAM_PRIORITY_FOREGROUND_SUPPORT);
}
void
memorystatus_proc_flags_unsafe(void * v, boolean_t *is_dirty, boolean_t *is_dirty_tracked, boolean_t *allow_idle_exit)
{
if (!v) {
*is_dirty = FALSE;
*is_dirty_tracked = FALSE;
*allow_idle_exit = FALSE;
} else {
proc_t p = (proc_t)v;
*is_dirty = (p->p_memstat_dirty & P_DIRTY_IS_DIRTY) != 0;
*is_dirty_tracked = (p->p_memstat_dirty & P_DIRTY_TRACK) != 0;
*allow_idle_exit = (p->p_memstat_dirty & P_DIRTY_ALLOW_IDLE_EXIT) != 0;
}
}
boolean_t
memorystatus_bg_pressure_eligible(proc_t p)
{
boolean_t eligible = FALSE;
proc_list_lock();
MEMORYSTATUS_DEBUG(1, "memorystatus_bg_pressure_eligible: pid %d, state 0x%x\n", p->p_pid, p->p_memstat_state);
if (!(p->p_memstat_state & (P_MEMSTAT_TERMINATED | P_MEMSTAT_LOCKED | P_MEMSTAT_SUSPENDED | P_MEMSTAT_FROZEN))) {
eligible = TRUE;
}
if (p->p_memstat_effectivepriority < JETSAM_PRIORITY_BACKGROUND_OPPORTUNISTIC) {
eligible = FALSE;
}
proc_list_unlock();
return eligible;
}
void
memorystatus_send_low_swap_note(void)
{
struct knote *kn = NULL;
memorystatus_klist_lock();
SLIST_FOREACH(kn, &memorystatus_klist, kn_selnext) {
if (is_knote_registered_modify_task_pressure_bits(kn, NOTE_MEMORYSTATUS_LOW_SWAP, NULL, 0, 0) == TRUE) {
KNOTE(&memorystatus_klist, kMemorystatusLowSwap);
break;
}
}
memorystatus_klist_unlock();
}
#endif
static boolean_t
is_knote_registered_modify_task_pressure_bits(struct knote *kn_max, int knote_pressure_level, task_t task, vm_pressure_level_t pressure_level_to_clear, vm_pressure_level_t pressure_level_to_set)
{
if (kn_max->kn_sfflags & knote_pressure_level) {
if (pressure_level_to_clear && task_has_been_notified(task, pressure_level_to_clear) == TRUE) {
task_clear_has_been_notified(task, pressure_level_to_clear);
}
task_mark_has_been_notified(task, pressure_level_to_set);
return TRUE;
}
return FALSE;
}
static void
memorystatus_klist_reset_all_for_level(vm_pressure_level_t pressure_level_to_clear)
{
struct knote *kn = NULL;
memorystatus_klist_lock();
SLIST_FOREACH(kn, &memorystatus_klist, kn_selnext) {
proc_t p = PROC_NULL;
struct task* t = TASK_NULL;
p = knote_get_kq(kn)->kq_p;
proc_list_lock();
if (p != proc_ref_locked(p)) {
p = PROC_NULL;
proc_list_unlock();
continue;
}
proc_list_unlock();
t = (struct task *)(p->task);
task_clear_has_been_notified(t, pressure_level_to_clear);
proc_rele(p);
}
memorystatus_klist_unlock();
}
void
consider_vm_pressure_events(void)
{
vm_dispatch_memory_pressure();
}
static void
vm_dispatch_memory_pressure(void)
{
memorystatus_update_vm_pressure(FALSE);
}
static struct knote *
vm_pressure_select_optimal_candidate_to_notify(struct klist *candidate_list, int level, boolean_t target_foreground_process)
{
struct knote *kn = NULL, *kn_max = NULL;
uint64_t resident_max = 0;
int selected_task_importance = 0;
static int pressure_snapshot = -1;
boolean_t pressure_increase = FALSE;
if (pressure_snapshot == -1) {
pressure_snapshot = level;
pressure_increase = TRUE;
} else {
if (level && (level >= pressure_snapshot)) {
pressure_increase = TRUE;
} else {
pressure_increase = FALSE;
}
pressure_snapshot = level;
}
if (pressure_increase == TRUE) {
selected_task_importance = INT_MAX;
} else {
selected_task_importance = 0;
}
SLIST_FOREACH(kn, candidate_list, kn_selnext) {
uint64_t resident_size = 0;
proc_t p = PROC_NULL;
struct task* t = TASK_NULL;
int curr_task_importance = 0;
boolean_t consider_knote = FALSE;
boolean_t privileged_listener = FALSE;
p = knote_get_kq(kn)->kq_p;
proc_list_lock();
if (p != proc_ref_locked(p)) {
p = PROC_NULL;
proc_list_unlock();
continue;
}
proc_list_unlock();
#if CONFIG_MEMORYSTATUS
if (target_foreground_process == TRUE && !memorystatus_is_foreground_locked(p)) {
proc_rele(p);
continue;
}
#endif
t = (struct task *)(p->task);
vm_pressure_level_t dispatch_level = convert_internal_pressure_level_to_dispatch_level(level);
if ((kn->kn_sfflags & dispatch_level) == 0) {
proc_rele(p);
continue;
}
#if CONFIG_MEMORYSTATUS
if (target_foreground_process == FALSE && !memorystatus_bg_pressure_eligible(p)) {
VM_PRESSURE_DEBUG(1, "[vm_pressure] skipping process %d\n", p->p_pid);
proc_rele(p);
continue;
}
#endif
#if XNU_TARGET_OS_OSX
curr_task_importance = task_importance_estimate(t);
#else
curr_task_importance = p->p_memstat_effectivepriority;
#endif
if (level > 0) {
if (task_has_been_notified(t, level) == FALSE) {
if (task_low_mem_privileged_listener(t, FALSE, &privileged_listener) == 0) {
if (privileged_listener) {
kn_max = kn;
proc_rele(p);
goto done_scanning;
}
}
} else {
proc_rele(p);
continue;
}
} else if (level == 0) {
if ((task_has_been_notified(t, kVMPressureWarning) == FALSE) && (task_has_been_notified(t, kVMPressureCritical) == FALSE)) {
proc_rele(p);
continue;
}
}
resident_size = (get_task_phys_footprint(t)) / (1024 * 1024ULL);
if (resident_size >= vm_pressure_task_footprint_min) {
if (level > 0) {
if (pressure_increase) {
if ((curr_task_importance < selected_task_importance) ||
((curr_task_importance == selected_task_importance) && (resident_size > resident_max))) {
consider_knote = TRUE;
}
} else {
if ((curr_task_importance > selected_task_importance) ||
((curr_task_importance == selected_task_importance) && (resident_size > resident_max))) {
consider_knote = TRUE;
}
}
} else if (level == 0) {
if ((curr_task_importance > selected_task_importance) ||
((curr_task_importance == selected_task_importance) && (resident_size > resident_max))) {
consider_knote = TRUE;
}
}
if (consider_knote) {
resident_max = resident_size;
kn_max = kn;
selected_task_importance = curr_task_importance;
consider_knote = FALSE;
}
} else {
VM_PRESSURE_DEBUG(0, "[vm_pressure] threshold failed for pid %d with %llu resident...\n", p->p_pid, resident_size);
}
proc_rele(p);
}
done_scanning:
if (kn_max) {
VM_DEBUG_CONSTANT_EVENT(vm_pressure_event, VM_PRESSURE_EVENT, DBG_FUNC_NONE, knote_get_kq(kn_max)->kq_p->p_pid, resident_max, 0, 0);
VM_PRESSURE_DEBUG(1, "[vm_pressure] sending event to pid %d with %llu resident\n", knote_get_kq(kn_max)->kq_p->p_pid, resident_max);
}
return kn_max;
}
uint64_t next_warning_notification_sent_at_ts = 0;
uint64_t next_critical_notification_sent_at_ts = 0;
boolean_t memorystatus_manual_testing_on = FALSE;
vm_pressure_level_t memorystatus_manual_testing_level = kVMPressureNormal;
kern_return_t
memorystatus_update_vm_pressure(boolean_t target_foreground_process)
{
struct knote *kn_max = NULL;
struct knote *kn_cur = NULL, *kn_temp = NULL;
pid_t target_pid = -1;
struct klist dispatch_klist = { NULL };
proc_t target_proc = PROC_NULL;
struct task *task = NULL;
boolean_t found_candidate = FALSE;
static vm_pressure_level_t level_snapshot = kVMPressureNormal;
static vm_pressure_level_t prev_level_snapshot = kVMPressureNormal;
boolean_t smoothing_window_started = FALSE;
struct timeval smoothing_window_start_tstamp = {0, 0};
struct timeval curr_tstamp = {0, 0};
int64_t elapsed_msecs = 0;
uint64_t curr_ts = mach_absolute_time();
#if !CONFIG_JETSAM
#define MAX_IDLE_KILLS 100
int idle_kill_counter = 0;
while ((memorystatus_vm_pressure_level != kVMPressureNormal) && (idle_kill_counter < MAX_IDLE_KILLS)) {
if (memorystatus_idle_exit_from_VM() == FALSE) {
break;
}
idle_kill_counter++;
if (memorystatus_manual_testing_on == TRUE) {
} else {
delay(1000000);
}
}
#endif
if (level_snapshot != kVMPressureNormal) {
level_snapshot = memorystatus_vm_pressure_level;
if (level_snapshot == kVMPressureWarning || level_snapshot == kVMPressureUrgent) {
if (next_warning_notification_sent_at_ts) {
if (curr_ts < next_warning_notification_sent_at_ts) {
delay(INTER_NOTIFICATION_DELAY * 4 );
return KERN_SUCCESS;
}
next_warning_notification_sent_at_ts = 0;
memorystatus_klist_reset_all_for_level(kVMPressureWarning);
}
} else if (level_snapshot == kVMPressureCritical) {
if (next_critical_notification_sent_at_ts) {
if (curr_ts < next_critical_notification_sent_at_ts) {
delay(INTER_NOTIFICATION_DELAY * 4 );
return KERN_SUCCESS;
}
next_critical_notification_sent_at_ts = 0;
memorystatus_klist_reset_all_for_level(kVMPressureCritical);
}
}
}
while (1) {
level_snapshot = memorystatus_vm_pressure_level;
if (prev_level_snapshot > level_snapshot) {
if (smoothing_window_started == FALSE) {
smoothing_window_started = TRUE;
microuptime(&smoothing_window_start_tstamp);
}
microuptime(&curr_tstamp);
timevalsub(&curr_tstamp, &smoothing_window_start_tstamp);
elapsed_msecs = curr_tstamp.tv_sec * 1000 + curr_tstamp.tv_usec / 1000;
if (elapsed_msecs < VM_PRESSURE_DECREASED_SMOOTHING_PERIOD) {
delay(INTER_NOTIFICATION_DELAY);
continue;
}
}
prev_level_snapshot = level_snapshot;
smoothing_window_started = FALSE;
memorystatus_klist_lock();
kn_max = vm_pressure_select_optimal_candidate_to_notify(&memorystatus_klist, level_snapshot, target_foreground_process);
if (kn_max == NULL) {
memorystatus_klist_unlock();
if (level_snapshot != kVMPressureNormal) {
if (level_snapshot == kVMPressureWarning || level_snapshot == kVMPressureUrgent) {
nanoseconds_to_absolutetime(WARNING_NOTIFICATION_RESTING_PERIOD * NSEC_PER_SEC, &curr_ts);
next_warning_notification_sent_at_ts = mach_absolute_time() + curr_ts;
}
if (level_snapshot == kVMPressureCritical) {
nanoseconds_to_absolutetime(CRITICAL_NOTIFICATION_RESTING_PERIOD * NSEC_PER_SEC, &curr_ts);
next_critical_notification_sent_at_ts = mach_absolute_time() + curr_ts;
}
}
return KERN_FAILURE;
}
target_proc = knote_get_kq(kn_max)->kq_p;
proc_list_lock();
if (target_proc != proc_ref_locked(target_proc)) {
target_proc = PROC_NULL;
proc_list_unlock();
memorystatus_klist_unlock();
continue;
}
proc_list_unlock();
target_pid = target_proc->p_pid;
task = (struct task *)(target_proc->task);
if (level_snapshot != kVMPressureNormal) {
if (level_snapshot == kVMPressureWarning || level_snapshot == kVMPressureUrgent) {
if (is_knote_registered_modify_task_pressure_bits(kn_max, NOTE_MEMORYSTATUS_PRESSURE_WARN, task, 0, kVMPressureWarning) == TRUE) {
found_candidate = TRUE;
}
} else {
if (level_snapshot == kVMPressureCritical) {
if (is_knote_registered_modify_task_pressure_bits(kn_max, NOTE_MEMORYSTATUS_PRESSURE_CRITICAL, task, 0, kVMPressureCritical) == TRUE) {
found_candidate = TRUE;
}
}
}
} else {
if (kn_max->kn_sfflags & NOTE_MEMORYSTATUS_PRESSURE_NORMAL) {
task_clear_has_been_notified(task, kVMPressureWarning);
task_clear_has_been_notified(task, kVMPressureCritical);
found_candidate = TRUE;
}
}
if (found_candidate == FALSE) {
proc_rele(target_proc);
memorystatus_klist_unlock();
continue;
}
SLIST_FOREACH_SAFE(kn_cur, &memorystatus_klist, kn_selnext, kn_temp) {
int knote_pressure_level = convert_internal_pressure_level_to_dispatch_level(level_snapshot);
if (is_knote_registered_modify_task_pressure_bits(kn_cur, knote_pressure_level, task, 0, level_snapshot) == TRUE) {
proc_t knote_proc = knote_get_kq(kn_cur)->kq_p;
pid_t knote_pid = knote_proc->p_pid;
if (knote_pid == target_pid) {
KNOTE_DETACH(&memorystatus_klist, kn_cur);
KNOTE_ATTACH(&dispatch_klist, kn_cur);
}
}
}
KNOTE(&dispatch_klist, (level_snapshot != kVMPressureNormal) ? kMemorystatusPressure : kMemorystatusNoPressure);
SLIST_FOREACH_SAFE(kn_cur, &dispatch_klist, kn_selnext, kn_temp) {
KNOTE_DETACH(&dispatch_klist, kn_cur);
KNOTE_ATTACH(&memorystatus_klist, kn_cur);
}
memorystatus_klist_unlock();
microuptime(&target_proc->vm_pressure_last_notify_tstamp);
proc_rele(target_proc);
if (memorystatus_manual_testing_on == TRUE && target_foreground_process == TRUE) {
break;
}
if (memorystatus_manual_testing_on == TRUE) {
} else {
uint32_t sleep_interval = INTER_NOTIFICATION_DELAY;
#if CONFIG_JETSAM
unsigned int page_delta = 0;
unsigned int skip_delay_page_threshold = 0;
assert(memorystatus_available_pages_pressure >= memorystatus_available_pages_critical_base);
page_delta = (memorystatus_available_pages_pressure - memorystatus_available_pages_critical_base) / 2;
skip_delay_page_threshold = memorystatus_available_pages_pressure - page_delta;
if (memorystatus_available_pages <= skip_delay_page_threshold) {
sleep_interval = 0;
}
#endif
if (sleep_interval) {
delay(sleep_interval);
}
}
}
return KERN_SUCCESS;
}
static uint32_t
convert_internal_pressure_level_to_dispatch_level(vm_pressure_level_t internal_pressure_level)
{
uint32_t dispatch_level = NOTE_MEMORYSTATUS_PRESSURE_NORMAL;
switch (internal_pressure_level) {
case kVMPressureNormal:
{
dispatch_level = NOTE_MEMORYSTATUS_PRESSURE_NORMAL;
break;
}
case kVMPressureWarning:
case kVMPressureUrgent:
{
dispatch_level = NOTE_MEMORYSTATUS_PRESSURE_WARN;
break;
}
case kVMPressureCritical:
{
dispatch_level = NOTE_MEMORYSTATUS_PRESSURE_CRITICAL;
break;
}
default:
break;
}
return dispatch_level;
}
void
memorystatus_issue_fg_band_notify(void)
{
uint64_t now;
lck_mtx_lock(&memorystatus_jetsam_fg_band_lock);
absolutetime_to_nanoseconds(mach_absolute_time(), &now);
if (now - memorystatus_jetsam_fg_band_timestamp_ns < memorystatus_jetsam_fg_band_delay_ns) {
lck_mtx_unlock(&memorystatus_jetsam_fg_band_lock);
return;
}
if (memorystatus_jetsam_fg_band_waiters > 0) {
thread_wakeup(&memorystatus_jetsam_fg_band_waiters);
memorystatus_jetsam_fg_band_waiters = 0;
memorystatus_jetsam_fg_band_timestamp_ns = now;
}
lck_mtx_unlock(&memorystatus_jetsam_fg_band_lock);
if (consider_buffer_cache_collect != NULL) {
(void)(*consider_buffer_cache_collect)(1);
}
}
static int
sysctl_memorystatus_vm_pressure_level SYSCTL_HANDLER_ARGS
{
#pragma unused(arg1, arg2, oidp)
#if !XNU_TARGET_OS_OSX
int error = 0;
error = priv_check_cred(kauth_cred_get(), PRIV_VM_PRESSURE, 0);
if (error) {
return error;
}
#endif
uint32_t dispatch_level = convert_internal_pressure_level_to_dispatch_level(memorystatus_vm_pressure_level);
return SYSCTL_OUT(req, &dispatch_level, sizeof(dispatch_level));
}
#if DEBUG || DEVELOPMENT
SYSCTL_PROC(_kern, OID_AUTO, memorystatus_vm_pressure_level, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_LOCKED,
0, 0, &sysctl_memorystatus_vm_pressure_level, "I", "");
#else
SYSCTL_PROC(_kern, OID_AUTO, memorystatus_vm_pressure_level, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_LOCKED | CTLFLAG_MASKED,
0, 0, &sysctl_memorystatus_vm_pressure_level, "I", "");
#endif
#define TEST_LOW_MEMORY_TRIGGER_ONE 1
#define TEST_LOW_MEMORY_TRIGGER_ALL 2
#define TEST_PURGEABLE_TRIGGER_ONE 3
#define TEST_PURGEABLE_TRIGGER_ALL 4
#define TEST_LOW_MEMORY_PURGEABLE_TRIGGER_ONE 5
#define TEST_LOW_MEMORY_PURGEABLE_TRIGGER_ALL 6
static int
sysctl_memorypressure_manual_trigger SYSCTL_HANDLER_ARGS
{
#pragma unused(arg1, arg2)
int level = 0;
int error = 0;
int pressure_level = 0;
int trigger_request = 0;
int force_purge;
error = sysctl_handle_int(oidp, &level, 0, req);
if (error || !req->newptr) {
return error;
}
memorystatus_manual_testing_on = TRUE;
trigger_request = (level >> 16) & 0xFFFF;
pressure_level = (level & 0xFFFF);
if (trigger_request < TEST_LOW_MEMORY_TRIGGER_ONE ||
trigger_request > TEST_LOW_MEMORY_PURGEABLE_TRIGGER_ALL) {
return EINVAL;
}
switch (pressure_level) {
case NOTE_MEMORYSTATUS_PRESSURE_NORMAL:
case NOTE_MEMORYSTATUS_PRESSURE_WARN:
case NOTE_MEMORYSTATUS_PRESSURE_CRITICAL:
break;
default:
return EINVAL;
}
if (pressure_level == NOTE_MEMORYSTATUS_PRESSURE_NORMAL) {
memorystatus_manual_testing_level = kVMPressureNormal;
force_purge = 0;
} else if (pressure_level == NOTE_MEMORYSTATUS_PRESSURE_WARN) {
memorystatus_manual_testing_level = kVMPressureWarning;
force_purge = vm_pageout_state.memorystatus_purge_on_warning;
} else if (pressure_level == NOTE_MEMORYSTATUS_PRESSURE_CRITICAL) {
memorystatus_manual_testing_level = kVMPressureCritical;
force_purge = vm_pageout_state.memorystatus_purge_on_critical;
}
memorystatus_vm_pressure_level = memorystatus_manual_testing_level;
switch (trigger_request) {
case TEST_PURGEABLE_TRIGGER_ONE:
case TEST_LOW_MEMORY_PURGEABLE_TRIGGER_ONE:
if (force_purge == 0) {
break;
}
vm_purgeable_object_purge_one_unlocked(force_purge);
break;
case TEST_PURGEABLE_TRIGGER_ALL:
case TEST_LOW_MEMORY_PURGEABLE_TRIGGER_ALL:
if (force_purge == 0) {
break;
}
while (vm_purgeable_object_purge_one_unlocked(force_purge)) {
;
}
break;
}
if ((trigger_request == TEST_LOW_MEMORY_TRIGGER_ONE) ||
(trigger_request == TEST_LOW_MEMORY_PURGEABLE_TRIGGER_ONE)) {
memorystatus_update_vm_pressure(TRUE);
}
if ((trigger_request == TEST_LOW_MEMORY_TRIGGER_ALL) ||
(trigger_request == TEST_LOW_MEMORY_PURGEABLE_TRIGGER_ALL)) {
while (memorystatus_update_vm_pressure(FALSE) == KERN_SUCCESS) {
continue;
}
}
if (pressure_level == NOTE_MEMORYSTATUS_PRESSURE_NORMAL) {
memorystatus_manual_testing_on = FALSE;
}
return 0;
}
SYSCTL_PROC(_kern, OID_AUTO, memorypressure_manual_trigger, CTLTYPE_INT | CTLFLAG_WR | CTLFLAG_LOCKED | CTLFLAG_MASKED,
0, 0, &sysctl_memorypressure_manual_trigger, "I", "");
SYSCTL_INT(_kern, OID_AUTO, memorystatus_purge_on_warning, CTLFLAG_RW | CTLFLAG_LOCKED, &vm_pageout_state.memorystatus_purge_on_warning, 0, "");
SYSCTL_INT(_kern, OID_AUTO, memorystatus_purge_on_urgent, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, &vm_pageout_state.memorystatus_purge_on_urgent, 0, "");
SYSCTL_INT(_kern, OID_AUTO, memorystatus_purge_on_critical, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, &vm_pageout_state.memorystatus_purge_on_critical, 0, "");
#if (XNU_TARGET_OS_OSX && __arm64__)
extern int vm_pressure_level_transition_threshold;
SYSCTL_INT(_kern, OID_AUTO, vm_pressure_level_transition_threshold, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, &vm_pressure_level_transition_threshold, 0, "");
#endif
#if DEBUG || DEVELOPMENT
SYSCTL_UINT(_kern, OID_AUTO, memorystatus_vm_pressure_events_enabled, CTLFLAG_RW | CTLFLAG_LOCKED, &vm_pressure_events_enabled, 0, "");
#if 0
#if CONFIG_JETSAM && VM_PRESSURE_EVENTS
static boolean_t
memorystatus_issue_pressure_kevent(boolean_t pressured)
{
memorystatus_klist_lock();
KNOTE(&memorystatus_klist, pressured ? kMemorystatusPressure : kMemorystatusNoPressure);
memorystatus_klist_unlock();
return TRUE;
}
#endif
#endif
static int
sysctl_memorystatus_vm_pressure_send SYSCTL_HANDLER_ARGS
{
#pragma unused(arg1, arg2)
if (!kauth_cred_issuser(kauth_cred_get()) && !IOTaskHasEntitlement(current_task(), MEMORYSTATUS_ENTITLEMENT)) {
return EPERM;
}
int error = 0, pid = 0;
struct knote *kn = NULL;
boolean_t found_knote = FALSE;
int fflags = 0;
uint64_t value = 0;
error = sysctl_handle_quad(oidp, &value, 0, req);
if (error || !req->newptr) {
return error;
}
pid = (int)(value & 0xFFFFFFFF);
fflags = (int)((value >> 32) & 0xFFFFFFFF);
if (fflags == 0) {
fflags = NOTE_MEMORYSTATUS_PRESSURE_WARN;
}
if (fflags == NOTE_MEMORYSTATUS_JETSAM_FG_BAND) {
memorystatus_issue_fg_band_notify();
return error;
}
if (!((fflags == NOTE_MEMORYSTATUS_PRESSURE_NORMAL) ||
(fflags == NOTE_MEMORYSTATUS_PRESSURE_WARN) ||
(fflags == NOTE_MEMORYSTATUS_PRESSURE_CRITICAL) ||
(fflags == NOTE_MEMORYSTATUS_LOW_SWAP) ||
(fflags == NOTE_MEMORYSTATUS_PROC_LIMIT_WARN) ||
(fflags == NOTE_MEMORYSTATUS_PROC_LIMIT_CRITICAL) ||
(((fflags & NOTE_MEMORYSTATUS_MSL_STATUS) != 0 &&
((fflags & ~NOTE_MEMORYSTATUS_MSL_STATUS) == 0))))) {
printf("memorystatus_vm_pressure_send: notification [0x%x] not supported \n", fflags);
error = 1;
return error;
}
memorystatus_klist_lock();
SLIST_FOREACH(kn, &memorystatus_klist, kn_selnext) {
proc_t knote_proc = knote_get_kq(kn)->kq_p;
pid_t knote_pid = knote_proc->p_pid;
if (knote_pid == pid) {
kn->kn_fflags = fflags;
found_knote = TRUE;
}
}
if (found_knote) {
KNOTE(&memorystatus_klist, 0);
printf("memorystatus_vm_pressure_send: (value 0x%llx) notification [0x%x] sent to process [%d] \n", value, fflags, pid);
error = 0;
} else {
printf("memorystatus_vm_pressure_send: (value 0x%llx) notification [0x%x] not sent to process [%d] (none registered?)\n", value, fflags, pid);
error = 1;
}
memorystatus_klist_unlock();
return error;
}
SYSCTL_PROC(_kern, OID_AUTO, memorystatus_vm_pressure_send, CTLTYPE_QUAD | CTLFLAG_WR | CTLFLAG_LOCKED | CTLFLAG_MASKED | CTLFLAG_ANYBODY,
0, 0, &sysctl_memorystatus_vm_pressure_send, "Q", "");
#endif
#endif