#include <libkern/libkern.h>
#include <mach/mach_types.h>
#include <mach/task.h>
#include <sys/proc_internal.h>
#include <sys/event.h>
#include <sys/eventvar.h>
#include <kern/locks.h>
#include <sys/queue.h>
#include <kern/vm_pressure.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/sysctl.h>
void vm_pressure_klist_lock(void);
void vm_pressure_klist_unlock(void);
void vm_dispatch_memory_pressure(void);
int vm_try_terminate_candidates(void);
int vm_try_pressure_candidates(void);
void vm_recharge_active_list(void);
struct klist vm_pressure_klist;
struct klist vm_pressure_klist_dormant;
void vm_pressure_klist_lock(void) {
lck_mtx_lock(&vm_pressure_klist_mutex);
}
void vm_pressure_klist_unlock(void) {
lck_mtx_unlock(&vm_pressure_klist_mutex);
}
int vm_knote_register(struct knote *kn) {
int rv = 0;
vm_pressure_klist_lock();
if ((kn->kn_sfflags & (NOTE_VM_PRESSURE))) {
#if DEBUG
printf("[vm_pressure] process %d registering pressure notification\n", kn->kn_kq->kq_p->p_pid);
#endif
KNOTE_ATTACH(&vm_pressure_klist, kn);
} else
rv = ENOTSUP;
vm_pressure_klist_unlock();
return rv;
}
void vm_knote_unregister(struct knote *kn) {
struct knote *kn_temp;
vm_pressure_klist_lock();
#if DEBUG
printf("[vm_pressure] process %d cancelling pressure notification\n", kn->kn_kq->kq_p->p_pid);
#endif
SLIST_FOREACH(kn_temp, &vm_pressure_klist, kn_selnext) {
if (kn_temp == kn) {
KNOTE_DETACH(&vm_pressure_klist, kn);
vm_pressure_klist_unlock();
return;
}
}
KNOTE_DETACH(&vm_pressure_klist_dormant, kn);
vm_pressure_klist_unlock();
}
void consider_pressure_events(void) {
vm_dispatch_memory_pressure();
}
void vm_dispatch_memory_pressure(void) {
vm_pressure_klist_lock();
if (!SLIST_EMPTY(&vm_pressure_klist)) {
#if DEBUG
printf("[vm_pressure] vm_dispatch_memory_pressure\n");
#endif
if (vm_try_pressure_candidates()) {
vm_pressure_klist_unlock();
return;
}
}
#if DEBUG
printf("[vm_pressure] could not find suitable event candidate\n");
#endif
vm_recharge_active_list();
vm_pressure_klist_unlock();
}
int vm_try_pressure_candidates(void) {
#define VM_PRESSURE_MINIMUM_RSIZE (10 * 1024 * 1024)
struct proc *p_max = NULL;
unsigned int resident_max = 0;
struct knote *kn_max = NULL;
struct knote *kn;
SLIST_FOREACH(kn, &vm_pressure_klist, kn_selnext) {
if ( (kn != NULL ) && ( kn->kn_kq != NULL ) && ( kn->kn_kq->kq_p != NULL ) ) {
if (kn->kn_sfflags & NOTE_VM_PRESSURE) {
struct proc *p = kn->kn_kq->kq_p;
if (!(kn->kn_status & KN_DISABLED)) {
kern_return_t kr = KERN_SUCCESS;
struct task *t = (struct task *)(p->task);
struct task_basic_info basic_info;
mach_msg_type_number_t size = TASK_BASIC_INFO_COUNT;
if( ( kr = task_info(t, TASK_BASIC_INFO, (task_info_t)(&basic_info), &size)) == KERN_SUCCESS ) {
unsigned int resident_size = basic_info.resident_size;
if (resident_size >= VM_PRESSURE_MINIMUM_RSIZE) {
if (resident_size > resident_max) {
p_max = p;
resident_max = resident_size;
kn_max = kn;
}
} else {
#if DEBUG
#endif
}
} else {
#if DEBUG
printf("[vm_pressure] task_info for pid %d failed with %d\n", p->p_pid, kr);
#endif
}
} else {
#if DEBUG
printf("[vm_pressure] pid %d currently disabled, skipping...\n", p->p_pid);
#endif
}
}
} else {
#if DEBUG
if (kn == NULL) {
printf("[vm_pressure] kn is NULL\n");
} else if (kn->kn_kq == NULL) {
printf("[vm_pressure] kn->kn_kq is NULL\n");
} else if (kn->kn_kq->kq_p == NULL) {
printf("[vm_pressure] kn->kn_kq->kq_p is NULL\n");
}
#endif
}
}
if (kn_max == NULL) return 0;
#if DEBUG
printf("[vm_pressure] sending event to pid %d with %u resident\n", kn_max->kn_kq->kq_p->p_pid, resident_max);
#endif
KNOTE_DETACH(&vm_pressure_klist, kn_max);
struct klist dispatch_klist = { NULL };
KNOTE_ATTACH(&dispatch_klist, kn_max);
KNOTE(&dispatch_klist, NOTE_VM_PRESSURE);
KNOTE_ATTACH(&vm_pressure_klist_dormant, kn_max);
return 1;
}
void vm_recharge_active_list(void) {
if (!SLIST_EMPTY(&vm_pressure_klist_dormant)) {
#if DEBUG
printf("[vm_pressure] recharging main list from dormant list\n");
#endif
struct knote *kn;
while (!SLIST_EMPTY(&vm_pressure_klist_dormant)) {
kn = SLIST_FIRST(&vm_pressure_klist_dormant);
SLIST_REMOVE_HEAD(&vm_pressure_klist_dormant, kn_selnext);
SLIST_INSERT_HEAD(&vm_pressure_klist, kn, kn_selnext);
}
}
}