4 #include <sys/sysctl.h>
6 #include <darwintest.h>
7 #include <dispatch/dispatch.h>
8 #include <mach-o/dyld.h>
11 #include <spawn_private.h>
12 #include <sys/coalition.h>
13 #include <sys/kern_memorystatus.h>
15 #define JETSAM_PRIORITY_IDLE 0
16 #define JETSAM_PRIORITY_FOREGROUND 10
18 #define kNumProcsInCoalition 4
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
];
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.
29 #define kMaxChildrenProcs 16
30 static pid_t children_pids
[kMaxChildrenProcs
];
31 static size_t num_children
= 0;
34 * Sets up a new coalition.
36 static void init_coalition(coalition_info_t
*);
39 * Places all procs in the coalition in the given band.
41 static void place_coalition_in_band(const coalition_info_t
*, int band
);
44 * Place the given proc in the given band.
46 static void place_proc_in_band(pid_t pid
, int band
);
49 * Cleans up any children processes.
51 static void cleanup_children(void);
54 * Check if we're on a kernel where we can test coalitions.
56 static bool has_unrestrict_coalitions(void);
59 * Unrestrict coalition syscalls.
61 static void unrestrict_coalitions(void);
64 * Restrict coalition syscalls
66 static void restrict_coalitions(void);
69 * Allocate the requested number of pages and fault them in.
70 * Used to achieve a desired footprint.
72 static void *allocate_pages(int);
75 * Get the vm page size.
77 static int get_vmpage_size(void);
80 * Launch a proc with a role in a coalition.
81 * If coalition_ids is NULL, skip adding the proc to the coalition.
84 launch_proc_in_coalition(uint64_t *coalition_ids
, int role
, int num_pages
);
87 * Background process that will munch some memory, signal its parent, and
90 T_HELPER_DECL(coalition_member
, "Mock coalition member") {
93 num_pages
= atoi(argv
[0]);
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!");
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.
112 T_DECL(memorystatus_sort_coalition
, "Coalition sort order",
113 T_META_ASROOT(true)) {
116 coalition_info_t coalition
;
117 if (!has_unrestrict_coalitions()) {
118 T_SKIP("Unable to test coalitions on this kernel.");
120 res
= signal(SIGUSR1
, SIG_IGN
);
121 T_WITH_ERRNO
; T_ASSERT_NE(res
, SIG_ERR
, "SIG_IGN SIGUSR1");
122 unrestrict_coalitions();
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
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.");
140 * Test that sorting the idle bucket in footprint order works properly.
142 * Spawns some children with very different footprints in the idle band,
143 * and then ensures that they get sorted properly.
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};
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.
154 static const int kExpectedOrder
[kNumChildren
] = {2, 0, 1};
155 static const int kJetsamBand
= JETSAM_PRIORITY_IDLE
;
158 dispatch_source_t ds_allocated
;
159 T_ATEND(cleanup_children
);
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)");
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
);
172 pid_t expected_order
[kNumChildren
] = {0};
174 for (int i
= 0; i
< kNumChildren
; i
++) {
175 expected_order
[i
] = children_pids
[kExpectedOrder
[i
]];
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.");
186 dispatch_activate(ds_allocated
);
188 pid
= launch_proc_in_coalition(NULL
, 0, kChildrenFootprints
[num_children
]);
189 place_proc_in_band(pid
, kJetsamBand
);
197 launch_proc_in_coalition(uint64_t *coalition_ids
, int role
, int num_pages
)
200 posix_spawnattr_t attr
;
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");
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
;
230 init_coalition(coalition_info_t
*coalition
)
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");
242 * Spawn procs for each coalition role, and construct the expected
245 for (size_t i
= 0; i
< kNumProcsInCoalition
; i
++) {
248 role
= COALITION_TASKROLE_LEADER
;
250 role
= COALITION_TASKROLE_EXT
;
252 role
= COALITION_TASKROLE_UNDEF
;
254 role
= COALITION_TASKROLE_XPC
;
256 pid_t pid
= launch_proc_in_coalition(coalition
->ids
, role
, 0);
257 coalition
->pids
[i
] = pid
;
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
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
;
271 coalition
->expected_order
[0] = pid
;
277 place_proc_in_band(pid_t pid
, int band
)
279 memorystatus_priority_properties_t props
= {0};
281 props
.priority
= band
;
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");
289 place_coalition_in_band(const coalition_info_t
*coalition
, int band
)
291 for (size_t i
= 0; i
< kNumProcsInCoalition
; i
++) {
292 pid_t curr
= coalition
->pids
[i
];
293 place_proc_in_band(curr
, band
);
298 cleanup_children(void)
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);
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");
316 has_unrestrict_coalitions()
322 val_sz
= sizeof(val
);
323 ret
= sysctlbyname("kern.unrestrict_coalitions", &val
, &val_sz
, NULL
, 0);
328 unrestrict_coalitions()
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");
338 restrict_coalitions()
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");
348 allocate_pages(int num_pages
)
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;
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");