]>
Commit | Line | Data |
---|---|---|
fe8ab488 A |
1 | /* |
2 | * Copyright (c) 2013 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
14 | * | |
15 | * Please obtain a copy of the License at | |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
25 | * | |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ | |
27 | */ | |
28 | ||
29 | #include <kern/kern_types.h> | |
30 | #include <mach/mach_types.h> | |
31 | #include <mach/boolean.h> | |
32 | ||
33 | #include <kern/coalition.h> | |
34 | #include <kern/host.h> | |
fe8ab488 | 35 | #include <kern/kalloc.h> |
3e170ce0 | 36 | #include <kern/ledger.h> |
fe8ab488 A |
37 | #include <kern/mach_param.h> /* for TASK_CHUNK */ |
38 | #include <kern/task.h> | |
39 | #include <kern/zalloc.h> | |
40 | ||
41 | #include <libkern/OSAtomic.h> | |
42 | ||
43 | #include <mach/coalition_notification_server.h> | |
44 | #include <mach/host_priv.h> | |
45 | #include <mach/host_special_ports.h> | |
46 | ||
47 | #include <sys/errno.h> | |
48 | ||
3e170ce0 A |
49 | /* |
50 | * BSD interface functions | |
51 | */ | |
52 | int coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, int list_sz); | |
53 | boolean_t coalition_is_leader(task_t task, int coal_type, coalition_t *coal); | |
54 | int coalition_get_task_count(coalition_t coal); | |
55 | uint64_t coalition_get_page_count(coalition_t coal, int *ntasks); | |
56 | int coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order, | |
57 | int *pid_list, int list_sz); | |
58 | ||
fe8ab488 A |
59 | /* defined in task.c */ |
60 | extern ledger_template_t task_ledger_template; | |
61 | ||
62 | /* | |
63 | * Coalition zone needs limits. We expect there will be as many coalitions as | |
64 | * tasks (same order of magnitude), so use the task zone's limits. | |
65 | * */ | |
66 | #define CONFIG_COALITION_MAX CONFIG_TASK_MAX | |
67 | #define COALITION_CHUNK TASK_CHUNK | |
68 | ||
69 | int unrestrict_coalition_syscalls; | |
70 | ||
71 | lck_attr_t coalitions_lck_attr; | |
72 | lck_grp_t coalitions_lck_grp; | |
73 | lck_grp_attr_t coalitions_lck_grp_attr; | |
74 | ||
75 | /* coalitions_list_lock protects coalition_count, coalitions queue, next_coalition_id. */ | |
76 | decl_lck_mtx_data(static,coalitions_list_lock); | |
77 | static uint64_t coalition_count; | |
78 | static uint64_t coalition_next_id = 1; | |
3e170ce0 | 79 | static queue_head_t coalitions_q; |
fe8ab488 | 80 | |
3e170ce0 | 81 | coalition_t init_coalition[COALITION_NUM_TYPES]; |
fe8ab488 A |
82 | |
83 | zone_t coalition_zone; | |
84 | ||
3e170ce0 A |
85 | static const char *coal_type_str(int type) |
86 | { | |
87 | switch(type) { | |
88 | case COALITION_TYPE_RESOURCE: | |
89 | return "RESOURCE"; | |
90 | case COALITION_TYPE_JETSAM: | |
91 | return "JETSAM"; | |
92 | default: | |
93 | return "<unknown>"; | |
94 | } | |
95 | } | |
96 | ||
97 | struct coalition_type { | |
98 | int type; | |
99 | int has_default; | |
100 | /* | |
101 | * init | |
102 | * pre-condition: coalition just allocated (unlocked), unreferenced, | |
103 | * type field set | |
104 | */ | |
105 | kern_return_t (*init)(coalition_t coal, boolean_t privileged); | |
106 | ||
107 | /* | |
108 | * dealloc | |
109 | * pre-condition: coalition unlocked | |
110 | * pre-condition: coalition refcount=0, active_count=0, | |
111 | * termrequested=1, terminated=1, reaped=1 | |
112 | */ | |
113 | void (*dealloc)(coalition_t coal); | |
114 | ||
115 | /* | |
116 | * adopt_task | |
117 | * pre-condition: coalition locked | |
118 | * pre-condition: coalition !repead and !terminated | |
119 | */ | |
120 | kern_return_t (*adopt_task)(coalition_t coal, task_t task); | |
121 | ||
122 | /* | |
123 | * remove_task | |
124 | * pre-condition: coalition locked | |
125 | * pre-condition: task has been removed from coalition's task list | |
126 | */ | |
127 | kern_return_t (*remove_task)(coalition_t coal, task_t task); | |
128 | ||
129 | /* | |
130 | * set_taskrole | |
131 | * pre-condition: coalition locked | |
132 | * pre-condition: task added to coalition's task list, | |
133 | * active_count >= 1 (at least the given task is active) | |
134 | */ | |
135 | kern_return_t (*set_taskrole)(coalition_t coal, task_t task, int role); | |
136 | ||
137 | /* | |
138 | * get_taskrole | |
139 | * pre-condition: coalition locked | |
140 | * pre-condition: task added to coalition's task list, | |
141 | * active_count >= 1 (at least the given task is active) | |
142 | */ | |
143 | int (*get_taskrole)(coalition_t coal, task_t task); | |
144 | ||
145 | /* | |
146 | * iterate_tasks | |
147 | * pre-condition: coalition locked | |
148 | */ | |
149 | void (*iterate_tasks)(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t)); | |
150 | }; | |
151 | ||
152 | /* | |
153 | * COALITION_TYPE_RESOURCE | |
154 | */ | |
fe8ab488 | 155 | |
3e170ce0 A |
156 | static kern_return_t i_coal_resource_init(coalition_t coal, boolean_t privileged); |
157 | static void i_coal_resource_dealloc(coalition_t coal); | |
158 | static kern_return_t i_coal_resource_adopt_task(coalition_t coal, task_t task); | |
159 | static kern_return_t i_coal_resource_remove_task(coalition_t coal, task_t task); | |
160 | static kern_return_t i_coal_resource_set_taskrole(coalition_t coal, | |
161 | task_t task, int role); | |
162 | static int i_coal_resource_get_taskrole(coalition_t coal, task_t task); | |
163 | static void i_coal_resource_iterate_tasks(coalition_t coal, void *ctx, | |
164 | void (*callback)(coalition_t, void *, task_t)); | |
165 | ||
166 | struct i_resource_coalition { | |
fe8ab488 A |
167 | ledger_t ledger; |
168 | uint64_t bytesread; | |
169 | uint64_t byteswritten; | |
170 | uint64_t gpu_time; | |
171 | ||
3e170ce0 A |
172 | uint64_t task_count; /* tasks that have started in this coalition */ |
173 | uint64_t dead_task_count; /* tasks that have exited in this coalition; | |
174 | subtract from task_count to get count | |
175 | of "active" tasks */ | |
fe8ab488 A |
176 | /* |
177 | * Count the length of time this coalition had at least one active task. | |
178 | * This can be a 'denominator' to turn e.g. cpu_time to %cpu. | |
179 | * */ | |
180 | uint64_t last_became_nonempty_time; | |
181 | uint64_t time_nonempty; | |
182 | ||
3e170ce0 A |
183 | queue_head_t tasks; /* List of active tasks in the coalition */ |
184 | }; | |
fe8ab488 | 185 | |
3e170ce0 A |
186 | /* |
187 | * COALITION_TYPE_JETSAM | |
188 | */ | |
fe8ab488 | 189 | |
3e170ce0 A |
190 | static kern_return_t i_coal_jetsam_init(coalition_t coal, boolean_t privileged); |
191 | static void i_coal_jetsam_dealloc(coalition_t coal); | |
192 | static kern_return_t i_coal_jetsam_adopt_task(coalition_t coal, task_t task); | |
193 | static kern_return_t i_coal_jetsam_remove_task(coalition_t coal, task_t task); | |
194 | static kern_return_t i_coal_jetsam_set_taskrole(coalition_t coal, | |
195 | task_t task, int role); | |
196 | static int i_coal_jetsam_get_taskrole(coalition_t coal, task_t task); | |
197 | static void i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx, | |
198 | void (*callback)(coalition_t, void *, task_t)); | |
199 | ||
200 | struct i_jetsam_coalition { | |
201 | task_t leader; | |
202 | queue_head_t extensions; | |
203 | queue_head_t services; | |
204 | queue_head_t other; | |
205 | }; | |
fe8ab488 | 206 | |
fe8ab488 | 207 | |
3e170ce0 A |
208 | /* |
209 | * main coalition structure | |
210 | */ | |
211 | struct coalition { | |
212 | uint64_t id; /* monotonically increasing */ | |
213 | uint32_t type; | |
214 | uint32_t ref_count; /* Number of references to the memory containing this struct */ | |
215 | uint32_t active_count; /* Number of members of (tasks in) the | |
216 | coalition, plus vouchers referring | |
217 | to the coalition */ | |
218 | uint32_t focal_task_count; /* Number of TASK_FOREGROUND_APPLICATION tasks in the coalition */ | |
219 | uint32_t nonfocal_task_count; /* Number of TASK_BACKGROUND_APPLICATION tasks in the coalition */ | |
220 | ||
221 | /* coalition flags */ | |
222 | uint32_t privileged : 1; /* Members of this coalition may create | |
223 | and manage coalitions and may posix_spawn | |
224 | processes into selected coalitions */ | |
fe8ab488 | 225 | /* ast? */ |
fe8ab488 | 226 | /* voucher */ |
3e170ce0 A |
227 | uint32_t termrequested : 1; /* launchd has requested termination when coalition becomes empty */ |
228 | uint32_t terminated : 1; /* coalition became empty and spawns are now forbidden */ | |
229 | uint32_t reaped : 1; /* reaped, invisible to userspace, but waiting for ref_count to go to zero */ | |
230 | uint32_t notified : 1; /* no-more-processes notification was sent via special port */ | |
231 | #if defined(DEVELOPMENT) || defined(DEBUG) | |
232 | uint32_t should_notify : 1; /* should this coalition send notifications (default: yes) */ | |
233 | #endif | |
fe8ab488 | 234 | |
3e170ce0 | 235 | queue_chain_t coalitions; /* global list of coalitions */ |
a1c7dba1 | 236 | |
3e170ce0 A |
237 | decl_lck_mtx_data(,lock) /* Coalition lock. */ |
238 | ||
239 | /* put coalition type-specific structures here */ | |
240 | union { | |
241 | struct i_resource_coalition r; | |
242 | struct i_jetsam_coalition j; | |
243 | }; | |
244 | }; | |
245 | ||
246 | /* | |
247 | * register different coalition types: | |
248 | * these must be kept in the order specified in coalition.h | |
249 | */ | |
250 | static const struct coalition_type | |
251 | s_coalition_types[COALITION_NUM_TYPES] = { | |
252 | { | |
253 | COALITION_TYPE_RESOURCE, | |
254 | 1, | |
255 | i_coal_resource_init, | |
256 | i_coal_resource_dealloc, | |
257 | i_coal_resource_adopt_task, | |
258 | i_coal_resource_remove_task, | |
259 | i_coal_resource_set_taskrole, | |
260 | i_coal_resource_get_taskrole, | |
261 | i_coal_resource_iterate_tasks, | |
262 | }, | |
263 | { | |
264 | COALITION_TYPE_JETSAM, | |
265 | 1, | |
266 | i_coal_jetsam_init, | |
267 | i_coal_jetsam_dealloc, | |
268 | i_coal_jetsam_adopt_task, | |
269 | i_coal_jetsam_remove_task, | |
270 | i_coal_jetsam_set_taskrole, | |
271 | i_coal_jetsam_get_taskrole, | |
272 | i_coal_jetsam_iterate_tasks, | |
273 | }, | |
fe8ab488 A |
274 | }; |
275 | ||
3e170ce0 A |
276 | #define coal_call(coal, func, ...) \ |
277 | (s_coalition_types[(coal)->type].func)(coal, ## __VA_ARGS__) | |
278 | ||
279 | ||
fe8ab488 A |
280 | #define coalition_lock(c) do{ lck_mtx_lock(&c->lock); }while(0) |
281 | #define coalition_unlock(c) do{ lck_mtx_unlock(&c->lock); }while(0) | |
282 | ||
283 | static void | |
284 | coalition_notify_user(uint64_t id, uint32_t flags) | |
285 | { | |
286 | mach_port_t user_port; | |
287 | kern_return_t kr; | |
288 | ||
289 | kr = host_get_coalition_port(host_priv_self(), &user_port); | |
290 | if ((kr != KERN_SUCCESS) || !IPC_PORT_VALID(user_port)) { | |
291 | return; | |
292 | } | |
293 | ||
294 | coalition_notification(user_port, id, flags); | |
295 | } | |
296 | ||
297 | /* | |
3e170ce0 A |
298 | * |
299 | * COALITION_TYPE_RESOURCE | |
300 | * | |
fe8ab488 | 301 | */ |
3e170ce0 A |
302 | static kern_return_t |
303 | i_coal_resource_init(coalition_t coal, boolean_t privileged) | |
fe8ab488 | 304 | { |
3e170ce0 A |
305 | (void)privileged; |
306 | assert(coal && coal->type == COALITION_TYPE_RESOURCE); | |
307 | coal->r.ledger = ledger_instantiate(task_ledger_template, | |
308 | LEDGER_CREATE_ACTIVE_ENTRIES); | |
309 | if (coal->r.ledger == NULL) | |
310 | return KERN_RESOURCE_SHORTAGE; | |
311 | ||
312 | queue_init(&coal->r.tasks); | |
313 | ||
314 | return KERN_SUCCESS; | |
315 | } | |
316 | ||
317 | static void | |
318 | i_coal_resource_dealloc(coalition_t coal) | |
319 | { | |
320 | assert(coal && coal->type == COALITION_TYPE_RESOURCE); | |
321 | ledger_dereference(coal->r.ledger); | |
322 | } | |
323 | ||
324 | static kern_return_t | |
325 | i_coal_resource_adopt_task(coalition_t coal, task_t task) | |
326 | { | |
327 | struct i_resource_coalition *cr; | |
328 | ||
329 | assert(coal && coal->type == COALITION_TYPE_RESOURCE); | |
330 | assert(queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE])); | |
331 | ||
332 | cr = &coal->r; | |
333 | cr->task_count++; | |
334 | ||
335 | if (cr->task_count < cr->dead_task_count) { | |
336 | panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)", | |
337 | __func__, coal, coal->id, coal_type_str(coal->type), | |
338 | cr->task_count, cr->dead_task_count); | |
fe8ab488 A |
339 | } |
340 | ||
3e170ce0 A |
341 | /* If moving from 0->1 active tasks */ |
342 | if (cr->task_count - cr->dead_task_count == 1) { | |
343 | cr->last_became_nonempty_time = mach_absolute_time(); | |
fe8ab488 | 344 | } |
3e170ce0 A |
345 | |
346 | /* put the task on the coalition's list of tasks */ | |
347 | enqueue_tail(&cr->tasks, &task->task_coalition[COALITION_TYPE_RESOURCE]); | |
348 | ||
349 | coal_dbg("Added PID:%d to id:%llu, task_count:%llu, dead_count:%llu, nonempty_time:%llu", | |
350 | task_pid(task), coal->id, cr->task_count, cr->dead_task_count, | |
351 | cr->last_became_nonempty_time); | |
352 | ||
353 | return KERN_SUCCESS; | |
354 | } | |
355 | ||
356 | static kern_return_t | |
357 | i_coal_resource_remove_task(coalition_t coal, task_t task) | |
358 | { | |
359 | struct i_resource_coalition *cr; | |
360 | ||
361 | assert(coal && coal->type == COALITION_TYPE_RESOURCE); | |
362 | assert(task->coalition[COALITION_TYPE_RESOURCE] == coal); | |
363 | assert(!queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE])); | |
364 | ||
365 | /* | |
366 | * handle resource coalition accounting rollup for dead tasks | |
367 | */ | |
368 | cr = &coal->r; | |
369 | ||
370 | cr->dead_task_count++; | |
371 | ||
372 | if (cr->task_count < cr->dead_task_count) { | |
373 | panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)", | |
374 | __func__, coal, coal->id, coal_type_str(coal->type), cr->task_count, cr->dead_task_count); | |
375 | } | |
376 | ||
377 | /* If moving from 1->0 active tasks */ | |
378 | if (cr->task_count - cr->dead_task_count == 0) { | |
379 | uint64_t last_time_nonempty = mach_absolute_time() - cr->last_became_nonempty_time; | |
380 | cr->last_became_nonempty_time = 0; | |
381 | cr->time_nonempty += last_time_nonempty; | |
382 | } | |
383 | ||
384 | ledger_rollup(cr->ledger, task->ledger); | |
385 | cr->bytesread += task->task_io_stats->disk_reads.size; | |
386 | cr->byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size; | |
387 | cr->gpu_time += task_gpu_utilisation(task); | |
388 | ||
389 | /* remove the task from the coalition's list */ | |
390 | remqueue(&task->task_coalition[COALITION_TYPE_RESOURCE]); | |
391 | queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]); | |
392 | ||
393 | coal_dbg("removed PID:%d from id:%llu, task_count:%llu, dead_count:%llu", | |
394 | task_pid(task), coal->id, cr->task_count, cr->dead_task_count); | |
395 | ||
396 | return KERN_SUCCESS; | |
397 | } | |
398 | ||
399 | static kern_return_t | |
400 | i_coal_resource_set_taskrole(__unused coalition_t coal, | |
401 | __unused task_t task, __unused int role) | |
402 | { | |
403 | return KERN_SUCCESS; | |
404 | } | |
405 | ||
406 | static int | |
407 | i_coal_resource_get_taskrole(__unused coalition_t coal, __unused task_t task) | |
408 | { | |
409 | task_t t; | |
410 | ||
411 | assert(coal && coal->type == COALITION_TYPE_RESOURCE); | |
412 | ||
413 | qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) { | |
414 | if (t == task) | |
415 | return COALITION_TASKROLE_UNDEF; | |
416 | } | |
417 | ||
418 | return -1; | |
419 | } | |
420 | ||
421 | static void | |
422 | i_coal_resource_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t)) | |
423 | { | |
424 | task_t t; | |
425 | assert(coal && coal->type == COALITION_TYPE_RESOURCE); | |
426 | ||
427 | qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) | |
428 | callback(coal, ctx, t); | |
fe8ab488 A |
429 | } |
430 | ||
431 | kern_return_t | |
432 | coalition_resource_usage_internal(coalition_t coal, struct coalition_resource_usage *cru_out) | |
433 | { | |
434 | kern_return_t kr; | |
435 | ledger_amount_t credit, debit; | |
436 | ||
3e170ce0 A |
437 | if (coal->type != COALITION_TYPE_RESOURCE) |
438 | return KERN_INVALID_ARGUMENT; | |
439 | ||
fe8ab488 | 440 | ledger_t sum_ledger = ledger_instantiate(task_ledger_template, LEDGER_CREATE_ACTIVE_ENTRIES); |
3e170ce0 | 441 | if (sum_ledger == LEDGER_NULL) |
fe8ab488 | 442 | return KERN_RESOURCE_SHORTAGE; |
fe8ab488 A |
443 | |
444 | coalition_lock(coal); | |
445 | ||
446 | /* | |
447 | * Start with the coalition's ledger, which holds the totals from all | |
448 | * the dead tasks. | |
449 | */ | |
3e170ce0 A |
450 | ledger_rollup(sum_ledger, coal->r.ledger); |
451 | uint64_t bytesread = coal->r.bytesread; | |
452 | uint64_t byteswritten = coal->r.byteswritten; | |
453 | uint64_t gpu_time = coal->r.gpu_time; | |
454 | int64_t cpu_time_billed_to_me = 0; | |
455 | int64_t cpu_time_billed_to_others = 0; | |
456 | ||
457 | kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_me, (int64_t *)&cpu_time_billed_to_me); | |
458 | if (kr != KERN_SUCCESS || cpu_time_billed_to_me < 0) { | |
459 | #if DEVELOPMENT || DEBUG | |
460 | printf("ledger_get_balance failed or ledger negative in coalition_resource_usage_internal: %lld\n", cpu_time_billed_to_me); | |
461 | #endif /* DEVELOPMENT || DEBUG */ | |
462 | cpu_time_billed_to_me = 0; | |
463 | } | |
464 | ||
465 | kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_others, (int64_t *)&cpu_time_billed_to_others); | |
466 | if (kr != KERN_SUCCESS || cpu_time_billed_to_others < 0) { | |
467 | #if DEVELOPMENT || DEBUG | |
468 | printf("ledger_get_balance failed or ledger negative in coalition_resource_usage_internal: %lld\n", cpu_time_billed_to_others); | |
469 | #endif /* DEVELOPMENT || DEBUG */ | |
470 | cpu_time_billed_to_others = 0; | |
471 | } | |
fe8ab488 A |
472 | |
473 | /* | |
474 | * Add to that all the active tasks' ledgers. Tasks cannot deallocate | |
475 | * out from under us, since we hold the coalition lock. | |
3e170ce0 A |
476 | * Do not use the on-behalf of cpu time from ledger for live tasks, since |
477 | * it will not have cpu time for active linkages between tasks. | |
fe8ab488 A |
478 | */ |
479 | task_t task; | |
3e170ce0 | 480 | qe_foreach_element(task, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) { |
fe8ab488 A |
481 | ledger_rollup(sum_ledger, task->ledger); |
482 | bytesread += task->task_io_stats->disk_reads.size; | |
483 | byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size; | |
484 | gpu_time += task_gpu_utilisation(task); | |
3e170ce0 A |
485 | cpu_time_billed_to_me += (int64_t)bank_billed_time(task->bank_context); |
486 | cpu_time_billed_to_others += (int64_t)bank_serviced_time(task->bank_context); | |
fe8ab488 A |
487 | } |
488 | ||
489 | /* collect information from the coalition itself */ | |
3e170ce0 A |
490 | cru_out->tasks_started = coal->r.task_count; |
491 | cru_out->tasks_exited = coal->r.dead_task_count; | |
fe8ab488 | 492 | |
3e170ce0 A |
493 | uint64_t time_nonempty = coal->r.time_nonempty; |
494 | uint64_t last_became_nonempty_time = coal->r.last_became_nonempty_time; | |
fe8ab488 A |
495 | |
496 | coalition_unlock(coal); | |
497 | ||
498 | /* Copy the totals out of sum_ledger */ | |
499 | kr = ledger_get_entries(sum_ledger, task_ledgers.cpu_time, | |
500 | &credit, &debit); | |
501 | if (kr != KERN_SUCCESS) { | |
502 | credit = 0; | |
503 | } | |
504 | cru_out->cpu_time = credit; | |
3e170ce0 A |
505 | cru_out->cpu_time_billed_to_me = (uint64_t)cpu_time_billed_to_me; |
506 | cru_out->cpu_time_billed_to_others = (uint64_t)cpu_time_billed_to_others; | |
fe8ab488 A |
507 | |
508 | kr = ledger_get_entries(sum_ledger, task_ledgers.interrupt_wakeups, | |
509 | &credit, &debit); | |
510 | if (kr != KERN_SUCCESS) { | |
511 | credit = 0; | |
512 | } | |
513 | cru_out->interrupt_wakeups = credit; | |
514 | ||
515 | kr = ledger_get_entries(sum_ledger, task_ledgers.platform_idle_wakeups, | |
516 | &credit, &debit); | |
517 | if (kr != KERN_SUCCESS) { | |
518 | credit = 0; | |
519 | } | |
520 | cru_out->platform_idle_wakeups = credit; | |
521 | ||
522 | cru_out->bytesread = bytesread; | |
523 | cru_out->byteswritten = byteswritten; | |
524 | cru_out->gpu_time = gpu_time; | |
525 | ||
526 | ledger_dereference(sum_ledger); | |
527 | sum_ledger = LEDGER_NULL; | |
528 | ||
529 | if (last_became_nonempty_time) { | |
530 | time_nonempty += mach_absolute_time() - last_became_nonempty_time; | |
531 | } | |
532 | absolutetime_to_nanoseconds(time_nonempty, &cru_out->time_nonempty); | |
533 | ||
534 | return KERN_SUCCESS; | |
535 | } | |
536 | ||
3e170ce0 A |
537 | /* |
538 | * | |
539 | * COALITION_TYPE_JETSAM | |
540 | * | |
541 | */ | |
542 | static kern_return_t | |
543 | i_coal_jetsam_init(coalition_t coal, boolean_t privileged) | |
544 | { | |
545 | assert(coal && coal->type == COALITION_TYPE_JETSAM); | |
546 | (void)privileged; | |
547 | ||
548 | coal->j.leader= TASK_NULL; | |
549 | queue_head_init(coal->j.extensions); | |
550 | queue_head_init(coal->j.services); | |
551 | queue_head_init(coal->j.other); | |
552 | ||
553 | return KERN_SUCCESS; | |
554 | } | |
555 | ||
556 | static void | |
557 | i_coal_jetsam_dealloc(__unused coalition_t coal) | |
558 | { | |
559 | assert(coal && coal->type == COALITION_TYPE_JETSAM); | |
560 | ||
561 | /* the coalition should be completely clear at this point */ | |
562 | assert(queue_empty(&coal->j.extensions)); | |
563 | assert(queue_empty(&coal->j.services)); | |
564 | assert(queue_empty(&coal->j.other)); | |
565 | assert(coal->j.leader == TASK_NULL); | |
566 | } | |
567 | ||
568 | static kern_return_t | |
569 | i_coal_jetsam_adopt_task(coalition_t coal, task_t task) | |
570 | { | |
571 | struct i_jetsam_coalition *cj; | |
572 | assert(coal && coal->type == COALITION_TYPE_JETSAM); | |
573 | ||
574 | cj = &coal->j; | |
575 | ||
576 | assert(queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM])); | |
577 | ||
578 | /* put each task initially in the "other" list */ | |
579 | enqueue_tail(&cj->other, &task->task_coalition[COALITION_TYPE_JETSAM]); | |
580 | coal_dbg("coalition %lld adopted PID:%d as UNDEF", | |
581 | coal->id, task_pid(task)); | |
582 | ||
583 | return KERN_SUCCESS; | |
584 | } | |
585 | ||
586 | static kern_return_t | |
587 | i_coal_jetsam_remove_task(coalition_t coal, task_t task) | |
588 | { | |
589 | assert(coal && coal->type == COALITION_TYPE_JETSAM); | |
590 | assert(task->coalition[COALITION_TYPE_JETSAM] == coal); | |
591 | ||
592 | coal_dbg("removing PID:%d from coalition id:%lld", | |
593 | task_pid(task), coal->id); | |
594 | ||
595 | if (task == coal->j.leader) { | |
596 | coal->j.leader = NULL; | |
597 | coal_dbg(" PID:%d was the leader!", task_pid(task)); | |
598 | } else { | |
599 | assert(!queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM])); | |
600 | } | |
601 | ||
602 | /* remove the task from the specific coalition role queue */ | |
603 | remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]); | |
604 | queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]); | |
605 | ||
606 | return KERN_SUCCESS; | |
607 | } | |
608 | ||
609 | static kern_return_t | |
610 | i_coal_jetsam_set_taskrole(coalition_t coal, task_t task, int role) | |
611 | { | |
612 | struct i_jetsam_coalition *cj; | |
613 | queue_t q = NULL; | |
614 | assert(coal && coal->type == COALITION_TYPE_JETSAM); | |
615 | assert(task->coalition[COALITION_TYPE_JETSAM] == coal); | |
616 | ||
617 | cj = &coal->j; | |
618 | ||
619 | switch (role) { | |
620 | case COALITION_TASKROLE_LEADER: | |
621 | coal_dbg("setting PID:%d as LEADER of %lld", | |
622 | task_pid(task), coal->id); | |
623 | if (cj->leader != TASK_NULL) { | |
624 | /* re-queue the exiting leader onto the "other" list */ | |
625 | coal_dbg(" re-queue existing leader (%d) as OTHER", | |
626 | task_pid(cj->leader)); | |
627 | re_queue_tail(&cj->other, &cj->leader->task_coalition[COALITION_TYPE_JETSAM]); | |
628 | } | |
629 | /* | |
630 | * remove the task from the "other" list | |
631 | * (where it was put by default) | |
632 | */ | |
633 | remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]); | |
634 | queue_chain_init(task->task_coalition[COALITION_TYPE_JETSAM]); | |
635 | ||
636 | /* set the coalition leader */ | |
637 | cj->leader = task; | |
638 | break; | |
639 | case COALITION_TASKROLE_UNDEF: | |
640 | coal_dbg("setting PID:%d as UNDEF in %lld", | |
641 | task_pid(task), coal->id); | |
642 | q = (queue_t)&cj->other; | |
643 | break; | |
644 | case COALITION_TASKROLE_XPC: | |
645 | coal_dbg("setting PID:%d as XPC in %lld", | |
646 | task_pid(task), coal->id); | |
647 | q = (queue_t)&cj->services; | |
648 | break; | |
649 | case COALITION_TASKROLE_EXT: | |
650 | coal_dbg("setting PID:%d as EXT in %lld", | |
651 | task_pid(task), coal->id); | |
652 | q = (queue_t)&cj->extensions; | |
653 | break; | |
654 | default: | |
655 | panic("%s: invalid role(%d) for task", __func__, role); | |
656 | return KERN_INVALID_ARGUMENT; | |
657 | } | |
658 | ||
659 | if (q != NULL) | |
660 | re_queue_tail(q, &task->task_coalition[COALITION_TYPE_JETSAM]); | |
661 | ||
662 | return KERN_SUCCESS; | |
663 | } | |
664 | ||
665 | static int | |
666 | i_coal_jetsam_get_taskrole(coalition_t coal, task_t task) | |
667 | { | |
668 | struct i_jetsam_coalition *cj; | |
669 | task_t t; | |
670 | ||
671 | assert(coal && coal->type == COALITION_TYPE_JETSAM); | |
672 | assert(task->coalition[COALITION_TYPE_JETSAM] == coal); | |
673 | ||
674 | cj = &coal->j; | |
675 | ||
676 | if (task == cj->leader) | |
677 | return COALITION_TASKROLE_LEADER; | |
678 | ||
679 | qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM]) { | |
680 | if (t == task) | |
681 | return COALITION_TASKROLE_XPC; | |
682 | } | |
683 | ||
684 | qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM]) { | |
685 | if (t == task) | |
686 | return COALITION_TASKROLE_EXT; | |
687 | } | |
688 | ||
689 | qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM]) { | |
690 | if (t == task) | |
691 | return COALITION_TASKROLE_UNDEF; | |
692 | } | |
693 | ||
694 | /* task not in the coalition?! */ | |
695 | return -1; | |
696 | } | |
697 | ||
698 | static void | |
699 | i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t)) | |
700 | { | |
701 | struct i_jetsam_coalition *cj; | |
702 | task_t t; | |
703 | ||
704 | assert(coal && coal->type == COALITION_TYPE_JETSAM); | |
705 | ||
706 | cj = &coal->j; | |
707 | ||
708 | if (cj->leader) | |
709 | callback(coal, ctx, cj->leader); | |
710 | ||
711 | qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM]) | |
712 | callback(coal, ctx, t); | |
713 | ||
714 | qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM]) | |
715 | callback(coal, ctx, t); | |
716 | ||
717 | qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM]) | |
718 | callback(coal, ctx, t); | |
719 | } | |
720 | ||
721 | ||
722 | /* | |
723 | * | |
724 | * Main Coalition implementation | |
725 | * | |
726 | */ | |
727 | ||
fe8ab488 A |
728 | /* |
729 | * coalition_create_internal | |
730 | * Returns: New coalition object, referenced for the caller and unlocked. | |
731 | * Condition: coalitions_list_lock must be UNLOCKED. | |
732 | */ | |
733 | kern_return_t | |
3e170ce0 | 734 | coalition_create_internal(int type, boolean_t privileged, coalition_t *out) |
fe8ab488 | 735 | { |
3e170ce0 A |
736 | kern_return_t kr; |
737 | struct coalition *new_coal; | |
738 | ||
739 | if (type < 0 || type > COALITION_TYPE_MAX) | |
740 | return KERN_INVALID_ARGUMENT; | |
741 | ||
742 | new_coal = (struct coalition *)zalloc(coalition_zone); | |
743 | if (new_coal == COALITION_NULL) | |
fe8ab488 | 744 | return KERN_RESOURCE_SHORTAGE; |
fe8ab488 A |
745 | bzero(new_coal, sizeof(*new_coal)); |
746 | ||
3e170ce0 A |
747 | new_coal->type = type; |
748 | ||
749 | /* initialize type-specific resources */ | |
750 | kr = coal_call(new_coal, init, privileged); | |
751 | if (kr != KERN_SUCCESS) { | |
fe8ab488 | 752 | zfree(coalition_zone, new_coal); |
3e170ce0 | 753 | return kr; |
fe8ab488 A |
754 | } |
755 | ||
756 | /* One for caller, one for coalitions list */ | |
757 | new_coal->ref_count = 2; | |
758 | ||
759 | new_coal->privileged = privileged ? TRUE : FALSE; | |
3e170ce0 A |
760 | #if defined(DEVELOPMENT) || defined(DEBUG) |
761 | new_coal->should_notify = 1; | |
762 | #endif | |
fe8ab488 A |
763 | |
764 | lck_mtx_init(&new_coal->lock, &coalitions_lck_grp, &coalitions_lck_attr); | |
fe8ab488 A |
765 | |
766 | lck_mtx_lock(&coalitions_list_lock); | |
767 | new_coal->id = coalition_next_id++; | |
768 | coalition_count++; | |
3e170ce0 | 769 | enqueue_tail(&coalitions_q, &new_coal->coalitions); |
fe8ab488 A |
770 | lck_mtx_unlock(&coalitions_list_lock); |
771 | ||
3e170ce0 | 772 | coal_dbg("id:%llu, type:%s", new_coal->id, coal_type_str(new_coal->type)); |
fe8ab488 A |
773 | |
774 | *out = new_coal; | |
775 | return KERN_SUCCESS; | |
776 | } | |
777 | ||
778 | /* | |
779 | * coalition_release | |
780 | * Condition: coalition must be UNLOCKED. | |
781 | * */ | |
782 | void | |
783 | coalition_release(coalition_t coal) | |
784 | { | |
fe8ab488 A |
785 | /* TODO: This can be done with atomics. */ |
786 | coalition_lock(coal); | |
787 | coal->ref_count--; | |
3e170ce0 | 788 | |
fe8ab488 A |
789 | #if COALITION_DEBUG |
790 | uint32_t rc = coal->ref_count; | |
3e170ce0 | 791 | uint32_t ac = coal->active_count; |
fe8ab488 A |
792 | #endif /* COALITION_DEBUG */ |
793 | ||
3e170ce0 A |
794 | coal_dbg("id:%llu type:%s ref_count:%u active_count:%u%s", |
795 | coal->id, coal_type_str(coal->type), rc, ac, | |
796 | rc <= 0 ? ", will deallocate now" : ""); | |
797 | ||
798 | if (coal->ref_count > 0) { | |
799 | coalition_unlock(coal); | |
800 | return; | |
801 | } | |
802 | ||
803 | assert(coal->termrequested); | |
804 | assert(coal->terminated); | |
805 | assert(coal->active_count == 0); | |
806 | assert(coal->reaped); | |
807 | assert(coal->focal_task_count == 0); | |
808 | assert(coal->nonfocal_task_count == 0); | |
809 | ||
810 | coal_call(coal, dealloc); | |
811 | ||
fe8ab488 A |
812 | coalition_unlock(coal); |
813 | ||
3e170ce0 | 814 | lck_mtx_destroy(&coal->lock, &coalitions_lck_grp); |
fe8ab488 | 815 | |
3e170ce0 A |
816 | zfree(coalition_zone, coal); |
817 | } | |
fe8ab488 | 818 | |
3e170ce0 A |
819 | /* |
820 | * coalition_find_by_id_internal | |
821 | * Returns: Coalition object with specified id, NOT referenced. | |
822 | * If not found, returns COALITION_NULL. | |
823 | * Condition: coalitions_list_lock must be LOCKED. | |
824 | */ | |
825 | static coalition_t | |
826 | coalition_find_by_id_internal(uint64_t coal_id) | |
827 | { | |
828 | if (coal_id == 0) { | |
829 | return COALITION_NULL; | |
fe8ab488 | 830 | } |
3e170ce0 A |
831 | |
832 | lck_mtx_assert(&coalitions_list_lock, LCK_MTX_ASSERT_OWNED); | |
833 | coalition_t coal; | |
834 | qe_foreach_element(coal, &coalitions_q, coalitions) { | |
835 | if (coal->id == coal_id) { | |
836 | return coal; | |
837 | } | |
838 | } | |
839 | return COALITION_NULL; | |
fe8ab488 A |
840 | } |
841 | ||
842 | /* | |
843 | * coalition_find_by_id | |
844 | * Returns: Coalition object with specified id, referenced. | |
845 | * Condition: coalitions_list_lock must be UNLOCKED. | |
846 | */ | |
847 | coalition_t | |
848 | coalition_find_by_id(uint64_t cid) | |
849 | { | |
850 | if (cid == 0) { | |
851 | return COALITION_NULL; | |
852 | } | |
853 | ||
854 | lck_mtx_lock(&coalitions_list_lock); | |
855 | ||
856 | coalition_t coal = coalition_find_by_id_internal(cid); | |
857 | if (coal == COALITION_NULL) { | |
858 | lck_mtx_unlock(&coalitions_list_lock); | |
859 | return COALITION_NULL; | |
860 | } | |
861 | ||
862 | coalition_lock(coal); | |
863 | ||
864 | if (coal->reaped) { | |
865 | coalition_unlock(coal); | |
866 | lck_mtx_unlock(&coalitions_list_lock); | |
867 | return COALITION_NULL; | |
868 | } | |
869 | ||
870 | if (coal->ref_count == 0) { | |
3e170ce0 A |
871 | panic("resurrecting coalition %p id:%llu type:%s, active_count:%u\n", |
872 | coal, coal->id, coal_type_str(coal->type), coal->active_count); | |
fe8ab488 A |
873 | } |
874 | coal->ref_count++; | |
875 | #if COALITION_DEBUG | |
876 | uint32_t rc = coal->ref_count; | |
877 | #endif | |
878 | ||
879 | coalition_unlock(coal); | |
880 | lck_mtx_unlock(&coalitions_list_lock); | |
881 | ||
3e170ce0 A |
882 | coal_dbg("id:%llu type:%s ref_count:%u", |
883 | coal->id, coal_type_str(coal->type), rc); | |
884 | ||
fe8ab488 A |
885 | return coal; |
886 | } | |
887 | ||
888 | /* | |
889 | * coalition_find_and_activate_by_id | |
890 | * Returns: Coalition object with specified id, referenced, and activated. | |
891 | * Condition: coalitions_list_lock must be UNLOCKED. | |
892 | * This is the function to use when putting a 'new' thing into a coalition, | |
893 | * like posix_spawn of an XPC service by launchd. | |
894 | * See also coalition_extend_active. | |
895 | */ | |
896 | coalition_t | |
897 | coalition_find_and_activate_by_id(uint64_t cid) | |
898 | { | |
899 | if (cid == 0) { | |
900 | return COALITION_NULL; | |
901 | } | |
902 | ||
903 | lck_mtx_lock(&coalitions_list_lock); | |
904 | ||
905 | coalition_t coal = coalition_find_by_id_internal(cid); | |
906 | if (coal == COALITION_NULL) { | |
907 | lck_mtx_unlock(&coalitions_list_lock); | |
908 | return COALITION_NULL; | |
909 | } | |
910 | ||
911 | coalition_lock(coal); | |
912 | ||
913 | if (coal->reaped || coal->terminated) { | |
914 | /* Too late to put something new into this coalition, it's | |
915 | * already on its way out the door */ | |
916 | coalition_unlock(coal); | |
917 | lck_mtx_unlock(&coalitions_list_lock); | |
918 | return COALITION_NULL; | |
919 | } | |
920 | ||
921 | if (coal->ref_count == 0) { | |
3e170ce0 A |
922 | panic("resurrecting coalition %p id:%llu type:%s, active_count:%u\n", |
923 | coal, coal->id, coal_type_str(coal->type), coal->active_count); | |
fe8ab488 A |
924 | } |
925 | ||
926 | coal->ref_count++; | |
927 | coal->active_count++; | |
928 | ||
929 | #if COALITION_DEBUG | |
930 | uint32_t rc = coal->ref_count; | |
931 | uint32_t ac = coal->active_count; | |
932 | #endif | |
933 | ||
934 | coalition_unlock(coal); | |
935 | lck_mtx_unlock(&coalitions_list_lock); | |
936 | ||
3e170ce0 A |
937 | coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u", |
938 | coal->id, coal_type_str(coal->type), rc, ac); | |
939 | ||
fe8ab488 A |
940 | return coal; |
941 | } | |
942 | ||
943 | uint64_t | |
944 | coalition_id(coalition_t coal) | |
945 | { | |
946 | return coal->id; | |
947 | } | |
948 | ||
3e170ce0 A |
949 | void |
950 | task_coalition_ids(task_t task, uint64_t ids[COALITION_NUM_TYPES]) | |
951 | { | |
952 | int i; | |
953 | for (i = 0; i < COALITION_NUM_TYPES; i++) { | |
954 | if (task->coalition[i]) | |
955 | ids[i] = task->coalition[i]->id; | |
956 | else | |
957 | ids[i] = 0; | |
958 | } | |
959 | } | |
960 | ||
961 | void | |
962 | task_coalition_roles(task_t task, int roles[COALITION_NUM_TYPES]) | |
fe8ab488 | 963 | { |
3e170ce0 A |
964 | int i; |
965 | memset(roles, 0, COALITION_NUM_TYPES * sizeof(roles[0])); | |
966 | ||
967 | for (i = 0; i < COALITION_NUM_TYPES; i++) { | |
968 | if (task->coalition[i]) { | |
969 | coalition_lock(task->coalition[i]); | |
970 | roles[i] = coal_call(task->coalition[i], | |
971 | get_taskrole, task); | |
972 | coalition_unlock(task->coalition[i]); | |
973 | } else { | |
974 | roles[i] = -1; | |
975 | } | |
976 | } | |
977 | } | |
978 | ||
979 | ||
980 | int | |
981 | coalition_type(coalition_t coal) | |
982 | { | |
983 | return coal->type; | |
fe8ab488 A |
984 | } |
985 | ||
986 | boolean_t | |
987 | coalition_is_privileged(coalition_t coal) | |
988 | { | |
989 | return coal->privileged || unrestrict_coalition_syscalls; | |
990 | } | |
991 | ||
992 | boolean_t | |
3e170ce0 | 993 | task_is_in_privileged_coalition(task_t task, int type) |
fe8ab488 | 994 | { |
3e170ce0 A |
995 | if (type < 0 || type > COALITION_TYPE_MAX) |
996 | return FALSE; | |
997 | if (unrestrict_coalition_syscalls) | |
998 | return TRUE; | |
999 | if (!task->coalition[type]) | |
1000 | return FALSE; | |
1001 | return task->coalition[type]->privileged; | |
fe8ab488 A |
1002 | } |
1003 | ||
3e170ce0 | 1004 | void task_coalition_update_gpu_stats(task_t task, uint64_t gpu_ns_delta) |
fe8ab488 | 1005 | { |
3e170ce0 A |
1006 | coalition_t coal; |
1007 | ||
1008 | assert(task != TASK_NULL); | |
1009 | if (gpu_ns_delta == 0) | |
1010 | return; | |
1011 | ||
1012 | coal = task->coalition[COALITION_TYPE_RESOURCE]; | |
1013 | assert(coal != COALITION_NULL); | |
1014 | ||
1015 | coalition_lock(coal); | |
1016 | coal->r.gpu_time += gpu_ns_delta; | |
1017 | coalition_unlock(coal); | |
fe8ab488 A |
1018 | } |
1019 | ||
3e170ce0 | 1020 | uint32_t task_coalition_adjust_focal_count(task_t task, int count) |
fe8ab488 | 1021 | { |
3e170ce0 A |
1022 | coalition_t coal; |
1023 | uint32_t ret; | |
fe8ab488 | 1024 | |
3e170ce0 A |
1025 | /* |
1026 | * For now: only use the resource coalition. Perhaps in the | |
1027 | * future we may combine all coalition types, or even make | |
1028 | * a special coalition type just for this. | |
1029 | */ | |
1030 | coal = task->coalition[COALITION_TYPE_RESOURCE]; | |
1031 | assert(coal != COALITION_NULL); | |
fe8ab488 | 1032 | |
3e170ce0 | 1033 | ret = hw_atomic_add(&coal->focal_task_count, count); |
fe8ab488 | 1034 | |
3e170ce0 A |
1035 | /* catch underflow */ |
1036 | assert(ret != UINT32_MAX); | |
1037 | return ret; | |
1038 | } | |
1039 | ||
1040 | uint32_t task_coalition_focal_count(task_t task) | |
1041 | { | |
1042 | coalition_t coal; | |
1043 | coal = task->coalition[COALITION_TYPE_RESOURCE]; | |
1044 | assert(coal != COALITION_NULL); | |
1045 | ||
1046 | return coal->focal_task_count; | |
1047 | } | |
1048 | ||
1049 | uint32_t task_coalition_adjust_nonfocal_count(task_t task, int count) | |
1050 | { | |
1051 | coalition_t coal; | |
1052 | uint32_t ret; | |
1053 | ||
1054 | /* | |
1055 | * For now: only use the resource coalition. Perhaps in the | |
1056 | * future we may combine all coalition types, or even make | |
1057 | * a special coalition type just for this. | |
1058 | */ | |
1059 | coal = task->coalition[COALITION_TYPE_RESOURCE]; | |
1060 | assert(coal != COALITION_NULL); | |
1061 | ||
1062 | ret = hw_atomic_add(&coal->nonfocal_task_count, count); | |
1063 | ||
1064 | /* catch underflow */ | |
1065 | assert(ret != UINT32_MAX); | |
1066 | return ret; | |
1067 | } | |
1068 | ||
1069 | uint32_t task_coalition_nonfocal_count(task_t task) | |
1070 | { | |
1071 | coalition_t coal; | |
1072 | coal = task->coalition[COALITION_TYPE_RESOURCE]; | |
1073 | assert(coal != COALITION_NULL); | |
1074 | ||
1075 | return coal->nonfocal_task_count; | |
1076 | } | |
1077 | ||
1078 | void coalition_for_each_task(coalition_t coal, void *ctx, | |
1079 | void (*callback)(coalition_t, void *, task_t)) | |
1080 | { | |
1081 | assert(coal != COALITION_NULL); | |
1082 | ||
1083 | coal_dbg("iterating tasks in coalition %p id:%llu type:%s, active_count:%u", | |
1084 | coal, coal->id, coal_type_str(coal->type), coal->active_count); | |
1085 | ||
1086 | coalition_lock(coal); | |
1087 | ||
1088 | coal_call(coal, iterate_tasks, ctx, callback); | |
fe8ab488 A |
1089 | |
1090 | coalition_unlock(coal); | |
fe8ab488 A |
1091 | } |
1092 | ||
3e170ce0 | 1093 | |
fe8ab488 A |
1094 | void |
1095 | coalition_remove_active(coalition_t coal) | |
1096 | { | |
1097 | coalition_lock(coal); | |
1098 | ||
1099 | assert(!coal->reaped); | |
1100 | assert(coal->active_count > 0); | |
1101 | ||
1102 | coal->active_count--; | |
1103 | ||
1104 | boolean_t do_notify = FALSE; | |
1105 | uint64_t notify_id = 0; | |
1106 | uint32_t notify_flags = 0; | |
1107 | if (coal->termrequested && coal->active_count == 0) { | |
1108 | /* We only notify once, when active_count reaches zero. | |
1109 | * We just decremented, so if it reached zero, we mustn't have | |
1110 | * notified already. | |
1111 | */ | |
1112 | assert(!coal->terminated); | |
1113 | coal->terminated = TRUE; | |
1114 | ||
1115 | assert(!coal->notified); | |
1116 | ||
1117 | coal->notified = TRUE; | |
3e170ce0 A |
1118 | #if defined(DEVELOPMENT) || defined(DEBUG) |
1119 | do_notify = coal->should_notify; | |
1120 | #else | |
fe8ab488 | 1121 | do_notify = TRUE; |
3e170ce0 | 1122 | #endif |
fe8ab488 A |
1123 | notify_id = coal->id; |
1124 | notify_flags = 0; | |
1125 | } | |
1126 | ||
3e170ce0 A |
1127 | #if COALITION_DEBUG |
1128 | uint64_t cid = coal->id; | |
1129 | uint32_t rc = coal->ref_count; | |
1130 | int ac = coal->active_count; | |
1131 | int ct = coal->type; | |
1132 | #endif | |
fe8ab488 A |
1133 | coalition_unlock(coal); |
1134 | ||
3e170ce0 A |
1135 | coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u,%s", |
1136 | cid, coal_type_str(ct), rc, ac, do_notify ? " NOTIFY" : " "); | |
1137 | ||
fe8ab488 A |
1138 | if (do_notify) { |
1139 | coalition_notify_user(notify_id, notify_flags); | |
1140 | } | |
1141 | } | |
1142 | ||
1143 | /* Used for kernel_task, launchd, launchd's early boot tasks... */ | |
1144 | kern_return_t | |
3e170ce0 | 1145 | coalitions_adopt_init_task(task_t task) |
fe8ab488 A |
1146 | { |
1147 | kern_return_t kr; | |
3e170ce0 | 1148 | kr = coalitions_adopt_task(init_coalition, task); |
fe8ab488 A |
1149 | if (kr != KERN_SUCCESS) { |
1150 | panic("failed to adopt task %p into default coalition: %d", task, kr); | |
1151 | } | |
1152 | return kr; | |
1153 | } | |
1154 | ||
1155 | /* | |
3e170ce0 | 1156 | * coalition_adopt_task_internal |
fe8ab488 A |
1157 | * Condition: Coalition must be referenced and unlocked. Will fail if coalition |
1158 | * is already terminated. | |
1159 | */ | |
3e170ce0 A |
1160 | static kern_return_t |
1161 | coalition_adopt_task_internal(coalition_t coal, task_t task) | |
fe8ab488 | 1162 | { |
3e170ce0 A |
1163 | kern_return_t kr; |
1164 | ||
1165 | if (task->coalition[coal->type]) { | |
fe8ab488 A |
1166 | return KERN_ALREADY_IN_SET; |
1167 | } | |
1168 | ||
1169 | coalition_lock(coal); | |
1170 | ||
1171 | if (coal->reaped || coal->terminated) { | |
1172 | coalition_unlock(coal); | |
1173 | return KERN_TERMINATED; | |
1174 | } | |
1175 | ||
3e170ce0 A |
1176 | kr = coal_call(coal, adopt_task, task); |
1177 | if (kr != KERN_SUCCESS) | |
1178 | goto out_unlock; | |
1179 | ||
fe8ab488 A |
1180 | coal->active_count++; |
1181 | ||
1182 | coal->ref_count++; | |
fe8ab488 | 1183 | |
3e170ce0 | 1184 | task->coalition[coal->type] = coal; |
fe8ab488 | 1185 | |
3e170ce0 | 1186 | out_unlock: |
fe8ab488 | 1187 | #if COALITION_DEBUG |
3e170ce0 A |
1188 | (void)coal; /* need expression after label */ |
1189 | uint64_t cid = coal->id; | |
fe8ab488 | 1190 | uint32_t rc = coal->ref_count; |
3e170ce0 | 1191 | uint32_t ct = coal->type; |
fe8ab488 | 1192 | #endif |
fe8ab488 A |
1193 | coalition_unlock(coal); |
1194 | ||
3e170ce0 A |
1195 | coal_dbg("task:%d, id:%llu type:%s ref_count:%u, kr=%d", |
1196 | task_pid(task), cid, coal_type_str(ct), rc, kr); | |
1197 | return kr; | |
1198 | } | |
1199 | ||
1200 | static kern_return_t | |
1201 | coalition_remove_task_internal(task_t task, int type) | |
1202 | { | |
1203 | kern_return_t kr; | |
1204 | ||
1205 | coalition_t coal = task->coalition[type]; | |
1206 | ||
1207 | if (!coal) | |
1208 | return KERN_SUCCESS; | |
1209 | ||
1210 | assert(coal->type == (uint32_t)type); | |
1211 | ||
1212 | coalition_lock(coal); | |
1213 | ||
1214 | kr = coal_call(coal, remove_task, task); | |
1215 | ||
fe8ab488 | 1216 | #if COALITION_DEBUG |
3e170ce0 A |
1217 | uint64_t cid = coal->id; |
1218 | uint32_t rc = coal->ref_count; | |
1219 | int ac = coal->active_count; | |
1220 | int ct = coal->type; | |
fe8ab488 | 1221 | #endif |
3e170ce0 A |
1222 | coalition_unlock(coal); |
1223 | ||
1224 | coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u, kr=%d", | |
1225 | cid, coal_type_str(ct), rc, ac, kr); | |
1226 | ||
1227 | coalition_remove_active(coal); | |
1228 | ||
1229 | return kr; | |
fe8ab488 A |
1230 | } |
1231 | ||
1232 | /* | |
3e170ce0 A |
1233 | * coalitions_adopt_task |
1234 | * Condition: All coalitions must be referenced and unlocked. | |
1235 | * Will fail if any coalition is already terminated. | |
fe8ab488 A |
1236 | */ |
1237 | kern_return_t | |
3e170ce0 | 1238 | coalitions_adopt_task(coalition_t *coals, task_t task) |
fe8ab488 | 1239 | { |
3e170ce0 A |
1240 | int i; |
1241 | kern_return_t kr; | |
fe8ab488 | 1242 | |
3e170ce0 A |
1243 | if (!coals || coals[COALITION_TYPE_RESOURCE] == COALITION_NULL) |
1244 | return KERN_INVALID_ARGUMENT; | |
1245 | ||
1246 | /* verify that the incoming coalitions are what they say they are */ | |
1247 | for (i = 0; i < COALITION_NUM_TYPES; i++) | |
1248 | if (coals[i] && coals[i]->type != (uint32_t)i) | |
1249 | return KERN_INVALID_ARGUMENT; | |
1250 | ||
1251 | for (i = 0; i < COALITION_NUM_TYPES; i++) { | |
1252 | kr = KERN_SUCCESS; | |
1253 | if (coals[i]) | |
1254 | kr = coalition_adopt_task_internal(coals[i], task); | |
1255 | if (kr != KERN_SUCCESS) { | |
1256 | /* dis-associate any coalitions that just adopted this task */ | |
1257 | while (--i >= 0) { | |
1258 | if (task->coalition[i]) | |
1259 | coalition_remove_task_internal(task, i); | |
1260 | } | |
1261 | break; | |
1262 | } | |
1263 | } | |
1264 | return kr; | |
1265 | } | |
fe8ab488 | 1266 | |
3e170ce0 A |
1267 | /* |
1268 | * coalitions_remove_task | |
1269 | * Condition: task must be referenced and UNLOCKED; all task's coalitions must be UNLOCKED | |
1270 | */ | |
1271 | kern_return_t | |
1272 | coalitions_remove_task(task_t task) | |
1273 | { | |
1274 | kern_return_t kr; | |
1275 | int i; | |
fe8ab488 | 1276 | |
3e170ce0 A |
1277 | for (i = 0; i < COALITION_NUM_TYPES; i++) { |
1278 | kr = coalition_remove_task_internal(task, i); | |
1279 | assert(kr == KERN_SUCCESS); | |
fe8ab488 A |
1280 | } |
1281 | ||
3e170ce0 A |
1282 | return kr; |
1283 | } | |
1284 | ||
1285 | /* | |
1286 | * task_release_coalitions | |
1287 | * helper function to release references to all coalitions in which | |
1288 | * 'task' is a member. | |
1289 | */ | |
1290 | void | |
1291 | task_release_coalitions(task_t task) | |
1292 | { | |
1293 | int i; | |
1294 | for (i = 0; i < COALITION_NUM_TYPES; i++) { | |
1295 | if (task->coalition[i]) | |
1296 | coalition_release(task->coalition[i]); | |
fe8ab488 | 1297 | } |
3e170ce0 | 1298 | } |
fe8ab488 | 1299 | |
3e170ce0 A |
1300 | /* |
1301 | * coalitions_set_roles | |
1302 | * for each type of coalition, if the task is a member of a coalition of | |
1303 | * that type (given in the coalitions parameter) then set the role of | |
1304 | * the task within that that coalition. | |
1305 | */ | |
1306 | kern_return_t coalitions_set_roles(coalition_t coalitions[COALITION_NUM_TYPES], | |
1307 | task_t task, int roles[COALITION_NUM_TYPES]) | |
1308 | { | |
1309 | kern_return_t kr = KERN_SUCCESS; | |
1310 | int i; | |
fe8ab488 | 1311 | |
3e170ce0 A |
1312 | for (i = 0; i < COALITION_NUM_TYPES; i++) { |
1313 | if (!coalitions[i]) | |
1314 | continue; | |
1315 | coalition_lock(coalitions[i]); | |
1316 | kr = coal_call(coalitions[i], set_taskrole, task, roles[i]); | |
1317 | coalition_unlock(coalitions[i]); | |
1318 | assert(kr == KERN_SUCCESS); | |
1319 | } | |
fe8ab488 | 1320 | |
3e170ce0 | 1321 | return kr; |
fe8ab488 A |
1322 | } |
1323 | ||
1324 | /* | |
1325 | * coalition_terminate_internal | |
1326 | * Condition: Coalition must be referenced and UNLOCKED. | |
1327 | */ | |
1328 | kern_return_t | |
1329 | coalition_request_terminate_internal(coalition_t coal) | |
1330 | { | |
3e170ce0 A |
1331 | assert(coal->type >= 0 && coal->type <= COALITION_TYPE_MAX); |
1332 | ||
1333 | if (coal == init_coalition[coal->type]) { | |
fe8ab488 A |
1334 | return KERN_DEFAULT_SET; |
1335 | } | |
1336 | ||
1337 | coalition_lock(coal); | |
1338 | ||
1339 | if (coal->reaped) { | |
1340 | coalition_unlock(coal); | |
1341 | return KERN_INVALID_NAME; | |
1342 | } | |
1343 | ||
1344 | if (coal->terminated || coal->termrequested) { | |
1345 | coalition_unlock(coal); | |
1346 | return KERN_TERMINATED; | |
1347 | } | |
1348 | ||
1349 | coal->termrequested = TRUE; | |
1350 | ||
1351 | boolean_t do_notify = FALSE; | |
1352 | uint64_t note_id = 0; | |
1353 | uint32_t note_flags = 0; | |
1354 | ||
1355 | if (coal->active_count == 0) { | |
1356 | /* | |
1357 | * We only notify once, when active_count reaches zero. | |
3e170ce0 A |
1358 | * We just set termrequested to zero. If the active count |
1359 | * was already at zero (tasks died before we could request | |
1360 | * a termination notification), we should notify. | |
fe8ab488 A |
1361 | */ |
1362 | assert(!coal->terminated); | |
1363 | coal->terminated = TRUE; | |
1364 | ||
1365 | assert(!coal->notified); | |
1366 | ||
1367 | coal->notified = TRUE; | |
3e170ce0 A |
1368 | #if defined(DEVELOPMENT) || defined(DEBUG) |
1369 | do_notify = coal->should_notify; | |
1370 | #else | |
fe8ab488 | 1371 | do_notify = TRUE; |
3e170ce0 | 1372 | #endif |
fe8ab488 A |
1373 | note_id = coal->id; |
1374 | note_flags = 0; | |
1375 | } | |
1376 | ||
1377 | coalition_unlock(coal); | |
1378 | ||
1379 | if (do_notify) { | |
1380 | coalition_notify_user(note_id, note_flags); | |
1381 | } | |
1382 | ||
1383 | return KERN_SUCCESS; | |
1384 | } | |
1385 | ||
1386 | /* | |
1387 | * coalition_reap_internal | |
1388 | * Condition: Coalition must be referenced and UNLOCKED. | |
1389 | */ | |
1390 | kern_return_t | |
1391 | coalition_reap_internal(coalition_t coal) | |
1392 | { | |
3e170ce0 A |
1393 | assert(coal->type <= COALITION_TYPE_MAX); |
1394 | ||
1395 | if (coal == init_coalition[coal->type]) { | |
fe8ab488 A |
1396 | return KERN_DEFAULT_SET; |
1397 | } | |
1398 | ||
1399 | coalition_lock(coal); | |
1400 | if (coal->reaped) { | |
1401 | coalition_unlock(coal); | |
1402 | return KERN_TERMINATED; | |
1403 | } | |
1404 | if (!coal->terminated) { | |
1405 | coalition_unlock(coal); | |
1406 | return KERN_FAILURE; | |
1407 | } | |
1408 | assert(coal->termrequested); | |
1409 | if (coal->active_count > 0) { | |
1410 | coalition_unlock(coal); | |
1411 | return KERN_FAILURE; | |
1412 | } | |
1413 | ||
1414 | coal->reaped = TRUE; | |
1415 | ||
1416 | /* Caller, launchd, and coalitions list should each have a reference */ | |
1417 | assert(coal->ref_count > 2); | |
1418 | ||
1419 | coalition_unlock(coal); | |
1420 | ||
1421 | lck_mtx_lock(&coalitions_list_lock); | |
1422 | coalition_count--; | |
3e170ce0 | 1423 | remqueue(&coal->coalitions); |
fe8ab488 A |
1424 | lck_mtx_unlock(&coalitions_list_lock); |
1425 | ||
1426 | /* Release the list's reference and launchd's reference. */ | |
1427 | coalition_release(coal); | |
1428 | coalition_release(coal); | |
1429 | ||
1430 | return KERN_SUCCESS; | |
1431 | } | |
1432 | ||
3e170ce0 A |
1433 | #if defined(DEVELOPMENT) || defined(DEBUG) |
1434 | int coalition_should_notify(coalition_t coal) | |
1435 | { | |
1436 | int should; | |
1437 | if (!coal) | |
1438 | return -1; | |
1439 | coalition_lock(coal); | |
1440 | should = coal->should_notify; | |
1441 | coalition_unlock(coal); | |
1442 | ||
1443 | return should; | |
1444 | } | |
1445 | ||
1446 | void coalition_set_notify(coalition_t coal, int notify) | |
1447 | { | |
1448 | if (!coal) | |
1449 | return; | |
1450 | coalition_lock(coal); | |
1451 | coal->should_notify = !!notify; | |
1452 | coalition_unlock(coal); | |
1453 | } | |
1454 | #endif | |
1455 | ||
fe8ab488 | 1456 | void |
3e170ce0 | 1457 | coalitions_init(void) |
fe8ab488 | 1458 | { |
3e170ce0 A |
1459 | kern_return_t kr; |
1460 | int i; | |
1461 | const struct coalition_type *ctype; | |
1462 | ||
fe8ab488 A |
1463 | coalition_zone = zinit( |
1464 | sizeof(struct coalition), | |
1465 | CONFIG_COALITION_MAX * sizeof(struct coalition), | |
1466 | COALITION_CHUNK * sizeof(struct coalition), | |
1467 | "coalitions"); | |
1468 | zone_change(coalition_zone, Z_NOENCRYPT, TRUE); | |
3e170ce0 | 1469 | queue_head_init(coalitions_q); |
fe8ab488 A |
1470 | |
1471 | if (!PE_parse_boot_argn("unrestrict_coalition_syscalls", &unrestrict_coalition_syscalls, | |
1472 | sizeof (unrestrict_coalition_syscalls))) { | |
1473 | unrestrict_coalition_syscalls = 0; | |
1474 | } | |
1475 | ||
1476 | lck_grp_attr_setdefault(&coalitions_lck_grp_attr); | |
1477 | lck_grp_init(&coalitions_lck_grp, "coalition", &coalitions_lck_grp_attr); | |
1478 | lck_attr_setdefault(&coalitions_lck_attr); | |
1479 | lck_mtx_init(&coalitions_list_lock, &coalitions_lck_grp, &coalitions_lck_attr); | |
1480 | ||
1481 | init_task_ledgers(); | |
1482 | ||
3e170ce0 A |
1483 | for (i = 0, ctype = &s_coalition_types[0]; i < COALITION_NUM_TYPES; ctype++, i++) { |
1484 | /* verify the entry in the global coalition types array */ | |
1485 | if (ctype->type != i || | |
1486 | !ctype->init || | |
1487 | !ctype->dealloc || | |
1488 | !ctype->adopt_task || | |
1489 | !ctype->remove_task) { | |
1490 | panic("%s: Malformed coalition type %s(%d) in slot for type:%s(%d)", | |
1491 | __func__, coal_type_str(ctype->type), ctype->type, coal_type_str(i), i); | |
1492 | } | |
1493 | if (!ctype->has_default) | |
1494 | continue; | |
1495 | kr = coalition_create_internal(ctype->type, TRUE, &init_coalition[ctype->type]); | |
1496 | if (kr != KERN_SUCCESS) | |
1497 | panic("%s: could not create init %s coalition: kr:%d", | |
1498 | __func__, coal_type_str(i), kr); | |
fe8ab488 | 1499 | } |
3e170ce0 | 1500 | |
fe8ab488 A |
1501 | /* "Leak" our reference to the global object */ |
1502 | } | |
1503 | ||
3e170ce0 A |
1504 | /* |
1505 | * BSD Kernel interface functions | |
1506 | * | |
1507 | */ | |
1508 | static void coalition_fill_procinfo(struct coalition *coal, | |
1509 | struct procinfo_coalinfo *coalinfo) | |
a1c7dba1 | 1510 | { |
3e170ce0 A |
1511 | coalinfo->coalition_id = coal->id; |
1512 | coalinfo->coalition_type = coal->type; | |
1513 | coalinfo->coalition_tasks = coalition_get_task_count(coal); | |
a1c7dba1 A |
1514 | } |
1515 | ||
3e170ce0 A |
1516 | |
1517 | int coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, int list_sz) | |
a1c7dba1 | 1518 | { |
3e170ce0 A |
1519 | int ncoals = 0; |
1520 | struct coalition *coal; | |
1521 | ||
1522 | lck_mtx_lock(&coalitions_list_lock); | |
1523 | qe_foreach_element(coal, &coalitions_q, coalitions) { | |
1524 | if (!coal->reaped && (type < 0 || type == (int)coal->type)) { | |
1525 | if (coal_list && ncoals < list_sz) | |
1526 | coalition_fill_procinfo(coal, &coal_list[ncoals]); | |
1527 | ++ncoals; | |
1528 | } | |
1529 | } | |
1530 | lck_mtx_unlock(&coalitions_list_lock); | |
1531 | ||
1532 | return ncoals; | |
a1c7dba1 A |
1533 | } |
1534 | ||
3e170ce0 A |
1535 | /* |
1536 | * Jetsam coalition interface | |
1537 | * | |
1538 | */ | |
1539 | boolean_t coalition_is_leader(task_t task, int coal_type, coalition_t *coal) | |
a1c7dba1 | 1540 | { |
3e170ce0 A |
1541 | coalition_t c; |
1542 | boolean_t ret; | |
1543 | ||
1544 | if (coal) /* handle the error cases gracefully */ | |
1545 | *coal = COALITION_NULL; | |
1546 | ||
1547 | if (!task) | |
1548 | return FALSE; | |
1549 | ||
1550 | if (coal_type > COALITION_TYPE_MAX) | |
1551 | return FALSE; | |
1552 | ||
1553 | c = task->coalition[coal_type]; | |
1554 | if (!c) | |
1555 | return FALSE; | |
1556 | ||
1557 | assert((int)c->type == coal_type); | |
1558 | ||
1559 | coalition_lock(c); | |
1560 | ||
1561 | if (coal) | |
1562 | *coal = c; | |
1563 | ||
1564 | ret = FALSE; | |
1565 | if (c->type == COALITION_TYPE_JETSAM && c->j.leader == task) | |
1566 | ret = TRUE; | |
1567 | ||
1568 | coalition_unlock(c); | |
1569 | ||
1570 | return ret; | |
a1c7dba1 A |
1571 | } |
1572 | ||
3e170ce0 A |
1573 | |
1574 | int coalition_get_task_count(coalition_t coal) | |
a1c7dba1 | 1575 | { |
3e170ce0 A |
1576 | int ntasks = 0; |
1577 | struct queue_entry *qe; | |
1578 | if (!coal) | |
1579 | return 0; | |
1580 | ||
1581 | coalition_lock(coal); | |
1582 | switch (coal->type) { | |
1583 | case COALITION_TYPE_RESOURCE: | |
1584 | qe_foreach(qe, &coal->r.tasks) | |
1585 | ntasks++; | |
1586 | break; | |
1587 | case COALITION_TYPE_JETSAM: | |
1588 | if (coal->j.leader) | |
1589 | ntasks++; | |
1590 | qe_foreach(qe, &coal->j.other) | |
1591 | ntasks++; | |
1592 | qe_foreach(qe, &coal->j.extensions) | |
1593 | ntasks++; | |
1594 | qe_foreach(qe, &coal->j.services) | |
1595 | ntasks++; | |
1596 | break; | |
1597 | default: | |
1598 | break; | |
1599 | } | |
1600 | coalition_unlock(coal); | |
1601 | ||
1602 | return ntasks; | |
a1c7dba1 A |
1603 | } |
1604 | ||
3e170ce0 A |
1605 | |
1606 | static uint64_t i_get_list_footprint(queue_t list, int type, int *ntasks) | |
1607 | { | |
a1c7dba1 | 1608 | task_t task; |
3e170ce0 | 1609 | uint64_t bytes = 0; |
a1c7dba1 | 1610 | |
3e170ce0 A |
1611 | qe_foreach_element(task, list, task_coalition[type]) { |
1612 | bytes += get_task_phys_footprint(task); | |
1613 | coal_dbg(" [%d] task_pid:%d, type:%d, footprint:%lld", | |
1614 | *ntasks, task_pid(task), type, bytes); | |
1615 | *ntasks += 1; | |
1616 | } | |
a1c7dba1 | 1617 | |
3e170ce0 A |
1618 | return bytes; |
1619 | } | |
a1c7dba1 | 1620 | |
3e170ce0 A |
1621 | uint64_t coalition_get_page_count(coalition_t coal, int *ntasks) |
1622 | { | |
1623 | uint64_t bytes = 0; | |
1624 | int num_tasks = 0; | |
1625 | ||
1626 | if (ntasks) | |
1627 | *ntasks = 0; | |
1628 | if (!coal) | |
1629 | return bytes; | |
a1c7dba1 | 1630 | |
3e170ce0 | 1631 | coalition_lock(coal); |
a1c7dba1 | 1632 | |
3e170ce0 A |
1633 | switch (coal->type) { |
1634 | case COALITION_TYPE_RESOURCE: | |
1635 | bytes += i_get_list_footprint(&coal->r.tasks, COALITION_TYPE_RESOURCE, &num_tasks); | |
1636 | break; | |
1637 | case COALITION_TYPE_JETSAM: | |
1638 | if (coal->j.leader) { | |
1639 | bytes += get_task_phys_footprint(coal->j.leader); | |
1640 | num_tasks = 1; | |
a1c7dba1 | 1641 | } |
3e170ce0 A |
1642 | bytes += i_get_list_footprint(&coal->j.extensions, COALITION_TYPE_JETSAM, &num_tasks); |
1643 | bytes += i_get_list_footprint(&coal->j.services, COALITION_TYPE_JETSAM, &num_tasks); | |
1644 | bytes += i_get_list_footprint(&coal->j.other, COALITION_TYPE_JETSAM, &num_tasks); | |
1645 | break; | |
1646 | default: | |
1647 | break; | |
a1c7dba1 | 1648 | } |
3e170ce0 | 1649 | |
a1c7dba1 | 1650 | coalition_unlock(coal); |
3e170ce0 A |
1651 | |
1652 | if (ntasks) | |
1653 | *ntasks = num_tasks; | |
1654 | ||
1655 | return bytes / PAGE_SIZE_64; | |
a1c7dba1 A |
1656 | } |
1657 | ||
3e170ce0 A |
1658 | struct coal_sort_s { |
1659 | int pid; | |
1660 | int usr_order; | |
1661 | uint64_t bytes; | |
1662 | }; | |
1663 | ||
1664 | /* | |
1665 | * return < 0 for a < b | |
1666 | * 0 for a == b | |
1667 | * > 0 for a > b | |
1668 | */ | |
1669 | typedef int (*cmpfunc_t)(const void *a, const void *b); | |
1670 | ||
1671 | extern void | |
1672 | qsort(void *a, size_t n, size_t es, cmpfunc_t cmp); | |
1673 | ||
1674 | static int dflt_cmp(const void *a, const void *b) | |
1675 | { | |
1676 | const struct coal_sort_s *csA = (const struct coal_sort_s *)a; | |
1677 | const struct coal_sort_s *csB = (const struct coal_sort_s *)b; | |
1678 | ||
1679 | /* | |
1680 | * if both A and B are equal, use a memory descending sort | |
1681 | */ | |
1682 | if (csA->usr_order == csB->usr_order) | |
1683 | return (int)((int64_t)csB->bytes - (int64_t)csA->bytes); | |
1684 | ||
1685 | /* otherwise, return the relationship between user specified orders */ | |
1686 | return (csA->usr_order - csB->usr_order); | |
1687 | } | |
1688 | ||
1689 | static int mem_asc_cmp(const void *a, const void *b) | |
1690 | { | |
1691 | const struct coal_sort_s *csA = (const struct coal_sort_s *)a; | |
1692 | const struct coal_sort_s *csB = (const struct coal_sort_s *)b; | |
1693 | ||
1694 | return (int)((int64_t)csA->bytes - (int64_t)csB->bytes); | |
1695 | } | |
1696 | ||
1697 | static int mem_dec_cmp(const void *a, const void *b) | |
1698 | { | |
1699 | const struct coal_sort_s *csA = (const struct coal_sort_s *)a; | |
1700 | const struct coal_sort_s *csB = (const struct coal_sort_s *)b; | |
1701 | ||
1702 | return (int)((int64_t)csB->bytes - (int64_t)csA->bytes); | |
1703 | } | |
1704 | ||
1705 | static int usr_asc_cmp(const void *a, const void *b) | |
1706 | { | |
1707 | const struct coal_sort_s *csA = (const struct coal_sort_s *)a; | |
1708 | const struct coal_sort_s *csB = (const struct coal_sort_s *)b; | |
1709 | ||
1710 | return (csA->usr_order - csB->usr_order); | |
1711 | } | |
1712 | ||
1713 | static int usr_dec_cmp(const void *a, const void *b) | |
1714 | { | |
1715 | const struct coal_sort_s *csA = (const struct coal_sort_s *)a; | |
1716 | const struct coal_sort_s *csB = (const struct coal_sort_s *)b; | |
1717 | ||
1718 | return (csB->usr_order - csA->usr_order); | |
1719 | } | |
1720 | ||
1721 | /* avoid dynamic allocation in this path */ | |
1722 | #define MAX_SORTED_PIDS 80 | |
1723 | ||
1724 | static int coalition_get_sort_list(coalition_t coal, int sort_order, queue_t list, | |
1725 | struct coal_sort_s *sort_array, int array_sz) | |
1726 | { | |
1727 | int ntasks = 0; | |
1728 | task_t task; | |
1729 | ||
1730 | assert(sort_array != NULL); | |
1731 | ||
1732 | if (array_sz <= 0) | |
1733 | return 0; | |
1734 | ||
1735 | if (!list) { | |
1736 | /* | |
1737 | * this function will only be called with a NULL | |
1738 | * list for JETSAM-type coalitions, and is intended | |
1739 | * to investigate the leader process | |
1740 | */ | |
1741 | if (coal->type != COALITION_TYPE_JETSAM || | |
1742 | coal->j.leader == TASK_NULL) | |
1743 | return 0; | |
1744 | sort_array[0].pid = task_pid(coal->j.leader); | |
1745 | switch (sort_order) { | |
1746 | case COALITION_SORT_DEFAULT: | |
1747 | sort_array[0].usr_order = 0; | |
1748 | /* fall-through */ | |
1749 | case COALITION_SORT_MEM_ASC: | |
1750 | case COALITION_SORT_MEM_DEC: | |
1751 | sort_array[0].bytes = get_task_phys_footprint(coal->j.leader); | |
1752 | break; | |
1753 | case COALITION_SORT_USER_ASC: | |
1754 | case COALITION_SORT_USER_DEC: | |
1755 | sort_array[0].usr_order = 0; | |
1756 | break; | |
1757 | default: | |
1758 | break; | |
1759 | } | |
1760 | return 1; | |
1761 | } | |
1762 | ||
1763 | qe_foreach_element(task, list, task_coalition[coal->type]) { | |
1764 | if (ntasks >= array_sz) { | |
1765 | printf("WARNING: more than %d pids in coalition %llu\n", | |
1766 | MAX_SORTED_PIDS, coal->id); | |
1767 | break; | |
1768 | } | |
1769 | ||
1770 | sort_array[ntasks].pid = task_pid(task); | |
1771 | ||
1772 | switch (sort_order) { | |
1773 | case COALITION_SORT_DEFAULT: | |
1774 | sort_array[ntasks].usr_order = 0; | |
1775 | /* fall-through */ | |
1776 | case COALITION_SORT_MEM_ASC: | |
1777 | case COALITION_SORT_MEM_DEC: | |
1778 | sort_array[ntasks].bytes = get_task_phys_footprint(task); | |
1779 | break; | |
1780 | case COALITION_SORT_USER_ASC: | |
1781 | case COALITION_SORT_USER_DEC: | |
1782 | sort_array[ntasks].usr_order = 0; | |
1783 | break; | |
1784 | default: | |
1785 | break; | |
1786 | } | |
1787 | ||
1788 | ntasks++; | |
1789 | } | |
1790 | ||
1791 | return ntasks; | |
1792 | } | |
1793 | ||
1794 | int coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order, | |
1795 | int *pid_list, int list_sz) | |
1796 | { | |
1797 | struct i_jetsam_coalition *cj; | |
1798 | int ntasks = 0; | |
1799 | cmpfunc_t cmp_func = NULL; | |
1800 | struct coal_sort_s sort_array[MAX_SORTED_PIDS] = { {0,0,0} }; /* keep to < 2k */ | |
1801 | ||
1802 | if (!coal || | |
1803 | !(rolemask & COALITION_ROLEMASK_ALLROLES) || | |
1804 | !pid_list || list_sz < 1) { | |
1805 | coal_dbg("Invalid parameters: coal:%p, type:%d, rolemask:0x%x, " | |
1806 | "pid_list:%p, list_sz:%d", coal, coal ? coal->type : -1, | |
1807 | rolemask, pid_list, list_sz); | |
1808 | return -EINVAL; | |
1809 | } | |
1810 | ||
1811 | switch (sort_order) { | |
1812 | case COALITION_SORT_NOSORT: | |
1813 | cmp_func = NULL; | |
1814 | break; | |
1815 | case COALITION_SORT_DEFAULT: | |
1816 | cmp_func = dflt_cmp; | |
1817 | break; | |
1818 | case COALITION_SORT_MEM_ASC: | |
1819 | cmp_func = mem_asc_cmp; | |
1820 | break; | |
1821 | case COALITION_SORT_MEM_DEC: | |
1822 | cmp_func = mem_dec_cmp; | |
1823 | break; | |
1824 | case COALITION_SORT_USER_ASC: | |
1825 | cmp_func = usr_asc_cmp; | |
1826 | break; | |
1827 | case COALITION_SORT_USER_DEC: | |
1828 | cmp_func = usr_dec_cmp; | |
1829 | break; | |
1830 | default: | |
1831 | return -ENOTSUP; | |
1832 | } | |
1833 | ||
1834 | coalition_lock(coal); | |
1835 | ||
1836 | if (coal->type == COALITION_TYPE_RESOURCE) { | |
1837 | ntasks += coalition_get_sort_list(coal, sort_order, &coal->r.tasks, | |
1838 | sort_array, MAX_SORTED_PIDS); | |
1839 | goto unlock_coal; | |
1840 | } | |
1841 | ||
1842 | cj = &coal->j; | |
1843 | ||
1844 | if (rolemask & COALITION_ROLEMASK_UNDEF) | |
1845 | ntasks += coalition_get_sort_list(coal, sort_order, &cj->other, | |
1846 | sort_array + ntasks, | |
1847 | MAX_SORTED_PIDS - ntasks); | |
1848 | ||
1849 | if (rolemask & COALITION_ROLEMASK_XPC) | |
1850 | ntasks += coalition_get_sort_list(coal, sort_order, &cj->services, | |
1851 | sort_array + ntasks, | |
1852 | MAX_SORTED_PIDS - ntasks); | |
1853 | ||
1854 | if (rolemask & COALITION_ROLEMASK_EXT) | |
1855 | ntasks += coalition_get_sort_list(coal, sort_order, &cj->extensions, | |
1856 | sort_array + ntasks, | |
1857 | MAX_SORTED_PIDS - ntasks); | |
1858 | ||
1859 | if (rolemask & COALITION_ROLEMASK_LEADER) | |
1860 | ntasks += coalition_get_sort_list(coal, sort_order, NULL, | |
1861 | sort_array + ntasks, | |
1862 | MAX_SORTED_PIDS - ntasks); | |
1863 | ||
1864 | unlock_coal: | |
1865 | coalition_unlock(coal); | |
1866 | ||
1867 | /* sort based on the chosen criterion (no sense sorting 1 item) */ | |
1868 | if (cmp_func && ntasks > 1) | |
1869 | qsort(sort_array, ntasks, sizeof(struct coal_sort_s), cmp_func); | |
1870 | ||
1871 | for (int i = 0; i < ntasks; i++) { | |
1872 | if (i >= list_sz) | |
1873 | break; | |
1874 | coal_dbg(" [%d] PID:%d, footprint:%lld, usr_order:%d", | |
1875 | i, sort_array[i].pid, sort_array[i].bytes, | |
1876 | sort_array[i].usr_order); | |
1877 | pid_list[i] = sort_array[i].pid; | |
1878 | } | |
1879 | ||
1880 | return ntasks; | |
1881 | } |