]> git.saurik.com Git - apple/xnu.git/blob - tests/vm/memorystatus_sort_test.c
xnu-7195.50.7.100.1.tar.gz
[apple/xnu.git] / tests / vm / memorystatus_sort_test.c
1 #include <signal.h>
2 #include <spawn.h>
3 #include <stdlib.h>
4 #include <sys/sysctl.h>
5
6 #include <darwintest.h>
7 #include <dispatch/dispatch.h>
8 #include <mach-o/dyld.h>
9
10 /* internal */
11 #include <spawn_private.h>
12 #include <sys/coalition.h>
13 #include <sys/kern_memorystatus.h>
14
15 #define JETSAM_PRIORITY_IDLE 0
16 #define JETSAM_PRIORITY_FOREGROUND 10
17
18 #define kNumProcsInCoalition 4
19 typedef struct {
20 pid_t pids[kNumProcsInCoalition]; // An array of pids in this coalition. Owned by this struct.
21 pid_t expected_order[kNumProcsInCoalition]; // An array of pids in this coalition in proper sorted order.
22 uint64_t ids[COALITION_NUM_TYPES];
23 } coalition_info_t;
24
25 /*
26 * Children pids spawned by this test that need to be cleaned up.
27 * Has to be a global because the T_ATEND API doesn't take any arguments.
28 */
29 #define kMaxChildrenProcs 16
30 static pid_t children_pids[kMaxChildrenProcs];
31 static size_t num_children = 0;
32
33 /*
34 * Sets up a new coalition.
35 */
36 static void init_coalition(coalition_info_t*);
37
38 /*
39 * Places all procs in the coalition in the given band.
40 */
41 static void place_coalition_in_band(const coalition_info_t *, int band);
42
43 /*
44 * Place the given proc in the given band.
45 */
46 static void place_proc_in_band(pid_t pid, int band);
47
48 /*
49 * Cleans up any children processes.
50 */
51 static void cleanup_children(void);
52
53 /*
54 * Check if we're on a kernel where we can test coalitions.
55 */
56 static bool has_unrestrict_coalitions(void);
57
58 /*
59 * Unrestrict coalition syscalls.
60 */
61 static void unrestrict_coalitions(void);
62
63 /*
64 * Restrict coalition syscalls
65 */
66 static void restrict_coalitions(void);
67
68 /*
69 * Allocate the requested number of pages and fault them in.
70 * Used to achieve a desired footprint.
71 */
72 static void *allocate_pages(int);
73
74 /*
75 * Get the vm page size.
76 */
77 static int get_vmpage_size(void);
78
79 /*
80 * Launch a proc with a role in a coalition.
81 * If coalition_ids is NULL, skip adding the proc to the coalition.
82 */
83 static pid_t
84 launch_proc_in_coalition(uint64_t *coalition_ids, int role, int num_pages);
85
86 /*
87 * Background process that will munch some memory, signal its parent, and
88 * then sit in a loop.
89 */
90 T_HELPER_DECL(coalition_member, "Mock coalition member") {
91 int num_pages = 0;
92 if (argc == 1) {
93 num_pages = atoi(argv[0]);
94 }
95 allocate_pages(num_pages);
96 // Signal to the parent that we've touched all of our pages.
97 if (kill(getppid(), SIGUSR1) != 0) {
98 T_LOG("Unable to signal to parent process!");
99 exit(1);
100 }
101 while (true) {
102 ;
103 }
104 }
105
106 /*
107 * Test that sorting the fg bucket in coalition order works properly.
108 * Spawns children in the same coalition in the fg band. Each child
109 * has a different coalition role. Verifies that the coalition
110 * is sorted properly by role.
111 */
112 T_DECL(memorystatus_sort_coalition, "Coalition sort order",
113 T_META_ASROOT(true)) {
114 int ret;
115 sig_t res;
116 coalition_info_t coalition;
117 if (!has_unrestrict_coalitions()) {
118 T_SKIP("Unable to test coalitions on this kernel.");
119 }
120 res = signal(SIGUSR1, SIG_IGN);
121 T_WITH_ERRNO; T_ASSERT_NE(res, SIG_ERR, "SIG_IGN SIGUSR1");
122 unrestrict_coalitions();
123
124 // Set up a new coalition with various members.
125 init_coalition(&coalition);
126 T_ATEND(cleanup_children);
127 T_ATEND(restrict_coalitions);
128 // Place all procs in the coalition in the foreground band
129 place_coalition_in_band(&coalition, JETSAM_PRIORITY_FOREGROUND);
130 // Have the kernel sort the foreground bucket and verify that it's
131 // sorted correctly.
132 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM_SORT, JETSAM_PRIORITY_FOREGROUND, 0,
133 coalition.expected_order, kNumProcsInCoalition * sizeof(pid_t));
134 T_QUIET; T_ASSERT_EQ(ret, 0, "Error while sorting or validating sorted order.\n"
135 "Check os log output for details.\n"
136 "Look for memorystatus_verify_sort_order.");
137 }
138
139 /*
140 * Test that sorting the idle bucket in footprint order works properly.
141 *
142 * Spawns some children with very different footprints in the idle band,
143 * and then ensures that they get sorted properly.
144 */
145 T_DECL(memorystatus_sort_footprint, "Footprint sort order",
146 T_META_ASROOT(true)) {
147 #define kNumChildren 3
148 static const int kChildrenFootprints[kNumChildren] = {500, 0, 2500};
149 /*
150 * The expected sort order of the children in the order that they were launched.
151 * Used to construct the expected_order pid array.
152 * Note that procs should be sorted in descending footprint order.
153 */
154 static const int kExpectedOrder[kNumChildren] = {2, 0, 1};
155 static const int kJetsamBand = JETSAM_PRIORITY_IDLE;
156 __block pid_t pid;
157 sig_t res;
158 dispatch_source_t ds_allocated;
159 T_ATEND(cleanup_children);
160
161 // After we spawn the children, they'll signal that they've touched their pages.
162 res = signal(SIGUSR1, SIG_IGN);
163 T_WITH_ERRNO; T_ASSERT_NE(res, SIG_ERR, "SIG_IGN SIGUSR1");
164 ds_allocated = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
165 T_QUIET; T_ASSERT_NOTNULL(ds_allocated, "dispatch_source_create (ds_allocated)");
166
167 dispatch_source_set_event_handler(ds_allocated, ^{
168 if (num_children < kNumChildren) {
169 pid = launch_proc_in_coalition(NULL, 0, kChildrenFootprints[num_children]);
170 place_proc_in_band(pid, kJetsamBand);
171 } else {
172 pid_t expected_order[kNumChildren] = {0};
173 int ret;
174 for (int i = 0; i < kNumChildren; i++) {
175 expected_order[i] = children_pids[kExpectedOrder[i]];
176 }
177 // Verify the sort order
178 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM_SORT, kJetsamBand, 0,
179 expected_order, sizeof(expected_order));
180 T_QUIET; T_ASSERT_EQ(ret, 0, "Error while sorting or validating sorted order.\n"
181 "Check os log output for details.\n"
182 "Look for memorystatus_verify_sort_order.");
183 T_END;
184 }
185 });
186 dispatch_activate(ds_allocated);
187
188 pid = launch_proc_in_coalition(NULL, 0, kChildrenFootprints[num_children]);
189 place_proc_in_band(pid, kJetsamBand);
190
191 dispatch_main();
192
193 #undef kNumChildren
194 }
195
196 static pid_t
197 launch_proc_in_coalition(uint64_t *coalition_ids, int role, int num_pages)
198 {
199 int ret;
200 posix_spawnattr_t attr;
201 pid_t pid;
202 char testpath[PATH_MAX];
203 uint32_t testpath_buf_size = PATH_MAX;
204 char num_pages_str[32] = {0};
205 char *argv[5] = {testpath, "-n", "coalition_member", num_pages_str, NULL};
206 extern char **environ;
207 T_QUIET; T_ASSERT_LT(num_children + 1, (size_t) kMaxChildrenProcs, "Don't create too many children.");
208 ret = posix_spawnattr_init(&attr);
209 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_init");
210 if (coalition_ids != NULL) {
211 for (int i = 0; i < COALITION_NUM_TYPES; i++) {
212 ret = posix_spawnattr_setcoalition_np(&attr, coalition_ids[i], i, role);
213 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setcoalition_np");
214 }
215 }
216
217 ret = snprintf(num_pages_str, sizeof(num_pages_str), "%d", num_pages);
218 T_QUIET; T_ASSERT_LE((size_t) ret, sizeof(num_pages_str), "Don't allocate too many pages.");
219 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
220 T_QUIET; T_ASSERT_EQ(ret, 0, "_NSGetExecutablePath");
221 ret = posix_spawn(&pid, argv[0], NULL, &attr, argv, environ);
222 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawn");
223 ret = posix_spawnattr_destroy(&attr);
224 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_destroy");
225 children_pids[num_children++] = pid;
226 return pid;
227 }
228
229 static void
230 init_coalition(coalition_info_t *coalition)
231 {
232 int ret;
233 uint32_t flags = 0;
234 memset(coalition, 0, sizeof(coalition_info_t));
235 for (int i = 0; i < COALITION_NUM_TYPES; i++) {
236 COALITION_CREATE_FLAGS_SET_TYPE(flags, i);
237 ret = coalition_create(&coalition->ids[i], flags);
238 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "coalition_create");
239 }
240
241 /*
242 * Spawn procs for each coalition role, and construct the expected
243 * sorted order.
244 */
245 for (size_t i = 0; i < kNumProcsInCoalition; i++) {
246 int role;
247 if (i == 0) {
248 role = COALITION_TASKROLE_LEADER;
249 } else if (i == 1) {
250 role = COALITION_TASKROLE_EXT;
251 } else if (i == 2) {
252 role = COALITION_TASKROLE_UNDEF;
253 } else {
254 role = COALITION_TASKROLE_XPC;
255 }
256 pid_t pid = launch_proc_in_coalition(coalition->ids, role, 0);
257 coalition->pids[i] = pid;
258 /*
259 * Determine the expected sorted order.
260 * After a bucket has been coalition sorted, coalition members should
261 * be in the following kill order:
262 * undefined coalition members, extensions, xpc services, leader
263 */
264 if (role == COALITION_TASKROLE_LEADER) {
265 coalition->expected_order[3] = pid;
266 } else if (role == COALITION_TASKROLE_XPC) {
267 coalition->expected_order[2] = pid;
268 } else if (role == COALITION_TASKROLE_EXT) {
269 coalition->expected_order[1] = pid;
270 } else {
271 coalition->expected_order[0] = pid;
272 }
273 }
274 }
275
276 static void
277 place_proc_in_band(pid_t pid, int band)
278 {
279 memorystatus_priority_properties_t props = {0};
280 int ret;
281 props.priority = band;
282 props.user_data = 0;
283 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props));
284 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "move proc to band");
285 }
286
287
288 static void
289 place_coalition_in_band(const coalition_info_t *coalition, int band)
290 {
291 for (size_t i = 0; i < kNumProcsInCoalition; i++) {
292 pid_t curr = coalition->pids[i];
293 place_proc_in_band(curr, band);
294 }
295 }
296
297 static void
298 cleanup_children(void)
299 {
300 int ret, status;
301 for (size_t i = 0; i < num_children; i++) {
302 pid_t exited_pid = 0;
303 pid_t curr = children_pids[i];
304 ret = kill(curr, SIGKILL);
305 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kill");
306 while (exited_pid == 0) {
307 exited_pid = waitpid(curr, &status, 0);
308 }
309 T_QUIET; T_ASSERT_POSIX_SUCCESS(exited_pid, "waitpid");
310 T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "proc was signaled.");
311 T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "proc was killed");
312 }
313 }
314
315 static bool
316 has_unrestrict_coalitions()
317 {
318 int ret, val;
319 size_t val_sz;
320
321 val = 0;
322 val_sz = sizeof(val);
323 ret = sysctlbyname("kern.unrestrict_coalitions", &val, &val_sz, NULL, 0);
324 return ret >= 0;
325 }
326
327 static void
328 unrestrict_coalitions()
329 {
330 int ret, val = 1;
331 size_t val_sz;
332 val_sz = sizeof(val);
333 ret = sysctlbyname("kern.unrestrict_coalitions", NULL, 0, &val, val_sz);
334 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.unrestrict_coalitions <- 1");
335 }
336
337 static void
338 restrict_coalitions()
339 {
340 int ret, val = 0;
341 size_t val_sz;
342 val_sz = sizeof(val);
343 ret = sysctlbyname("kern.unrestrict_coalitions", NULL, 0, &val, val_sz);
344 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.unrestrict_coalitions <- 0");
345 }
346
347 static void *
348 allocate_pages(int num_pages)
349 {
350 int page_size, i;
351 unsigned char *buf;
352
353 page_size = get_vmpage_size();
354 buf = malloc((unsigned long)(num_pages * page_size));
355 for (i = 0; i < num_pages; i++) {
356 ((volatile unsigned char *)buf)[i * page_size] = 1;
357 }
358 return buf;
359 }
360
361 static int
362 get_vmpage_size()
363 {
364 int vmpage_size;
365 size_t size = sizeof(vmpage_size);
366 int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0);
367 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
368 T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0");
369 return vmpage_size;
370 }