]> git.saurik.com Git - apple/xnu.git/blob - osfmk/kern/coalition.c
xnu-7195.81.3.tar.gz
[apple/xnu.git] / osfmk / kern / coalition.c
1 /*
2 * Copyright (c) 2019-2020 Apple 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/exc_resource.h>
35 #include <kern/host.h>
36 #include <kern/ledger.h>
37 #include <kern/mach_param.h> /* for TASK_CHUNK */
38 #if MONOTONIC
39 #include <kern/monotonic.h>
40 #endif /* MONOTONIC */
41 #include <kern/policy_internal.h>
42 #include <kern/task.h>
43 #include <kern/thread_group.h>
44 #include <kern/zalloc.h>
45
46 #include <libkern/OSAtomic.h>
47
48 #include <mach/coalition_notification_server.h>
49 #include <mach/host_priv.h>
50 #include <mach/host_special_ports.h>
51
52 #include <os/log.h>
53
54 #include <sys/errno.h>
55
56 /*
57 * BSD interface functions
58 */
59 int coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, int list_sz);
60 coalition_t task_get_coalition(task_t task, int type);
61 boolean_t coalition_is_leader(task_t task, coalition_t coal);
62 task_t coalition_get_leader(coalition_t coal);
63 int coalition_get_task_count(coalition_t coal);
64 uint64_t coalition_get_page_count(coalition_t coal, int *ntasks);
65 int coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order,
66 int *pid_list, int list_sz);
67
68 /* defined in task.c */
69 extern ledger_template_t task_ledger_template;
70
71 /*
72 * Templates; task template is copied due to potential allocation limits on
73 * task ledgers.
74 */
75 ledger_template_t coalition_task_ledger_template = NULL;
76 ledger_template_t coalition_ledger_template = NULL;
77
78 extern int proc_selfpid(void);
79 /*
80 * Coalition zone needs limits. We expect there will be as many coalitions as
81 * tasks (same order of magnitude), so use the task zone's limits.
82 * */
83 #define CONFIG_COALITION_MAX CONFIG_TASK_MAX
84 #define COALITION_CHUNK TASK_CHUNK
85
86 int unrestrict_coalition_syscalls;
87 int merge_adaptive_coalitions;
88
89 LCK_GRP_DECLARE(coalitions_lck_grp, "coalition");
90
91 /* coalitions_list_lock protects coalition_count, coalitions queue, next_coalition_id. */
92 static LCK_MTX_DECLARE(coalitions_list_lock, &coalitions_lck_grp);
93 static uint64_t coalition_count;
94 static uint64_t coalition_next_id = 1;
95 static queue_head_t coalitions_q;
96
97 coalition_t init_coalition[COALITION_NUM_TYPES];
98 coalition_t corpse_coalition[COALITION_NUM_TYPES];
99
100 static const char *
101 coal_type_str(int type)
102 {
103 switch (type) {
104 case COALITION_TYPE_RESOURCE:
105 return "RESOURCE";
106 case COALITION_TYPE_JETSAM:
107 return "JETSAM";
108 default:
109 return "<unknown>";
110 }
111 }
112
113 struct coalition_type {
114 int type;
115 int has_default;
116 /*
117 * init
118 * pre-condition: coalition just allocated (unlocked), unreferenced,
119 * type field set
120 */
121 kern_return_t (*init)(coalition_t coal, boolean_t privileged);
122
123 /*
124 * dealloc
125 * pre-condition: coalition unlocked
126 * pre-condition: coalition refcount=0, active_count=0,
127 * termrequested=1, terminated=1, reaped=1
128 */
129 void (*dealloc)(coalition_t coal);
130
131 /*
132 * adopt_task
133 * pre-condition: coalition locked
134 * pre-condition: coalition !repead and !terminated
135 */
136 kern_return_t (*adopt_task)(coalition_t coal, task_t task);
137
138 /*
139 * remove_task
140 * pre-condition: coalition locked
141 * pre-condition: task has been removed from coalition's task list
142 */
143 kern_return_t (*remove_task)(coalition_t coal, task_t task);
144
145 /*
146 * set_taskrole
147 * pre-condition: coalition locked
148 * pre-condition: task added to coalition's task list,
149 * active_count >= 1 (at least the given task is active)
150 */
151 kern_return_t (*set_taskrole)(coalition_t coal, task_t task, int role);
152
153 /*
154 * get_taskrole
155 * pre-condition: coalition locked
156 * pre-condition: task added to coalition's task list,
157 * active_count >= 1 (at least the given task is active)
158 */
159 int (*get_taskrole)(coalition_t coal, task_t task);
160
161 /*
162 * iterate_tasks
163 * pre-condition: coalition locked
164 */
165 void (*iterate_tasks)(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t));
166 };
167
168 /*
169 * COALITION_TYPE_RESOURCE
170 */
171
172 static kern_return_t i_coal_resource_init(coalition_t coal, boolean_t privileged);
173 static void i_coal_resource_dealloc(coalition_t coal);
174 static kern_return_t i_coal_resource_adopt_task(coalition_t coal, task_t task);
175 static kern_return_t i_coal_resource_remove_task(coalition_t coal, task_t task);
176 static kern_return_t i_coal_resource_set_taskrole(coalition_t coal,
177 task_t task, int role);
178 static int i_coal_resource_get_taskrole(coalition_t coal, task_t task);
179 static void i_coal_resource_iterate_tasks(coalition_t coal, void *ctx,
180 void (*callback)(coalition_t, void *, task_t));
181
182 /*
183 * Ensure COALITION_NUM_THREAD_QOS_TYPES defined in mach/coalition.h still
184 * matches THREAD_QOS_LAST defined in mach/thread_policy.h
185 */
186 static_assert(COALITION_NUM_THREAD_QOS_TYPES == THREAD_QOS_LAST);
187
188 struct i_resource_coalition {
189 /*
190 * This keeps track of resource utilization of tasks that are no longer active
191 * in the coalition and is updated when a task is removed from the coalition.
192 */
193 ledger_t ledger;
194 uint64_t bytesread;
195 uint64_t byteswritten;
196 uint64_t energy;
197 uint64_t gpu_time;
198 uint64_t logical_immediate_writes;
199 uint64_t logical_deferred_writes;
200 uint64_t logical_invalidated_writes;
201 uint64_t logical_metadata_writes;
202 uint64_t logical_immediate_writes_to_external;
203 uint64_t logical_deferred_writes_to_external;
204 uint64_t logical_invalidated_writes_to_external;
205 uint64_t logical_metadata_writes_to_external;
206 uint64_t cpu_ptime;
207 uint64_t cpu_time_eqos[COALITION_NUM_THREAD_QOS_TYPES]; /* cpu time per effective QoS class */
208 uint64_t cpu_time_rqos[COALITION_NUM_THREAD_QOS_TYPES]; /* cpu time per requested QoS class */
209 uint64_t cpu_instructions;
210 uint64_t cpu_cycles;
211
212 uint64_t task_count; /* tasks that have started in this coalition */
213 uint64_t dead_task_count; /* tasks that have exited in this coalition;
214 * subtract from task_count to get count
215 * of "active" tasks */
216 /*
217 * Count the length of time this coalition had at least one active task.
218 * This can be a 'denominator' to turn e.g. cpu_time to %cpu.
219 * */
220 uint64_t last_became_nonempty_time;
221 uint64_t time_nonempty;
222
223 queue_head_t tasks; /* List of active tasks in the coalition */
224 /*
225 * This ledger is used for triggering resource exception. For the tracked resources, this is updated
226 * when the member tasks' resource usage changes.
227 */
228 ledger_t resource_monitor_ledger;
229 #if CONFIG_PHYS_WRITE_ACCT
230 uint64_t fs_metadata_writes;
231 #endif /* CONFIG_PHYS_WRITE_ACCT */
232 };
233
234 /*
235 * COALITION_TYPE_JETSAM
236 */
237
238 static kern_return_t i_coal_jetsam_init(coalition_t coal, boolean_t privileged);
239 static void i_coal_jetsam_dealloc(coalition_t coal);
240 static kern_return_t i_coal_jetsam_adopt_task(coalition_t coal, task_t task);
241 static kern_return_t i_coal_jetsam_remove_task(coalition_t coal, task_t task);
242 static kern_return_t i_coal_jetsam_set_taskrole(coalition_t coal,
243 task_t task, int role);
244 int i_coal_jetsam_get_taskrole(coalition_t coal, task_t task);
245 static void i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx,
246 void (*callback)(coalition_t, void *, task_t));
247
248 struct i_jetsam_coalition {
249 task_t leader;
250 queue_head_t extensions;
251 queue_head_t services;
252 queue_head_t other;
253 struct thread_group *thread_group;
254 };
255
256
257 /*
258 * main coalition structure
259 */
260 struct coalition {
261 uint64_t id; /* monotonically increasing */
262 uint32_t type;
263 uint32_t role; /* default task role (background, adaptive, interactive, etc) */
264 uint32_t ref_count; /* Number of references to the memory containing this struct */
265 uint32_t active_count; /* Number of members of (tasks in) the
266 * coalition, plus vouchers referring
267 * to the coalition */
268 uint32_t focal_task_count; /* Number of TASK_FOREGROUND_APPLICATION tasks in the coalition */
269 uint32_t nonfocal_task_count; /* Number of TASK_BACKGROUND_APPLICATION tasks in the coalition */
270
271 /* coalition flags */
272 uint32_t privileged : 1; /* Members of this coalition may create
273 * and manage coalitions and may posix_spawn
274 * processes into selected coalitions */
275 /* ast? */
276 /* voucher */
277 uint32_t termrequested : 1; /* launchd has requested termination when coalition becomes empty */
278 uint32_t terminated : 1; /* coalition became empty and spawns are now forbidden */
279 uint32_t reaped : 1; /* reaped, invisible to userspace, but waiting for ref_count to go to zero */
280 uint32_t notified : 1; /* no-more-processes notification was sent via special port */
281 uint32_t efficient : 1; /* launchd has marked the coalition as efficient */
282 #if DEVELOPMENT || DEBUG
283 uint32_t should_notify : 1; /* should this coalition send notifications (default: yes) */
284 #endif
285
286 queue_chain_t coalitions; /* global list of coalitions */
287
288 decl_lck_mtx_data(, lock); /* Coalition lock. */
289
290 /* put coalition type-specific structures here */
291 union {
292 struct i_resource_coalition r;
293 struct i_jetsam_coalition j;
294 };
295 };
296
297 /*
298 * register different coalition types:
299 * these must be kept in the order specified in coalition.h
300 */
301 static const struct coalition_type
302 s_coalition_types[COALITION_NUM_TYPES] = {
303 {
304 COALITION_TYPE_RESOURCE,
305 1,
306 i_coal_resource_init,
307 i_coal_resource_dealloc,
308 i_coal_resource_adopt_task,
309 i_coal_resource_remove_task,
310 i_coal_resource_set_taskrole,
311 i_coal_resource_get_taskrole,
312 i_coal_resource_iterate_tasks,
313 },
314 {
315 COALITION_TYPE_JETSAM,
316 1,
317 i_coal_jetsam_init,
318 i_coal_jetsam_dealloc,
319 i_coal_jetsam_adopt_task,
320 i_coal_jetsam_remove_task,
321 i_coal_jetsam_set_taskrole,
322 i_coal_jetsam_get_taskrole,
323 i_coal_jetsam_iterate_tasks,
324 },
325 };
326
327 ZONE_DECLARE(coalition_zone, "coalitions",
328 sizeof(struct coalition), ZC_NOENCRYPT | ZC_ZFREE_CLEARMEM);
329
330 #define coal_call(coal, func, ...) \
331 (s_coalition_types[(coal)->type].func)(coal, ## __VA_ARGS__)
332
333
334 #define coalition_lock(c) do{ lck_mtx_lock(&c->lock); }while(0)
335 #define coalition_unlock(c) do{ lck_mtx_unlock(&c->lock); }while(0)
336
337 /*
338 * Define the coalition type to track focal tasks.
339 * On embedded, track them using jetsam coalitions since they have associated thread
340 * groups which reflect this property as a flag (and pass it down to CLPC).
341 * On non-embedded platforms, since not all coalitions have jetsam coalitions
342 * track focal counts on the resource coalition.
343 */
344 #if !XNU_TARGET_OS_OSX
345 #define COALITION_FOCAL_TASKS_ACCOUNTING COALITION_TYPE_JETSAM
346 #else /* !XNU_TARGET_OS_OSX */
347 #define COALITION_FOCAL_TASKS_ACCOUNTING COALITION_TYPE_RESOURCE
348 #endif /* !XNU_TARGET_OS_OSX */
349
350
351 /*
352 *
353 * Coalition ledger implementation
354 *
355 */
356
357 struct coalition_ledger_indices coalition_ledgers =
358 {.logical_writes = -1, };
359 void __attribute__((noinline)) SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO(int flavor);
360
361 ledger_t
362 coalition_ledger_get_from_task(task_t task)
363 {
364 ledger_t ledger = LEDGER_NULL;
365 coalition_t coal = task->coalition[COALITION_TYPE_RESOURCE];
366
367 if (coal != NULL && (!queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]))) {
368 ledger = coal->r.resource_monitor_ledger;
369 ledger_reference(ledger);
370 }
371 return ledger;
372 }
373
374
375 enum {
376 COALITION_IO_LEDGER_ENABLE,
377 COALITION_IO_LEDGER_DISABLE
378 };
379
380 void
381 coalition_io_monitor_ctl(struct coalition *coalition, uint32_t flags, int64_t limit)
382 {
383 ledger_t ledger = coalition->r.resource_monitor_ledger;
384
385 if (flags == COALITION_IO_LEDGER_ENABLE) {
386 /* Configure the logical I/O ledger */
387 ledger_set_limit(ledger, coalition_ledgers.logical_writes, (limit * 1024 * 1024), 0);
388 ledger_set_period(ledger, coalition_ledgers.logical_writes, (COALITION_LEDGER_MONITOR_INTERVAL_SECS * NSEC_PER_SEC));
389 } else if (flags == COALITION_IO_LEDGER_DISABLE) {
390 ledger_disable_refill(ledger, coalition_ledgers.logical_writes);
391 ledger_disable_callback(ledger, coalition_ledgers.logical_writes);
392 }
393 }
394
395 int
396 coalition_ledger_set_logical_writes_limit(struct coalition *coalition, int64_t limit)
397 {
398 int error = 0;
399
400 /* limit = -1 will be used to disable the limit and the callback */
401 if (limit > COALITION_MAX_LOGICAL_WRITES_LIMIT || limit == 0 || limit < -1) {
402 error = EINVAL;
403 goto out;
404 }
405
406 coalition_lock(coalition);
407 if (limit == -1) {
408 coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_DISABLE, limit);
409 } else {
410 coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_ENABLE, limit);
411 }
412 coalition_unlock(coalition);
413 out:
414 return error;
415 }
416
417 void __attribute__((noinline))
418 SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO(int flavor)
419 {
420 int pid = proc_selfpid();
421 ledger_amount_t new_limit;
422 task_t task = current_task();
423 struct ledger_entry_info lei;
424 kern_return_t kr;
425 ledger_t ledger;
426 struct coalition *coalition = task->coalition[COALITION_TYPE_RESOURCE];
427
428 assert(coalition != NULL);
429 ledger = coalition->r.resource_monitor_ledger;
430
431 switch (flavor) {
432 case FLAVOR_IO_LOGICAL_WRITES:
433 ledger_get_entry_info(ledger, coalition_ledgers.logical_writes, &lei);
434 trace_resource_violation(RMON_LOGWRITES_VIOLATED, &lei);
435 break;
436 default:
437 goto Exit;
438 }
439
440 os_log(OS_LOG_DEFAULT, "Coalition [%lld] caught causing excessive I/O (flavor: %d). Task I/O: %lld MB. [Limit : %lld MB per %lld secs]. Triggered by process [%d]\n",
441 coalition->id, flavor, (lei.lei_balance / (1024 * 1024)), (lei.lei_limit / (1024 * 1024)),
442 (lei.lei_refill_period / NSEC_PER_SEC), pid);
443
444 kr = send_resource_violation(send_disk_writes_violation, task, &lei, kRNFlagsNone);
445 if (kr) {
446 os_log(OS_LOG_DEFAULT, "ERROR %#x returned from send_resource_violation(disk_writes, ...)\n", kr);
447 }
448
449 /*
450 * Continue to monitor the coalition after it hits the initital limit, but increase
451 * the limit exponentially so that we don't spam the listener.
452 */
453 new_limit = (lei.lei_limit / 1024 / 1024) * 4;
454 coalition_lock(coalition);
455 if (new_limit > COALITION_MAX_LOGICAL_WRITES_LIMIT) {
456 coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_DISABLE, -1);
457 } else {
458 coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_ENABLE, new_limit);
459 }
460 coalition_unlock(coalition);
461
462 Exit:
463 return;
464 }
465
466 void
467 coalition_io_rate_exceeded(int warning, const void *param0, __unused const void *param1)
468 {
469 if (warning == 0) {
470 SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO((int)param0);
471 }
472 }
473
474 void
475 init_coalition_ledgers(void)
476 {
477 ledger_template_t t;
478 assert(coalition_ledger_template == NULL);
479
480 if ((t = ledger_template_create("Per-coalition ledgers")) == NULL) {
481 panic("couldn't create coalition ledger template");
482 }
483
484 coalition_ledgers.logical_writes = ledger_entry_add(t, "logical_writes", "res", "bytes");
485
486 if (coalition_ledgers.logical_writes < 0) {
487 panic("couldn't create entries for coaliton ledger template");
488 }
489
490 ledger_set_callback(t, coalition_ledgers.logical_writes, coalition_io_rate_exceeded, (void *)FLAVOR_IO_LOGICAL_WRITES, NULL);
491 ledger_template_complete(t);
492
493 coalition_task_ledger_template = ledger_template_copy(task_ledger_template, "Coalition task ledgers");
494
495 if (coalition_task_ledger_template == NULL) {
496 panic("couldn't create coalition task ledger template");
497 }
498
499 ledger_template_complete(coalition_task_ledger_template);
500
501 coalition_ledger_template = t;
502 }
503
504 void
505 coalition_io_ledger_update(task_t task, int32_t flavor, boolean_t is_credit, uint32_t io_size)
506 {
507 ledger_t ledger;
508 coalition_t coal = task->coalition[COALITION_TYPE_RESOURCE];
509
510 assert(coal != NULL);
511 ledger = coal->r.resource_monitor_ledger;
512 if (LEDGER_VALID(ledger)) {
513 if (flavor == FLAVOR_IO_LOGICAL_WRITES) {
514 if (is_credit) {
515 ledger_credit(ledger, coalition_ledgers.logical_writes, io_size);
516 } else {
517 ledger_debit(ledger, coalition_ledgers.logical_writes, io_size);
518 }
519 }
520 }
521 }
522
523 static void
524 coalition_notify_user(uint64_t id, uint32_t flags)
525 {
526 mach_port_t user_port;
527 kern_return_t kr;
528
529 kr = host_get_coalition_port(host_priv_self(), &user_port);
530 if ((kr != KERN_SUCCESS) || !IPC_PORT_VALID(user_port)) {
531 return;
532 }
533
534 coalition_notification(user_port, id, flags);
535 ipc_port_release_send(user_port);
536 }
537
538 /*
539 *
540 * COALITION_TYPE_RESOURCE
541 *
542 */
543 static kern_return_t
544 i_coal_resource_init(coalition_t coal, boolean_t privileged)
545 {
546 (void)privileged;
547 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
548 coal->r.ledger = ledger_instantiate(coalition_task_ledger_template,
549 LEDGER_CREATE_ACTIVE_ENTRIES);
550 if (coal->r.ledger == NULL) {
551 return KERN_RESOURCE_SHORTAGE;
552 }
553
554 coal->r.resource_monitor_ledger = ledger_instantiate(coalition_ledger_template,
555 LEDGER_CREATE_ACTIVE_ENTRIES);
556 if (coal->r.resource_monitor_ledger == NULL) {
557 return KERN_RESOURCE_SHORTAGE;
558 }
559
560 queue_init(&coal->r.tasks);
561
562 return KERN_SUCCESS;
563 }
564
565 static void
566 i_coal_resource_dealloc(coalition_t coal)
567 {
568 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
569
570 ledger_dereference(coal->r.ledger);
571 ledger_dereference(coal->r.resource_monitor_ledger);
572 }
573
574 static kern_return_t
575 i_coal_resource_adopt_task(coalition_t coal, task_t task)
576 {
577 struct i_resource_coalition *cr;
578
579 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
580 assert(queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]));
581
582 cr = &coal->r;
583 cr->task_count++;
584
585 if (cr->task_count < cr->dead_task_count) {
586 panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)",
587 __func__, coal, coal->id, coal_type_str(coal->type),
588 cr->task_count, cr->dead_task_count);
589 }
590
591 /* If moving from 0->1 active tasks */
592 if (cr->task_count - cr->dead_task_count == 1) {
593 cr->last_became_nonempty_time = mach_absolute_time();
594 }
595
596 /* put the task on the coalition's list of tasks */
597 enqueue_tail(&cr->tasks, &task->task_coalition[COALITION_TYPE_RESOURCE]);
598
599 coal_dbg("Added PID:%d to id:%llu, task_count:%llu, dead_count:%llu, nonempty_time:%llu",
600 task_pid(task), coal->id, cr->task_count, cr->dead_task_count,
601 cr->last_became_nonempty_time);
602
603 return KERN_SUCCESS;
604 }
605
606 static kern_return_t
607 i_coal_resource_remove_task(coalition_t coal, task_t task)
608 {
609 struct i_resource_coalition *cr;
610
611 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
612 assert(task->coalition[COALITION_TYPE_RESOURCE] == coal);
613 assert(!queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]));
614
615 /*
616 * handle resource coalition accounting rollup for dead tasks
617 */
618 cr = &coal->r;
619
620 cr->dead_task_count++;
621
622 if (cr->task_count < cr->dead_task_count) {
623 panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)",
624 __func__, coal, coal->id, coal_type_str(coal->type), cr->task_count, cr->dead_task_count);
625 }
626
627 /* If moving from 1->0 active tasks */
628 if (cr->task_count - cr->dead_task_count == 0) {
629 uint64_t last_time_nonempty = mach_absolute_time() - cr->last_became_nonempty_time;
630 cr->last_became_nonempty_time = 0;
631 cr->time_nonempty += last_time_nonempty;
632 }
633
634 /* Do not roll up for exec'd task or exec copy task */
635 if (!task_is_exec_copy(task) && !task_did_exec(task)) {
636 ledger_rollup(cr->ledger, task->ledger);
637 cr->bytesread += task->task_io_stats->disk_reads.size;
638 cr->byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size;
639 #if defined(__x86_64__)
640 cr->gpu_time += task_gpu_utilisation(task);
641 #endif /* defined(__x86_64__) */
642
643 #if defined(__arm__) || defined(__arm64__)
644 cr->energy += task_energy(task);
645 #endif /* defined(__arm__) || defined(__arm64__) */
646
647 cr->logical_immediate_writes += task->task_writes_counters_internal.task_immediate_writes;
648 cr->logical_deferred_writes += task->task_writes_counters_internal.task_deferred_writes;
649 cr->logical_invalidated_writes += task->task_writes_counters_internal.task_invalidated_writes;
650 cr->logical_metadata_writes += task->task_writes_counters_internal.task_metadata_writes;
651 cr->logical_immediate_writes_to_external += task->task_writes_counters_external.task_immediate_writes;
652 cr->logical_deferred_writes_to_external += task->task_writes_counters_external.task_deferred_writes;
653 cr->logical_invalidated_writes_to_external += task->task_writes_counters_external.task_invalidated_writes;
654 cr->logical_metadata_writes_to_external += task->task_writes_counters_external.task_metadata_writes;
655 #if CONFIG_PHYS_WRITE_ACCT
656 cr->fs_metadata_writes += task->task_fs_metadata_writes;
657 #endif /* CONFIG_PHYS_WRITE_ACCT */
658 cr->cpu_ptime += task_cpu_ptime(task);
659 task_update_cpu_time_qos_stats(task, cr->cpu_time_eqos, cr->cpu_time_rqos);
660 #if MONOTONIC
661 uint64_t counts[MT_CORE_NFIXED] = {};
662 (void)mt_fixed_task_counts(task, counts);
663 cr->cpu_cycles += counts[MT_CORE_CYCLES];
664 #if defined(MT_CORE_INSTRS)
665 cr->cpu_instructions += counts[MT_CORE_INSTRS];
666 #endif /* defined(MT_CORE_INSTRS) */
667 #endif /* MONOTONIC */
668 }
669
670 /* remove the task from the coalition's list */
671 remqueue(&task->task_coalition[COALITION_TYPE_RESOURCE]);
672 queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]);
673
674 coal_dbg("removed PID:%d from id:%llu, task_count:%llu, dead_count:%llu",
675 task_pid(task), coal->id, cr->task_count, cr->dead_task_count);
676
677 return KERN_SUCCESS;
678 }
679
680 static kern_return_t
681 i_coal_resource_set_taskrole(__unused coalition_t coal,
682 __unused task_t task, __unused int role)
683 {
684 return KERN_SUCCESS;
685 }
686
687 static int
688 i_coal_resource_get_taskrole(__unused coalition_t coal, __unused task_t task)
689 {
690 task_t t;
691
692 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
693
694 qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) {
695 if (t == task) {
696 return COALITION_TASKROLE_UNDEF;
697 }
698 }
699
700 return -1;
701 }
702
703 static void
704 i_coal_resource_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t))
705 {
706 task_t t;
707 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
708
709 qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE])
710 callback(coal, ctx, t);
711 }
712
713 #if CONFIG_PHYS_WRITE_ACCT
714 extern uint64_t kernel_pm_writes;
715 #endif /* CONFIG_PHYS_WRITE_ACCT */
716
717 kern_return_t
718 coalition_resource_usage_internal(coalition_t coal, struct coalition_resource_usage *cru_out)
719 {
720 kern_return_t kr;
721 ledger_amount_t credit, debit;
722 int i;
723
724 if (coal->type != COALITION_TYPE_RESOURCE) {
725 return KERN_INVALID_ARGUMENT;
726 }
727
728 /* Return KERN_INVALID_ARGUMENT for Corpse coalition */
729 for (i = 0; i < COALITION_NUM_TYPES; i++) {
730 if (coal == corpse_coalition[i]) {
731 return KERN_INVALID_ARGUMENT;
732 }
733 }
734
735 ledger_t sum_ledger = ledger_instantiate(coalition_task_ledger_template, LEDGER_CREATE_ACTIVE_ENTRIES);
736 if (sum_ledger == LEDGER_NULL) {
737 return KERN_RESOURCE_SHORTAGE;
738 }
739
740 coalition_lock(coal);
741
742 /*
743 * Start with the coalition's ledger, which holds the totals from all
744 * the dead tasks.
745 */
746 ledger_rollup(sum_ledger, coal->r.ledger);
747 uint64_t bytesread = coal->r.bytesread;
748 uint64_t byteswritten = coal->r.byteswritten;
749 uint64_t gpu_time = coal->r.gpu_time;
750 uint64_t energy = coal->r.energy;
751 uint64_t logical_immediate_writes = coal->r.logical_immediate_writes;
752 uint64_t logical_deferred_writes = coal->r.logical_deferred_writes;
753 uint64_t logical_invalidated_writes = coal->r.logical_invalidated_writes;
754 uint64_t logical_metadata_writes = coal->r.logical_metadata_writes;
755 uint64_t logical_immediate_writes_to_external = coal->r.logical_immediate_writes_to_external;
756 uint64_t logical_deferred_writes_to_external = coal->r.logical_deferred_writes_to_external;
757 uint64_t logical_invalidated_writes_to_external = coal->r.logical_invalidated_writes_to_external;
758 uint64_t logical_metadata_writes_to_external = coal->r.logical_metadata_writes_to_external;
759 #if CONFIG_PHYS_WRITE_ACCT
760 uint64_t fs_metadata_writes = coal->r.fs_metadata_writes;
761 #endif /* CONFIG_PHYS_WRITE_ACCT */
762 int64_t cpu_time_billed_to_me = 0;
763 int64_t cpu_time_billed_to_others = 0;
764 int64_t energy_billed_to_me = 0;
765 int64_t energy_billed_to_others = 0;
766 uint64_t cpu_ptime = coal->r.cpu_ptime;
767 uint64_t cpu_time_eqos[COALITION_NUM_THREAD_QOS_TYPES];
768 memcpy(cpu_time_eqos, coal->r.cpu_time_eqos, sizeof(cpu_time_eqos));
769 uint64_t cpu_time_rqos[COALITION_NUM_THREAD_QOS_TYPES];
770 memcpy(cpu_time_rqos, coal->r.cpu_time_rqos, sizeof(cpu_time_rqos));
771 uint64_t cpu_instructions = coal->r.cpu_instructions;
772 uint64_t cpu_cycles = coal->r.cpu_cycles;
773
774 /*
775 * Add to that all the active tasks' ledgers. Tasks cannot deallocate
776 * out from under us, since we hold the coalition lock.
777 */
778 task_t task;
779 qe_foreach_element(task, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) {
780 /*
781 * Rolling up stats for exec copy task or exec'd task will lead to double accounting.
782 * Cannot take task lock after taking coaliton lock
783 */
784 if (task_is_exec_copy(task) || task_did_exec(task)) {
785 continue;
786 }
787
788 ledger_rollup(sum_ledger, task->ledger);
789 bytesread += task->task_io_stats->disk_reads.size;
790 byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size;
791 #if defined(__x86_64__)
792 gpu_time += task_gpu_utilisation(task);
793 #endif /* defined(__x86_64__) */
794
795 #if defined(__arm__) || defined(__arm64__)
796 energy += task_energy(task);
797 #endif /* defined(__arm__) || defined(__arm64__) */
798
799 logical_immediate_writes += task->task_writes_counters_internal.task_immediate_writes;
800 logical_deferred_writes += task->task_writes_counters_internal.task_deferred_writes;
801 logical_invalidated_writes += task->task_writes_counters_internal.task_invalidated_writes;
802 logical_metadata_writes += task->task_writes_counters_internal.task_metadata_writes;
803 logical_immediate_writes_to_external += task->task_writes_counters_external.task_immediate_writes;
804 logical_deferred_writes_to_external += task->task_writes_counters_external.task_deferred_writes;
805 logical_invalidated_writes_to_external += task->task_writes_counters_external.task_invalidated_writes;
806 logical_metadata_writes_to_external += task->task_writes_counters_external.task_metadata_writes;
807 #if CONFIG_PHYS_WRITE_ACCT
808 fs_metadata_writes += task->task_fs_metadata_writes;
809 #endif /* CONFIG_PHYS_WRITE_ACCT */
810
811 cpu_ptime += task_cpu_ptime(task);
812 task_update_cpu_time_qos_stats(task, cpu_time_eqos, cpu_time_rqos);
813 #if MONOTONIC
814 uint64_t counts[MT_CORE_NFIXED] = {};
815 (void)mt_fixed_task_counts(task, counts);
816 cpu_cycles += counts[MT_CORE_CYCLES];
817 #if defined(MT_CORE_INSTRS)
818 cpu_instructions += counts[MT_CORE_INSTRS];
819 #endif /* defined(MT_CORE_INSTRS) */
820 #endif /* MONOTONIC */
821 }
822
823 kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_me, (int64_t *)&cpu_time_billed_to_me);
824 if (kr != KERN_SUCCESS || cpu_time_billed_to_me < 0) {
825 cpu_time_billed_to_me = 0;
826 }
827
828 kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_others, (int64_t *)&cpu_time_billed_to_others);
829 if (kr != KERN_SUCCESS || cpu_time_billed_to_others < 0) {
830 cpu_time_billed_to_others = 0;
831 }
832
833 kr = ledger_get_balance(sum_ledger, task_ledgers.energy_billed_to_me, (int64_t *)&energy_billed_to_me);
834 if (kr != KERN_SUCCESS || energy_billed_to_me < 0) {
835 energy_billed_to_me = 0;
836 }
837
838 kr = ledger_get_balance(sum_ledger, task_ledgers.energy_billed_to_others, (int64_t *)&energy_billed_to_others);
839 if (kr != KERN_SUCCESS || energy_billed_to_others < 0) {
840 energy_billed_to_others = 0;
841 }
842
843 /* collect information from the coalition itself */
844 cru_out->tasks_started = coal->r.task_count;
845 cru_out->tasks_exited = coal->r.dead_task_count;
846
847 uint64_t time_nonempty = coal->r.time_nonempty;
848 uint64_t last_became_nonempty_time = coal->r.last_became_nonempty_time;
849
850 coalition_unlock(coal);
851
852 /* Copy the totals out of sum_ledger */
853 kr = ledger_get_entries(sum_ledger, task_ledgers.cpu_time,
854 &credit, &debit);
855 if (kr != KERN_SUCCESS) {
856 credit = 0;
857 }
858 cru_out->cpu_time = credit;
859 cru_out->cpu_time_billed_to_me = (uint64_t)cpu_time_billed_to_me;
860 cru_out->cpu_time_billed_to_others = (uint64_t)cpu_time_billed_to_others;
861 cru_out->energy_billed_to_me = (uint64_t)energy_billed_to_me;
862 cru_out->energy_billed_to_others = (uint64_t)energy_billed_to_others;
863
864 kr = ledger_get_entries(sum_ledger, task_ledgers.interrupt_wakeups,
865 &credit, &debit);
866 if (kr != KERN_SUCCESS) {
867 credit = 0;
868 }
869 cru_out->interrupt_wakeups = credit;
870
871 kr = ledger_get_entries(sum_ledger, task_ledgers.platform_idle_wakeups,
872 &credit, &debit);
873 if (kr != KERN_SUCCESS) {
874 credit = 0;
875 }
876 cru_out->platform_idle_wakeups = credit;
877
878 cru_out->bytesread = bytesread;
879 cru_out->byteswritten = byteswritten;
880 cru_out->gpu_time = gpu_time;
881 cru_out->energy = energy;
882 cru_out->logical_immediate_writes = logical_immediate_writes;
883 cru_out->logical_deferred_writes = logical_deferred_writes;
884 cru_out->logical_invalidated_writes = logical_invalidated_writes;
885 cru_out->logical_metadata_writes = logical_metadata_writes;
886 cru_out->logical_immediate_writes_to_external = logical_immediate_writes_to_external;
887 cru_out->logical_deferred_writes_to_external = logical_deferred_writes_to_external;
888 cru_out->logical_invalidated_writes_to_external = logical_invalidated_writes_to_external;
889 cru_out->logical_metadata_writes_to_external = logical_metadata_writes_to_external;
890 #if CONFIG_PHYS_WRITE_ACCT
891 cru_out->fs_metadata_writes = fs_metadata_writes;
892 #else
893 cru_out->fs_metadata_writes = 0;
894 #endif /* CONFIG_PHYS_WRITE_ACCT */
895 cru_out->cpu_ptime = cpu_ptime;
896 cru_out->cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES;
897 memcpy(cru_out->cpu_time_eqos, cpu_time_eqos, sizeof(cru_out->cpu_time_eqos));
898 cru_out->cpu_cycles = cpu_cycles;
899 cru_out->cpu_instructions = cpu_instructions;
900 ledger_dereference(sum_ledger);
901 sum_ledger = LEDGER_NULL;
902
903 #if CONFIG_PHYS_WRITE_ACCT
904 // kernel_pm_writes are only recorded under kernel_task coalition
905 if (coalition_id(coal) == COALITION_ID_KERNEL) {
906 cru_out->pm_writes = kernel_pm_writes;
907 } else {
908 cru_out->pm_writes = 0;
909 }
910 #else
911 cru_out->pm_writes = 0;
912 #endif /* CONFIG_PHYS_WRITE_ACCT */
913
914 if (last_became_nonempty_time) {
915 time_nonempty += mach_absolute_time() - last_became_nonempty_time;
916 }
917 absolutetime_to_nanoseconds(time_nonempty, &cru_out->time_nonempty);
918
919 return KERN_SUCCESS;
920 }
921
922 /*
923 *
924 * COALITION_TYPE_JETSAM
925 *
926 */
927 static kern_return_t
928 i_coal_jetsam_init(coalition_t coal, boolean_t privileged)
929 {
930 assert(coal && coal->type == COALITION_TYPE_JETSAM);
931 (void)privileged;
932
933 coal->j.leader = TASK_NULL;
934 queue_head_init(coal->j.extensions);
935 queue_head_init(coal->j.services);
936 queue_head_init(coal->j.other);
937
938 #if CONFIG_THREAD_GROUPS
939 switch (coal->role) {
940 case COALITION_ROLE_SYSTEM:
941 coal->j.thread_group = thread_group_find_by_id_and_retain(THREAD_GROUP_SYSTEM);
942 break;
943 case COALITION_ROLE_BACKGROUND:
944 coal->j.thread_group = thread_group_find_by_id_and_retain(THREAD_GROUP_BACKGROUND);
945 break;
946 case COALITION_ROLE_ADAPTIVE:
947 if (merge_adaptive_coalitions) {
948 coal->j.thread_group = thread_group_find_by_id_and_retain(THREAD_GROUP_ADAPTIVE);
949 } else {
950 coal->j.thread_group = thread_group_create_and_retain();
951 }
952 break;
953 default:
954 coal->j.thread_group = thread_group_create_and_retain();
955 }
956 assert(coal->j.thread_group != NULL);
957 #endif
958 return KERN_SUCCESS;
959 }
960
961 static void
962 i_coal_jetsam_dealloc(__unused coalition_t coal)
963 {
964 assert(coal && coal->type == COALITION_TYPE_JETSAM);
965
966 /* the coalition should be completely clear at this point */
967 assert(queue_empty(&coal->j.extensions));
968 assert(queue_empty(&coal->j.services));
969 assert(queue_empty(&coal->j.other));
970 assert(coal->j.leader == TASK_NULL);
971
972 #if CONFIG_THREAD_GROUPS
973 /* disassociate from the thread group */
974 assert(coal->j.thread_group != NULL);
975 thread_group_release(coal->j.thread_group);
976 coal->j.thread_group = NULL;
977 #endif
978 }
979
980 static kern_return_t
981 i_coal_jetsam_adopt_task(coalition_t coal, task_t task)
982 {
983 struct i_jetsam_coalition *cj;
984 assert(coal && coal->type == COALITION_TYPE_JETSAM);
985
986 cj = &coal->j;
987
988 assert(queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM]));
989
990 /* put each task initially in the "other" list */
991 enqueue_tail(&cj->other, &task->task_coalition[COALITION_TYPE_JETSAM]);
992 coal_dbg("coalition %lld adopted PID:%d as UNDEF",
993 coal->id, task_pid(task));
994
995 return KERN_SUCCESS;
996 }
997
998 static kern_return_t
999 i_coal_jetsam_remove_task(coalition_t coal, task_t task)
1000 {
1001 assert(coal && coal->type == COALITION_TYPE_JETSAM);
1002 assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
1003
1004 coal_dbg("removing PID:%d from coalition id:%lld",
1005 task_pid(task), coal->id);
1006
1007 if (task == coal->j.leader) {
1008 coal->j.leader = NULL;
1009 coal_dbg(" PID:%d was the leader!", task_pid(task));
1010 } else {
1011 assert(!queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM]));
1012 }
1013
1014 /* remove the task from the specific coalition role queue */
1015 remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]);
1016 queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]);
1017
1018 return KERN_SUCCESS;
1019 }
1020
1021 static kern_return_t
1022 i_coal_jetsam_set_taskrole(coalition_t coal, task_t task, int role)
1023 {
1024 struct i_jetsam_coalition *cj;
1025 queue_t q = NULL;
1026 assert(coal && coal->type == COALITION_TYPE_JETSAM);
1027 assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
1028
1029 cj = &coal->j;
1030
1031 switch (role) {
1032 case COALITION_TASKROLE_LEADER:
1033 coal_dbg("setting PID:%d as LEADER of %lld",
1034 task_pid(task), coal->id);
1035 if (cj->leader != TASK_NULL) {
1036 /* re-queue the exiting leader onto the "other" list */
1037 coal_dbg(" re-queue existing leader (%d) as OTHER",
1038 task_pid(cj->leader));
1039 re_queue_tail(&cj->other, &cj->leader->task_coalition[COALITION_TYPE_JETSAM]);
1040 }
1041 /*
1042 * remove the task from the "other" list
1043 * (where it was put by default)
1044 */
1045 remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]);
1046 queue_chain_init(task->task_coalition[COALITION_TYPE_JETSAM]);
1047
1048 /* set the coalition leader */
1049 cj->leader = task;
1050 break;
1051 case COALITION_TASKROLE_XPC:
1052 coal_dbg("setting PID:%d as XPC in %lld",
1053 task_pid(task), coal->id);
1054 q = (queue_t)&cj->services;
1055 break;
1056 case COALITION_TASKROLE_EXT:
1057 coal_dbg("setting PID:%d as EXT in %lld",
1058 task_pid(task), coal->id);
1059 q = (queue_t)&cj->extensions;
1060 break;
1061 case COALITION_TASKROLE_NONE:
1062 /*
1063 * Tasks with a role of "none" should fall through to an
1064 * undefined role so long as the task is currently a member
1065 * of the coalition. This scenario can happen if a task is
1066 * killed (usually via jetsam) during exec.
1067 */
1068 if (task->coalition[COALITION_TYPE_JETSAM] != coal) {
1069 panic("%s: task %p attempting to set role %d "
1070 "in coalition %p to which it does not belong!", __func__, task, role, coal);
1071 }
1072 OS_FALLTHROUGH;
1073 case COALITION_TASKROLE_UNDEF:
1074 coal_dbg("setting PID:%d as UNDEF in %lld",
1075 task_pid(task), coal->id);
1076 q = (queue_t)&cj->other;
1077 break;
1078 default:
1079 panic("%s: invalid role(%d) for task", __func__, role);
1080 return KERN_INVALID_ARGUMENT;
1081 }
1082
1083 if (q != NULL) {
1084 re_queue_tail(q, &task->task_coalition[COALITION_TYPE_JETSAM]);
1085 }
1086
1087 return KERN_SUCCESS;
1088 }
1089
1090 int
1091 i_coal_jetsam_get_taskrole(coalition_t coal, task_t task)
1092 {
1093 struct i_jetsam_coalition *cj;
1094 task_t t;
1095
1096 assert(coal && coal->type == COALITION_TYPE_JETSAM);
1097 assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
1098
1099 cj = &coal->j;
1100
1101 if (task == cj->leader) {
1102 return COALITION_TASKROLE_LEADER;
1103 }
1104
1105 qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM]) {
1106 if (t == task) {
1107 return COALITION_TASKROLE_XPC;
1108 }
1109 }
1110
1111 qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM]) {
1112 if (t == task) {
1113 return COALITION_TASKROLE_EXT;
1114 }
1115 }
1116
1117 qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM]) {
1118 if (t == task) {
1119 return COALITION_TASKROLE_UNDEF;
1120 }
1121 }
1122
1123 /* task not in the coalition?! */
1124 return COALITION_TASKROLE_NONE;
1125 }
1126
1127 static void
1128 i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t))
1129 {
1130 struct i_jetsam_coalition *cj;
1131 task_t t;
1132
1133 assert(coal && coal->type == COALITION_TYPE_JETSAM);
1134
1135 cj = &coal->j;
1136
1137 if (cj->leader) {
1138 callback(coal, ctx, cj->leader);
1139 }
1140
1141 qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM])
1142 callback(coal, ctx, t);
1143
1144 qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM])
1145 callback(coal, ctx, t);
1146
1147 qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM])
1148 callback(coal, ctx, t);
1149 }
1150
1151
1152 /*
1153 *
1154 * Main Coalition implementation
1155 *
1156 */
1157
1158 /*
1159 * coalition_create_internal
1160 * Returns: New coalition object, referenced for the caller and unlocked.
1161 * Condition: coalitions_list_lock must be UNLOCKED.
1162 */
1163 kern_return_t
1164 coalition_create_internal(int type, int role, boolean_t privileged, coalition_t *out, uint64_t *coalition_id)
1165 {
1166 kern_return_t kr;
1167 struct coalition *new_coal;
1168 uint64_t cid;
1169 uint32_t ctype;
1170
1171 if (type < 0 || type > COALITION_TYPE_MAX) {
1172 return KERN_INVALID_ARGUMENT;
1173 }
1174
1175 new_coal = (struct coalition *)zalloc(coalition_zone);
1176 if (new_coal == COALITION_NULL) {
1177 return KERN_RESOURCE_SHORTAGE;
1178 }
1179 bzero(new_coal, sizeof(*new_coal));
1180
1181 new_coal->type = type;
1182 new_coal->role = role;
1183
1184 /* initialize type-specific resources */
1185 kr = coal_call(new_coal, init, privileged);
1186 if (kr != KERN_SUCCESS) {
1187 zfree(coalition_zone, new_coal);
1188 return kr;
1189 }
1190
1191 /* One for caller, one for coalitions list */
1192 new_coal->ref_count = 2;
1193
1194 new_coal->privileged = privileged ? TRUE : FALSE;
1195 #if DEVELOPMENT || DEBUG
1196 new_coal->should_notify = 1;
1197 #endif
1198
1199 lck_mtx_init(&new_coal->lock, &coalitions_lck_grp, LCK_ATTR_NULL);
1200
1201 lck_mtx_lock(&coalitions_list_lock);
1202 new_coal->id = coalition_next_id++;
1203 coalition_count++;
1204 enqueue_tail(&coalitions_q, &new_coal->coalitions);
1205
1206 #if CONFIG_THREAD_GROUPS
1207 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_NEW),
1208 new_coal->id, new_coal->type,
1209 (new_coal->type == COALITION_TYPE_JETSAM && new_coal->j.thread_group) ?
1210 thread_group_get_id(new_coal->j.thread_group) : 0);
1211
1212 #else
1213 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_NEW),
1214 new_coal->id, new_coal->type);
1215 #endif
1216 cid = new_coal->id;
1217 ctype = new_coal->type;
1218 lck_mtx_unlock(&coalitions_list_lock);
1219
1220 coal_dbg("id:%llu, type:%s", cid, coal_type_str(ctype));
1221
1222 if (coalition_id != NULL) {
1223 *coalition_id = cid;
1224 }
1225
1226 *out = new_coal;
1227 return KERN_SUCCESS;
1228 }
1229
1230 /*
1231 * coalition_release
1232 * Condition: coalition must be UNLOCKED.
1233 * */
1234 void
1235 coalition_release(coalition_t coal)
1236 {
1237 /* TODO: This can be done with atomics. */
1238 coalition_lock(coal);
1239 coal->ref_count--;
1240
1241 #if COALITION_DEBUG
1242 uint32_t rc = coal->ref_count;
1243 uint32_t ac = coal->active_count;
1244 #endif /* COALITION_DEBUG */
1245
1246 coal_dbg("id:%llu type:%s ref_count:%u active_count:%u%s",
1247 coal->id, coal_type_str(coal->type), rc, ac,
1248 rc <= 0 ? ", will deallocate now" : "");
1249
1250 if (coal->ref_count > 0) {
1251 coalition_unlock(coal);
1252 return;
1253 }
1254
1255 assert(coal->termrequested);
1256 assert(coal->terminated);
1257 assert(coal->active_count == 0);
1258 assert(coal->reaped);
1259 assert(coal->focal_task_count == 0);
1260 assert(coal->nonfocal_task_count == 0);
1261 #if CONFIG_THREAD_GROUPS
1262 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_FREE),
1263 coal->id, coal->type,
1264 coal->type == COALITION_TYPE_JETSAM ?
1265 coal->j.thread_group : 0);
1266 #else
1267 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_FREE),
1268 coal->id, coal->type);
1269 #endif
1270
1271 coal_call(coal, dealloc);
1272
1273 coalition_unlock(coal);
1274
1275 lck_mtx_destroy(&coal->lock, &coalitions_lck_grp);
1276
1277 zfree(coalition_zone, coal);
1278 }
1279
1280 /*
1281 * coalition_find_by_id_internal
1282 * Returns: Coalition object with specified id, NOT referenced.
1283 * If not found, returns COALITION_NULL.
1284 * Condition: coalitions_list_lock must be LOCKED.
1285 */
1286 static coalition_t
1287 coalition_find_by_id_internal(uint64_t coal_id)
1288 {
1289 if (coal_id == 0) {
1290 return COALITION_NULL;
1291 }
1292
1293 lck_mtx_assert(&coalitions_list_lock, LCK_MTX_ASSERT_OWNED);
1294 coalition_t coal;
1295 qe_foreach_element(coal, &coalitions_q, coalitions) {
1296 if (coal->id == coal_id) {
1297 return coal;
1298 }
1299 }
1300 return COALITION_NULL;
1301 }
1302
1303 /*
1304 * coalition_find_by_id
1305 * Returns: Coalition object with specified id, referenced.
1306 * Condition: coalitions_list_lock must be UNLOCKED.
1307 */
1308 coalition_t
1309 coalition_find_by_id(uint64_t cid)
1310 {
1311 if (cid == 0) {
1312 return COALITION_NULL;
1313 }
1314
1315 lck_mtx_lock(&coalitions_list_lock);
1316
1317 coalition_t coal = coalition_find_by_id_internal(cid);
1318 if (coal == COALITION_NULL) {
1319 lck_mtx_unlock(&coalitions_list_lock);
1320 return COALITION_NULL;
1321 }
1322
1323 coalition_lock(coal);
1324
1325 if (coal->reaped) {
1326 coalition_unlock(coal);
1327 lck_mtx_unlock(&coalitions_list_lock);
1328 return COALITION_NULL;
1329 }
1330
1331 if (coal->ref_count == 0) {
1332 panic("resurrecting coalition %p id:%llu type:%s, active_count:%u\n",
1333 coal, coal->id, coal_type_str(coal->type), coal->active_count);
1334 }
1335 coal->ref_count++;
1336 #if COALITION_DEBUG
1337 uint32_t rc = coal->ref_count;
1338 #endif
1339
1340 coalition_unlock(coal);
1341 lck_mtx_unlock(&coalitions_list_lock);
1342
1343 coal_dbg("id:%llu type:%s ref_count:%u",
1344 coal->id, coal_type_str(coal->type), rc);
1345
1346 return coal;
1347 }
1348
1349 /*
1350 * coalition_find_and_activate_by_id
1351 * Returns: Coalition object with specified id, referenced, and activated.
1352 * Condition: coalitions_list_lock must be UNLOCKED.
1353 * This is the function to use when putting a 'new' thing into a coalition,
1354 * like posix_spawn of an XPC service by launchd.
1355 * See also coalition_extend_active.
1356 */
1357 coalition_t
1358 coalition_find_and_activate_by_id(uint64_t cid)
1359 {
1360 if (cid == 0) {
1361 return COALITION_NULL;
1362 }
1363
1364 lck_mtx_lock(&coalitions_list_lock);
1365
1366 coalition_t coal = coalition_find_by_id_internal(cid);
1367 if (coal == COALITION_NULL) {
1368 lck_mtx_unlock(&coalitions_list_lock);
1369 return COALITION_NULL;
1370 }
1371
1372 coalition_lock(coal);
1373
1374 if (coal->reaped || coal->terminated) {
1375 /* Too late to put something new into this coalition, it's
1376 * already on its way out the door */
1377 coalition_unlock(coal);
1378 lck_mtx_unlock(&coalitions_list_lock);
1379 return COALITION_NULL;
1380 }
1381
1382 if (coal->ref_count == 0) {
1383 panic("resurrecting coalition %p id:%llu type:%s, active_count:%u\n",
1384 coal, coal->id, coal_type_str(coal->type), coal->active_count);
1385 }
1386
1387 coal->ref_count++;
1388 coal->active_count++;
1389
1390 #if COALITION_DEBUG
1391 uint32_t rc = coal->ref_count;
1392 uint32_t ac = coal->active_count;
1393 #endif
1394
1395 coalition_unlock(coal);
1396 lck_mtx_unlock(&coalitions_list_lock);
1397
1398 coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u",
1399 coal->id, coal_type_str(coal->type), rc, ac);
1400
1401 return coal;
1402 }
1403
1404 uint64_t
1405 coalition_id(coalition_t coal)
1406 {
1407 assert(coal != COALITION_NULL);
1408 return coal->id;
1409 }
1410
1411 void
1412 task_coalition_ids(task_t task, uint64_t ids[COALITION_NUM_TYPES])
1413 {
1414 int i;
1415 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1416 if (task->coalition[i]) {
1417 ids[i] = task->coalition[i]->id;
1418 } else {
1419 ids[i] = 0;
1420 }
1421 }
1422 }
1423
1424 void
1425 task_coalition_roles(task_t task, int roles[COALITION_NUM_TYPES])
1426 {
1427 int i;
1428 memset(roles, 0, COALITION_NUM_TYPES * sizeof(roles[0]));
1429
1430 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1431 if (task->coalition[i]) {
1432 coalition_lock(task->coalition[i]);
1433 roles[i] = coal_call(task->coalition[i],
1434 get_taskrole, task);
1435 coalition_unlock(task->coalition[i]);
1436 } else {
1437 roles[i] = COALITION_TASKROLE_NONE;
1438 }
1439 }
1440 }
1441
1442
1443 int
1444 coalition_type(coalition_t coal)
1445 {
1446 return coal->type;
1447 }
1448
1449 boolean_t
1450 coalition_term_requested(coalition_t coal)
1451 {
1452 return coal->termrequested;
1453 }
1454
1455 boolean_t
1456 coalition_is_terminated(coalition_t coal)
1457 {
1458 return coal->terminated;
1459 }
1460
1461 boolean_t
1462 coalition_is_reaped(coalition_t coal)
1463 {
1464 return coal->reaped;
1465 }
1466
1467 boolean_t
1468 coalition_is_privileged(coalition_t coal)
1469 {
1470 return coal->privileged || unrestrict_coalition_syscalls;
1471 }
1472
1473 boolean_t
1474 task_is_in_privileged_coalition(task_t task, int type)
1475 {
1476 if (type < 0 || type > COALITION_TYPE_MAX) {
1477 return FALSE;
1478 }
1479 if (unrestrict_coalition_syscalls) {
1480 return TRUE;
1481 }
1482 if (!task->coalition[type]) {
1483 return FALSE;
1484 }
1485 return task->coalition[type]->privileged;
1486 }
1487
1488 void
1489 task_coalition_update_gpu_stats(task_t task, uint64_t gpu_ns_delta)
1490 {
1491 coalition_t coal;
1492
1493 assert(task != TASK_NULL);
1494 if (gpu_ns_delta == 0) {
1495 return;
1496 }
1497
1498 coal = task->coalition[COALITION_TYPE_RESOURCE];
1499 assert(coal != COALITION_NULL);
1500
1501 coalition_lock(coal);
1502 coal->r.gpu_time += gpu_ns_delta;
1503 coalition_unlock(coal);
1504 }
1505
1506 boolean_t
1507 task_coalition_adjust_focal_count(task_t task, int count, uint32_t *new_count)
1508 {
1509 coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1510 if (coal == COALITION_NULL) {
1511 return FALSE;
1512 }
1513
1514 *new_count = os_atomic_add(&coal->focal_task_count, count, relaxed);
1515 assert(*new_count != UINT32_MAX);
1516 return TRUE;
1517 }
1518
1519 uint32_t
1520 task_coalition_focal_count(task_t task)
1521 {
1522 coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1523 if (coal == COALITION_NULL) {
1524 return 0;
1525 }
1526
1527 return coal->focal_task_count;
1528 }
1529
1530 boolean_t
1531 task_coalition_adjust_nonfocal_count(task_t task, int count, uint32_t *new_count)
1532 {
1533 coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1534 if (coal == COALITION_NULL) {
1535 return FALSE;
1536 }
1537
1538 *new_count = os_atomic_add(&coal->nonfocal_task_count, count, relaxed);
1539 assert(*new_count != UINT32_MAX);
1540 return TRUE;
1541 }
1542
1543 uint32_t
1544 task_coalition_nonfocal_count(task_t task)
1545 {
1546 coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1547 if (coal == COALITION_NULL) {
1548 return 0;
1549 }
1550
1551 return coal->nonfocal_task_count;
1552 }
1553
1554 void
1555 coalition_set_efficient(coalition_t coal)
1556 {
1557 coalition_lock(coal);
1558 coal->efficient = TRUE;
1559 coalition_unlock(coal);
1560 }
1561
1562 #if CONFIG_THREAD_GROUPS
1563 struct thread_group *
1564 task_coalition_get_thread_group(task_t task)
1565 {
1566 coalition_t coal = task->coalition[COALITION_TYPE_JETSAM];
1567 /* return system thread group for non-jetsam coalitions */
1568 if (coal == COALITION_NULL) {
1569 return init_coalition[COALITION_TYPE_JETSAM]->j.thread_group;
1570 }
1571 return coal->j.thread_group;
1572 }
1573
1574
1575 struct thread_group *
1576 kdp_coalition_get_thread_group(coalition_t coal)
1577 {
1578 if (coal->type != COALITION_TYPE_JETSAM) {
1579 return NULL;
1580 }
1581 assert(coal->j.thread_group != NULL);
1582 return coal->j.thread_group;
1583 }
1584
1585 struct thread_group *
1586 coalition_get_thread_group(coalition_t coal)
1587 {
1588 if (coal->type != COALITION_TYPE_JETSAM) {
1589 return NULL;
1590 }
1591 assert(coal->j.thread_group != NULL);
1592 return thread_group_retain(coal->j.thread_group);
1593 }
1594
1595 void
1596 coalition_set_thread_group(coalition_t coal, struct thread_group *tg)
1597 {
1598 assert(coal != COALITION_NULL);
1599 assert(tg != NULL);
1600
1601 if (coal->type != COALITION_TYPE_JETSAM) {
1602 return;
1603 }
1604 struct thread_group *old_tg = coal->j.thread_group;
1605 assert(old_tg != NULL);
1606 coal->j.thread_group = tg;
1607
1608 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_THREAD_GROUP_SET),
1609 coal->id, coal->type, thread_group_get_id(tg));
1610
1611 thread_group_release(old_tg);
1612 }
1613
1614 void
1615 task_coalition_thread_group_focal_update(task_t task)
1616 {
1617 assert(task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING] != COALITION_NULL);
1618 thread_group_flags_update_lock();
1619 uint32_t focal_count = task_coalition_focal_count(task);
1620 if (focal_count) {
1621 thread_group_set_flags_locked(task_coalition_get_thread_group(task), THREAD_GROUP_FLAGS_UI_APP);
1622 } else {
1623 thread_group_clear_flags_locked(task_coalition_get_thread_group(task), THREAD_GROUP_FLAGS_UI_APP);
1624 }
1625 thread_group_flags_update_unlock();
1626 }
1627
1628 #endif
1629
1630 void
1631 coalition_for_each_task(coalition_t coal, void *ctx,
1632 void (*callback)(coalition_t, void *, task_t))
1633 {
1634 assert(coal != COALITION_NULL);
1635
1636 coal_dbg("iterating tasks in coalition %p id:%llu type:%s, active_count:%u",
1637 coal, coal->id, coal_type_str(coal->type), coal->active_count);
1638
1639 coalition_lock(coal);
1640
1641 coal_call(coal, iterate_tasks, ctx, callback);
1642
1643 coalition_unlock(coal);
1644 }
1645
1646
1647 void
1648 coalition_remove_active(coalition_t coal)
1649 {
1650 coalition_lock(coal);
1651
1652 assert(!coal->reaped);
1653 assert(coal->active_count > 0);
1654
1655 coal->active_count--;
1656
1657 boolean_t do_notify = FALSE;
1658 uint64_t notify_id = 0;
1659 uint32_t notify_flags = 0;
1660 if (coal->termrequested && coal->active_count == 0) {
1661 /* We only notify once, when active_count reaches zero.
1662 * We just decremented, so if it reached zero, we mustn't have
1663 * notified already.
1664 */
1665 assert(!coal->terminated);
1666 coal->terminated = TRUE;
1667
1668 assert(!coal->notified);
1669
1670 coal->notified = TRUE;
1671 #if DEVELOPMENT || DEBUG
1672 do_notify = coal->should_notify;
1673 #else
1674 do_notify = TRUE;
1675 #endif
1676 notify_id = coal->id;
1677 notify_flags = 0;
1678 }
1679
1680 #if COALITION_DEBUG
1681 uint64_t cid = coal->id;
1682 uint32_t rc = coal->ref_count;
1683 int ac = coal->active_count;
1684 int ct = coal->type;
1685 #endif
1686 coalition_unlock(coal);
1687
1688 coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u,%s",
1689 cid, coal_type_str(ct), rc, ac, do_notify ? " NOTIFY" : " ");
1690
1691 if (do_notify) {
1692 coalition_notify_user(notify_id, notify_flags);
1693 }
1694 }
1695
1696 /* Used for kernel_task, launchd, launchd's early boot tasks... */
1697 kern_return_t
1698 coalitions_adopt_init_task(task_t task)
1699 {
1700 kern_return_t kr;
1701 kr = coalitions_adopt_task(init_coalition, task);
1702 if (kr != KERN_SUCCESS) {
1703 panic("failed to adopt task %p into default coalition: %d", task, kr);
1704 }
1705 return kr;
1706 }
1707
1708 /* Used for forked corpses. */
1709 kern_return_t
1710 coalitions_adopt_corpse_task(task_t task)
1711 {
1712 kern_return_t kr;
1713 kr = coalitions_adopt_task(corpse_coalition, task);
1714 if (kr != KERN_SUCCESS) {
1715 panic("failed to adopt task %p into corpse coalition: %d", task, kr);
1716 }
1717 return kr;
1718 }
1719
1720 /*
1721 * coalition_adopt_task_internal
1722 * Condition: Coalition must be referenced and unlocked. Will fail if coalition
1723 * is already terminated.
1724 */
1725 static kern_return_t
1726 coalition_adopt_task_internal(coalition_t coal, task_t task)
1727 {
1728 kern_return_t kr;
1729
1730 if (task->coalition[coal->type]) {
1731 return KERN_ALREADY_IN_SET;
1732 }
1733
1734 coalition_lock(coal);
1735
1736 if (coal->reaped || coal->terminated) {
1737 coalition_unlock(coal);
1738 return KERN_TERMINATED;
1739 }
1740
1741 kr = coal_call(coal, adopt_task, task);
1742 if (kr != KERN_SUCCESS) {
1743 goto out_unlock;
1744 }
1745
1746 coal->active_count++;
1747
1748 coal->ref_count++;
1749
1750 task->coalition[coal->type] = coal;
1751
1752 out_unlock:
1753 #if COALITION_DEBUG
1754 (void)coal; /* need expression after label */
1755 uint64_t cid = coal->id;
1756 uint32_t rc = coal->ref_count;
1757 uint32_t ct = coal->type;
1758 #endif
1759 if (get_task_uniqueid(task) != UINT64_MAX) {
1760 /* On 32-bit targets, uniqueid will get truncated to 32 bits */
1761 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_ADOPT),
1762 coal->id, get_task_uniqueid(task));
1763 }
1764
1765 coalition_unlock(coal);
1766
1767 coal_dbg("task:%d, id:%llu type:%s ref_count:%u, kr=%d",
1768 task_pid(task), cid, coal_type_str(ct), rc, kr);
1769 return kr;
1770 }
1771
1772 static kern_return_t
1773 coalition_remove_task_internal(task_t task, int type)
1774 {
1775 kern_return_t kr;
1776
1777 coalition_t coal = task->coalition[type];
1778
1779 if (!coal) {
1780 return KERN_SUCCESS;
1781 }
1782
1783 assert(coal->type == (uint32_t)type);
1784
1785 coalition_lock(coal);
1786
1787 kr = coal_call(coal, remove_task, task);
1788
1789 #if COALITION_DEBUG
1790 uint64_t cid = coal->id;
1791 uint32_t rc = coal->ref_count;
1792 int ac = coal->active_count;
1793 int ct = coal->type;
1794 #endif
1795 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_REMOVE),
1796 coal->id, get_task_uniqueid(task));
1797 coalition_unlock(coal);
1798
1799 coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u, kr=%d",
1800 cid, coal_type_str(ct), rc, ac, kr);
1801
1802 coalition_remove_active(coal);
1803
1804 return kr;
1805 }
1806
1807 /*
1808 * coalitions_adopt_task
1809 * Condition: All coalitions must be referenced and unlocked.
1810 * Will fail if any coalition is already terminated.
1811 */
1812 kern_return_t
1813 coalitions_adopt_task(coalition_t *coals, task_t task)
1814 {
1815 int i;
1816 kern_return_t kr;
1817
1818 if (!coals || coals[COALITION_TYPE_RESOURCE] == COALITION_NULL) {
1819 return KERN_INVALID_ARGUMENT;
1820 }
1821
1822 /* verify that the incoming coalitions are what they say they are */
1823 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1824 if (coals[i] && coals[i]->type != (uint32_t)i) {
1825 return KERN_INVALID_ARGUMENT;
1826 }
1827 }
1828
1829 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1830 kr = KERN_SUCCESS;
1831 if (coals[i]) {
1832 kr = coalition_adopt_task_internal(coals[i], task);
1833 }
1834 if (kr != KERN_SUCCESS) {
1835 /* dis-associate any coalitions that just adopted this task */
1836 while (--i >= 0) {
1837 if (task->coalition[i]) {
1838 coalition_remove_task_internal(task, i);
1839 }
1840 }
1841 break;
1842 }
1843 }
1844 return kr;
1845 }
1846
1847 /*
1848 * coalitions_remove_task
1849 * Condition: task must be referenced and UNLOCKED; all task's coalitions must be UNLOCKED
1850 */
1851 kern_return_t
1852 coalitions_remove_task(task_t task)
1853 {
1854 kern_return_t kr;
1855 int i;
1856
1857 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1858 kr = coalition_remove_task_internal(task, i);
1859 assert(kr == KERN_SUCCESS);
1860 }
1861
1862 return kr;
1863 }
1864
1865 /*
1866 * task_release_coalitions
1867 * helper function to release references to all coalitions in which
1868 * 'task' is a member.
1869 */
1870 void
1871 task_release_coalitions(task_t task)
1872 {
1873 int i;
1874 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1875 if (task->coalition[i]) {
1876 coalition_release(task->coalition[i]);
1877 } else if (i == COALITION_TYPE_RESOURCE) {
1878 panic("deallocating task %p was not a member of a resource coalition", task);
1879 }
1880 }
1881 }
1882
1883 /*
1884 * coalitions_set_roles
1885 * for each type of coalition, if the task is a member of a coalition of
1886 * that type (given in the coalitions parameter) then set the role of
1887 * the task within that that coalition.
1888 */
1889 kern_return_t
1890 coalitions_set_roles(coalition_t coalitions[COALITION_NUM_TYPES],
1891 task_t task, int roles[COALITION_NUM_TYPES])
1892 {
1893 kern_return_t kr = KERN_SUCCESS;
1894 int i;
1895
1896 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1897 if (!coalitions[i]) {
1898 continue;
1899 }
1900 coalition_lock(coalitions[i]);
1901 kr = coal_call(coalitions[i], set_taskrole, task, roles[i]);
1902 coalition_unlock(coalitions[i]);
1903 assert(kr == KERN_SUCCESS);
1904 }
1905
1906 return kr;
1907 }
1908
1909 /*
1910 * coalition_terminate_internal
1911 * Condition: Coalition must be referenced and UNLOCKED.
1912 */
1913 kern_return_t
1914 coalition_request_terminate_internal(coalition_t coal)
1915 {
1916 assert(coal->type >= 0 && coal->type <= COALITION_TYPE_MAX);
1917
1918 if (coal == init_coalition[coal->type]) {
1919 return KERN_DEFAULT_SET;
1920 }
1921
1922 coalition_lock(coal);
1923
1924 if (coal->reaped) {
1925 coalition_unlock(coal);
1926 return KERN_INVALID_NAME;
1927 }
1928
1929 if (coal->terminated || coal->termrequested) {
1930 coalition_unlock(coal);
1931 return KERN_TERMINATED;
1932 }
1933
1934 coal->termrequested = TRUE;
1935
1936 boolean_t do_notify = FALSE;
1937 uint64_t note_id = 0;
1938 uint32_t note_flags = 0;
1939
1940 if (coal->active_count == 0) {
1941 /*
1942 * We only notify once, when active_count reaches zero.
1943 * We just set termrequested to zero. If the active count
1944 * was already at zero (tasks died before we could request
1945 * a termination notification), we should notify.
1946 */
1947 assert(!coal->terminated);
1948 coal->terminated = TRUE;
1949
1950 assert(!coal->notified);
1951
1952 coal->notified = TRUE;
1953 #if DEVELOPMENT || DEBUG
1954 do_notify = coal->should_notify;
1955 #else
1956 do_notify = TRUE;
1957 #endif
1958 note_id = coal->id;
1959 note_flags = 0;
1960 }
1961
1962 coalition_unlock(coal);
1963
1964 if (do_notify) {
1965 coalition_notify_user(note_id, note_flags);
1966 }
1967
1968 return KERN_SUCCESS;
1969 }
1970
1971 /*
1972 * coalition_reap_internal
1973 * Condition: Coalition must be referenced and UNLOCKED.
1974 */
1975 kern_return_t
1976 coalition_reap_internal(coalition_t coal)
1977 {
1978 assert(coal->type <= COALITION_TYPE_MAX);
1979
1980 if (coal == init_coalition[coal->type]) {
1981 return KERN_DEFAULT_SET;
1982 }
1983
1984 coalition_lock(coal);
1985 if (coal->reaped) {
1986 coalition_unlock(coal);
1987 return KERN_TERMINATED;
1988 }
1989 if (!coal->terminated) {
1990 coalition_unlock(coal);
1991 return KERN_FAILURE;
1992 }
1993 assert(coal->termrequested);
1994 if (coal->active_count > 0) {
1995 coalition_unlock(coal);
1996 return KERN_FAILURE;
1997 }
1998
1999 coal->reaped = TRUE;
2000
2001 /* Caller, launchd, and coalitions list should each have a reference */
2002 assert(coal->ref_count > 2);
2003
2004 coalition_unlock(coal);
2005
2006 lck_mtx_lock(&coalitions_list_lock);
2007 coalition_count--;
2008 remqueue(&coal->coalitions);
2009 lck_mtx_unlock(&coalitions_list_lock);
2010
2011 /* Release the list's reference and launchd's reference. */
2012 coalition_release(coal);
2013 coalition_release(coal);
2014
2015 return KERN_SUCCESS;
2016 }
2017
2018 #if DEVELOPMENT || DEBUG
2019 int
2020 coalition_should_notify(coalition_t coal)
2021 {
2022 int should;
2023 if (!coal) {
2024 return -1;
2025 }
2026 coalition_lock(coal);
2027 should = coal->should_notify;
2028 coalition_unlock(coal);
2029
2030 return should;
2031 }
2032
2033 void
2034 coalition_set_notify(coalition_t coal, int notify)
2035 {
2036 if (!coal) {
2037 return;
2038 }
2039 coalition_lock(coal);
2040 coal->should_notify = !!notify;
2041 coalition_unlock(coal);
2042 }
2043 #endif
2044
2045 void
2046 coalitions_init(void)
2047 {
2048 kern_return_t kr;
2049 int i;
2050 const struct coalition_type *ctype;
2051
2052 queue_head_init(coalitions_q);
2053
2054 if (!PE_parse_boot_argn("unrestrict_coalition_syscalls", &unrestrict_coalition_syscalls,
2055 sizeof(unrestrict_coalition_syscalls))) {
2056 unrestrict_coalition_syscalls = 0;
2057 }
2058
2059 if (!PE_parse_boot_argn("tg_adaptive", &merge_adaptive_coalitions,
2060 sizeof(merge_adaptive_coalitions))) {
2061 merge_adaptive_coalitions = 0;
2062 }
2063
2064 init_task_ledgers();
2065
2066 init_coalition_ledgers();
2067
2068 for (i = 0, ctype = &s_coalition_types[0]; i < COALITION_NUM_TYPES; ctype++, i++) {
2069 /* verify the entry in the global coalition types array */
2070 if (ctype->type != i ||
2071 !ctype->init ||
2072 !ctype->dealloc ||
2073 !ctype->adopt_task ||
2074 !ctype->remove_task) {
2075 panic("%s: Malformed coalition type %s(%d) in slot for type:%s(%d)",
2076 __func__, coal_type_str(ctype->type), ctype->type, coal_type_str(i), i);
2077 }
2078 if (!ctype->has_default) {
2079 continue;
2080 }
2081 kr = coalition_create_internal(ctype->type, COALITION_ROLE_SYSTEM, TRUE, &init_coalition[ctype->type], NULL);
2082 if (kr != KERN_SUCCESS) {
2083 panic("%s: could not create init %s coalition: kr:%d",
2084 __func__, coal_type_str(i), kr);
2085 }
2086 if (i == COALITION_TYPE_RESOURCE) {
2087 assert(COALITION_ID_KERNEL == init_coalition[ctype->type]->id);
2088 }
2089 kr = coalition_create_internal(ctype->type, COALITION_ROLE_SYSTEM, FALSE, &corpse_coalition[ctype->type], NULL);
2090 if (kr != KERN_SUCCESS) {
2091 panic("%s: could not create corpse %s coalition: kr:%d",
2092 __func__, coal_type_str(i), kr);
2093 }
2094 }
2095
2096 /* "Leak" our reference to the global object */
2097 }
2098
2099 /*
2100 * BSD Kernel interface functions
2101 *
2102 */
2103 static void
2104 coalition_fill_procinfo(struct coalition *coal,
2105 struct procinfo_coalinfo *coalinfo)
2106 {
2107 coalinfo->coalition_id = coal->id;
2108 coalinfo->coalition_type = coal->type;
2109 coalinfo->coalition_tasks = coalition_get_task_count(coal);
2110 }
2111
2112
2113 int
2114 coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, int list_sz)
2115 {
2116 int ncoals = 0;
2117 struct coalition *coal;
2118
2119 lck_mtx_lock(&coalitions_list_lock);
2120 qe_foreach_element(coal, &coalitions_q, coalitions) {
2121 if (!coal->reaped && (type < 0 || type == (int)coal->type)) {
2122 if (coal_list && ncoals < list_sz) {
2123 coalition_fill_procinfo(coal, &coal_list[ncoals]);
2124 }
2125 ++ncoals;
2126 }
2127 }
2128 lck_mtx_unlock(&coalitions_list_lock);
2129
2130 return ncoals;
2131 }
2132
2133 /*
2134 * Return the coaltion of the given type to which the task belongs.
2135 */
2136 coalition_t
2137 task_get_coalition(task_t task, int coal_type)
2138 {
2139 coalition_t c;
2140
2141 if (task == NULL || coal_type > COALITION_TYPE_MAX) {
2142 return COALITION_NULL;
2143 }
2144
2145 c = task->coalition[coal_type];
2146 assert(c == COALITION_NULL || (int)c->type == coal_type);
2147 return c;
2148 }
2149
2150 /*
2151 * Report if the given task is the leader of the given jetsam coalition.
2152 */
2153 boolean_t
2154 coalition_is_leader(task_t task, coalition_t coal)
2155 {
2156 boolean_t ret = FALSE;
2157
2158 if (coal != COALITION_NULL) {
2159 coalition_lock(coal);
2160
2161 ret = (coal->type == COALITION_TYPE_JETSAM && coal->j.leader == task);
2162
2163 coalition_unlock(coal);
2164 }
2165
2166 return ret;
2167 }
2168
2169 kern_return_t
2170 coalition_iterate_stackshot(coalition_iterate_fn_t callout, void *arg, uint32_t coalition_type)
2171 {
2172 coalition_t coal;
2173 int i = 0;
2174
2175 qe_foreach_element(coal, &coalitions_q, coalitions) {
2176 if (coal == NULL || !ml_validate_nofault((vm_offset_t)coal, sizeof(struct coalition))) {
2177 return KERN_FAILURE;
2178 }
2179
2180 if (coalition_type == coal->type) {
2181 callout(arg, i++, coal);
2182 }
2183 }
2184
2185 return KERN_SUCCESS;
2186 }
2187
2188 task_t
2189 kdp_coalition_get_leader(coalition_t coal)
2190 {
2191 if (!coal) {
2192 return TASK_NULL;
2193 }
2194
2195 if (coal->type == COALITION_TYPE_JETSAM) {
2196 return coal->j.leader;
2197 }
2198 return TASK_NULL;
2199 }
2200
2201 task_t
2202 coalition_get_leader(coalition_t coal)
2203 {
2204 task_t leader = TASK_NULL;
2205
2206 if (!coal) {
2207 return TASK_NULL;
2208 }
2209
2210 coalition_lock(coal);
2211 if (coal->type != COALITION_TYPE_JETSAM) {
2212 goto out_unlock;
2213 }
2214
2215 leader = coal->j.leader;
2216 if (leader != TASK_NULL) {
2217 task_reference(leader);
2218 }
2219
2220 out_unlock:
2221 coalition_unlock(coal);
2222 return leader;
2223 }
2224
2225
2226 int
2227 coalition_get_task_count(coalition_t coal)
2228 {
2229 int ntasks = 0;
2230 struct queue_entry *qe;
2231 if (!coal) {
2232 return 0;
2233 }
2234
2235 coalition_lock(coal);
2236 switch (coal->type) {
2237 case COALITION_TYPE_RESOURCE:
2238 qe_foreach(qe, &coal->r.tasks)
2239 ntasks++;
2240 break;
2241 case COALITION_TYPE_JETSAM:
2242 if (coal->j.leader) {
2243 ntasks++;
2244 }
2245 qe_foreach(qe, &coal->j.other)
2246 ntasks++;
2247 qe_foreach(qe, &coal->j.extensions)
2248 ntasks++;
2249 qe_foreach(qe, &coal->j.services)
2250 ntasks++;
2251 break;
2252 default:
2253 break;
2254 }
2255 coalition_unlock(coal);
2256
2257 return ntasks;
2258 }
2259
2260
2261 static uint64_t
2262 i_get_list_footprint(queue_t list, int type, int *ntasks)
2263 {
2264 task_t task;
2265 uint64_t bytes = 0;
2266
2267 qe_foreach_element(task, list, task_coalition[type]) {
2268 bytes += get_task_phys_footprint(task);
2269 coal_dbg(" [%d] task_pid:%d, type:%d, footprint:%lld",
2270 *ntasks, task_pid(task), type, bytes);
2271 *ntasks += 1;
2272 }
2273
2274 return bytes;
2275 }
2276
2277 uint64_t
2278 coalition_get_page_count(coalition_t coal, int *ntasks)
2279 {
2280 uint64_t bytes = 0;
2281 int num_tasks = 0;
2282
2283 if (ntasks) {
2284 *ntasks = 0;
2285 }
2286 if (!coal) {
2287 return bytes;
2288 }
2289
2290 coalition_lock(coal);
2291
2292 switch (coal->type) {
2293 case COALITION_TYPE_RESOURCE:
2294 bytes += i_get_list_footprint(&coal->r.tasks, COALITION_TYPE_RESOURCE, &num_tasks);
2295 break;
2296 case COALITION_TYPE_JETSAM:
2297 if (coal->j.leader) {
2298 bytes += get_task_phys_footprint(coal->j.leader);
2299 num_tasks = 1;
2300 }
2301 bytes += i_get_list_footprint(&coal->j.extensions, COALITION_TYPE_JETSAM, &num_tasks);
2302 bytes += i_get_list_footprint(&coal->j.services, COALITION_TYPE_JETSAM, &num_tasks);
2303 bytes += i_get_list_footprint(&coal->j.other, COALITION_TYPE_JETSAM, &num_tasks);
2304 break;
2305 default:
2306 break;
2307 }
2308
2309 coalition_unlock(coal);
2310
2311 if (ntasks) {
2312 *ntasks = num_tasks;
2313 }
2314
2315 return bytes / PAGE_SIZE_64;
2316 }
2317
2318 struct coal_sort_s {
2319 int pid;
2320 int usr_order;
2321 uint64_t bytes;
2322 };
2323
2324 /*
2325 * return < 0 for a < b
2326 * 0 for a == b
2327 * > 0 for a > b
2328 */
2329 typedef int (*cmpfunc_t)(const void *a, const void *b);
2330
2331 extern void
2332 qsort(void *a, size_t n, size_t es, cmpfunc_t cmp);
2333
2334 static int
2335 dflt_cmp(const void *a, const void *b)
2336 {
2337 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2338 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2339
2340 /*
2341 * if both A and B are equal, use a memory descending sort
2342 */
2343 if (csA->usr_order == csB->usr_order) {
2344 return (int)((int64_t)csB->bytes - (int64_t)csA->bytes);
2345 }
2346
2347 /* otherwise, return the relationship between user specified orders */
2348 return csA->usr_order - csB->usr_order;
2349 }
2350
2351 static int
2352 mem_asc_cmp(const void *a, const void *b)
2353 {
2354 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2355 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2356
2357 return (int)((int64_t)csA->bytes - (int64_t)csB->bytes);
2358 }
2359
2360 static int
2361 mem_dec_cmp(const void *a, const void *b)
2362 {
2363 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2364 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2365
2366 return (int)((int64_t)csB->bytes - (int64_t)csA->bytes);
2367 }
2368
2369 static int
2370 usr_asc_cmp(const void *a, const void *b)
2371 {
2372 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2373 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2374
2375 return csA->usr_order - csB->usr_order;
2376 }
2377
2378 static int
2379 usr_dec_cmp(const void *a, const void *b)
2380 {
2381 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2382 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2383
2384 return csB->usr_order - csA->usr_order;
2385 }
2386
2387 /* avoid dynamic allocation in this path */
2388 #define MAX_SORTED_PIDS 80
2389
2390 static int
2391 coalition_get_sort_list(coalition_t coal, int sort_order, queue_t list,
2392 struct coal_sort_s *sort_array, int array_sz)
2393 {
2394 int ntasks = 0;
2395 task_t task;
2396
2397 assert(sort_array != NULL);
2398
2399 if (array_sz <= 0) {
2400 return 0;
2401 }
2402
2403 if (!list) {
2404 /*
2405 * this function will only be called with a NULL
2406 * list for JETSAM-type coalitions, and is intended
2407 * to investigate the leader process
2408 */
2409 if (coal->type != COALITION_TYPE_JETSAM ||
2410 coal->j.leader == TASK_NULL) {
2411 return 0;
2412 }
2413 sort_array[0].pid = task_pid(coal->j.leader);
2414 switch (sort_order) {
2415 case COALITION_SORT_DEFAULT:
2416 sort_array[0].usr_order = 0;
2417 OS_FALLTHROUGH;
2418 case COALITION_SORT_MEM_ASC:
2419 case COALITION_SORT_MEM_DEC:
2420 sort_array[0].bytes = get_task_phys_footprint(coal->j.leader);
2421 break;
2422 case COALITION_SORT_USER_ASC:
2423 case COALITION_SORT_USER_DEC:
2424 sort_array[0].usr_order = 0;
2425 break;
2426 default:
2427 break;
2428 }
2429 return 1;
2430 }
2431
2432 qe_foreach_element(task, list, task_coalition[coal->type]) {
2433 if (ntasks >= array_sz) {
2434 printf("WARNING: more than %d pids in coalition %llu\n",
2435 MAX_SORTED_PIDS, coal->id);
2436 break;
2437 }
2438
2439 sort_array[ntasks].pid = task_pid(task);
2440
2441 switch (sort_order) {
2442 case COALITION_SORT_DEFAULT:
2443 sort_array[ntasks].usr_order = 0;
2444 OS_FALLTHROUGH;
2445 case COALITION_SORT_MEM_ASC:
2446 case COALITION_SORT_MEM_DEC:
2447 sort_array[ntasks].bytes = get_task_phys_footprint(task);
2448 break;
2449 case COALITION_SORT_USER_ASC:
2450 case COALITION_SORT_USER_DEC:
2451 sort_array[ntasks].usr_order = 0;
2452 break;
2453 default:
2454 break;
2455 }
2456
2457 ntasks++;
2458 }
2459
2460 return ntasks;
2461 }
2462
2463 int
2464 coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order,
2465 int *pid_list, int list_sz)
2466 {
2467 struct i_jetsam_coalition *cj;
2468 int ntasks = 0;
2469 cmpfunc_t cmp_func = NULL;
2470 struct coal_sort_s sort_array[MAX_SORTED_PIDS] = { {0, 0, 0} }; /* keep to < 2k */
2471
2472 if (!coal ||
2473 !(rolemask & COALITION_ROLEMASK_ALLROLES) ||
2474 !pid_list || list_sz < 1) {
2475 coal_dbg("Invalid parameters: coal:%p, type:%d, rolemask:0x%x, "
2476 "pid_list:%p, list_sz:%d", coal, coal ? coal->type : -1,
2477 rolemask, pid_list, list_sz);
2478 return -EINVAL;
2479 }
2480
2481 switch (sort_order) {
2482 case COALITION_SORT_NOSORT:
2483 cmp_func = NULL;
2484 break;
2485 case COALITION_SORT_DEFAULT:
2486 cmp_func = dflt_cmp;
2487 break;
2488 case COALITION_SORT_MEM_ASC:
2489 cmp_func = mem_asc_cmp;
2490 break;
2491 case COALITION_SORT_MEM_DEC:
2492 cmp_func = mem_dec_cmp;
2493 break;
2494 case COALITION_SORT_USER_ASC:
2495 cmp_func = usr_asc_cmp;
2496 break;
2497 case COALITION_SORT_USER_DEC:
2498 cmp_func = usr_dec_cmp;
2499 break;
2500 default:
2501 return -ENOTSUP;
2502 }
2503
2504 coalition_lock(coal);
2505
2506 if (coal->type == COALITION_TYPE_RESOURCE) {
2507 ntasks += coalition_get_sort_list(coal, sort_order, &coal->r.tasks,
2508 sort_array, MAX_SORTED_PIDS);
2509 goto unlock_coal;
2510 }
2511
2512 cj = &coal->j;
2513
2514 if (rolemask & COALITION_ROLEMASK_UNDEF) {
2515 ntasks += coalition_get_sort_list(coal, sort_order, &cj->other,
2516 sort_array + ntasks,
2517 MAX_SORTED_PIDS - ntasks);
2518 }
2519
2520 if (rolemask & COALITION_ROLEMASK_XPC) {
2521 ntasks += coalition_get_sort_list(coal, sort_order, &cj->services,
2522 sort_array + ntasks,
2523 MAX_SORTED_PIDS - ntasks);
2524 }
2525
2526 if (rolemask & COALITION_ROLEMASK_EXT) {
2527 ntasks += coalition_get_sort_list(coal, sort_order, &cj->extensions,
2528 sort_array + ntasks,
2529 MAX_SORTED_PIDS - ntasks);
2530 }
2531
2532 if (rolemask & COALITION_ROLEMASK_LEADER) {
2533 ntasks += coalition_get_sort_list(coal, sort_order, NULL,
2534 sort_array + ntasks,
2535 MAX_SORTED_PIDS - ntasks);
2536 }
2537
2538 unlock_coal:
2539 coalition_unlock(coal);
2540
2541 /* sort based on the chosen criterion (no sense sorting 1 item) */
2542 if (cmp_func && ntasks > 1) {
2543 qsort(sort_array, ntasks, sizeof(struct coal_sort_s), cmp_func);
2544 }
2545
2546 for (int i = 0; i < ntasks; i++) {
2547 if (i >= list_sz) {
2548 break;
2549 }
2550 coal_dbg(" [%d] PID:%d, footprint:%lld, usr_order:%d",
2551 i, sort_array[i].pid, sort_array[i].bytes,
2552 sort_array[i].usr_order);
2553 pid_list[i] = sort_array[i].pid;
2554 }
2555
2556 return ntasks;
2557 }