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