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