X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/060df5ea7c632b1ac8cc8aac1fb59758165c2084..6d2010ae8f7a6078e10b361c6962983bab233e0f:/bsd/kern/vm_pressure.c diff --git a/bsd/kern/vm_pressure.c b/bsd/kern/vm_pressure.c new file mode 100644 index 000000000..b5fc2f072 --- /dev/null +++ b/bsd/kern/vm_pressure.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2009-2010 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(); +} + +/* Interface for event dispatch from vm_pageout_garbage_collect thread */ +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; + } + + } + + /* Else... */ + +#if DEBUG + printf("[vm_pressure] could not find suitable event candidate\n"); +#endif + + vm_recharge_active_list(); + + vm_pressure_klist_unlock(); +} + +/* + * Try standard pressure event candidates. Called with klist lock held. + */ +int vm_try_pressure_candidates(void) { + /* + * This value is the threshold that a process must meet to be considered for scavenging. + * If a process has sufficiently little resident memory, there is probably no use scavenging it. + * At best, we'll scavenge very little memory. At worst, we'll page in code pages or malloc metadata. + */ + +#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; + /* + * We don't want a small process to block large processes from + * being notified again. + */ + 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 + /* There was no candidate with enough resident memory to scavenge */ + /* This debug print makes too much noise now */ + //printf("[vm_pressure] threshold failed for pid %d with %u resident, skipping...\n", p->p_pid, resident_size); +#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; +} + + +/* + * Remove all elements from the dormant list and place them on the active list. + * Called with klist lock held. + */ +void vm_recharge_active_list(void) { + /* Re-charge the main list from the dormant list if possible */ + 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); + } + } +}