2 * Copyright (c) 2013 Apple Computer, Inc. All rights reserved.
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
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.
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
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.
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
29 #include <kern/kern_types.h>
30 #include <mach/mach_types.h>
31 #include <mach/boolean.h>
33 #include <kern/coalition.h>
34 #include <kern/host.h>
35 #include <kern/ledger.h>
36 #include <kern/kalloc.h>
37 #include <kern/mach_param.h> /* for TASK_CHUNK */
38 #include <kern/task.h>
39 #include <kern/zalloc.h>
42 #include <libkern/OSAtomic.h>
44 #include <mach/coalition_notification_server.h>
45 #include <mach/host_priv.h>
46 #include <mach/host_special_ports.h>
48 #include <sys/errno.h>
50 /* defined in task.c */
51 extern ledger_template_t task_ledger_template
;
54 * Coalition zone needs limits. We expect there will be as many coalitions as
55 * tasks (same order of magnitude), so use the task zone's limits.
57 #define CONFIG_COALITION_MAX CONFIG_TASK_MAX
58 #define COALITION_CHUNK TASK_CHUNK
60 int unrestrict_coalition_syscalls
;
62 lck_attr_t coalitions_lck_attr
;
63 lck_grp_t coalitions_lck_grp
;
64 lck_grp_attr_t coalitions_lck_grp_attr
;
66 /* coalitions_list_lock protects coalition_count, coalitions queue, next_coalition_id. */
67 decl_lck_mtx_data(static,coalitions_list_lock
);
68 static uint64_t coalition_count
;
69 static uint64_t coalition_next_id
= 1;
70 static queue_head_t coalitions
;
72 coalition_t default_coalition
;
74 zone_t coalition_zone
;
77 uint64_t id
; /* monotonically increasing */
81 uint64_t byteswritten
;
85 * Count the length of time this coalition had at least one active task.
86 * This can be a 'denominator' to turn e.g. cpu_time to %cpu.
88 uint64_t last_became_nonempty_time
;
89 uint64_t time_nonempty
;
91 uint64_t task_count
; /* Count of tasks that have started in this coalition */
92 uint64_t dead_task_count
; /* Count of tasks that have exited in this coalition; subtract from task_count to get count of "active" */
93 queue_head_t tasks
; /* List of active tasks in the coalition */
95 queue_chain_t coalitions
; /* global list of coalitions */
97 decl_lck_mtx_data(,lock
) /* Coalition lock. */
99 uint32_t ref_count
; /* Number of references to the memory containing this struct */
100 uint32_t active_count
; /* Number of members of (tasks in) the coalition, plus vouchers referring to the coalition */
102 unsigned int privileged
: 1; /* Members of this coalition may create and manage coalitions and may posix_spawn processes into selected coalitions */
108 /* state of the coalition */
109 unsigned int termrequested
: 1; /* launchd has requested termination when coalition becomes empty */
110 unsigned int terminated
: 1; /* coalition became empty and spawns are now forbidden */
111 unsigned int reaped
: 1; /* reaped, invisible to userspace, but waiting for ref_count to go to zero */
112 unsigned int notified
: 1; /* no-more-processes notification was sent via special port */
114 uint32_t focal_tasks_count
; /* count of TASK_FOREGROUND_APPLICATION tasks in the coalition */
115 uint32_t non_focal_tasks_count
; /* count of TASK_BACKGROUND_APPLICATION tasks in the coalition */
118 #define coalition_lock(c) do{ lck_mtx_lock(&c->lock); }while(0)
119 #define coalition_unlock(c) do{ lck_mtx_unlock(&c->lock); }while(0)
122 coalition_notify_user(uint64_t id
, uint32_t flags
)
124 mach_port_t user_port
;
127 kr
= host_get_coalition_port(host_priv_self(), &user_port
);
128 if ((kr
!= KERN_SUCCESS
) || !IPC_PORT_VALID(user_port
)) {
132 coalition_notification(user_port
, id
, flags
);
136 * coalition_find_by_id_internal
137 * Returns: Coalition object with specified id, NOT referenced.
138 * If not found, returns COALITION_NULL.
139 * Condition: coalitions_list_lock must be LOCKED.
142 coalition_find_by_id_internal(uint64_t coal_id
)
145 return COALITION_NULL
;
148 lck_mtx_assert(&coalitions_list_lock
, LCK_MTX_ASSERT_OWNED
);
150 queue_iterate(&coalitions
, coal
, coalition_t
, coalitions
) {
151 if (coal
->id
== coal_id
) {
155 return COALITION_NULL
;
159 coalition_resource_usage_internal(coalition_t coal
, struct coalition_resource_usage
*cru_out
)
162 ledger_amount_t credit
, debit
;
164 ledger_t sum_ledger
= ledger_instantiate(task_ledger_template
, LEDGER_CREATE_ACTIVE_ENTRIES
);
165 if (sum_ledger
== LEDGER_NULL
) {
166 return KERN_RESOURCE_SHORTAGE
;
169 coalition_lock(coal
);
172 * Start with the coalition's ledger, which holds the totals from all
175 ledger_rollup(sum_ledger
, coal
->ledger
);
176 uint64_t bytesread
= coal
->bytesread
;
177 uint64_t byteswritten
= coal
->byteswritten
;
178 uint64_t gpu_time
= coal
->gpu_time
;
181 * Add to that all the active tasks' ledgers. Tasks cannot deallocate
182 * out from under us, since we hold the coalition lock.
185 queue_iterate(&coal
->tasks
, task
, task_t
, coalition_tasks
) {
186 ledger_rollup(sum_ledger
, task
->ledger
);
187 bytesread
+= task
->task_io_stats
->disk_reads
.size
;
188 byteswritten
+= task
->task_io_stats
->total_io
.size
- task
->task_io_stats
->disk_reads
.size
;
189 gpu_time
+= task_gpu_utilisation(task
);
192 /* collect information from the coalition itself */
193 cru_out
->tasks_started
= coal
->task_count
;
194 cru_out
->tasks_exited
= coal
->dead_task_count
;
196 uint64_t time_nonempty
= coal
->time_nonempty
;
197 uint64_t last_became_nonempty_time
= coal
->last_became_nonempty_time
;
199 coalition_unlock(coal
);
201 /* Copy the totals out of sum_ledger */
202 kr
= ledger_get_entries(sum_ledger
, task_ledgers
.cpu_time
,
204 if (kr
!= KERN_SUCCESS
) {
207 cru_out
->cpu_time
= credit
;
209 kr
= ledger_get_entries(sum_ledger
, task_ledgers
.interrupt_wakeups
,
211 if (kr
!= KERN_SUCCESS
) {
214 cru_out
->interrupt_wakeups
= credit
;
216 kr
= ledger_get_entries(sum_ledger
, task_ledgers
.platform_idle_wakeups
,
218 if (kr
!= KERN_SUCCESS
) {
221 cru_out
->platform_idle_wakeups
= credit
;
223 cru_out
->bytesread
= bytesread
;
224 cru_out
->byteswritten
= byteswritten
;
225 cru_out
->gpu_time
= gpu_time
;
227 ledger_dereference(sum_ledger
);
228 sum_ledger
= LEDGER_NULL
;
230 if (last_became_nonempty_time
) {
231 time_nonempty
+= mach_absolute_time() - last_became_nonempty_time
;
233 absolutetime_to_nanoseconds(time_nonempty
, &cru_out
->time_nonempty
);
239 * coalition_create_internal
240 * Returns: New coalition object, referenced for the caller and unlocked.
241 * Condition: coalitions_list_lock must be UNLOCKED.
244 coalition_create_internal(coalition_t
*out
, boolean_t privileged
)
246 struct coalition
*new_coal
= (struct coalition
*)zalloc(coalition_zone
);
247 if (new_coal
== COALITION_NULL
) {
248 return KERN_RESOURCE_SHORTAGE
;
250 bzero(new_coal
, sizeof(*new_coal
));
252 new_coal
->ledger
= ledger_instantiate(task_ledger_template
, LEDGER_CREATE_ACTIVE_ENTRIES
);
253 if (new_coal
->ledger
== NULL
) {
254 zfree(coalition_zone
, new_coal
);
255 return KERN_RESOURCE_SHORTAGE
;
258 /* One for caller, one for coalitions list */
259 new_coal
->ref_count
= 2;
261 new_coal
->privileged
= privileged
? TRUE
: FALSE
;
263 lck_mtx_init(&new_coal
->lock
, &coalitions_lck_grp
, &coalitions_lck_attr
);
264 queue_init(&new_coal
->tasks
);
266 lck_mtx_lock(&coalitions_list_lock
);
267 new_coal
->id
= coalition_next_id
++;
269 queue_enter(&coalitions
, new_coal
, coalition_t
, coalitions
);
270 lck_mtx_unlock(&coalitions_list_lock
);
273 printf("%s: new coal id %llu\n", __func__
, new_coal
->id
);
282 * Condition: coalition must be UNLOCKED.
285 coalition_release(coalition_t coal
)
287 boolean_t do_dealloc
= FALSE
;
289 /* TODO: This can be done with atomics. */
290 coalition_lock(coal
);
292 if (coal
->ref_count
== 0) {
296 uint32_t rc
= coal
->ref_count
;
297 #endif /* COALITION_DEBUG */
299 coalition_unlock(coal
);
302 printf("%s: coal %llu ref_count-- -> %u%s\n", __func__
, coal
->id
, rc
,
303 do_dealloc
? ", will deallocate now" : "");
304 #endif /* COALITION_DEBUG */
307 assert(coal
->termrequested
);
308 assert(coal
->terminated
);
309 assert(coal
->active_count
== 0);
310 assert(coal
->reaped
);
311 assert(coal
->focal_tasks_count
== 0);
312 assert(coal
->non_focal_tasks_count
== 0);
314 ledger_dereference(coal
->ledger
);
315 lck_mtx_destroy(&coal
->lock
, &coalitions_lck_grp
);
316 zfree(coalition_zone
, coal
);
321 * coalition_find_by_id
322 * Returns: Coalition object with specified id, referenced.
323 * Condition: coalitions_list_lock must be UNLOCKED.
326 coalition_find_by_id(uint64_t cid
)
329 return COALITION_NULL
;
332 lck_mtx_lock(&coalitions_list_lock
);
334 coalition_t coal
= coalition_find_by_id_internal(cid
);
335 if (coal
== COALITION_NULL
) {
336 lck_mtx_unlock(&coalitions_list_lock
);
337 return COALITION_NULL
;
340 coalition_lock(coal
);
343 coalition_unlock(coal
);
344 lck_mtx_unlock(&coalitions_list_lock
);
345 return COALITION_NULL
;
348 if (coal
->ref_count
== 0) {
349 panic("resurrecting coalition %p id %llu, active_count = %u\n",
350 coal
, coal
->id
, coal
->active_count
);
354 uint32_t rc
= coal
->ref_count
;
357 coalition_unlock(coal
);
358 lck_mtx_unlock(&coalitions_list_lock
);
361 printf("%s: coal %llu ref_count++ -> %u\n", __func__
, coal
->id
, rc
);
367 * coalition_find_and_activate_by_id
368 * Returns: Coalition object with specified id, referenced, and activated.
369 * Condition: coalitions_list_lock must be UNLOCKED.
370 * This is the function to use when putting a 'new' thing into a coalition,
371 * like posix_spawn of an XPC service by launchd.
372 * See also coalition_extend_active.
375 coalition_find_and_activate_by_id(uint64_t cid
)
378 return COALITION_NULL
;
381 lck_mtx_lock(&coalitions_list_lock
);
383 coalition_t coal
= coalition_find_by_id_internal(cid
);
384 if (coal
== COALITION_NULL
) {
385 lck_mtx_unlock(&coalitions_list_lock
);
386 return COALITION_NULL
;
389 coalition_lock(coal
);
391 if (coal
->reaped
|| coal
->terminated
) {
392 /* Too late to put something new into this coalition, it's
393 * already on its way out the door */
394 coalition_unlock(coal
);
395 lck_mtx_unlock(&coalitions_list_lock
);
396 return COALITION_NULL
;
399 if (coal
->ref_count
== 0) {
400 panic("resurrecting coalition %p id %llu, active_count = %u\n",
401 coal
, coal
->id
, coal
->active_count
);
405 coal
->active_count
++;
408 uint32_t rc
= coal
->ref_count
;
409 uint32_t ac
= coal
->active_count
;
412 coalition_unlock(coal
);
413 lck_mtx_unlock(&coalitions_list_lock
);
416 printf("%s: coal %llu ref_count++ -> %u, active_count++ -> %u\n",
417 __func__
, coal
->id
, rc
, ac
);
423 coalition_id(coalition_t coal
)
429 task_coalition_id(task_t task
)
431 return task
->coalition
->id
;
435 coalition_is_privileged(coalition_t coal
)
437 return coal
->privileged
|| unrestrict_coalition_syscalls
;
441 task_is_in_privileged_coalition(task_t task
)
443 return task
->coalition
->privileged
|| unrestrict_coalition_syscalls
;
447 * coalition_get_ledger
448 * Returns: Coalition's ledger, NOT referenced.
449 * Condition: Caller must have a coalition reference.
452 coalition_get_ledger(coalition_t coal
)
458 * This is the function to use when you already hold an activation on the
459 * coalition, and want to extend it to a second activation owned by a new
460 * object, like when a task in the coalition calls fork(). This is analogous
461 * to taking a second reference when you already hold one.
462 * See also coalition_find_and_activate_by_id.
465 coalition_extend_active(coalition_t coal
)
467 coalition_lock(coal
);
470 panic("cannot make a reaped coalition active again");
473 if (coal
->terminated
) {
474 coalition_unlock(coal
);
475 return KERN_TERMINATED
;
478 assert(coal
->active_count
> 0);
479 coal
->active_count
++;
481 coalition_unlock(coal
);
486 coalition_remove_active(coalition_t coal
)
488 coalition_lock(coal
);
490 assert(!coal
->reaped
);
491 assert(coal
->active_count
> 0);
493 coal
->active_count
--;
495 boolean_t do_notify
= FALSE
;
496 uint64_t notify_id
= 0;
497 uint32_t notify_flags
= 0;
498 if (coal
->termrequested
&& coal
->active_count
== 0) {
499 /* We only notify once, when active_count reaches zero.
500 * We just decremented, so if it reached zero, we mustn't have
503 assert(!coal
->terminated
);
504 coal
->terminated
= TRUE
;
506 assert(!coal
->notified
);
508 coal
->notified
= TRUE
;
510 notify_id
= coal
->id
;
514 coalition_unlock(coal
);
517 coalition_notify_user(notify_id
, notify_flags
);
521 /* Used for kernel_task, launchd, launchd's early boot tasks... */
523 coalition_default_adopt_task(task_t task
)
526 kr
= coalition_adopt_task(default_coalition
, task
);
527 if (kr
!= KERN_SUCCESS
) {
528 panic("failed to adopt task %p into default coalition: %d", task
, kr
);
534 * coalition_adopt_task
535 * Condition: Coalition must be referenced and unlocked. Will fail if coalition
536 * is already terminated.
539 coalition_adopt_task(coalition_t coal
, task_t task
)
541 if (task
->coalition
) {
542 return KERN_ALREADY_IN_SET
;
545 coalition_lock(coal
);
547 if (coal
->reaped
|| coal
->terminated
) {
548 coalition_unlock(coal
);
549 return KERN_TERMINATED
;
552 coal
->active_count
++;
555 task
->coalition
= coal
;
557 queue_enter(&coal
->tasks
, task
, task_t
, coalition_tasks
);
560 if(coal
->task_count
< coal
->dead_task_count
) {
561 panic("%s: coalition %p id %llu task_count < dead_task_count", __func__
, coal
, coal
->id
);
564 /* If moving from 0->1 active tasks */
565 if (coal
->task_count
- coal
->dead_task_count
== 1) {
566 coal
->last_became_nonempty_time
= mach_absolute_time();
570 uint32_t rc
= coal
->ref_count
;
573 coalition_unlock(coal
);
577 printf("%s: coal %llu ref_count++ -> %u\n", __func__
, coal
->id
, rc
);
584 * coalition_remove_task
585 * Condition: task must be referenced and UNLOCKED; task's coalition must be UNLOCKED
588 coalition_remove_task(task_t task
)
590 coalition_t coal
= task
->coalition
;
593 coalition_lock(coal
);
595 queue_remove(&coal
->tasks
, task
, task_t
, coalition_tasks
);
596 coal
->dead_task_count
++;
598 if(coal
->task_count
< coal
->dead_task_count
) {
599 panic("%s: coalition %p id %llu task_count < dead_task_count", __func__
, coal
, coal
->id
);
602 /* If moving from 1->0 active tasks */
603 if (coal
->task_count
- coal
->dead_task_count
== 0) {
604 uint64_t last_time_nonempty
= mach_absolute_time() - coal
->last_became_nonempty_time
;
605 coal
->last_became_nonempty_time
= 0;
606 coal
->time_nonempty
+= last_time_nonempty
;
609 ledger_rollup(coal
->ledger
, task
->ledger
);
610 coal
->bytesread
+= task
->task_io_stats
->disk_reads
.size
;
611 coal
->byteswritten
+= task
->task_io_stats
->total_io
.size
- task
->task_io_stats
->disk_reads
.size
;
612 coal
->gpu_time
+= task_gpu_utilisation(task
);
614 coalition_unlock(coal
);
616 coalition_remove_active(coal
);
621 * coalition_terminate_internal
622 * Condition: Coalition must be referenced and UNLOCKED.
625 coalition_request_terminate_internal(coalition_t coal
)
627 if (coal
== default_coalition
) {
628 return KERN_DEFAULT_SET
;
631 coalition_lock(coal
);
634 coalition_unlock(coal
);
635 return KERN_INVALID_NAME
;
638 if (coal
->terminated
|| coal
->termrequested
) {
639 coalition_unlock(coal
);
640 return KERN_TERMINATED
;
643 coal
->termrequested
= TRUE
;
645 boolean_t do_notify
= FALSE
;
646 uint64_t note_id
= 0;
647 uint32_t note_flags
= 0;
649 if (coal
->active_count
== 0) {
651 * We only notify once, when active_count reaches zero.
652 * We just decremented, so if it reached zero, we mustn't have
655 assert(!coal
->terminated
);
656 coal
->terminated
= TRUE
;
658 assert(!coal
->notified
);
660 coal
->notified
= TRUE
;
666 coalition_unlock(coal
);
669 coalition_notify_user(note_id
, note_flags
);
676 * coalition_reap_internal
677 * Condition: Coalition must be referenced and UNLOCKED.
680 coalition_reap_internal(coalition_t coal
)
682 if (coal
== default_coalition
) {
683 return KERN_DEFAULT_SET
;
686 coalition_lock(coal
);
688 coalition_unlock(coal
);
689 return KERN_TERMINATED
;
691 if (!coal
->terminated
) {
692 coalition_unlock(coal
);
695 assert(coal
->termrequested
);
696 if (coal
->active_count
> 0) {
697 coalition_unlock(coal
);
703 /* Caller, launchd, and coalitions list should each have a reference */
704 assert(coal
->ref_count
> 2);
706 coalition_unlock(coal
);
708 lck_mtx_lock(&coalitions_list_lock
);
710 queue_remove(&coalitions
, coal
, coalition_t
, coalitions
);
711 lck_mtx_unlock(&coalitions_list_lock
);
713 /* Release the list's reference and launchd's reference. */
714 coalition_release(coal
);
715 coalition_release(coal
);
723 coalition_zone
= zinit(
724 sizeof(struct coalition
),
725 CONFIG_COALITION_MAX
* sizeof(struct coalition
),
726 COALITION_CHUNK
* sizeof(struct coalition
),
728 zone_change(coalition_zone
, Z_NOENCRYPT
, TRUE
);
729 queue_init(&coalitions
);
731 if (!PE_parse_boot_argn("unrestrict_coalition_syscalls", &unrestrict_coalition_syscalls
,
732 sizeof (unrestrict_coalition_syscalls
))) {
733 unrestrict_coalition_syscalls
= 0;
736 lck_grp_attr_setdefault(&coalitions_lck_grp_attr
);
737 lck_grp_init(&coalitions_lck_grp
, "coalition", &coalitions_lck_grp_attr
);
738 lck_attr_setdefault(&coalitions_lck_attr
);
739 lck_mtx_init(&coalitions_list_lock
, &coalitions_lck_grp
, &coalitions_lck_attr
);
743 kern_return_t kr
= coalition_create_internal(&default_coalition
, TRUE
);
744 if (kr
!= KERN_SUCCESS
) {
745 panic("%s: could not create default coalition: %d", __func__
, kr
);
747 /* "Leak" our reference to the global object */
750 /* coalition focal tasks */
751 uint32_t coalition_adjust_focal_task_count(coalition_t coal
, int count
)
753 return hw_atomic_add(&coal
->focal_tasks_count
, count
);
756 uint32_t coalition_focal_task_count(coalition_t coal
)
758 return coal
->focal_tasks_count
;
761 uint32_t coalition_adjust_non_focal_task_count(coalition_t coal
, int count
)
763 return hw_atomic_add(&coal
->non_focal_tasks_count
, count
);
766 uint32_t coalition_non_focal_task_count(coalition_t coal
)
768 return coal
->non_focal_tasks_count
;
771 /* Call sfi_reevaluate() for every thread in the coalition */
772 void coalition_sfi_reevaluate(coalition_t coal
, task_t updated_task
) {
776 coalition_lock(coal
);
778 queue_iterate(&coal
->tasks
, task
, task_t
, coalition_tasks
) {
780 /* Skip the task we're doing this on behalf of - it's already updated */
781 if (task
== updated_task
)
786 queue_iterate(&task
->threads
, thread
, thread_t
, task_threads
) {
787 sfi_reevaluate(thread
);
791 coalition_unlock(coal
);