]>
Commit | Line | Data |
---|---|---|
d9a64523 A |
1 | #include <stdio.h> |
2 | #include <signal.h> | |
3 | #include <sys/sysctl.h> | |
4 | #include <sys/kern_memorystatus.h> | |
ea3f0419 | 5 | #include <time.h> |
d9a64523 | 6 | #include <mach-o/dyld.h> |
cb323159 A |
7 | #include <mach/mach_vm.h> |
8 | #include <mach/vm_page_size.h> /* Needed for vm_region info */ | |
9 | #include <mach/shared_region.h> | |
10 | #include <mach/mach.h> | |
d9a64523 A |
11 | |
12 | #ifdef T_NAMESPACE | |
13 | #undef T_NAMESPACE | |
14 | #endif | |
15 | #include <darwintest.h> | |
16 | #include <darwintest_utils.h> | |
17 | ||
cb323159 A |
18 | #include "memorystatus_assertion_helpers.h" |
19 | ||
d9a64523 A |
20 | T_GLOBAL_META( |
21 | T_META_NAMESPACE("xnu.vm"), | |
22 | T_META_CHECK_LEAKS(false) | |
0a7de745 | 23 | ); |
d9a64523 | 24 | |
0a7de745 A |
25 | #define MEM_SIZE_MB 10 |
26 | #define NUM_ITERATIONS 5 | |
cb323159 | 27 | #define FREEZE_PAGES_MAX 256 |
d9a64523 A |
28 | |
29 | #define CREATE_LIST(X) \ | |
30 | X(SUCCESS) \ | |
31 | X(TOO_FEW_ARGUMENTS) \ | |
32 | X(SYSCTL_VM_PAGESIZE_FAILED) \ | |
33 | X(VM_PAGESIZE_IS_ZERO) \ | |
d9a64523 A |
34 | X(DISPATCH_SOURCE_CREATE_FAILED) \ |
35 | X(INITIAL_SIGNAL_TO_PARENT_FAILED) \ | |
36 | X(SIGNAL_TO_PARENT_FAILED) \ | |
37 | X(MEMORYSTATUS_CONTROL_FAILED) \ | |
38 | X(IS_FREEZABLE_NOT_AS_EXPECTED) \ | |
39 | X(MEMSTAT_PRIORITY_CHANGE_FAILED) \ | |
ea3f0419 | 40 | X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \ |
d9a64523 A |
41 | X(EXIT_CODE_MAX) |
42 | ||
43 | #define EXIT_CODES_ENUM(VAR) VAR, | |
44 | enum exit_codes_num { | |
45 | CREATE_LIST(EXIT_CODES_ENUM) | |
46 | }; | |
47 | ||
48 | #define EXIT_CODES_STRING(VAR) #VAR, | |
49 | static const char *exit_codes_str[] = { | |
50 | CREATE_LIST(EXIT_CODES_STRING) | |
51 | }; | |
52 | ||
cb323159 A |
53 | static int |
54 | get_vmpage_size() | |
55 | { | |
56 | int vmpage_size; | |
57 | size_t size = sizeof(vmpage_size); | |
58 | int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0); | |
59 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize"); | |
60 | T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0"); | |
61 | return vmpage_size; | |
62 | } | |
d9a64523 | 63 | |
cb323159 | 64 | static pid_t child_pid = -1; |
d9a64523 A |
65 | static int freeze_count = 0; |
66 | ||
67 | void move_to_idle_band(void); | |
cb323159 | 68 | void run_freezer_test(int); |
d9a64523 | 69 | void freeze_helper_process(void); |
cb323159 A |
70 | /* Gets and optionally sets the freeze pages max threshold */ |
71 | int sysctl_freeze_pages_max(int* new_value); | |
72 | ||
73 | /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test. | |
74 | * We're moving away from those unit tests, so they're copied here. | |
75 | */ | |
76 | ||
77 | /* Cribbed from 'top'... */ | |
78 | static int | |
79 | in_shared_region(mach_vm_address_t addr, cpu_type_t type) | |
80 | { | |
81 | mach_vm_address_t base = 0, size = 0; | |
82 | ||
83 | switch (type) { | |
84 | case CPU_TYPE_ARM: | |
85 | base = SHARED_REGION_BASE_ARM; | |
86 | size = SHARED_REGION_SIZE_ARM; | |
87 | break; | |
88 | ||
89 | case CPU_TYPE_ARM64: | |
90 | base = SHARED_REGION_BASE_ARM64; | |
91 | size = SHARED_REGION_SIZE_ARM64; | |
92 | break; | |
93 | ||
94 | ||
95 | case CPU_TYPE_X86_64: | |
96 | base = SHARED_REGION_BASE_X86_64; | |
97 | size = SHARED_REGION_SIZE_X86_64; | |
98 | break; | |
99 | ||
100 | case CPU_TYPE_I386: | |
101 | base = SHARED_REGION_BASE_I386; | |
102 | size = SHARED_REGION_SIZE_I386; | |
103 | break; | |
104 | ||
105 | case CPU_TYPE_POWERPC: | |
106 | base = SHARED_REGION_BASE_PPC; | |
107 | size = SHARED_REGION_SIZE_PPC; | |
108 | break; | |
109 | ||
110 | case CPU_TYPE_POWERPC64: | |
111 | base = SHARED_REGION_BASE_PPC64; | |
112 | size = SHARED_REGION_SIZE_PPC64; | |
113 | break; | |
114 | ||
115 | default: { | |
116 | int t = type; | |
117 | ||
118 | fprintf(stderr, "unknown CPU type: 0x%x\n", t); | |
119 | abort(); | |
120 | } | |
121 | } | |
122 | ||
123 | return addr >= base && addr < (base + size); | |
124 | } | |
125 | ||
126 | /* Get the resident private memory of the given pid */ | |
127 | static unsigned long long | |
128 | get_rprvt(pid_t pid) | |
129 | { | |
130 | mach_port_name_t task; | |
131 | kern_return_t kr; | |
132 | ||
133 | mach_vm_size_t rprvt = 0; | |
134 | mach_vm_size_t empty = 0; | |
135 | mach_vm_size_t fw_private = 0; | |
136 | mach_vm_size_t pagesize = vm_kernel_page_size; // The vm_region page info is reported | |
137 | // in terms of vm_kernel_page_size. | |
138 | mach_vm_size_t regs = 0; | |
139 | ||
140 | mach_vm_address_t addr; | |
141 | mach_vm_size_t size; | |
142 | ||
143 | int split = 0; | |
144 | ||
145 | kr = task_for_pid(mach_task_self(), pid, &task); | |
146 | T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS, "Unable to get task_for_pid of child"); | |
147 | ||
148 | for (addr = 0;; addr += size) { | |
149 | vm_region_top_info_data_t info; | |
150 | mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT; | |
151 | mach_port_t object_name; | |
152 | ||
153 | kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name); | |
154 | if (kr != KERN_SUCCESS) { | |
155 | break; | |
156 | } | |
157 | ||
158 | #if defined (__arm64__) | |
159 | if (in_shared_region(addr, CPU_TYPE_ARM64)) { | |
160 | #else | |
161 | if (in_shared_region(addr, CPU_TYPE_ARM)) { | |
162 | #endif | |
163 | // Private Shared | |
164 | fw_private += info.private_pages_resident * pagesize; | |
165 | ||
166 | /* | |
167 | * Check if this process has the globally shared | |
168 | * text and data regions mapped in. If so, set | |
169 | * split to TRUE and avoid checking | |
170 | * again. | |
171 | */ | |
172 | if (split == FALSE && info.share_mode == SM_EMPTY) { | |
173 | vm_region_basic_info_data_64_t b_info; | |
174 | mach_vm_address_t b_addr = addr; | |
175 | mach_vm_size_t b_size = size; | |
176 | count = VM_REGION_BASIC_INFO_COUNT_64; | |
177 | ||
178 | kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&b_info, &count, &object_name); | |
179 | if (kr != KERN_SUCCESS) { | |
180 | break; | |
181 | } | |
182 | ||
183 | if (b_info.reserved) { | |
184 | split = TRUE; | |
185 | } | |
186 | } | |
187 | ||
188 | /* | |
189 | * Short circuit the loop if this isn't a shared | |
190 | * private region, since that's the only region | |
191 | * type we care about within the current address | |
192 | * range. | |
193 | */ | |
194 | if (info.share_mode != SM_PRIVATE) { | |
195 | continue; | |
196 | } | |
197 | } | |
d9a64523 | 198 | |
cb323159 A |
199 | regs++; |
200 | ||
201 | /* | |
202 | * Update counters according to the region type. | |
203 | */ | |
204 | ||
205 | if (info.share_mode == SM_COW && info.ref_count == 1) { | |
206 | // Treat single reference SM_COW as SM_PRIVATE | |
207 | info.share_mode = SM_PRIVATE; | |
208 | } | |
209 | ||
210 | switch (info.share_mode) { | |
211 | case SM_LARGE_PAGE: | |
212 | // Treat SM_LARGE_PAGE the same as SM_PRIVATE | |
213 | // since they are not shareable and are wired. | |
214 | case SM_PRIVATE: | |
215 | rprvt += info.private_pages_resident * pagesize; | |
216 | rprvt += info.shared_pages_resident * pagesize; | |
217 | break; | |
218 | ||
219 | case SM_EMPTY: | |
220 | empty += size; | |
221 | break; | |
222 | ||
223 | case SM_COW: | |
224 | case SM_SHARED: | |
225 | if (pid == 0) { | |
226 | // Treat kernel_task specially | |
227 | if (info.share_mode == SM_COW) { | |
228 | rprvt += info.private_pages_resident * pagesize; | |
229 | } | |
230 | break; | |
231 | } | |
232 | ||
233 | if (info.share_mode == SM_COW) { | |
234 | rprvt += info.private_pages_resident * pagesize; | |
235 | } | |
236 | break; | |
237 | ||
238 | default: | |
239 | assert(0); | |
240 | break; | |
241 | } | |
242 | } | |
243 | ||
244 | return rprvt; | |
245 | } | |
d9a64523 | 246 | |
0a7de745 A |
247 | void |
248 | move_to_idle_band(void) | |
249 | { | |
d9a64523 A |
250 | memorystatus_priority_properties_t props; |
251 | /* | |
252 | * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits. | |
253 | * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario. | |
254 | */ | |
255 | props.priority = JETSAM_PRIORITY_IDLE; | |
256 | props.user_data = 0; | |
257 | ||
258 | /* | |
259 | * This requires us to run as root (in the absence of entitlement). | |
260 | * Hence the T_META_ASROOT(true) in the T_HELPER_DECL. | |
261 | */ | |
262 | if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, getpid(), 0, &props, sizeof(props))) { | |
263 | exit(MEMSTAT_PRIORITY_CHANGE_FAILED); | |
264 | } | |
265 | } | |
266 | ||
0a7de745 A |
267 | void |
268 | freeze_helper_process(void) | |
269 | { | |
270 | size_t length; | |
271 | int ret, freeze_enabled, errno_freeze_sysctl; | |
cb323159 A |
272 | uint64_t resident_memory_before, resident_memory_after, vmpage_size; |
273 | vmpage_size = (uint64_t) get_vmpage_size(); | |
274 | resident_memory_before = get_rprvt(child_pid) / vmpage_size; | |
d9a64523 | 275 | |
cb323159 A |
276 | T_LOG("Freezing child pid %d", child_pid); |
277 | ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid)); | |
0a7de745 | 278 | errno_freeze_sysctl = errno; |
d9a64523 A |
279 | sleep(1); |
280 | ||
0a7de745 A |
281 | /* |
282 | * The child process toggles its freezable state on each iteration. | |
283 | * So a failure for every alternate freeze is expected. | |
284 | */ | |
cb323159 | 285 | if (freeze_count % 2) { |
0a7de745 A |
286 | length = sizeof(freeze_enabled); |
287 | T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0), | |
288 | "failed to query vm.freeze_enabled"); | |
289 | if (freeze_enabled) { | |
290 | errno = errno_freeze_sysctl; | |
291 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed"); | |
292 | } else { | |
293 | /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */ | |
294 | T_LOG("Freeze has been disabled. Terminating early."); | |
295 | T_END; | |
296 | } | |
cb323159 A |
297 | resident_memory_after = get_rprvt(child_pid) / vmpage_size; |
298 | uint64_t freeze_pages_max = (uint64_t) sysctl_freeze_pages_max(NULL); | |
299 | T_QUIET; T_ASSERT_LT(resident_memory_after, resident_memory_before, "Freeze didn't reduce resident memory set"); | |
300 | if (resident_memory_before > freeze_pages_max) { | |
301 | T_QUIET; T_ASSERT_LE(resident_memory_before - resident_memory_after, freeze_pages_max, "Freeze pages froze more than the threshold."); | |
302 | } | |
303 | ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid)); | |
d9a64523 A |
304 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed"); |
305 | } else { | |
306 | T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed"); | |
307 | T_LOG("Freeze failed as expected"); | |
308 | } | |
309 | ||
310 | freeze_count++; | |
311 | ||
cb323159 | 312 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process"); |
d9a64523 A |
313 | } |
314 | ||
0a7de745 | 315 | void |
cb323159 | 316 | run_freezer_test(int num_pages) |
0a7de745 A |
317 | { |
318 | int ret, freeze_enabled; | |
d9a64523 A |
319 | char sz_str[50]; |
320 | char **launch_tool_args; | |
321 | char testpath[PATH_MAX]; | |
322 | uint32_t testpath_buf_size; | |
323 | dispatch_source_t ds_freeze, ds_proc; | |
0a7de745 A |
324 | size_t length; |
325 | ||
326 | length = sizeof(freeze_enabled); | |
327 | T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0), | |
328 | "failed to query vm.freeze_enabled"); | |
329 | if (!freeze_enabled) { | |
330 | /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */ | |
331 | T_SKIP("Freeze has been disabled. Skipping test."); | |
332 | } | |
d9a64523 A |
333 | |
334 | signal(SIGUSR1, SIG_IGN); | |
335 | ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue()); | |
336 | T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)"); | |
337 | ||
338 | dispatch_source_set_event_handler(ds_freeze, ^{ | |
339 | if (freeze_count < NUM_ITERATIONS) { | |
0a7de745 | 340 | freeze_helper_process(); |
d9a64523 | 341 | } else { |
cb323159 | 342 | kill(child_pid, SIGKILL); |
0a7de745 | 343 | dispatch_source_cancel(ds_freeze); |
d9a64523 A |
344 | } |
345 | }); | |
346 | dispatch_activate(ds_freeze); | |
347 | ||
348 | testpath_buf_size = sizeof(testpath); | |
349 | ret = _NSGetExecutablePath(testpath, &testpath_buf_size); | |
350 | T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath"); | |
351 | T_LOG("Executable path: %s", testpath); | |
352 | ||
cb323159 | 353 | sprintf(sz_str, "%d", num_pages); |
d9a64523 A |
354 | launch_tool_args = (char *[]){ |
355 | testpath, | |
356 | "-n", | |
357 | "allocate_pages", | |
358 | "--", | |
359 | sz_str, | |
360 | NULL | |
361 | }; | |
362 | ||
363 | /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */ | |
cb323159 | 364 | ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL); |
d9a64523 A |
365 | if (ret != 0) { |
366 | T_LOG("dt_launch tool returned %d with error code %d", ret, errno); | |
367 | } | |
cb323159 | 368 | T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool"); |
d9a64523 | 369 | |
cb323159 | 370 | ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue()); |
d9a64523 A |
371 | T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)"); |
372 | ||
373 | dispatch_source_set_event_handler(ds_proc, ^{ | |
374 | int status = 0, code = 0; | |
cb323159 A |
375 | pid_t rc = waitpid(child_pid, &status, 0); |
376 | T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid"); | |
d9a64523 A |
377 | code = WEXITSTATUS(status); |
378 | ||
379 | if (code == 0) { | |
0a7de745 | 380 | T_END; |
d9a64523 | 381 | } else if (code > 0 && code < EXIT_CODE_MAX) { |
0a7de745 | 382 | T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]); |
d9a64523 | 383 | } else { |
0a7de745 | 384 | T_ASSERT_FAIL("Child exited with unknown exit code %d", code); |
d9a64523 A |
385 | } |
386 | }); | |
387 | dispatch_activate(ds_proc); | |
388 | ||
cb323159 | 389 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process"); |
d9a64523 A |
390 | dispatch_main(); |
391 | } | |
392 | ||
cb323159 A |
393 | static void |
394 | allocate_pages(int num_pages) | |
395 | { | |
396 | int i, j, vmpgsize; | |
d9a64523 | 397 | char val; |
cb323159 | 398 | __block int num_iter = 0; |
d9a64523 A |
399 | __block char **buf; |
400 | dispatch_source_t ds_signal; | |
cb323159 A |
401 | vmpgsize = get_vmpage_size(); |
402 | if (num_pages < 1) { | |
403 | printf("Invalid number of pages to allocate: %d\n", num_pages); | |
404 | exit(INVALID_ALLOCATE_PAGES_ARGUMENTS); | |
d9a64523 A |
405 | } |
406 | ||
d9a64523 A |
407 | buf = (char**)malloc(sizeof(char*) * (size_t)num_pages); |
408 | ||
409 | /* Gives us the compression ratio we see in the typical case (~2.7) */ | |
410 | for (j = 0; j < num_pages; j++) { | |
411 | buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char)); | |
412 | val = 0; | |
413 | for (i = 0; i < vmpgsize; i += 16) { | |
414 | memset(&buf[j][i], val, 16); | |
415 | if (i < 3400 * (vmpgsize / 4096)) { | |
416 | val++; | |
417 | } | |
418 | } | |
419 | } | |
420 | ||
421 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{ | |
422 | /* Signal to the parent that we're done allocating and it's ok to freeze us */ | |
0a7de745 | 423 | printf("[%d] Sending initial signal to parent to begin freezing\n", getpid()); |
d9a64523 | 424 | if (kill(getppid(), SIGUSR1) != 0) { |
0a7de745 | 425 | exit(INITIAL_SIGNAL_TO_PARENT_FAILED); |
d9a64523 A |
426 | } |
427 | }); | |
428 | ||
429 | signal(SIGUSR1, SIG_IGN); | |
430 | ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue()); | |
431 | if (ds_signal == NULL) { | |
432 | exit(DISPATCH_SOURCE_CREATE_FAILED); | |
433 | } | |
434 | ||
435 | dispatch_source_set_event_handler(ds_signal, ^{ | |
436 | int current_state, new_state; | |
437 | volatile int tmp; | |
438 | ||
439 | /* Make sure all the pages are accessed before trying to freeze again */ | |
440 | for (int x = 0; x < num_pages; x++) { | |
0a7de745 | 441 | tmp = buf[x][0]; |
d9a64523 A |
442 | } |
443 | ||
444 | current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0); | |
cb323159 A |
445 | /* Sysprocs start off as unfreezable. Verify that first. */ |
446 | if (num_iter == 0 && current_state != 0) { | |
447 | exit(IS_FREEZABLE_NOT_AS_EXPECTED); | |
448 | } | |
d9a64523 A |
449 | |
450 | /* Toggle freezable state */ | |
451 | new_state = (current_state) ? 0: 1; | |
0a7de745 A |
452 | printf("[%d] Changing state from %s to %s\n", getpid(), |
453 | (current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable"); | |
d9a64523 | 454 | if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) { |
0a7de745 | 455 | exit(MEMORYSTATUS_CONTROL_FAILED); |
d9a64523 A |
456 | } |
457 | ||
458 | /* Verify that the state has been set correctly */ | |
459 | current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0); | |
460 | if (new_state != current_state) { | |
0a7de745 | 461 | exit(IS_FREEZABLE_NOT_AS_EXPECTED); |
d9a64523 A |
462 | } |
463 | num_iter++; | |
464 | ||
465 | if (kill(getppid(), SIGUSR1) != 0) { | |
0a7de745 | 466 | exit(SIGNAL_TO_PARENT_FAILED); |
d9a64523 A |
467 | } |
468 | }); | |
469 | dispatch_activate(ds_signal); | |
470 | move_to_idle_band(); | |
471 | ||
472 | dispatch_main(); | |
473 | } | |
474 | ||
cb323159 A |
475 | T_HELPER_DECL(allocate_pages, |
476 | "allocates pages to freeze", | |
477 | T_META_ASROOT(true)) { | |
478 | if (argc < 1) { | |
479 | exit(TOO_FEW_ARGUMENTS); | |
480 | } | |
481 | ||
482 | int num_pages = atoi(argv[0]); | |
483 | allocate_pages(num_pages); | |
484 | } | |
485 | ||
486 | T_DECL(freeze, "VM freezer test", T_META_ASROOT(true)) { | |
487 | run_freezer_test( | |
488 | (MEM_SIZE_MB << 20) / get_vmpage_size()); | |
489 | } | |
490 | ||
491 | static int old_freeze_pages_max = 0; | |
492 | static void | |
493 | reset_freeze_pages_max() | |
494 | { | |
495 | if (old_freeze_pages_max != 0) { | |
496 | sysctl_freeze_pages_max(&old_freeze_pages_max); | |
497 | } | |
498 | } | |
499 | ||
500 | int | |
501 | sysctl_freeze_pages_max(int* new_value) | |
502 | { | |
503 | static int set_end_handler = false; | |
504 | int freeze_pages_max, ret; | |
505 | size_t size = sizeof(freeze_pages_max); | |
506 | ret = sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max, &size, new_value, size); | |
507 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to query kern.memorystatus_freeze_pages_max"); | |
508 | if (!set_end_handler) { | |
509 | // Save the original value and instruct darwintest to restore it after the test completes | |
510 | old_freeze_pages_max = freeze_pages_max; | |
511 | T_ATEND(reset_freeze_pages_max); | |
512 | set_end_handler = true; | |
513 | } | |
514 | return old_freeze_pages_max; | |
515 | } | |
516 | ||
517 | T_DECL(freeze_over_max_threshold, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) { | |
518 | int freeze_pages_max = FREEZE_PAGES_MAX; | |
519 | sysctl_freeze_pages_max(&freeze_pages_max); | |
520 | run_freezer_test(FREEZE_PAGES_MAX * 2); | |
521 | } | |
522 | ||
523 | T_HELPER_DECL(frozen_background, "Frozen background process", T_META_ASROOT(true)) { | |
524 | kern_return_t kern_ret; | |
525 | /* Set the process to freezable */ | |
526 | kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0); | |
527 | T_QUIET; T_ASSERT_EQ(kern_ret, KERN_SUCCESS, "set process is freezable"); | |
528 | /* Signal to our parent that we can be frozen */ | |
529 | if (kill(getppid(), SIGUSR1) != 0) { | |
530 | T_LOG("Unable to signal to parent process!"); | |
531 | exit(1); | |
532 | } | |
533 | while (1) { | |
534 | ; | |
535 | } | |
536 | } | |
537 | ||
538 | /* Launches the frozen_background helper as a managed process. */ | |
539 | static pid_t | |
540 | launch_frozen_background_process() | |
541 | { | |
542 | pid_t pid; | |
543 | char **launch_tool_args; | |
544 | char testpath[PATH_MAX]; | |
545 | uint32_t testpath_buf_size; | |
546 | int ret; | |
547 | ||
548 | testpath_buf_size = sizeof(testpath); | |
549 | ret = _NSGetExecutablePath(testpath, &testpath_buf_size); | |
550 | printf("Launching %s\n", testpath); | |
551 | launch_tool_args = (char *[]){ | |
552 | testpath, | |
553 | "-n", | |
554 | "frozen_background", | |
555 | NULL | |
556 | }; | |
557 | ret = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL); | |
558 | if (ret != 0) { | |
559 | T_LOG("dt_launch tool returned %d with error code %d", ret, errno); | |
560 | } | |
561 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dt_launch_tool"); | |
562 | /* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */ | |
563 | ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED, pid, 1, NULL, 0); | |
564 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control"); | |
565 | return pid; | |
566 | } | |
567 | ||
568 | static void | |
569 | freeze_process(pid_t pid) | |
570 | { | |
571 | int ret, freeze_enabled, errno_freeze_sysctl; | |
572 | size_t length; | |
573 | T_LOG("Freezing pid %d", pid); | |
574 | ||
575 | ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid)); | |
576 | errno_freeze_sysctl = errno; | |
577 | length = sizeof(freeze_enabled); | |
578 | T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0), | |
579 | "failed to query vm.freeze_enabled"); | |
580 | if (freeze_enabled) { | |
581 | errno = errno_freeze_sysctl; | |
582 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed"); | |
583 | } else { | |
584 | /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */ | |
585 | T_LOG("Freeze has been disabled. Terminating early."); | |
586 | T_END; | |
587 | } | |
588 | } | |
589 | ||
590 | static void | |
591 | memorystatus_assertion_test_demote_frozen() | |
592 | { | |
593 | #if !CONFIG_EMBEDDED | |
594 | T_SKIP("Freezing processes is only supported on embedded"); | |
595 | #endif | |
596 | /* | |
597 | * Test that if we assert a priority on a process, freeze it, and then demote all frozen processes, it does not get demoted below the asserted priority. | |
598 | * Then remove thee assertion, and ensure it gets demoted properly. | |
599 | */ | |
600 | /* these values will remain fixed during testing */ | |
601 | int active_limit_mb = 15; /* arbitrary */ | |
602 | int inactive_limit_mb = 7; /* arbitrary */ | |
ea3f0419 | 603 | int demote_value = 1; |
cb323159 A |
604 | /* Launch the child process, and elevate its priority */ |
605 | int requestedpriority; | |
606 | dispatch_source_t ds_signal, ds_exit; | |
607 | requestedpriority = JETSAM_PRIORITY_UI_SUPPORT; | |
608 | ||
609 | /* Wait for the child process to tell us that it's ready, and then freeze it */ | |
610 | signal(SIGUSR1, SIG_IGN); | |
611 | ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue()); | |
612 | T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create"); | |
613 | dispatch_source_set_event_handler(ds_signal, ^{ | |
614 | int sysctl_ret; | |
615 | /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */ | |
616 | freeze_process(child_pid); | |
617 | /* Agressive demotion */ | |
ea3f0419 A |
618 | sysctl_ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &demote_value, sizeof(demote_value)); |
619 | T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded"); | |
cb323159 A |
620 | /* Check */ |
621 | (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set"); | |
622 | T_LOG("Relinquishing our assertion."); | |
623 | /* Relinquish our assertion, and check that it gets demoted. */ | |
624 | relinquish_assertion_priority(child_pid, 0x0); | |
625 | (void)check_properties(child_pid, JETSAM_PRIORITY_AGING_BAND2, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_RELINQUISHED, "Assertion was reqlinquished."); | |
626 | /* Kill the child */ | |
ea3f0419 | 627 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process"); |
cb323159 A |
628 | T_END; |
629 | }); | |
630 | ||
631 | /* Launch the child process and set the initial properties on it. */ | |
632 | child_pid = launch_frozen_background_process(); | |
633 | set_memlimits(child_pid, active_limit_mb, inactive_limit_mb, false, false); | |
634 | set_assertion_priority(child_pid, requestedpriority, 0x0); | |
635 | (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set"); | |
636 | /* Listen for exit. */ | |
637 | ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue()); | |
638 | dispatch_source_set_event_handler(ds_exit, ^{ | |
639 | int status = 0, code = 0; | |
640 | pid_t rc = waitpid(child_pid, &status, 0); | |
641 | T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid"); | |
642 | code = WEXITSTATUS(status); | |
643 | T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly"); | |
644 | T_END; | |
645 | }); | |
646 | ||
647 | dispatch_activate(ds_exit); | |
648 | dispatch_activate(ds_signal); | |
649 | dispatch_main(); | |
650 | } | |
651 | ||
652 | T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) { | |
653 | memorystatus_assertion_test_demote_frozen(); | |
d9a64523 | 654 | } |
ea3f0419 A |
655 | |
656 | T_DECL(budget_replenishment, "budget replenishes properly") { | |
657 | size_t length; | |
658 | int ret; | |
659 | static unsigned int kTestIntervalSecs = 60 * 60 * 32; // 32 Hours | |
660 | unsigned int memorystatus_freeze_daily_mb_max, memorystatus_freeze_daily_pages_max; | |
661 | static unsigned int kFixedPointFactor = 100; | |
662 | static unsigned int kNumSecondsInDay = 60 * 60 * 24; | |
663 | unsigned int new_budget, expected_new_budget_pages; | |
664 | size_t new_budget_ln; | |
665 | unsigned int page_size = (unsigned int) get_vmpage_size(); | |
666 | ||
667 | /* | |
668 | * Calculate a new budget as if the previous interval expired kTestIntervalSecs | |
669 | * ago and we used up its entire budget. | |
670 | */ | |
671 | length = sizeof(kTestIntervalSecs); | |
672 | new_budget_ln = sizeof(new_budget); | |
673 | ret = sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget, &new_budget_ln, &kTestIntervalSecs, length); | |
bca245ac | 674 | T_ASSERT_POSIX_SUCCESS(ret, "vm.memorystatus_freeze_calculate_new_budget"); |
ea3f0419 A |
675 | |
676 | // Grab the daily budget. | |
677 | length = sizeof(memorystatus_freeze_daily_mb_max); | |
678 | ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max, &length, NULL, 0); | |
bca245ac | 679 | T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_daily_mb_max"); |
ea3f0419 | 680 | |
bca245ac A |
681 | memorystatus_freeze_daily_pages_max = memorystatus_freeze_daily_mb_max * 1024UL * 1024UL / page_size; |
682 | T_LOG("memorystatus_freeze_daily_mb_max %u", memorystatus_freeze_daily_mb_max); | |
683 | T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max); | |
684 | T_LOG("page_size %u", page_size); | |
ea3f0419 A |
685 | |
686 | /* | |
687 | * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay | |
688 | * seconds of budget. | |
689 | */ | |
690 | expected_new_budget_pages = memorystatus_freeze_daily_pages_max; | |
bca245ac A |
691 | T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages); |
692 | T_ASSERT_EQ(kTestIntervalSecs, 60 * 60 * 32, "kTestIntervalSecs did not change"); | |
ea3f0419 A |
693 | expected_new_budget_pages += ((kTestIntervalSecs * kFixedPointFactor) / (kNumSecondsInDay) |
694 | * memorystatus_freeze_daily_pages_max) / kFixedPointFactor; | |
bca245ac A |
695 | T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages); |
696 | T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max); | |
ea3f0419 A |
697 | |
698 | T_QUIET; T_ASSERT_EQ(new_budget, expected_new_budget_pages, "Calculate new budget behaves correctly."); | |
699 | } |