]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/kern_memorystatus.c
xnu-1504.9.17.tar.gz
[apple/xnu.git] / bsd / kern / kern_memorystatus.c
index 78380edcfe6e8bce5c13e6cbd3a3876b440c0342..dfbaa794d2c54dea622f5e699472bdbacb9e6ffb 100644 (file)
  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
  *
  */
-/*-
- * Copyright (c) 1999,2000,2001 Jonathan Lemon <jlemon@FreeBSD.org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
 
 #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_pause = 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, "");
@@ -75,27 +82,212 @@ SYSCTL_UINT(_kern, OID_AUTO, memorystatus_kev_failure_count, CTLFLAG_RD, &kern_m
 __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++;
+               /* skip empty slots in the list */
+               if (aPid == 0) {
+                       continue; // with lock held
+               }
+               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 /* DEBUG */
+                       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;    
+               /* skip empty or non-hiwat slots in the list */
+               if (aPid == 0 || (hiwat < 0)) {
+                       continue; // with lock held
+               }
+               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 /* DEBUG */
+                               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 /* DEBUG */
+                               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;
 
-               /* pass the memory status level in the event code (as percent used) */
-               ev_msg.event_code     = 100 - kern_memorystatus_last_level;
+               /* pass the memory status level (percent free) */
+               ev_msg.event_code     = kMemoryStatusLevelNote;
 
-               ev_msg.dv[0].data_length = 0;
+               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) {
@@ -103,8 +295,19 @@ kern_memorystatus_thread(void)
                        printf("%s: kev_post_msg() failed, err %d\n", __func__, ret);
                }
 
-               assert_wait_timeout((event_t)&kern_memorystatus_pause, THREAD_UNINT, 1, 250*1000*NSEC_PER_USEC);
-               (void)thread_block(THREAD_CONTINUE_NULL);
+               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)
@@ -114,3 +317,109 @@ kern_memorystatus_thread(void)
                (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;
+
+    /* Copy blob out */
+    error = SYSCTL_OUT(req, pValue, currentsize);
+
+    /* error or nothing to set */
+    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 /* DEBUG */
+               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", "");