3 #include <sys/sysctl.h>
4 #include <sys/kern_memorystatus.h>
5 #include <mach-o/dyld.h>
10 #include <darwintest.h>
11 #include <darwintest_utils.h>
14 T_META_NAMESPACE("xnu.vm"),
15 T_META_CHECK_LEAKS(false)
18 #define MEM_SIZE_MB 10
19 #define NUM_ITERATIONS 5
21 #define CREATE_LIST(X) \
23 X(TOO_FEW_ARGUMENTS) \
24 X(SYSCTL_VM_PAGESIZE_FAILED) \
25 X(VM_PAGESIZE_IS_ZERO) \
26 X(SYSCTL_VM_FREEZE_ENABLED_FAILED) \
28 X(DISPATCH_SOURCE_CREATE_FAILED) \
29 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
30 X(SIGNAL_TO_PARENT_FAILED) \
31 X(MEMORYSTATUS_CONTROL_FAILED) \
32 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
33 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
36 #define EXIT_CODES_ENUM(VAR) VAR,
38 CREATE_LIST(EXIT_CODES_ENUM
)
41 #define EXIT_CODES_STRING(VAR) #VAR,
42 static const char *exit_codes_str
[] = {
43 CREATE_LIST(EXIT_CODES_STRING
)
47 static pid_t pid
= -1;
48 static int freeze_count
= 0;
50 void move_to_idle_band(void);
51 void run_freezer_test(int size_mb
);
52 void freeze_helper_process(void);
55 void move_to_idle_band(void) {
57 memorystatus_priority_properties_t props
;
59 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
60 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
62 props
.priority
= JETSAM_PRIORITY_IDLE
;
66 * This requires us to run as root (in the absence of entitlement).
67 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
69 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES
, getpid(), 0, &props
, sizeof(props
))) {
70 exit(MEMSTAT_PRIORITY_CHANGE_FAILED
);
74 void freeze_helper_process(void) {
77 T_LOG("Freezing child pid %d", pid
);
78 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &pid
, sizeof(pid
));
81 if (freeze_count
% 2 == 0) {
83 * The child process toggles its freezable state on each iteration.
84 * So a failure for every alternate freeze is expected.
86 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed");
87 T_LOG("Freeze succeeded. Thawing child pid %d", pid
);
88 ret
= sysctlbyname("kern.memorystatus_thaw", NULL
, NULL
, &pid
, sizeof(pid
));
89 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_thaw failed");
91 T_QUIET
; T_ASSERT_TRUE(ret
!= KERN_SUCCESS
, "Freeze should have failed");
92 T_LOG("Freeze failed as expected");
97 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(pid
, SIGUSR1
), "failed to send SIGUSR1 to child process");
100 void run_freezer_test(int size_mb
) {
103 char **launch_tool_args
;
104 char testpath
[PATH_MAX
];
105 uint32_t testpath_buf_size
;
106 dispatch_source_t ds_freeze
, ds_proc
;
108 #ifndef CONFIG_FREEZE
109 T_SKIP("Task freeze not supported.");
112 signal(SIGUSR1
, SIG_IGN
);
113 ds_freeze
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
114 T_QUIET
; T_ASSERT_NOTNULL(ds_freeze
, "dispatch_source_create (ds_freeze)");
116 dispatch_source_set_event_handler(ds_freeze
, ^{
117 if (freeze_count
< NUM_ITERATIONS
) {
118 freeze_helper_process();
121 dispatch_source_cancel(ds_freeze
);
124 dispatch_activate(ds_freeze
);
126 testpath_buf_size
= sizeof(testpath
);
127 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
128 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, "_NSGetExecutablePath");
129 T_LOG("Executable path: %s", testpath
);
131 sprintf(sz_str
, "%d", size_mb
);
132 launch_tool_args
= (char *[]){
141 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
142 ret
= dt_launch_tool(&pid
, launch_tool_args
, true, NULL
, NULL
);
144 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
146 T_QUIET
; T_ASSERT_POSIX_SUCCESS(pid
, "dt_launch_tool");
148 ds_proc
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
149 T_QUIET
; T_ASSERT_NOTNULL(ds_proc
, "dispatch_source_create (ds_proc)");
151 dispatch_source_set_event_handler(ds_proc
, ^{
152 int status
= 0, code
= 0;
153 pid_t rc
= waitpid(pid
, &status
, 0);
154 T_QUIET
; T_ASSERT_EQ(rc
, pid
, "waitpid");
155 code
= WEXITSTATUS(status
);
159 } else if (code
> 0 && code
< EXIT_CODE_MAX
) {
160 T_ASSERT_FAIL("Child exited with %s", exit_codes_str
[code
]);
162 T_ASSERT_FAIL("Child exited with unknown exit code %d", code
);
165 dispatch_activate(ds_proc
);
167 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(pid
, SIGCONT
), "failed to send SIGCONT to child process");
171 T_HELPER_DECL(allocate_pages
,
172 "allocates pages to freeze",
173 T_META_ASROOT(true)) {
174 int i
, j
, temp
, ret
, size_mb
, vmpgsize
;
177 __block
int num_pages
, num_iter
= 0;
179 dispatch_source_t ds_signal
;
181 len
= sizeof(vmpgsize
);
182 ret
= sysctlbyname("vm.pagesize", &vmpgsize
, &len
, NULL
, 0);
184 exit(SYSCTL_VM_PAGESIZE_FAILED
);
187 exit(VM_PAGESIZE_IS_ZERO
);
191 exit(TOO_FEW_ARGUMENTS
);
195 ret
= sysctlbyname("vm.freeze_enabled", &temp
, &len
, NULL
, 0);
197 exit(SYSCTL_VM_FREEZE_ENABLED_FAILED
);
200 exit(FREEZER_DISABLED
);
203 size_mb
= atoi(argv
[0]);
204 num_pages
= size_mb
* 1024 * 1024 / vmpgsize
;
205 buf
= (char**)malloc(sizeof(char*) * (size_t)num_pages
);
207 /* Gives us the compression ratio we see in the typical case (~2.7) */
208 for (j
= 0; j
< num_pages
; j
++) {
209 buf
[j
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
211 for (i
= 0; i
< vmpgsize
; i
+= 16) {
212 memset(&buf
[j
][i
], val
, 16);
213 if (i
< 3400 * (vmpgsize
/ 4096)) {
219 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, NSEC_PER_SEC
), dispatch_get_main_queue(), ^{
220 /* Signal to the parent that we're done allocating and it's ok to freeze us */
221 printf("Sending initial signal to parent to begin freezing\n");
222 if (kill(getppid(), SIGUSR1
) != 0) {
223 exit(INITIAL_SIGNAL_TO_PARENT_FAILED
);
227 signal(SIGUSR1
, SIG_IGN
);
228 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
229 if (ds_signal
== NULL
) {
230 exit(DISPATCH_SOURCE_CREATE_FAILED
);
233 dispatch_source_set_event_handler(ds_signal
, ^{
234 int current_state
, new_state
;
237 /* Make sure all the pages are accessed before trying to freeze again */
238 for (int x
= 0; x
< num_pages
; x
++) {
242 current_state
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE
, getpid(), 0, NULL
, 0);
244 /* Toggle freezable state */
245 new_state
= (current_state
) ? 0: 1;
246 printf("Changing state from %s to %s\n", (current_state
) ? "freezable": "unfreezable", (new_state
) ? "freezable": "unfreezable");
247 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), (uint32_t)new_state
, NULL
, 0) != KERN_SUCCESS
) {
248 exit(MEMORYSTATUS_CONTROL_FAILED
);
251 /* Verify that the state has been set correctly */
252 current_state
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE
, getpid(), 0, NULL
, 0);
253 if (new_state
!= current_state
) {
254 exit(IS_FREEZABLE_NOT_AS_EXPECTED
);
258 if (kill(getppid(), SIGUSR1
) != 0) {
259 exit(SIGNAL_TO_PARENT_FAILED
);
262 dispatch_activate(ds_signal
);
268 T_DECL(freeze
, "VM freezer test") {
269 run_freezer_test(MEM_SIZE_MB
);