kern_memorystatus.c [plain text]
#include <sys/kern_event.h>
#include <sys/kern_memorystatus.h>
#include <kern/sched_prim.h>
#include <kern/lock.h>
#include <kern/task.h>
#include <kern/thread.h>
#include <libkern/libkern.h>
#include <mach/task.h>
#include <mach/task_info.h>
#include <sys/proc.h>
#include <sys/signal.h>
#include <sys/signalvar.h>
#include <sys/sysctl.h>
#include <sys/wait.h>
extern unsigned int vm_page_free_count;
extern unsigned int vm_page_active_count;
extern unsigned int vm_page_inactive_count;
extern unsigned int vm_page_purgeable_count;
extern unsigned int vm_page_wire_count;
static void kern_memorystatus_thread(void);
int kern_memorystatus_wakeup = 0;
int kern_memorystatus_level = 0;
int kern_memorystatus_last_level = 0;
unsigned int kern_memorystatus_kev_failure_count = 0;
int kern_memorystatus_level_critical = 5;
#define kern_memorystatus_level_highwater (kern_memorystatus_level_critical + 5)
static struct {
jetsam_kernel_stats_t stats;
size_t entry_count;
jetsam_snapshot_entry_t entries[kMaxSnapshotEntries];
} jetsam_snapshot;
static jetsam_priority_entry_t jetsam_priority_list[kMaxPriorityEntries];
#define jetsam_snapshot_list jetsam_snapshot.entries
static int jetsam_priority_list_index = 0;
static int jetsam_priority_list_count = 0;
static int jetsam_snapshot_list_count = 0;
static lck_mtx_t * jetsam_list_mlock;
static lck_attr_t * jetsam_lck_attr;
static lck_grp_t * jetsam_lck_grp;
static lck_grp_attr_t * jetsam_lck_grp_attr;
SYSCTL_INT(_kern, OID_AUTO, memorystatus_level, CTLFLAG_RD, &kern_memorystatus_level, 0, "");
SYSCTL_UINT(_kern, OID_AUTO, memorystatus_kev_failure_count, CTLFLAG_RD, &kern_memorystatus_kev_failure_count, 0, "");
__private_extern__ void
kern_memorystatus_init(void)
{
jetsam_lck_attr = lck_attr_alloc_init();
jetsam_lck_grp_attr= lck_grp_attr_alloc_init();
jetsam_lck_grp = lck_grp_alloc_init("jetsam", jetsam_lck_grp_attr);
jetsam_list_mlock = lck_mtx_alloc_init(jetsam_lck_grp, jetsam_lck_attr);
(void)kernel_thread(kernel_task, kern_memorystatus_thread);
}
static uint32_t
jetsam_task_page_count(task_t task)
{
kern_return_t ret;
static task_info_data_t data;
static struct task_basic_info *info = (struct task_basic_info *)&data;
static mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
ret = task_info(task, TASK_BASIC_INFO, (task_info_t)&data, &count);
if (ret == KERN_SUCCESS) {
return info->resident_size / PAGE_SIZE;
}
return 0;
}
static uint32_t
jetsam_flags_for_pid(pid_t pid)
{
int i;
for (i = 0; i < jetsam_priority_list_count; i++) {
if (pid == jetsam_priority_list[i].pid) {
return jetsam_priority_list[i].flags;
}
}
return 0;
}
static void
jetsam_snapshot_procs(void)
{
proc_t p;
int i = 0;
jetsam_snapshot.stats.free_pages = vm_page_free_count;
jetsam_snapshot.stats.active_pages = vm_page_active_count;
jetsam_snapshot.stats.inactive_pages = vm_page_inactive_count;
jetsam_snapshot.stats.purgeable_pages = vm_page_purgeable_count;
jetsam_snapshot.stats.wired_pages = vm_page_wire_count;
proc_list_lock();
LIST_FOREACH(p, &allproc, p_list) {
task_t task = p->task;
jetsam_snapshot_list[i].pid = p->p_pid;
jetsam_snapshot_list[i].pages = jetsam_task_page_count(task);
jetsam_snapshot_list[i].flags = jetsam_flags_for_pid(p->p_pid);
strlcpy(&jetsam_snapshot_list[i].name[0], p->p_comm, MAXCOMLEN+1);
#ifdef DEBUG
printf("jetsam snapshot pid = %d, uuid = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
p->p_pid,
p->p_uuid[0], p->p_uuid[1], p->p_uuid[2], p->p_uuid[3], p->p_uuid[4], p->p_uuid[5], p->p_uuid[6], p->p_uuid[7],
p->p_uuid[8], p->p_uuid[9], p->p_uuid[10], p->p_uuid[11], p->p_uuid[12], p->p_uuid[13], p->p_uuid[14], p->p_uuid[15]);
#endif
memcpy(&jetsam_snapshot_list[i].uuid[0], &p->p_uuid[0], sizeof(p->p_uuid));
i++;
if (i == kMaxSnapshotEntries) {
break;
}
}
proc_list_unlock();
jetsam_snapshot.entry_count = jetsam_snapshot_list_count = i - 1;
}
static void
jetsam_mark_pid_in_snapshot(pid_t pid, int flag)
{
int i = 0;
for (i = 0; i < jetsam_snapshot_list_count; i++) {
if (jetsam_snapshot_list[i].pid == pid) {
jetsam_snapshot_list[i].flags |= flag;
return;
}
}
}
int
jetsam_kill_top_proc(void)
{
proc_t p;
if (jetsam_snapshot_list_count == 0) {
jetsam_snapshot_procs();
}
lck_mtx_lock(jetsam_list_mlock);
while (jetsam_priority_list_index < jetsam_priority_list_count) {
pid_t aPid;
aPid = jetsam_priority_list[jetsam_priority_list_index].pid;
jetsam_priority_list_index++;
if (aPid == 0) {
continue; }
lck_mtx_unlock(jetsam_list_mlock);
jetsam_mark_pid_in_snapshot(aPid, kJetsamFlagsKilled);
p = proc_find(aPid);
if (p != NULL) {
printf("jetsam: killing pid %d [%s] - memory_status_level: %d - ",
aPid, (p->p_comm ? p->p_comm : "(unknown)"), kern_memorystatus_level);
exit1(p, W_EXITCODE(0, SIGKILL), (int *)NULL);
proc_rele(p);
#if DEBUG
printf("jetsam: pid %d killed - memory_status_level: %d\n", aPid, kern_memorystatus_level);
#endif
return 0;
}
lck_mtx_lock(jetsam_list_mlock);
}
lck_mtx_unlock(jetsam_list_mlock);
return -1;
}
static int
jetsam_kill_hiwat_proc(void)
{
proc_t p;
int i;
if (jetsam_snapshot_list_count == 0) {
jetsam_snapshot_procs();
}
lck_mtx_lock(jetsam_list_mlock);
for (i = jetsam_priority_list_index; i < jetsam_priority_list_count; i++) {
pid_t aPid;
int32_t hiwat;
aPid = jetsam_priority_list[i].pid;
hiwat = jetsam_priority_list[i].hiwat_pages;
if (aPid == 0 || (hiwat < 0)) {
continue; }
lck_mtx_unlock(jetsam_list_mlock);
p = proc_find(aPid);
if (p != NULL) {
int32_t pages = (int32_t)jetsam_task_page_count(p->task);
if (pages > hiwat) {
#if DEBUG
printf("jetsam: killing pid %d [%s] - %d pages > hiwat (%d)\n", aPid, p->p_comm, pages, hiwat);
#endif
exit1(p, W_EXITCODE(0, SIGKILL), (int *)NULL);
proc_rele(p);
#if DEBUG
printf("jetsam: pid %d killed - memory_status_level: %d\n", aPid, kern_memorystatus_level);
#endif
jetsam_mark_pid_in_snapshot(aPid, kJetsamFlagsKilledHiwat);
jetsam_priority_list[i].pid = 0;
return 0;
} else {
proc_rele(p);
}
}
lck_mtx_lock(jetsam_list_mlock);
}
lck_mtx_unlock(jetsam_list_mlock);
return -1;
}
static void
kern_memorystatus_thread(void)
{
struct kev_msg ev_msg;
jetsam_kernel_stats_t data;
int ret;
while(1) {
while (kern_memorystatus_level <= kern_memorystatus_level_critical) {
if (jetsam_kill_top_proc() < 0) {
break;
}
}
while (kern_memorystatus_level <= kern_memorystatus_level_highwater) {
if (jetsam_kill_hiwat_proc() < 0) {
break;
}
}
kern_memorystatus_last_level = kern_memorystatus_level;
ev_msg.vendor_code = KEV_VENDOR_APPLE;
ev_msg.kev_class = KEV_SYSTEM_CLASS;
ev_msg.kev_subclass = KEV_MEMORYSTATUS_SUBCLASS;
ev_msg.event_code = kMemoryStatusLevelNote;
ev_msg.dv[0].data_length = sizeof kern_memorystatus_last_level;
ev_msg.dv[0].data_ptr = &kern_memorystatus_last_level;
ev_msg.dv[1].data_length = sizeof data;
ev_msg.dv[1].data_ptr = &data;
ev_msg.dv[2].data_length = 0;
data.free_pages = vm_page_free_count;
data.active_pages = vm_page_active_count;
data.inactive_pages = vm_page_inactive_count;
data.purgeable_pages = vm_page_purgeable_count;
data.wired_pages = vm_page_wire_count;
ret = kev_post_msg(&ev_msg);
if (ret) {
kern_memorystatus_kev_failure_count++;
printf("%s: kev_post_msg() failed, err %d\n", __func__, ret);
}
if (jetsam_snapshot_list_count) {
size_t snapshot_size = sizeof(jetsam_kernel_stats_t) + sizeof(size_t) + sizeof(jetsam_snapshot_entry_t) * jetsam_snapshot_list_count;
ev_msg.event_code = kMemoryStatusSnapshotNote;
ev_msg.dv[0].data_length = sizeof snapshot_size;
ev_msg.dv[0].data_ptr = &snapshot_size;
ev_msg.dv[1].data_length = 0;
ret = kev_post_msg(&ev_msg);
if (ret) {
kern_memorystatus_kev_failure_count++;
printf("%s: kev_post_msg() failed, err %d\n", __func__, ret);
}
}
if (kern_memorystatus_level >= kern_memorystatus_last_level + 5 ||
kern_memorystatus_level <= kern_memorystatus_last_level - 5)
continue;
assert_wait(&kern_memorystatus_wakeup, THREAD_UNINT);
(void)thread_block((thread_continue_t)kern_memorystatus_thread);
}
}
static int
sysctl_io_variable(struct sysctl_req *req, void *pValue, size_t currentsize, size_t maxsize, size_t *newsize)
{
int error;
error = SYSCTL_OUT(req, pValue, currentsize);
if (error || !req->newptr)
return(error);
if (req->newlen > maxsize) {
return EINVAL;
}
error = SYSCTL_IN(req, pValue, req->newlen);
if (!error) {
*newsize = req->newlen;
}
return(error);
}
static int
sysctl_handle_kern_memorystatus_priority_list(__unused struct sysctl_oid *oid, __unused void *arg1, __unused int arg2, struct sysctl_req *req)
{
int i, ret;
jetsam_priority_entry_t temp_list[kMaxPriorityEntries];
size_t newsize, currentsize;
if (req->oldptr) {
lck_mtx_lock(jetsam_list_mlock);
for (i = 0; i < jetsam_priority_list_count; i++) {
temp_list[i] = jetsam_priority_list[i];
}
lck_mtx_unlock(jetsam_list_mlock);
}
currentsize = sizeof(jetsam_priority_list[0]) * jetsam_priority_list_count;
ret = sysctl_io_variable(req, &temp_list[0], currentsize, sizeof(temp_list), &newsize);
if (!ret && req->newptr) {
jetsam_priority_list_count = newsize / sizeof(jetsam_priority_list[0]);
#if DEBUG
printf("set jetsam priority pids = { ");
for (i = 0; i < jetsam_priority_list_count; i++) {
printf("(%d, 0x%08x, %d) ", temp_list[i].pid, temp_list[i].flags, temp_list[i].hiwat_pages);
}
printf("}\n");
#endif
lck_mtx_lock(jetsam_list_mlock);
for (i = 0; i < jetsam_priority_list_count; i++) {
jetsam_priority_list[i] = temp_list[i];
}
for (i = jetsam_priority_list_count; i < kMaxPriorityEntries; i++) {
jetsam_priority_list[i].pid = 0;
jetsam_priority_list[i].flags = 0;
jetsam_priority_list[i].hiwat_pages = -1;
jetsam_priority_list[i].hiwat_reserved1 = -1;
jetsam_priority_list[i].hiwat_reserved2 = -1;
jetsam_priority_list[i].hiwat_reserved3 = -1;
}
jetsam_priority_list_index = 0;
lck_mtx_unlock(jetsam_list_mlock);
}
return ret;
}
static int
sysctl_handle_kern_memorystatus_snapshot(__unused struct sysctl_oid *oid, __unused void *arg1, __unused int arg2, struct sysctl_req *req)
{
int ret;
size_t currentsize = 0;
if (jetsam_snapshot_list_count > 0) {
currentsize = sizeof(jetsam_kernel_stats_t) + sizeof(size_t) + sizeof(jetsam_snapshot_entry_t) * jetsam_snapshot_list_count;
}
if (!currentsize) {
if (req->oldptr) {
#ifdef DEBUG
printf("kern.memorystatus_snapshot returning EINVAL\n");
#endif
return EINVAL;
}
else {
#ifdef DEBUG
printf("kern.memorystatus_snapshot returning 0 for size\n");
#endif
}
} else {
#ifdef DEBUG
printf("kern.memorystatus_snapshot returning %ld for size\n", (long)currentsize);
#endif
}
ret = sysctl_io_variable(req, &jetsam_snapshot, currentsize, 0, NULL);
if (!ret && req->oldptr) {
jetsam_snapshot.entry_count = jetsam_snapshot_list_count = 0;
}
return ret;
}
SYSCTL_PROC(_kern, OID_AUTO, memorystatus_priority_list, CTLTYPE_OPAQUE|CTLFLAG_RW, 0, 0, sysctl_handle_kern_memorystatus_priority_list, "S,jetsam_priorities", "");
SYSCTL_PROC(_kern, OID_AUTO, memorystatus_snapshot, CTLTYPE_OPAQUE|CTLFLAG_RD, 0, 0, sysctl_handle_kern_memorystatus_snapshot, "S,jetsam_snapshot", "");