]>
Commit | Line | Data |
---|---|---|
2d21ac55 A |
1 | /* |
2 | * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
14 | * | |
15 | * Please obtain a copy of the License at | |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
25 | * | |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ | |
27 | * | |
28 | */ | |
2d21ac55 A |
29 | |
30 | #include <sys/kern_event.h> | |
31 | #include <sys/kern_memorystatus.h> | |
32 | ||
33 | #include <kern/sched_prim.h> | |
b0d623f7 | 34 | #include <kern/lock.h> |
2d21ac55 A |
35 | #include <kern/task.h> |
36 | #include <kern/thread.h> | |
37 | #include <libkern/libkern.h> | |
b0d623f7 A |
38 | #include <mach/task.h> |
39 | #include <mach/task_info.h> | |
40 | #include <sys/proc.h> | |
41 | #include <sys/signal.h> | |
42 | #include <sys/signalvar.h> | |
2d21ac55 | 43 | #include <sys/sysctl.h> |
b0d623f7 | 44 | #include <sys/wait.h> |
2d21ac55 | 45 | |
593a1d5f A |
46 | extern unsigned int vm_page_free_count; |
47 | extern unsigned int vm_page_active_count; | |
48 | extern unsigned int vm_page_inactive_count; | |
49 | extern unsigned int vm_page_purgeable_count; | |
50 | extern unsigned int vm_page_wire_count; | |
51 | ||
2d21ac55 A |
52 | static void kern_memorystatus_thread(void); |
53 | ||
54 | int kern_memorystatus_wakeup = 0; | |
2d21ac55 A |
55 | int kern_memorystatus_level = 0; |
56 | int kern_memorystatus_last_level = 0; | |
57 | unsigned int kern_memorystatus_kev_failure_count = 0; | |
b0d623f7 | 58 | int kern_memorystatus_level_critical = 5; |
d1ecb069 | 59 | #define kern_memorystatus_level_highwater (kern_memorystatus_level_critical + 5) |
b0d623f7 A |
60 | |
61 | static struct { | |
62 | jetsam_kernel_stats_t stats; | |
63 | size_t entry_count; | |
64 | jetsam_snapshot_entry_t entries[kMaxSnapshotEntries]; | |
65 | } jetsam_snapshot; | |
66 | ||
67 | static jetsam_priority_entry_t jetsam_priority_list[kMaxPriorityEntries]; | |
68 | #define jetsam_snapshot_list jetsam_snapshot.entries | |
69 | ||
70 | static int jetsam_priority_list_index = 0; | |
71 | static int jetsam_priority_list_count = 0; | |
72 | static int jetsam_snapshot_list_count = 0; | |
73 | ||
74 | static lck_mtx_t * jetsam_list_mlock; | |
75 | static lck_attr_t * jetsam_lck_attr; | |
76 | static lck_grp_t * jetsam_lck_grp; | |
77 | static lck_grp_attr_t * jetsam_lck_grp_attr; | |
2d21ac55 A |
78 | |
79 | SYSCTL_INT(_kern, OID_AUTO, memorystatus_level, CTLFLAG_RD, &kern_memorystatus_level, 0, ""); | |
80 | SYSCTL_UINT(_kern, OID_AUTO, memorystatus_kev_failure_count, CTLFLAG_RD, &kern_memorystatus_kev_failure_count, 0, ""); | |
81 | ||
82 | __private_extern__ void | |
83 | kern_memorystatus_init(void) | |
84 | { | |
b0d623f7 A |
85 | jetsam_lck_attr = lck_attr_alloc_init(); |
86 | jetsam_lck_grp_attr= lck_grp_attr_alloc_init(); | |
87 | jetsam_lck_grp = lck_grp_alloc_init("jetsam", jetsam_lck_grp_attr); | |
88 | jetsam_list_mlock = lck_mtx_alloc_init(jetsam_lck_grp, jetsam_lck_attr); | |
89 | ||
2d21ac55 A |
90 | (void)kernel_thread(kernel_task, kern_memorystatus_thread); |
91 | } | |
92 | ||
b0d623f7 A |
93 | static uint32_t |
94 | jetsam_task_page_count(task_t task) | |
95 | { | |
96 | kern_return_t ret; | |
97 | static task_info_data_t data; | |
98 | static struct task_basic_info *info = (struct task_basic_info *)&data; | |
99 | static mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; | |
100 | ||
101 | ret = task_info(task, TASK_BASIC_INFO, (task_info_t)&data, &count); | |
102 | if (ret == KERN_SUCCESS) { | |
103 | return info->resident_size / PAGE_SIZE; | |
104 | } | |
105 | return 0; | |
106 | } | |
107 | ||
108 | static uint32_t | |
109 | jetsam_flags_for_pid(pid_t pid) | |
110 | { | |
111 | int i; | |
112 | ||
113 | for (i = 0; i < jetsam_priority_list_count; i++) { | |
114 | if (pid == jetsam_priority_list[i].pid) { | |
115 | return jetsam_priority_list[i].flags; | |
116 | } | |
117 | } | |
118 | return 0; | |
119 | } | |
120 | ||
121 | static void | |
122 | jetsam_snapshot_procs(void) | |
123 | { | |
124 | proc_t p; | |
125 | int i = 0; | |
126 | ||
127 | jetsam_snapshot.stats.free_pages = vm_page_free_count; | |
128 | jetsam_snapshot.stats.active_pages = vm_page_active_count; | |
129 | jetsam_snapshot.stats.inactive_pages = vm_page_inactive_count; | |
130 | jetsam_snapshot.stats.purgeable_pages = vm_page_purgeable_count; | |
131 | jetsam_snapshot.stats.wired_pages = vm_page_wire_count; | |
132 | proc_list_lock(); | |
133 | LIST_FOREACH(p, &allproc, p_list) { | |
134 | task_t task = p->task; | |
135 | jetsam_snapshot_list[i].pid = p->p_pid; | |
136 | jetsam_snapshot_list[i].pages = jetsam_task_page_count(task); | |
137 | jetsam_snapshot_list[i].flags = jetsam_flags_for_pid(p->p_pid); | |
138 | strlcpy(&jetsam_snapshot_list[i].name[0], p->p_comm, MAXCOMLEN+1); | |
139 | #ifdef DEBUG | |
140 | printf("jetsam snapshot pid = %d, uuid = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", | |
141 | p->p_pid, | |
142 | 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], | |
143 | 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]); | |
144 | #endif | |
145 | memcpy(&jetsam_snapshot_list[i].uuid[0], &p->p_uuid[0], sizeof(p->p_uuid)); | |
146 | i++; | |
147 | if (i == kMaxSnapshotEntries) { | |
148 | break; | |
149 | } | |
150 | } | |
151 | proc_list_unlock(); | |
152 | jetsam_snapshot.entry_count = jetsam_snapshot_list_count = i - 1; | |
153 | } | |
154 | ||
155 | static void | |
d1ecb069 | 156 | jetsam_mark_pid_in_snapshot(pid_t pid, int flag) |
b0d623f7 A |
157 | { |
158 | ||
159 | int i = 0; | |
160 | ||
161 | for (i = 0; i < jetsam_snapshot_list_count; i++) { | |
162 | if (jetsam_snapshot_list[i].pid == pid) { | |
d1ecb069 | 163 | jetsam_snapshot_list[i].flags |= flag; |
b0d623f7 A |
164 | return; |
165 | } | |
166 | } | |
167 | } | |
168 | ||
d1ecb069 | 169 | int |
b0d623f7 A |
170 | jetsam_kill_top_proc(void) |
171 | { | |
172 | proc_t p; | |
173 | ||
174 | if (jetsam_snapshot_list_count == 0) { | |
175 | jetsam_snapshot_procs(); | |
176 | } | |
177 | lck_mtx_lock(jetsam_list_mlock); | |
178 | while (jetsam_priority_list_index < jetsam_priority_list_count) { | |
179 | pid_t aPid; | |
180 | aPid = jetsam_priority_list[jetsam_priority_list_index].pid; | |
181 | jetsam_priority_list_index++; | |
182 | /* skip empty slots in the list */ | |
183 | if (aPid == 0) { | |
184 | continue; // with lock held | |
185 | } | |
186 | lck_mtx_unlock(jetsam_list_mlock); | |
d1ecb069 | 187 | jetsam_mark_pid_in_snapshot(aPid, kJetsamFlagsKilled); |
b0d623f7 A |
188 | p = proc_find(aPid); |
189 | if (p != NULL) { | |
d1ecb069 A |
190 | printf("jetsam: killing pid %d [%s] - memory_status_level: %d - ", |
191 | aPid, (p->p_comm ? p->p_comm : "(unknown)"), kern_memorystatus_level); | |
b0d623f7 A |
192 | exit1(p, W_EXITCODE(0, SIGKILL), (int *)NULL); |
193 | proc_rele(p); | |
194 | #if DEBUG | |
195 | printf("jetsam: pid %d killed - memory_status_level: %d\n", aPid, kern_memorystatus_level); | |
196 | #endif /* DEBUG */ | |
197 | return 0; | |
198 | } | |
199 | lck_mtx_lock(jetsam_list_mlock); | |
200 | } | |
201 | lck_mtx_unlock(jetsam_list_mlock); | |
202 | return -1; | |
203 | } | |
204 | ||
d1ecb069 A |
205 | static int |
206 | jetsam_kill_hiwat_proc(void) | |
207 | { | |
208 | proc_t p; | |
209 | int i; | |
210 | if (jetsam_snapshot_list_count == 0) { | |
211 | jetsam_snapshot_procs(); | |
212 | } | |
213 | lck_mtx_lock(jetsam_list_mlock); | |
214 | for (i = jetsam_priority_list_index; i < jetsam_priority_list_count; i++) { | |
215 | pid_t aPid; | |
216 | int32_t hiwat; | |
217 | aPid = jetsam_priority_list[i].pid; | |
218 | hiwat = jetsam_priority_list[i].hiwat_pages; | |
219 | /* skip empty or non-hiwat slots in the list */ | |
220 | if (aPid == 0 || (hiwat < 0)) { | |
221 | continue; // with lock held | |
222 | } | |
223 | lck_mtx_unlock(jetsam_list_mlock); | |
224 | p = proc_find(aPid); | |
225 | if (p != NULL) { | |
226 | int32_t pages = (int32_t)jetsam_task_page_count(p->task); | |
227 | if (pages > hiwat) { | |
228 | #if DEBUG | |
229 | printf("jetsam: killing pid %d [%s] - %d pages > hiwat (%d)\n", aPid, p->p_comm, pages, hiwat); | |
230 | #endif /* DEBUG */ | |
231 | exit1(p, W_EXITCODE(0, SIGKILL), (int *)NULL); | |
232 | proc_rele(p); | |
233 | #if DEBUG | |
234 | printf("jetsam: pid %d killed - memory_status_level: %d\n", aPid, kern_memorystatus_level); | |
235 | #endif /* DEBUG */ | |
236 | jetsam_mark_pid_in_snapshot(aPid, kJetsamFlagsKilledHiwat); | |
237 | jetsam_priority_list[i].pid = 0; | |
238 | return 0; | |
239 | } else { | |
240 | proc_rele(p); | |
241 | } | |
242 | ||
243 | } | |
244 | lck_mtx_lock(jetsam_list_mlock); | |
245 | } | |
246 | lck_mtx_unlock(jetsam_list_mlock); | |
247 | return -1; | |
248 | } | |
249 | ||
2d21ac55 A |
250 | static void |
251 | kern_memorystatus_thread(void) | |
252 | { | |
253 | struct kev_msg ev_msg; | |
b0d623f7 | 254 | jetsam_kernel_stats_t data; |
2d21ac55 A |
255 | int ret; |
256 | ||
257 | while(1) { | |
b0d623f7 A |
258 | |
259 | while (kern_memorystatus_level <= kern_memorystatus_level_critical) { | |
260 | if (jetsam_kill_top_proc() < 0) { | |
261 | break; | |
262 | } | |
263 | } | |
264 | ||
d1ecb069 A |
265 | while (kern_memorystatus_level <= kern_memorystatus_level_highwater) { |
266 | if (jetsam_kill_hiwat_proc() < 0) { | |
267 | break; | |
268 | } | |
269 | } | |
270 | ||
2d21ac55 A |
271 | kern_memorystatus_last_level = kern_memorystatus_level; |
272 | ||
273 | ev_msg.vendor_code = KEV_VENDOR_APPLE; | |
274 | ev_msg.kev_class = KEV_SYSTEM_CLASS; | |
275 | ev_msg.kev_subclass = KEV_MEMORYSTATUS_SUBCLASS; | |
276 | ||
b0d623f7 A |
277 | /* pass the memory status level (percent free) */ |
278 | ev_msg.event_code = kMemoryStatusLevelNote; | |
2d21ac55 | 279 | |
b0d623f7 A |
280 | ev_msg.dv[0].data_length = sizeof kern_memorystatus_last_level; |
281 | ev_msg.dv[0].data_ptr = &kern_memorystatus_last_level; | |
282 | ev_msg.dv[1].data_length = sizeof data; | |
283 | ev_msg.dv[1].data_ptr = &data; | |
284 | ev_msg.dv[2].data_length = 0; | |
593a1d5f A |
285 | |
286 | data.free_pages = vm_page_free_count; | |
287 | data.active_pages = vm_page_active_count; | |
288 | data.inactive_pages = vm_page_inactive_count; | |
289 | data.purgeable_pages = vm_page_purgeable_count; | |
290 | data.wired_pages = vm_page_wire_count; | |
2d21ac55 A |
291 | |
292 | ret = kev_post_msg(&ev_msg); | |
293 | if (ret) { | |
294 | kern_memorystatus_kev_failure_count++; | |
295 | printf("%s: kev_post_msg() failed, err %d\n", __func__, ret); | |
296 | } | |
297 | ||
b0d623f7 A |
298 | if (jetsam_snapshot_list_count) { |
299 | size_t snapshot_size = sizeof(jetsam_kernel_stats_t) + sizeof(size_t) + sizeof(jetsam_snapshot_entry_t) * jetsam_snapshot_list_count; | |
300 | ev_msg.event_code = kMemoryStatusSnapshotNote; | |
301 | ev_msg.dv[0].data_length = sizeof snapshot_size; | |
302 | ev_msg.dv[0].data_ptr = &snapshot_size; | |
303 | ev_msg.dv[1].data_length = 0; | |
304 | ||
305 | ret = kev_post_msg(&ev_msg); | |
306 | if (ret) { | |
307 | kern_memorystatus_kev_failure_count++; | |
308 | printf("%s: kev_post_msg() failed, err %d\n", __func__, ret); | |
309 | } | |
310 | } | |
311 | ||
2d21ac55 A |
312 | if (kern_memorystatus_level >= kern_memorystatus_last_level + 5 || |
313 | kern_memorystatus_level <= kern_memorystatus_last_level - 5) | |
314 | continue; | |
315 | ||
316 | assert_wait(&kern_memorystatus_wakeup, THREAD_UNINT); | |
317 | (void)thread_block((thread_continue_t)kern_memorystatus_thread); | |
318 | } | |
319 | } | |
b0d623f7 A |
320 | |
321 | static int | |
322 | sysctl_io_variable(struct sysctl_req *req, void *pValue, size_t currentsize, size_t maxsize, size_t *newsize) | |
323 | { | |
324 | int error; | |
325 | ||
326 | /* Copy blob out */ | |
327 | error = SYSCTL_OUT(req, pValue, currentsize); | |
328 | ||
329 | /* error or nothing to set */ | |
330 | if (error || !req->newptr) | |
331 | return(error); | |
332 | ||
333 | if (req->newlen > maxsize) { | |
334 | return EINVAL; | |
335 | } | |
336 | error = SYSCTL_IN(req, pValue, req->newlen); | |
337 | ||
338 | if (!error) { | |
339 | *newsize = req->newlen; | |
340 | } | |
341 | ||
342 | return(error); | |
343 | } | |
344 | ||
345 | static int | |
346 | sysctl_handle_kern_memorystatus_priority_list(__unused struct sysctl_oid *oid, __unused void *arg1, __unused int arg2, struct sysctl_req *req) | |
347 | { | |
348 | int i, ret; | |
349 | jetsam_priority_entry_t temp_list[kMaxPriorityEntries]; | |
350 | size_t newsize, currentsize; | |
351 | ||
352 | if (req->oldptr) { | |
353 | lck_mtx_lock(jetsam_list_mlock); | |
354 | for (i = 0; i < jetsam_priority_list_count; i++) { | |
355 | temp_list[i] = jetsam_priority_list[i]; | |
356 | } | |
357 | lck_mtx_unlock(jetsam_list_mlock); | |
358 | } | |
359 | ||
360 | currentsize = sizeof(jetsam_priority_list[0]) * jetsam_priority_list_count; | |
361 | ||
362 | ret = sysctl_io_variable(req, &temp_list[0], currentsize, sizeof(temp_list), &newsize); | |
363 | ||
364 | if (!ret && req->newptr) { | |
365 | jetsam_priority_list_count = newsize / sizeof(jetsam_priority_list[0]); | |
366 | #if DEBUG | |
367 | printf("set jetsam priority pids = { "); | |
368 | for (i = 0; i < jetsam_priority_list_count; i++) { | |
b7266188 | 369 | printf("(%d, 0x%08x, %d) ", temp_list[i].pid, temp_list[i].flags, temp_list[i].hiwat_pages); |
b0d623f7 A |
370 | } |
371 | printf("}\n"); | |
372 | #endif /* DEBUG */ | |
373 | lck_mtx_lock(jetsam_list_mlock); | |
374 | for (i = 0; i < jetsam_priority_list_count; i++) { | |
375 | jetsam_priority_list[i] = temp_list[i]; | |
376 | } | |
377 | for (i = jetsam_priority_list_count; i < kMaxPriorityEntries; i++) { | |
378 | jetsam_priority_list[i].pid = 0; | |
379 | jetsam_priority_list[i].flags = 0; | |
b7266188 A |
380 | jetsam_priority_list[i].hiwat_pages = -1; |
381 | jetsam_priority_list[i].hiwat_reserved1 = -1; | |
382 | jetsam_priority_list[i].hiwat_reserved2 = -1; | |
383 | jetsam_priority_list[i].hiwat_reserved3 = -1; | |
b0d623f7 A |
384 | } |
385 | jetsam_priority_list_index = 0; | |
386 | lck_mtx_unlock(jetsam_list_mlock); | |
387 | } | |
388 | return ret; | |
389 | } | |
390 | ||
391 | static int | |
392 | sysctl_handle_kern_memorystatus_snapshot(__unused struct sysctl_oid *oid, __unused void *arg1, __unused int arg2, struct sysctl_req *req) | |
393 | { | |
394 | int ret; | |
395 | size_t currentsize = 0; | |
396 | ||
397 | if (jetsam_snapshot_list_count > 0) { | |
398 | currentsize = sizeof(jetsam_kernel_stats_t) + sizeof(size_t) + sizeof(jetsam_snapshot_entry_t) * jetsam_snapshot_list_count; | |
399 | } | |
400 | if (!currentsize) { | |
401 | if (req->oldptr) { | |
402 | #ifdef DEBUG | |
403 | printf("kern.memorystatus_snapshot returning EINVAL\n"); | |
404 | #endif | |
405 | return EINVAL; | |
406 | } | |
407 | else { | |
408 | #ifdef DEBUG | |
409 | printf("kern.memorystatus_snapshot returning 0 for size\n"); | |
410 | #endif | |
411 | } | |
412 | } else { | |
413 | #ifdef DEBUG | |
414 | printf("kern.memorystatus_snapshot returning %ld for size\n", (long)currentsize); | |
415 | #endif | |
416 | } | |
417 | ret = sysctl_io_variable(req, &jetsam_snapshot, currentsize, 0, NULL); | |
418 | if (!ret && req->oldptr) { | |
419 | jetsam_snapshot.entry_count = jetsam_snapshot_list_count = 0; | |
420 | } | |
421 | return ret; | |
422 | } | |
423 | ||
424 | SYSCTL_PROC(_kern, OID_AUTO, memorystatus_priority_list, CTLTYPE_OPAQUE|CTLFLAG_RW, 0, 0, sysctl_handle_kern_memorystatus_priority_list, "S,jetsam_priorities", ""); | |
425 | SYSCTL_PROC(_kern, OID_AUTO, memorystatus_snapshot, CTLTYPE_OPAQUE|CTLFLAG_RD, 0, 0, sysctl_handle_kern_memorystatus_snapshot, "S,jetsam_snapshot", ""); |