3 #include <sys/sysctl.h>
4 #include <sys/kern_memorystatus.h>
5 #include <sys/kern_memorystatus_freeze.h>
7 #include <mach-o/dyld.h>
8 #include <mach/mach_vm.h>
9 #include <mach/vm_page_size.h> /* Needed for vm_region info */
10 #include <mach/shared_region.h>
11 #include <mach/mach.h>
16 #include <darwintest.h>
17 #include <darwintest_utils.h>
19 #include "memorystatus_assertion_helpers.h"
22 T_META_NAMESPACE("xnu.vm"),
23 T_META_CHECK_LEAKS(false)
26 #define MEM_SIZE_MB 10
27 #define NUM_ITERATIONS 5
28 #define FREEZE_PAGES_MAX 256
30 #define CREATE_LIST(X) \
32 X(TOO_FEW_ARGUMENTS) \
33 X(SYSCTL_VM_PAGESIZE_FAILED) \
34 X(VM_PAGESIZE_IS_ZERO) \
35 X(DISPATCH_SOURCE_CREATE_FAILED) \
36 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
37 X(SIGNAL_TO_PARENT_FAILED) \
38 X(MEMORYSTATUS_CONTROL_FAILED) \
39 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
40 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
41 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
43 X(FROZEN_BIT_NOT_SET) \
44 X(MEMORYSTATUS_CONTROL_ERROR) \
47 #define EXIT_CODES_ENUM(VAR) VAR,
49 CREATE_LIST(EXIT_CODES_ENUM
)
52 #define EXIT_CODES_STRING(VAR) #VAR,
53 static const char *exit_codes_str
[] = {
54 CREATE_LIST(EXIT_CODES_STRING
)
61 size_t size
= sizeof(vmpage_size
);
62 int ret
= sysctlbyname("vm.pagesize", &vmpage_size
, &size
, NULL
, 0);
63 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "failed to query vm.pagesize");
64 T_QUIET
; T_ASSERT_GT(vmpage_size
, 0, "vm.pagesize is not > 0");
68 static pid_t child_pid
= -1;
69 static int freeze_count
= 0;
71 void move_to_idle_band(pid_t
);
72 void run_freezer_test(int);
73 void freeze_helper_process(void);
74 /* Gets and optionally sets the freeze pages max threshold */
75 int sysctl_freeze_pages_max(int* new_value
);
77 /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
78 * We're moving away from those unit tests, so they're copied here.
81 /* Cribbed from 'top'... */
83 in_shared_region(mach_vm_address_t addr
, cpu_type_t type
)
85 mach_vm_address_t base
= 0, size
= 0;
89 base
= SHARED_REGION_BASE_ARM
;
90 size
= SHARED_REGION_SIZE_ARM
;
94 base
= SHARED_REGION_BASE_ARM64
;
95 size
= SHARED_REGION_SIZE_ARM64
;
100 base
= SHARED_REGION_BASE_X86_64
;
101 size
= SHARED_REGION_SIZE_X86_64
;
105 base
= SHARED_REGION_BASE_I386
;
106 size
= SHARED_REGION_SIZE_I386
;
109 case CPU_TYPE_POWERPC
:
110 base
= SHARED_REGION_BASE_PPC
;
111 size
= SHARED_REGION_SIZE_PPC
;
114 case CPU_TYPE_POWERPC64
:
115 base
= SHARED_REGION_BASE_PPC64
;
116 size
= SHARED_REGION_SIZE_PPC64
;
122 fprintf(stderr
, "unknown CPU type: 0x%x\n", t
);
127 return addr
>= base
&& addr
< (base
+ size
);
130 /* Get the resident private memory of the given pid */
131 static unsigned long long
134 mach_port_name_t task
;
137 mach_vm_size_t rprvt
= 0;
138 mach_vm_size_t empty
= 0;
139 mach_vm_size_t fw_private
= 0;
140 mach_vm_size_t pagesize
= vm_kernel_page_size
; // The vm_region page info is reported
141 // in terms of vm_kernel_page_size.
142 mach_vm_size_t regs
= 0;
144 mach_vm_address_t addr
;
149 kr
= task_for_pid(mach_task_self(), pid
, &task
);
150 T_QUIET
; T_ASSERT_TRUE(kr
== KERN_SUCCESS
, "Unable to get task_for_pid of child");
152 for (addr
= 0;; addr
+= size
) {
153 vm_region_top_info_data_t info
;
154 mach_msg_type_number_t count
= VM_REGION_TOP_INFO_COUNT
;
155 mach_port_t object_name
;
157 kr
= mach_vm_region(task
, &addr
, &size
, VM_REGION_TOP_INFO
, (vm_region_info_t
)&info
, &count
, &object_name
);
158 if (kr
!= KERN_SUCCESS
) {
162 #if defined (__arm64__)
163 if (in_shared_region(addr
, CPU_TYPE_ARM64
)) {
165 if (in_shared_region(addr
, CPU_TYPE_ARM
)) {
168 fw_private
+= info
.private_pages_resident
* pagesize
;
171 * Check if this process has the globally shared
172 * text and data regions mapped in. If so, set
173 * split to TRUE and avoid checking
176 if (split
== FALSE
&& info
.share_mode
== SM_EMPTY
) {
177 vm_region_basic_info_data_64_t b_info
;
178 mach_vm_address_t b_addr
= addr
;
179 mach_vm_size_t b_size
= size
;
180 count
= VM_REGION_BASIC_INFO_COUNT_64
;
182 kr
= mach_vm_region(task
, &b_addr
, &b_size
, VM_REGION_BASIC_INFO_64
, (vm_region_info_t
)&b_info
, &count
, &object_name
);
183 if (kr
!= KERN_SUCCESS
) {
187 if (b_info
.reserved
) {
193 * Short circuit the loop if this isn't a shared
194 * private region, since that's the only region
195 * type we care about within the current address
198 if (info
.share_mode
!= SM_PRIVATE
) {
206 * Update counters according to the region type.
209 if (info
.share_mode
== SM_COW
&& info
.ref_count
== 1) {
210 // Treat single reference SM_COW as SM_PRIVATE
211 info
.share_mode
= SM_PRIVATE
;
214 switch (info
.share_mode
) {
216 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
217 // since they are not shareable and are wired.
219 rprvt
+= info
.private_pages_resident
* pagesize
;
220 rprvt
+= info
.shared_pages_resident
* pagesize
;
230 // Treat kernel_task specially
231 if (info
.share_mode
== SM_COW
) {
232 rprvt
+= info
.private_pages_resident
* pagesize
;
237 if (info
.share_mode
== SM_COW
) {
238 rprvt
+= info
.private_pages_resident
* pagesize
;
252 move_to_idle_band(pid_t pid
)
254 memorystatus_priority_properties_t props
;
256 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
257 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
259 props
.priority
= JETSAM_PRIORITY_IDLE
;
263 * This requires us to run as root (in the absence of entitlement).
264 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
266 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES
, pid
, 0, &props
, sizeof(props
))) {
267 exit(MEMSTAT_PRIORITY_CHANGE_FAILED
);
272 freeze_helper_process(void)
275 int ret
, freeze_enabled
, errno_freeze_sysctl
;
276 uint64_t resident_memory_before
, resident_memory_after
, vmpage_size
;
277 vmpage_size
= (uint64_t) get_vmpage_size();
278 resident_memory_before
= get_rprvt(child_pid
) / vmpage_size
;
280 T_LOG("Freezing child pid %d", child_pid
);
281 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &child_pid
, sizeof(child_pid
));
282 errno_freeze_sysctl
= errno
;
286 * The child process toggles its freezable state on each iteration.
287 * So a failure for every alternate freeze is expected.
289 if (freeze_count
% 2) {
290 length
= sizeof(freeze_enabled
);
291 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
292 "failed to query vm.freeze_enabled");
293 if (freeze_enabled
) {
294 errno
= errno_freeze_sysctl
;
295 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed");
297 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
298 T_LOG("Freeze has been disabled. Terminating early.");
301 resident_memory_after
= get_rprvt(child_pid
) / vmpage_size
;
302 uint64_t freeze_pages_max
= (uint64_t) sysctl_freeze_pages_max(NULL
);
303 T_QUIET
; T_ASSERT_LT(resident_memory_after
, resident_memory_before
, "Freeze didn't reduce resident memory set");
304 if (resident_memory_before
> freeze_pages_max
) {
305 T_QUIET
; T_ASSERT_LE(resident_memory_before
- resident_memory_after
, freeze_pages_max
, "Freeze pages froze more than the threshold.");
307 ret
= sysctlbyname("kern.memorystatus_thaw", NULL
, NULL
, &child_pid
, sizeof(child_pid
));
308 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_thaw failed");
310 T_QUIET
; T_ASSERT_TRUE(ret
!= KERN_SUCCESS
, "Freeze should have failed");
311 T_LOG("Freeze failed as expected");
316 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGUSR1
), "failed to send SIGUSR1 to child process");
320 skip_if_freezer_is_disabled()
323 size_t length
= sizeof(freeze_enabled
);
325 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
326 "failed to query vm.freeze_enabled");
327 if (!freeze_enabled
) {
328 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
329 T_SKIP("Freeze has been disabled. Skipping test.");
334 run_freezer_test(int num_pages
)
338 char **launch_tool_args
;
339 char testpath
[PATH_MAX
];
340 uint32_t testpath_buf_size
;
341 dispatch_source_t ds_freeze
, ds_proc
;
343 skip_if_freezer_is_disabled();
345 signal(SIGUSR1
, SIG_IGN
);
346 ds_freeze
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
347 T_QUIET
; T_ASSERT_NOTNULL(ds_freeze
, "dispatch_source_create (ds_freeze)");
349 dispatch_source_set_event_handler(ds_freeze
, ^{
350 if (freeze_count
< NUM_ITERATIONS
) {
351 freeze_helper_process();
353 kill(child_pid
, SIGKILL
);
354 dispatch_source_cancel(ds_freeze
);
357 dispatch_activate(ds_freeze
);
359 testpath_buf_size
= sizeof(testpath
);
360 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
361 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, "_NSGetExecutablePath");
362 T_LOG("Executable path: %s", testpath
);
364 sprintf(sz_str
, "%d", num_pages
);
365 launch_tool_args
= (char *[]){
374 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
375 ret
= dt_launch_tool(&child_pid
, launch_tool_args
, true, NULL
, NULL
);
377 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
379 T_QUIET
; T_ASSERT_POSIX_SUCCESS(child_pid
, "dt_launch_tool");
381 ds_proc
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)child_pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
382 T_QUIET
; T_ASSERT_NOTNULL(ds_proc
, "dispatch_source_create (ds_proc)");
384 dispatch_source_set_event_handler(ds_proc
, ^{
385 int status
= 0, code
= 0;
386 pid_t rc
= waitpid(child_pid
, &status
, 0);
387 T_QUIET
; T_ASSERT_EQ(rc
, child_pid
, "waitpid");
388 code
= WEXITSTATUS(status
);
392 } else if (code
> 0 && code
< EXIT_CODE_MAX
) {
393 T_ASSERT_FAIL("Child exited with %s", exit_codes_str
[code
]);
395 T_ASSERT_FAIL("Child exited with unknown exit code %d", code
);
398 dispatch_activate(ds_proc
);
400 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGCONT
), "failed to send SIGCONT to child process");
405 allocate_pages(int num_pages
)
409 __block
int num_iter
= 0;
411 dispatch_source_t ds_signal
;
412 vmpgsize
= get_vmpage_size();
414 printf("Invalid number of pages to allocate: %d\n", num_pages
);
415 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS
);
418 buf
= (char**)malloc(sizeof(char*) * (size_t)num_pages
);
420 /* Gives us the compression ratio we see in the typical case (~2.7) */
421 for (j
= 0; j
< num_pages
; j
++) {
422 buf
[j
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
424 for (i
= 0; i
< vmpgsize
; i
+= 16) {
425 memset(&buf
[j
][i
], val
, 16);
426 if (i
< 3400 * (vmpgsize
/ 4096)) {
432 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, NSEC_PER_SEC
), dispatch_get_main_queue(), ^{
433 /* Signal to the parent that we're done allocating and it's ok to freeze us */
434 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
435 if (kill(getppid(), SIGUSR1
) != 0) {
436 exit(INITIAL_SIGNAL_TO_PARENT_FAILED
);
440 signal(SIGUSR1
, SIG_IGN
);
441 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
442 if (ds_signal
== NULL
) {
443 exit(DISPATCH_SOURCE_CREATE_FAILED
);
446 dispatch_source_set_event_handler(ds_signal
, ^{
447 int current_state
, new_state
;
450 /* Make sure all the pages are accessed before trying to freeze again */
451 for (int x
= 0; x
< num_pages
; x
++) {
455 current_state
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE
, getpid(), 0, NULL
, 0);
456 /* Sysprocs start off as unfreezable. Verify that first. */
457 if (num_iter
== 0 && current_state
!= 0) {
458 exit(IS_FREEZABLE_NOT_AS_EXPECTED
);
461 /* Toggle freezable state */
462 new_state
= (current_state
) ? 0: 1;
463 printf("[%d] Changing state from %s to %s\n", getpid(),
464 (current_state
) ? "freezable": "unfreezable", (new_state
) ? "freezable": "unfreezable");
465 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), (uint32_t)new_state
, NULL
, 0) != KERN_SUCCESS
) {
466 exit(MEMORYSTATUS_CONTROL_FAILED
);
469 /* Verify that the state has been set correctly */
470 current_state
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE
, getpid(), 0, NULL
, 0);
471 if (new_state
!= current_state
) {
472 exit(IS_FREEZABLE_NOT_AS_EXPECTED
);
476 if (kill(getppid(), SIGUSR1
) != 0) {
477 exit(SIGNAL_TO_PARENT_FAILED
);
480 dispatch_activate(ds_signal
);
481 move_to_idle_band(getpid());
486 T_HELPER_DECL(allocate_pages
,
487 "allocates pages to freeze",
488 T_META_ASROOT(true)) {
490 exit(TOO_FEW_ARGUMENTS
);
493 int num_pages
= atoi(argv
[0]);
494 allocate_pages(num_pages
);
497 T_DECL(freeze
, "VM freezer test", T_META_ASROOT(true)) {
499 (MEM_SIZE_MB
<< 20) / get_vmpage_size());
502 static int old_freeze_pages_max
= 0;
504 reset_freeze_pages_max()
506 if (old_freeze_pages_max
!= 0) {
507 sysctl_freeze_pages_max(&old_freeze_pages_max
);
512 sysctl_freeze_pages_max(int* new_value
)
514 static int set_end_handler
= false;
515 int freeze_pages_max
, ret
;
516 size_t size
= sizeof(freeze_pages_max
);
517 ret
= sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max
, &size
, new_value
, size
);
518 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "Unable to query kern.memorystatus_freeze_pages_max");
519 if (!set_end_handler
) {
520 // Save the original value and instruct darwintest to restore it after the test completes
521 old_freeze_pages_max
= freeze_pages_max
;
522 T_ATEND(reset_freeze_pages_max
);
523 set_end_handler
= true;
525 return old_freeze_pages_max
;
528 T_DECL(freeze_over_max_threshold
, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) {
529 int freeze_pages_max
= FREEZE_PAGES_MAX
;
530 sysctl_freeze_pages_max(&freeze_pages_max
);
531 run_freezer_test(FREEZE_PAGES_MAX
* 2);
534 T_HELPER_DECL(frozen_background
, "Frozen background process", T_META_ASROOT(true)) {
535 kern_return_t kern_ret
;
536 /* Set the process to freezable */
537 kern_ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), 1, NULL
, 0);
538 T_QUIET
; T_ASSERT_EQ(kern_ret
, KERN_SUCCESS
, "set process is freezable");
539 /* Signal to our parent that we can be frozen */
540 if (kill(getppid(), SIGUSR1
) != 0) {
541 T_LOG("Unable to signal to parent process!");
549 /* Launches the frozen_background helper as a managed process. */
551 launch_background_helper(const char* variant
)
554 char **launch_tool_args
;
555 char testpath
[PATH_MAX
];
556 uint32_t testpath_buf_size
;
559 testpath_buf_size
= sizeof(testpath
);
560 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
561 printf("Launching %s\n", testpath
);
562 launch_tool_args
= (char *[]){
568 ret
= dt_launch_tool(&pid
, launch_tool_args
, false, NULL
, NULL
);
570 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
572 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "dt_launch_tool");
573 /* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */
574 ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED
, pid
, 1, NULL
, 0);
575 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "memorystatus_control");
580 freeze_process(pid_t pid
)
582 int ret
, freeze_enabled
, errno_freeze_sysctl
;
584 T_LOG("Freezing pid %d", pid
);
586 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &pid
, sizeof(pid
));
587 errno_freeze_sysctl
= errno
;
588 length
= sizeof(freeze_enabled
);
589 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
590 "failed to query vm.freeze_enabled");
591 if (freeze_enabled
) {
592 errno
= errno_freeze_sysctl
;
593 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed");
595 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
596 T_LOG("Freeze has been disabled. Terminating early.");
602 memorystatus_assertion_test_demote_frozen()
605 * 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.
606 * Then remove thee assertion, and ensure it gets demoted properly.
608 /* these values will remain fixed during testing */
609 int active_limit_mb
= 15; /* arbitrary */
610 int inactive_limit_mb
= 7; /* arbitrary */
611 int demote_value
= 1;
612 /* Launch the child process, and elevate its priority */
613 int requestedpriority
;
614 dispatch_source_t ds_signal
, ds_exit
;
615 requestedpriority
= JETSAM_PRIORITY_UI_SUPPORT
;
617 /* Wait for the child process to tell us that it's ready, and then freeze it */
618 signal(SIGUSR1
, SIG_IGN
);
619 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
620 T_QUIET
; T_ASSERT_NOTNULL(ds_signal
, "dispatch_source_create");
621 dispatch_source_set_event_handler(ds_signal
, ^{
623 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
624 freeze_process(child_pid
);
625 /* Agressive demotion */
626 sysctl_ret
= sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL
, NULL
, &demote_value
, sizeof(demote_value
));
627 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctl_ret
, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
629 (void)check_properties(child_pid
, requestedpriority
, inactive_limit_mb
, 0x0, ASSERTION_STATE_IS_SET
, "Priority was set");
630 T_LOG("Relinquishing our assertion.");
631 /* Relinquish our assertion, and check that it gets demoted. */
632 relinquish_assertion_priority(child_pid
, 0x0);
633 (void)check_properties(child_pid
, JETSAM_PRIORITY_AGING_BAND2
, inactive_limit_mb
, 0x0, ASSERTION_STATE_IS_RELINQUISHED
, "Assertion was reqlinquished.");
635 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
639 /* Launch the child process and set the initial properties on it. */
640 child_pid
= launch_background_helper("frozen_background");
641 set_memlimits(child_pid
, active_limit_mb
, inactive_limit_mb
, false, false);
642 set_assertion_priority(child_pid
, requestedpriority
, 0x0);
643 (void)check_properties(child_pid
, requestedpriority
, inactive_limit_mb
, 0x0, ASSERTION_STATE_IS_SET
, "Priority was set");
644 /* Listen for exit. */
645 ds_exit
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)child_pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
646 dispatch_source_set_event_handler(ds_exit
, ^{
647 int status
= 0, code
= 0;
648 pid_t rc
= waitpid(child_pid
, &status
, 0);
649 T_QUIET
; T_ASSERT_EQ(rc
, child_pid
, "waitpid");
650 code
= WEXITSTATUS(status
);
651 T_QUIET
; T_ASSERT_EQ(code
, 0, "Child exited cleanly");
655 dispatch_activate(ds_exit
);
656 dispatch_activate(ds_signal
);
660 T_DECL(assertion_test_demote_frozen
, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
661 memorystatus_assertion_test_demote_frozen();
664 T_DECL(budget_replenishment
, "budget replenishes properly") {
667 static unsigned int kTestIntervalSecs
= 60 * 60 * 32; // 32 Hours
668 unsigned int memorystatus_freeze_daily_mb_max
, memorystatus_freeze_daily_pages_max
;
669 static unsigned int kFixedPointFactor
= 100;
670 static unsigned int kNumSecondsInDay
= 60 * 60 * 24;
671 unsigned int new_budget
, expected_new_budget_pages
;
672 size_t new_budget_ln
;
673 unsigned int page_size
= (unsigned int) get_vmpage_size();
676 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
677 * ago and we used up its entire budget.
679 length
= sizeof(kTestIntervalSecs
);
680 new_budget_ln
= sizeof(new_budget
);
681 ret
= sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget
, &new_budget_ln
, &kTestIntervalSecs
, length
);
682 T_ASSERT_POSIX_SUCCESS(ret
, "vm.memorystatus_freeze_calculate_new_budget");
684 // Grab the daily budget.
685 length
= sizeof(memorystatus_freeze_daily_mb_max
);
686 ret
= sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max
, &length
, NULL
, 0);
687 T_ASSERT_POSIX_SUCCESS(ret
, "kern.memorystatus_freeze_daily_mb_max");
689 memorystatus_freeze_daily_pages_max
= memorystatus_freeze_daily_mb_max
* 1024UL * 1024UL / page_size
;
690 T_LOG("memorystatus_freeze_daily_mb_max %u", memorystatus_freeze_daily_mb_max
);
691 T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max
);
692 T_LOG("page_size %u", page_size
);
695 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
698 expected_new_budget_pages
= memorystatus_freeze_daily_pages_max
;
699 T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages
);
700 T_ASSERT_EQ(kTestIntervalSecs
, 60 * 60 * 32, "kTestIntervalSecs did not change");
701 expected_new_budget_pages
+= ((kTestIntervalSecs
* kFixedPointFactor
) / (kNumSecondsInDay
)
702 * memorystatus_freeze_daily_pages_max
) / kFixedPointFactor
;
703 T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages
);
704 T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max
);
706 T_QUIET
; T_ASSERT_EQ(new_budget
, expected_new_budget_pages
, "Calculate new budget behaves correctly.");
711 is_proc_in_frozen_list(pid_t pid
, char* name
, size_t name_len
)
715 global_frozen_procs_t
*frozen_procs
= malloc(sizeof(global_frozen_procs_t
));
716 T_QUIET
; T_ASSERT_NOTNULL(frozen_procs
, "malloc");
718 bytes_written
= memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL
, 0, FREEZER_CONTROL_GET_PROCS
, frozen_procs
, sizeof(global_frozen_procs_t
));
719 T_QUIET
; T_ASSERT_LE((size_t) bytes_written
, sizeof(global_frozen_procs_t
), "Didn't overflow buffer");
720 T_QUIET
; T_ASSERT_GT(bytes_written
, 0, "Wrote someting");
722 for (size_t i
= 0; i
< frozen_procs
->gfp_num_frozen
; i
++) {
723 if (frozen_procs
->gfp_procs
[i
].fp_pid
== pid
) {
725 strlcpy(name
, frozen_procs
->gfp_procs
[i
].fp_name
, name_len
);
732 drop_jetsam_snapshot_ownership(void)
735 ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_JETSAM_SNAPSHOT_OWNERSHIP
, 0, MEMORYSTATUS_FLAGS_SNAPSHOT_DROP_OWNERSHIP
, NULL
, 0);
736 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, 0, "Drop ownership of jetsam snapshot");
740 take_jetsam_snapshot_ownership(void)
743 ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_JETSAM_SNAPSHOT_OWNERSHIP
, 0, MEMORYSTATUS_FLAGS_SNAPSHOT_TAKE_OWNERSHIP
, NULL
, 0);
744 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "Take ownership of jetsam snapshot");
745 T_ATEND(drop_jetsam_snapshot_ownership
);
749 * Retrieve a jetsam snapshot.
752 * pointer to snapshot.
754 * Caller is responsible for freeing snapshot.
757 memorystatus_jetsam_snapshot_t
*
758 get_jetsam_snapshot(uint32_t flags
, bool empty_allowed
)
760 memorystatus_jetsam_snapshot_t
* snapshot
= NULL
;
764 ret
= memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT
, 0, flags
, NULL
, 0);
765 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, 0, "Get jetsam snapshot size");
766 size
= (uint32_t) ret
;
767 if (size
== 0 && empty_allowed
) {
771 snapshot
= (memorystatus_jetsam_snapshot_t
*)malloc(size
);
772 T_QUIET
; T_ASSERT_NOTNULL(snapshot
, "Allocate snapshot of size %d", size
);
774 ret
= memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT
, 0, flags
, snapshot
, size
);
775 T_QUIET
; T_ASSERT_GT(size
, 0, "Get jetsam snapshot");
777 if (((size
- sizeof(memorystatus_jetsam_snapshot_t
)) / sizeof(memorystatus_jetsam_snapshot_entry_t
)) != snapshot
->entry_count
) {
778 T_FAIL("Malformed snapshot: %d! Expected %ld + %zd x %ld = %ld\n", size
,
779 sizeof(memorystatus_jetsam_snapshot_t
), snapshot
->entry_count
, sizeof(memorystatus_jetsam_snapshot_entry_t
),
780 sizeof(memorystatus_jetsam_snapshot_t
) + (snapshot
->entry_count
* sizeof(memorystatus_jetsam_snapshot_entry_t
)));
790 * Look for the given pid in the snapshot.
793 * pointer to pid's entry or NULL if pid is not found.
795 * Caller has ownership of snapshot before and after call.
798 memorystatus_jetsam_snapshot_entry_t
*
799 get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t
*snapshot
, pid_t pid
)
801 T_QUIET
; T_ASSERT_NOTNULL(snapshot
, "Got snapshot");
802 for (size_t i
= 0; i
< snapshot
->entry_count
; i
++) {
803 memorystatus_jetsam_snapshot_entry_t
*curr
= &(snapshot
->entries
[i
]);
804 if (curr
->pid
== pid
) {
813 * Launches the child & runs the given block after the child signals.
814 * If exit_with_child is true, the test will exit when the child exits.
817 test_after_background_helper_launches(bool exit_with_child
, const char* variant
, dispatch_block_t test_block
)
819 dispatch_source_t ds_signal
, ds_exit
;
821 /* Run the test block after the child launches & signals it's ready. */
822 signal(SIGUSR1
, SIG_IGN
);
823 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
824 T_QUIET
; T_ASSERT_NOTNULL(ds_signal
, "dispatch_source_create");
825 dispatch_source_set_event_handler(ds_signal
, test_block
);
826 /* Launch the child process. */
827 child_pid
= launch_background_helper(variant
);
828 /* Listen for exit. */
829 if (exit_with_child
) {
830 ds_exit
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)child_pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
831 dispatch_source_set_event_handler(ds_exit
, ^{
832 int status
= 0, code
= 0;
833 pid_t rc
= waitpid(child_pid
, &status
, 0);
834 T_QUIET
; T_ASSERT_EQ(rc
, child_pid
, "waitpid");
835 code
= WEXITSTATUS(status
);
837 T_LOG("Child exited with error: %s", exit_codes_str
[code
]);
839 T_QUIET
; T_ASSERT_EQ(code
, 0, "Child exited cleanly");
843 dispatch_activate(ds_exit
);
845 dispatch_activate(ds_signal
);
849 T_DECL(get_frozen_procs
, "List processes in the freezer") {
850 skip_if_freezer_is_disabled();
852 test_after_background_helper_launches(true, "frozen_background", ^{
854 /* Place the child in the idle band so that it gets elevated like a typical app. */
855 move_to_idle_band(child_pid
);
856 /* Freeze the process, and check that it's in the list of frozen processes. */
857 freeze_process(child_pid
);
859 T_QUIET
; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid
, name
, sizeof(name
)), "Found proc in frozen list");
860 T_QUIET
; T_EXPECT_EQ_STR(name
, "memorystatus_freeze_test", "Proc has correct name");
862 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
867 T_DECL(frozen_to_swap_accounting
, "jetsam snapshot has frozen_to_swap accounting") {
868 static const size_t kSnapshotSleepDelay
= 5;
869 static const size_t kFreezeToDiskMaxDelay
= 60;
871 skip_if_freezer_is_disabled();
873 test_after_background_helper_launches(true, "frozen_background", ^{
874 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
875 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
876 /* Place the child in the idle band so that it gets elevated like a typical app. */
877 move_to_idle_band(child_pid
);
878 freeze_process(child_pid
);
880 * Wait until the child's pages get paged out to disk.
881 * If we don't see any pages get sent to disk before kFreezeToDiskMaxDelay seconds,
882 * something is either wrong with the compactor or the accounting.
884 for (size_t i
= 0; i
< kFreezeToDiskMaxDelay
/ kSnapshotSleepDelay
; i
++) {
885 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND
, false);
886 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
887 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Found child in snapshot");
888 if (child_entry
->jse_frozen_to_swap_pages
> 0) {
892 sleep(kSnapshotSleepDelay
);
894 T_QUIET
; T_ASSERT_GT(child_entry
->jse_frozen_to_swap_pages
, 0ULL, "child has some pages in swap");
897 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
902 T_DECL(freezer_snapshot
, "App kills are recorded in the freezer snapshot") {
903 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
904 take_jetsam_snapshot_ownership();
906 test_after_background_helper_launches(false, "frozen_background", ^{
908 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
909 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
911 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
912 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
914 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, false);
915 T_ASSERT_NOTNULL(snapshot
, "Got freezer snapshot");
916 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
917 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Child is in freezer snapshot");
918 T_QUIET
; T_ASSERT_EQ(child_entry
->killed
, (unsigned long long) JETSAM_REASON_GENERIC
, "Child entry was killed");
925 T_DECL(freezer_snapshot_consume
, "Freezer snapshot is consumed on read") {
926 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
927 take_jetsam_snapshot_ownership();
929 test_after_background_helper_launches(false, "frozen_background", ^{
931 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
932 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
934 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
935 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
937 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, false);
938 T_ASSERT_NOTNULL(snapshot
, "Got first freezer snapshot");
939 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
940 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Child is in first freezer snapshot");
942 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, true);
943 if (snapshot
!= NULL
) {
944 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
945 T_QUIET
; T_ASSERT_NULL(child_entry
, "Child is not in second freezer snapshot");
953 T_DECL(freezer_snapshot_frozen_state
, "Frozen state is recorded in freezer snapshot") {
954 skip_if_freezer_is_disabled();
955 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
956 take_jetsam_snapshot_ownership();
958 test_after_background_helper_launches(false, "frozen_background", ^{
960 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
961 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
963 move_to_idle_band(child_pid
);
964 freeze_process(child_pid
);
966 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
967 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
969 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, false);
970 T_ASSERT_NOTNULL(snapshot
, "Got freezer snapshot");
971 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
972 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Child is in freezer snapshot");
973 T_QUIET
; T_ASSERT_TRUE(child_entry
->state
& kMemorystatusFrozen
, "Child entry's frozen bit is set");
980 T_DECL(freezer_snapshot_thaw_state
, "Thaw count is recorded in freezer snapshot") {
981 skip_if_freezer_is_disabled();
982 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
983 take_jetsam_snapshot_ownership();
985 test_after_background_helper_launches(false, "frozen_background", ^{
987 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
988 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
990 move_to_idle_band(child_pid
);
991 ret
= pid_suspend(child_pid
);
992 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child suspended");
993 freeze_process(child_pid
);
994 ret
= pid_resume(child_pid
);
995 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child resumed after freeze");
997 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
998 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
1000 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, false);
1001 T_ASSERT_NOTNULL(snapshot
, "Got freezer snapshot");
1002 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
1003 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Child is in freezer snapshot");
1004 T_QUIET
; T_ASSERT_TRUE(child_entry
->state
& kMemorystatusFrozen
, "Child entry's frozen bit is still set after thaw");
1005 T_QUIET
; T_ASSERT_TRUE(child_entry
->state
& kMemorystatusWasThawed
, "Child entry was thawed");
1006 T_QUIET
; T_ASSERT_EQ(child_entry
->jse_thaw_count
, 1ULL, "Child entry's thaw count was incremented");
1013 T_HELPER_DECL(check_frozen
, "Check frozen state", T_META_ASROOT(true)) {
1015 dispatch_source_t ds_signal
;
1016 __block
int is_frozen
;
1017 /* Set the process to freezable */
1018 kern_ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), 1, NULL
, 0);
1019 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kern_ret
, "set process is freezable");
1020 /* Signal to our parent that we can be frozen */
1021 if (kill(getppid(), SIGUSR1
) != 0) {
1022 T_LOG("Unable to signal to parent process!");
1023 exit(SIGNAL_TO_PARENT_FAILED
);
1026 /* We should not be frozen yet. */
1027 is_frozen
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN
, getpid(), 0, NULL
, 0);
1028 if (is_frozen
== -1) {
1029 T_LOG("memorystatus_control error: %s", strerror(errno
));
1030 exit(MEMORYSTATUS_CONTROL_ERROR
);
1033 exit(FROZEN_BIT_SET
);
1037 sig_t sig_ret
= signal(SIGUSR1
, SIG_IGN
);
1038 T_QUIET
; T_WITH_ERRNO
; T_ASSERT_NE(sig_ret
, SIG_ERR
, "signal(SIGUSR1, SIG_IGN)");
1039 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
1040 if (ds_signal
== NULL
) {
1041 exit(DISPATCH_SOURCE_CREATE_FAILED
);
1044 dispatch_source_set_event_handler(ds_signal
, ^{
1045 /* We should now be frozen. */
1046 is_frozen
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN
, getpid(), 0, NULL
, 0);
1047 if (is_frozen
== -1) {
1048 T_LOG("memorystatus_control error: %s", strerror(errno
));
1049 exit(MEMORYSTATUS_CONTROL_ERROR
);
1052 exit(FROZEN_BIT_NOT_SET
);
1056 dispatch_activate(ds_signal
);
1061 T_DECL(memorystatus_get_process_is_frozen
, "MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN returns correct state") {
1062 skip_if_freezer_is_disabled();
1064 test_after_background_helper_launches(true, "check_frozen", ^{
1066 /* Freeze the child, resume it, and signal it to check its state */
1067 move_to_idle_band(child_pid
);
1068 ret
= pid_suspend(child_pid
);
1069 T_ASSERT_POSIX_SUCCESS(ret
, "child suspended");
1070 freeze_process(child_pid
);
1071 ret
= pid_resume(child_pid
);
1072 T_ASSERT_POSIX_SUCCESS(ret
, "child resumed after freeze");
1074 kill(child_pid
, SIGUSR1
);
1075 /* The child will checks its own frozen state & exit. */