]> git.saurik.com Git - apple/xnu.git/blob - tests/memorystatus_freeze_test.c
xnu-4903.241.1.tar.gz
[apple/xnu.git] / tests / memorystatus_freeze_test.c
1 #include <stdio.h>
2 #include <signal.h>
3 #include <sys/sysctl.h>
4 #include <sys/kern_memorystatus.h>
5 #include <mach-o/dyld.h>
6
7 #ifdef T_NAMESPACE
8 #undef T_NAMESPACE
9 #endif
10 #include <darwintest.h>
11 #include <darwintest_utils.h>
12
13 T_GLOBAL_META(
14 T_META_NAMESPACE("xnu.vm"),
15 T_META_CHECK_LEAKS(false)
16 );
17
18 #define MEM_SIZE_MB 10
19 #define NUM_ITERATIONS 5
20
21 #define CREATE_LIST(X) \
22 X(SUCCESS) \
23 X(TOO_FEW_ARGUMENTS) \
24 X(SYSCTL_VM_PAGESIZE_FAILED) \
25 X(VM_PAGESIZE_IS_ZERO) \
26 X(SYSCTL_VM_FREEZE_ENABLED_FAILED) \
27 X(FREEZER_DISABLED) \
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) \
34 X(EXIT_CODE_MAX)
35
36 #define EXIT_CODES_ENUM(VAR) VAR,
37 enum exit_codes_num {
38 CREATE_LIST(EXIT_CODES_ENUM)
39 };
40
41 #define EXIT_CODES_STRING(VAR) #VAR,
42 static const char *exit_codes_str[] = {
43 CREATE_LIST(EXIT_CODES_STRING)
44 };
45
46
47 static pid_t pid = -1;
48 static int freeze_count = 0;
49
50 void move_to_idle_band(void);
51 void run_freezer_test(int size_mb);
52 void freeze_helper_process(void);
53
54
55 void move_to_idle_band(void) {
56
57 memorystatus_priority_properties_t props;
58 /*
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.
61 */
62 props.priority = JETSAM_PRIORITY_IDLE;
63 props.user_data = 0;
64
65 /*
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.
68 */
69 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, getpid(), 0, &props, sizeof(props))) {
70 exit(MEMSTAT_PRIORITY_CHANGE_FAILED);
71 }
72 }
73
74 void freeze_helper_process(void) {
75 int ret;
76
77 T_LOG("Freezing child pid %d", pid);
78 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid));
79 sleep(1);
80
81 if (freeze_count % 2 == 0) {
82 /*
83 * The child process toggles its freezable state on each iteration.
84 * So a failure for every alternate freeze is expected.
85 */
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");
90 } else {
91 T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed");
92 T_LOG("Freeze failed as expected");
93 }
94
95 freeze_count++;
96
97 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGUSR1), "failed to send SIGUSR1 to child process");
98 }
99
100 void run_freezer_test(int size_mb) {
101 int ret;
102 char sz_str[50];
103 char **launch_tool_args;
104 char testpath[PATH_MAX];
105 uint32_t testpath_buf_size;
106 dispatch_source_t ds_freeze, ds_proc;
107
108 #ifndef CONFIG_FREEZE
109 T_SKIP("Task freeze not supported.");
110 #endif
111
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)");
115
116 dispatch_source_set_event_handler(ds_freeze, ^{
117 if (freeze_count < NUM_ITERATIONS) {
118 freeze_helper_process();
119 } else {
120 kill(pid, SIGKILL);
121 dispatch_source_cancel(ds_freeze);
122 }
123 });
124 dispatch_activate(ds_freeze);
125
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);
130
131 sprintf(sz_str, "%d", size_mb);
132 launch_tool_args = (char *[]){
133 testpath,
134 "-n",
135 "allocate_pages",
136 "--",
137 sz_str,
138 NULL
139 };
140
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);
143 if (ret != 0) {
144 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
145 }
146 T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "dt_launch_tool");
147
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)");
150
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);
156
157 if (code == 0) {
158 T_END;
159 } else if (code > 0 && code < EXIT_CODE_MAX) {
160 T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
161 } else {
162 T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
163 }
164 });
165 dispatch_activate(ds_proc);
166
167 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGCONT), "failed to send SIGCONT to child process");
168 dispatch_main();
169 }
170
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;
175 size_t len;
176 char val;
177 __block int num_pages, num_iter = 0;
178 __block char **buf;
179 dispatch_source_t ds_signal;
180
181 len = sizeof(vmpgsize);
182 ret = sysctlbyname("vm.pagesize", &vmpgsize, &len, NULL, 0);
183 if (ret != 0) {
184 exit(SYSCTL_VM_PAGESIZE_FAILED);
185 }
186 if (vmpgsize == 0) {
187 exit(VM_PAGESIZE_IS_ZERO);
188 }
189
190 if (argc < 1) {
191 exit(TOO_FEW_ARGUMENTS);
192 }
193
194 len = sizeof(temp);
195 ret = sysctlbyname("vm.freeze_enabled", &temp, &len, NULL, 0);
196 if (ret != 0) {
197 exit(SYSCTL_VM_FREEZE_ENABLED_FAILED);
198 }
199 if (temp == 0) {
200 exit(FREEZER_DISABLED);
201 }
202
203 size_mb = atoi(argv[0]);
204 num_pages = size_mb * 1024 * 1024 / vmpgsize;
205 buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
206
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));
210 val = 0;
211 for (i = 0; i < vmpgsize; i += 16) {
212 memset(&buf[j][i], val, 16);
213 if (i < 3400 * (vmpgsize / 4096)) {
214 val++;
215 }
216 }
217 }
218
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);
224 }
225 });
226
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);
231 }
232
233 dispatch_source_set_event_handler(ds_signal, ^{
234 int current_state, new_state;
235 volatile int tmp;
236
237 /* Make sure all the pages are accessed before trying to freeze again */
238 for (int x = 0; x < num_pages; x++) {
239 tmp = buf[x][0];
240 }
241
242 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
243
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);
249 }
250
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);
255 }
256 num_iter++;
257
258 if (kill(getppid(), SIGUSR1) != 0) {
259 exit(SIGNAL_TO_PARENT_FAILED);
260 }
261 });
262 dispatch_activate(ds_signal);
263 move_to_idle_band();
264
265 dispatch_main();
266 }
267
268 T_DECL(freeze, "VM freezer test") {
269 run_freezer_test(MEM_SIZE_MB);
270 }