3 #include <sys/sysctl.h>
4 #include <sys/kern_memorystatus.h>
5 #include <mach-o/dyld.h>
6 #include <mach/mach_vm.h>
7 #include <mach/vm_page_size.h> /* Needed for vm_region info */
8 #include <mach/shared_region.h>
14 #include <darwintest.h>
15 #include <darwintest_utils.h>
17 #include "memorystatus_assertion_helpers.h"
20 T_META_NAMESPACE("xnu.vm"),
21 T_META_CHECK_LEAKS(false)
24 #define MEM_SIZE_MB 10
25 #define NUM_ITERATIONS 5
26 #define FREEZE_PAGES_MAX 256
28 #define CREATE_LIST(X) \
30 X(TOO_FEW_ARGUMENTS) \
31 X(SYSCTL_VM_PAGESIZE_FAILED) \
32 X(VM_PAGESIZE_IS_ZERO) \
33 X(DISPATCH_SOURCE_CREATE_FAILED) \
34 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
35 X(SIGNAL_TO_PARENT_FAILED) \
36 X(MEMORYSTATUS_CONTROL_FAILED) \
37 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
38 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
39 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
42 #define EXIT_CODES_ENUM(VAR) VAR,
44 CREATE_LIST(EXIT_CODES_ENUM
)
47 #define EXIT_CODES_STRING(VAR) #VAR,
48 static const char *exit_codes_str
[] = {
49 CREATE_LIST(EXIT_CODES_STRING
)
56 size_t size
= sizeof(vmpage_size
);
57 int ret
= sysctlbyname("vm.pagesize", &vmpage_size
, &size
, NULL
, 0);
58 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "failed to query vm.pagesize");
59 T_QUIET
; T_ASSERT_GT(vmpage_size
, 0, "vm.pagesize is not > 0");
63 static pid_t child_pid
= -1;
64 static int freeze_count
= 0;
66 void move_to_idle_band(void);
67 void run_freezer_test(int);
68 void freeze_helper_process(void);
69 /* Gets and optionally sets the freeze pages max threshold */
70 int sysctl_freeze_pages_max(int* new_value
);
72 /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
73 * We're moving away from those unit tests, so they're copied here.
76 /* Cribbed from 'top'... */
78 in_shared_region(mach_vm_address_t addr
, cpu_type_t type
)
80 mach_vm_address_t base
= 0, size
= 0;
84 base
= SHARED_REGION_BASE_ARM
;
85 size
= SHARED_REGION_SIZE_ARM
;
89 base
= SHARED_REGION_BASE_ARM64
;
90 size
= SHARED_REGION_SIZE_ARM64
;
95 base
= SHARED_REGION_BASE_X86_64
;
96 size
= SHARED_REGION_SIZE_X86_64
;
100 base
= SHARED_REGION_BASE_I386
;
101 size
= SHARED_REGION_SIZE_I386
;
104 case CPU_TYPE_POWERPC
:
105 base
= SHARED_REGION_BASE_PPC
;
106 size
= SHARED_REGION_SIZE_PPC
;
109 case CPU_TYPE_POWERPC64
:
110 base
= SHARED_REGION_BASE_PPC64
;
111 size
= SHARED_REGION_SIZE_PPC64
;
117 fprintf(stderr
, "unknown CPU type: 0x%x\n", t
);
122 return addr
>= base
&& addr
< (base
+ size
);
125 /* Get the resident private memory of the given pid */
126 static unsigned long long
129 mach_port_name_t task
;
132 mach_vm_size_t rprvt
= 0;
133 mach_vm_size_t empty
= 0;
134 mach_vm_size_t fw_private
= 0;
135 mach_vm_size_t pagesize
= vm_kernel_page_size
; // The vm_region page info is reported
136 // in terms of vm_kernel_page_size.
137 mach_vm_size_t regs
= 0;
139 mach_vm_address_t addr
;
144 kr
= task_for_pid(mach_task_self(), pid
, &task
);
145 T_QUIET
; T_ASSERT_TRUE(kr
== KERN_SUCCESS
, "Unable to get task_for_pid of child");
147 for (addr
= 0;; addr
+= size
) {
148 vm_region_top_info_data_t info
;
149 mach_msg_type_number_t count
= VM_REGION_TOP_INFO_COUNT
;
150 mach_port_t object_name
;
152 kr
= mach_vm_region(task
, &addr
, &size
, VM_REGION_TOP_INFO
, (vm_region_info_t
)&info
, &count
, &object_name
);
153 if (kr
!= KERN_SUCCESS
) {
157 #if defined (__arm64__)
158 if (in_shared_region(addr
, CPU_TYPE_ARM64
)) {
160 if (in_shared_region(addr
, CPU_TYPE_ARM
)) {
163 fw_private
+= info
.private_pages_resident
* pagesize
;
166 * Check if this process has the globally shared
167 * text and data regions mapped in. If so, set
168 * split to TRUE and avoid checking
171 if (split
== FALSE
&& info
.share_mode
== SM_EMPTY
) {
172 vm_region_basic_info_data_64_t b_info
;
173 mach_vm_address_t b_addr
= addr
;
174 mach_vm_size_t b_size
= size
;
175 count
= VM_REGION_BASIC_INFO_COUNT_64
;
177 kr
= mach_vm_region(task
, &b_addr
, &b_size
, VM_REGION_BASIC_INFO_64
, (vm_region_info_t
)&b_info
, &count
, &object_name
);
178 if (kr
!= KERN_SUCCESS
) {
182 if (b_info
.reserved
) {
188 * Short circuit the loop if this isn't a shared
189 * private region, since that's the only region
190 * type we care about within the current address
193 if (info
.share_mode
!= SM_PRIVATE
) {
201 * Update counters according to the region type.
204 if (info
.share_mode
== SM_COW
&& info
.ref_count
== 1) {
205 // Treat single reference SM_COW as SM_PRIVATE
206 info
.share_mode
= SM_PRIVATE
;
209 switch (info
.share_mode
) {
211 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
212 // since they are not shareable and are wired.
214 rprvt
+= info
.private_pages_resident
* pagesize
;
215 rprvt
+= info
.shared_pages_resident
* pagesize
;
225 // Treat kernel_task specially
226 if (info
.share_mode
== SM_COW
) {
227 rprvt
+= info
.private_pages_resident
* pagesize
;
232 if (info
.share_mode
== SM_COW
) {
233 rprvt
+= info
.private_pages_resident
* pagesize
;
247 move_to_idle_band(void)
249 memorystatus_priority_properties_t props
;
251 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
252 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
254 props
.priority
= JETSAM_PRIORITY_IDLE
;
258 * This requires us to run as root (in the absence of entitlement).
259 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
261 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES
, getpid(), 0, &props
, sizeof(props
))) {
262 exit(MEMSTAT_PRIORITY_CHANGE_FAILED
);
267 freeze_helper_process(void)
270 int ret
, freeze_enabled
, errno_freeze_sysctl
;
271 uint64_t resident_memory_before
, resident_memory_after
, vmpage_size
;
272 vmpage_size
= (uint64_t) get_vmpage_size();
273 resident_memory_before
= get_rprvt(child_pid
) / vmpage_size
;
275 T_LOG("Freezing child pid %d", child_pid
);
276 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &child_pid
, sizeof(child_pid
));
277 errno_freeze_sysctl
= errno
;
281 * The child process toggles its freezable state on each iteration.
282 * So a failure for every alternate freeze is expected.
284 if (freeze_count
% 2) {
285 length
= sizeof(freeze_enabled
);
286 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
287 "failed to query vm.freeze_enabled");
288 if (freeze_enabled
) {
289 errno
= errno_freeze_sysctl
;
290 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed");
292 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
293 T_LOG("Freeze has been disabled. Terminating early.");
296 resident_memory_after
= get_rprvt(child_pid
) / vmpage_size
;
297 uint64_t freeze_pages_max
= (uint64_t) sysctl_freeze_pages_max(NULL
);
298 T_QUIET
; T_ASSERT_LT(resident_memory_after
, resident_memory_before
, "Freeze didn't reduce resident memory set");
299 if (resident_memory_before
> freeze_pages_max
) {
300 T_QUIET
; T_ASSERT_LE(resident_memory_before
- resident_memory_after
, freeze_pages_max
, "Freeze pages froze more than the threshold.");
302 ret
= sysctlbyname("kern.memorystatus_thaw", NULL
, NULL
, &child_pid
, sizeof(child_pid
));
303 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_thaw failed");
305 T_QUIET
; T_ASSERT_TRUE(ret
!= KERN_SUCCESS
, "Freeze should have failed");
306 T_LOG("Freeze failed as expected");
311 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGUSR1
), "failed to send SIGUSR1 to child process");
315 run_freezer_test(int num_pages
)
317 int ret
, freeze_enabled
;
319 char **launch_tool_args
;
320 char testpath
[PATH_MAX
];
321 uint32_t testpath_buf_size
;
322 dispatch_source_t ds_freeze
, ds_proc
;
325 length
= sizeof(freeze_enabled
);
326 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
327 "failed to query vm.freeze_enabled");
328 if (!freeze_enabled
) {
329 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
330 T_SKIP("Freeze has been disabled. Skipping test.");
333 signal(SIGUSR1
, SIG_IGN
);
334 ds_freeze
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
335 T_QUIET
; T_ASSERT_NOTNULL(ds_freeze
, "dispatch_source_create (ds_freeze)");
337 dispatch_source_set_event_handler(ds_freeze
, ^{
338 if (freeze_count
< NUM_ITERATIONS
) {
339 freeze_helper_process();
341 kill(child_pid
, SIGKILL
);
342 dispatch_source_cancel(ds_freeze
);
345 dispatch_activate(ds_freeze
);
347 testpath_buf_size
= sizeof(testpath
);
348 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
349 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, "_NSGetExecutablePath");
350 T_LOG("Executable path: %s", testpath
);
352 sprintf(sz_str
, "%d", num_pages
);
353 launch_tool_args
= (char *[]){
362 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
363 ret
= dt_launch_tool(&child_pid
, launch_tool_args
, true, NULL
, NULL
);
365 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
367 T_QUIET
; T_ASSERT_POSIX_SUCCESS(child_pid
, "dt_launch_tool");
369 ds_proc
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)child_pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
370 T_QUIET
; T_ASSERT_NOTNULL(ds_proc
, "dispatch_source_create (ds_proc)");
372 dispatch_source_set_event_handler(ds_proc
, ^{
373 int status
= 0, code
= 0;
374 pid_t rc
= waitpid(child_pid
, &status
, 0);
375 T_QUIET
; T_ASSERT_EQ(rc
, child_pid
, "waitpid");
376 code
= WEXITSTATUS(status
);
380 } else if (code
> 0 && code
< EXIT_CODE_MAX
) {
381 T_ASSERT_FAIL("Child exited with %s", exit_codes_str
[code
]);
383 T_ASSERT_FAIL("Child exited with unknown exit code %d", code
);
386 dispatch_activate(ds_proc
);
388 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGCONT
), "failed to send SIGCONT to child process");
393 allocate_pages(int num_pages
)
397 __block
int num_iter
= 0;
399 dispatch_source_t ds_signal
;
400 vmpgsize
= get_vmpage_size();
402 printf("Invalid number of pages to allocate: %d\n", num_pages
);
403 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS
);
406 buf
= (char**)malloc(sizeof(char*) * (size_t)num_pages
);
408 /* Gives us the compression ratio we see in the typical case (~2.7) */
409 for (j
= 0; j
< num_pages
; j
++) {
410 buf
[j
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
412 for (i
= 0; i
< vmpgsize
; i
+= 16) {
413 memset(&buf
[j
][i
], val
, 16);
414 if (i
< 3400 * (vmpgsize
/ 4096)) {
420 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, NSEC_PER_SEC
), dispatch_get_main_queue(), ^{
421 /* Signal to the parent that we're done allocating and it's ok to freeze us */
422 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
423 if (kill(getppid(), SIGUSR1
) != 0) {
424 exit(INITIAL_SIGNAL_TO_PARENT_FAILED
);
428 signal(SIGUSR1
, SIG_IGN
);
429 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
430 if (ds_signal
== NULL
) {
431 exit(DISPATCH_SOURCE_CREATE_FAILED
);
434 dispatch_source_set_event_handler(ds_signal
, ^{
435 int current_state
, new_state
;
438 /* Make sure all the pages are accessed before trying to freeze again */
439 for (int x
= 0; x
< num_pages
; x
++) {
443 current_state
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE
, getpid(), 0, NULL
, 0);
444 /* Sysprocs start off as unfreezable. Verify that first. */
445 if (num_iter
== 0 && current_state
!= 0) {
446 exit(IS_FREEZABLE_NOT_AS_EXPECTED
);
449 /* Toggle freezable state */
450 new_state
= (current_state
) ? 0: 1;
451 printf("[%d] Changing state from %s to %s\n", getpid(),
452 (current_state
) ? "freezable": "unfreezable", (new_state
) ? "freezable": "unfreezable");
453 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), (uint32_t)new_state
, NULL
, 0) != KERN_SUCCESS
) {
454 exit(MEMORYSTATUS_CONTROL_FAILED
);
457 /* Verify that the state has been set correctly */
458 current_state
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE
, getpid(), 0, NULL
, 0);
459 if (new_state
!= current_state
) {
460 exit(IS_FREEZABLE_NOT_AS_EXPECTED
);
464 if (kill(getppid(), SIGUSR1
) != 0) {
465 exit(SIGNAL_TO_PARENT_FAILED
);
468 dispatch_activate(ds_signal
);
474 T_HELPER_DECL(allocate_pages
,
475 "allocates pages to freeze",
476 T_META_ASROOT(true)) {
478 exit(TOO_FEW_ARGUMENTS
);
481 int num_pages
= atoi(argv
[0]);
482 allocate_pages(num_pages
);
485 T_DECL(freeze
, "VM freezer test", T_META_ASROOT(true)) {
487 (MEM_SIZE_MB
<< 20) / get_vmpage_size());
490 static int old_freeze_pages_max
= 0;
492 reset_freeze_pages_max()
494 if (old_freeze_pages_max
!= 0) {
495 sysctl_freeze_pages_max(&old_freeze_pages_max
);
500 sysctl_freeze_pages_max(int* new_value
)
502 static int set_end_handler
= false;
503 int freeze_pages_max
, ret
;
504 size_t size
= sizeof(freeze_pages_max
);
505 ret
= sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max
, &size
, new_value
, size
);
506 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "Unable to query kern.memorystatus_freeze_pages_max");
507 if (!set_end_handler
) {
508 // Save the original value and instruct darwintest to restore it after the test completes
509 old_freeze_pages_max
= freeze_pages_max
;
510 T_ATEND(reset_freeze_pages_max
);
511 set_end_handler
= true;
513 return old_freeze_pages_max
;
516 T_DECL(freeze_over_max_threshold
, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) {
517 int freeze_pages_max
= FREEZE_PAGES_MAX
;
518 sysctl_freeze_pages_max(&freeze_pages_max
);
519 run_freezer_test(FREEZE_PAGES_MAX
* 2);
522 T_HELPER_DECL(frozen_background
, "Frozen background process", T_META_ASROOT(true)) {
523 kern_return_t kern_ret
;
524 /* Set the process to freezable */
525 kern_ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), 1, NULL
, 0);
526 T_QUIET
; T_ASSERT_EQ(kern_ret
, KERN_SUCCESS
, "set process is freezable");
527 /* Signal to our parent that we can be frozen */
528 if (kill(getppid(), SIGUSR1
) != 0) {
529 T_LOG("Unable to signal to parent process!");
537 /* Launches the frozen_background helper as a managed process. */
539 launch_frozen_background_process()
542 char **launch_tool_args
;
543 char testpath
[PATH_MAX
];
544 uint32_t testpath_buf_size
;
547 testpath_buf_size
= sizeof(testpath
);
548 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
549 printf("Launching %s\n", testpath
);
550 launch_tool_args
= (char *[]){
556 ret
= dt_launch_tool(&pid
, launch_tool_args
, false, NULL
, NULL
);
558 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
560 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "dt_launch_tool");
561 /* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */
562 ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED
, pid
, 1, NULL
, 0);
563 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "memorystatus_control");
568 freeze_process(pid_t pid
)
570 int ret
, freeze_enabled
, errno_freeze_sysctl
;
572 T_LOG("Freezing pid %d", pid
);
574 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &pid
, sizeof(pid
));
575 errno_freeze_sysctl
= errno
;
576 length
= sizeof(freeze_enabled
);
577 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
578 "failed to query vm.freeze_enabled");
579 if (freeze_enabled
) {
580 errno
= errno_freeze_sysctl
;
581 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed");
583 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
584 T_LOG("Freeze has been disabled. Terminating early.");
590 memorystatus_assertion_test_demote_frozen()
593 T_SKIP("Freezing processes is only supported on embedded");
596 * 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.
597 * Then remove thee assertion, and ensure it gets demoted properly.
599 /* these values will remain fixed during testing */
600 int active_limit_mb
= 15; /* arbitrary */
601 int inactive_limit_mb
= 7; /* arbitrary */
602 /* Launch the child process, and elevate its priority */
603 int requestedpriority
;
604 dispatch_source_t ds_signal
, ds_exit
;
605 requestedpriority
= JETSAM_PRIORITY_UI_SUPPORT
;
607 /* Wait for the child process to tell us that it's ready, and then freeze it */
608 signal(SIGUSR1
, SIG_IGN
);
609 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
610 T_QUIET
; T_ASSERT_NOTNULL(ds_signal
, "dispatch_source_create");
611 dispatch_source_set_event_handler(ds_signal
, ^{
613 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
614 freeze_process(child_pid
);
615 /* Agressive demotion */
616 sysctl_ret
= sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL
, NULL
, NULL
, 0);
617 T_ASSERT_POSIX_SUCCESS(sysctl_ret
, "sysctl kern.memorystatus_demote_frozen_processes failed");
619 (void)check_properties(child_pid
, requestedpriority
, inactive_limit_mb
, 0x0, ASSERTION_STATE_IS_SET
, "Priority was set");
620 T_LOG("Relinquishing our assertion.");
621 /* Relinquish our assertion, and check that it gets demoted. */
622 relinquish_assertion_priority(child_pid
, 0x0);
623 (void)check_properties(child_pid
, JETSAM_PRIORITY_AGING_BAND2
, inactive_limit_mb
, 0x0, ASSERTION_STATE_IS_RELINQUISHED
, "Assertion was reqlinquished.");
625 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Unable to kill child process");
629 /* Launch the child process and set the initial properties on it. */
630 child_pid
= launch_frozen_background_process();
631 set_memlimits(child_pid
, active_limit_mb
, inactive_limit_mb
, false, false);
632 set_assertion_priority(child_pid
, requestedpriority
, 0x0);
633 (void)check_properties(child_pid
, requestedpriority
, inactive_limit_mb
, 0x0, ASSERTION_STATE_IS_SET
, "Priority was set");
634 /* Listen for exit. */
635 ds_exit
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)child_pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
636 dispatch_source_set_event_handler(ds_exit
, ^{
637 int status
= 0, code
= 0;
638 pid_t rc
= waitpid(child_pid
, &status
, 0);
639 T_QUIET
; T_ASSERT_EQ(rc
, child_pid
, "waitpid");
640 code
= WEXITSTATUS(status
);
641 T_QUIET
; T_ASSERT_EQ(code
, 0, "Child exited cleanly");
645 dispatch_activate(ds_exit
);
646 dispatch_activate(ds_signal
);
650 T_DECL(assertion_test_demote_frozen
, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
651 memorystatus_assertion_test_demote_frozen();