Task suspension, resume tokens, and thread enumeration

osfmk/kern/task.c · 10796 lines · browse source

Task-wide holds and waits, user suspension counts, legacy task_suspend/task_resume bookkeeping, explicit suspension tokens, and conversion of task thread lists to port arrays.

task_hold_locked source
task_hold_locked: Suspend execution of the specified task. This is a recursive-style suspension of the task, a count of suspends is maintained. CONDITIONS: the task is locked and active. Returns true if this was first suspension
bool
task_hold_locked(
	task_t          task)
{
	thread_t        thread;
	void *bsd_info = get_bsdtask_info(task);

	assert(task->active);

	if (task->suspend_count++ > 0) {
		return false;
	}

	KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_SUSPENSION, MACH_TASK_SUSPEND),
	    task_pid(task), task->user_stop_count, task->pidsuspended);

	if (bsd_info) {
		workq_proc_suspended(bsd_info);
	}

	/*
	 *	Iterate through all the threads and hold them.
	 */
	queue_iterate(&task->threads, thread, thread_t, task_threads) {
		thread_mtx_lock(thread);
		thread_hold(thread);
		thread_mtx_unlock(thread);
	}

#ifdef CONFIG_TASK_SUSPEND_STATS
… more in source
task_wait_locked source
task_wait_locked: Wait for all threads in task to stop. Conditions: Called with task locked, active, and held.
void
task_wait_locked(
	task_t          task,
	boolean_t               until_not_runnable)
{
	thread_t        thread, self;

	assert(task->active);
	assert(task->suspend_count > 0);

	self = current_thread();

	/*
	 *	Iterate through all the threads and wait for them to
	 *	stop.  Do not wait for the current thread if it is within
	 *	the task.
	 */
	queue_iterate(&task->threads, thread, thread_t, task_threads) {
		if (thread != self) {
			thread_wait(thread, until_not_runnable);
		}
	}
}
task_release_locked source
task_release_locked: Release a kernel hold on a task. CONDITIONS: the task is locked and active
void
task_release_locked(
	task_t          task)
{
	thread_t        thread;
	void *bsd_info = get_bsdtask_info(task);

	assert(task->active);
	assert(task->suspend_count > 0);

	if (--task->suspend_count > 0) {
		return;
	}

	if (bsd_info) {
		workq_proc_resumed(bsd_info);
	}

	queue_iterate(&task->threads, thread, thread_t, task_threads) {
		thread_mtx_lock(thread);
		thread_release(thread);
		thread_mtx_unlock(thread);
	}

	KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_SUSPENSION, MACH_TASK_RESUME) | DBG_FUNC_NONE, task_pid(task));

#if CONFIG_TASK_SUSPEND_STATS
	_task_mark_suspend_end(task);
#endif
… more in source
task_threads_internal source
static kern_return_t
task_threads_internal(
	task_t                  task,
	thread_act_array_t     *threads_out,
	mach_msg_type_number_t *countp,
	mach_thread_flavor_t    flavor)
{
	mach_msg_type_number_t  actual, count, count_needed;
	thread_act_array_t      thread_list;
	thread_t                thread;
	unsigned int            i;

	count = 0;
	thread_list = NULL;

	if (task == TASK_NULL) {
		return KERN_INVALID_ARGUMENT;
	}

	assert(flavor <= THREAD_FLAVOR_INSPECT);

	for (;;) {
		task_lock(task);
		if (!task->active) {
			task_unlock(task);

			mach_port_array_free(thread_list, count);
			return KERN_FAILURE;
		}
… more in source
task_threads_from_user source
kern_return_t
task_threads_from_user(
	mach_port_t                 port,
	thread_act_array_t         *threads_out,
	mach_msg_type_number_t     *count)
{
	ipc_kobject_type_t kotype;
	kern_return_t kr;

	task_t task = convert_port_to_task_inspect_no_eval(port);

	if (task == TASK_NULL) {
		return KERN_INVALID_ARGUMENT;
	}

	kotype = ip_type(port);

	switch (kotype) {
	case IKOT_TASK_CONTROL:
		kr = task_threads_internal(task, threads_out, count, THREAD_FLAVOR_CONTROL);
		break;
	case IKOT_TASK_READ:
		kr = task_threads_internal(task, threads_out, count, THREAD_FLAVOR_READ);
		break;
	case IKOT_TASK_INSPECT:
		kr = task_threads_internal(task, threads_out, count, THREAD_FLAVOR_INSPECT);
		break;
	default:
		panic("strange kobject type");
		break;
… more in source
place_task_hold source
static kern_return_t
place_task_hold(
	task_t task,
	int mode)
{
	if (!task->active && !task_is_a_corpse(task)) {
		return KERN_FAILURE;
	}

	/* Return success for corpse task */
	if (task_is_a_corpse(task)) {
		return KERN_SUCCESS;
	}

#if MACH_ASSERT
	current_task()->suspends_outstanding++;
#endif

	if (mode == TASK_HOLD_LEGACY) {
		task->legacy_stop_count++;
	}

#ifdef CONFIG_TASK_SUSPEND_STATS
	_task_mark_suspend_source(task);
#endif /* CONFIG_TASK_SUSPEND_STATS */

	if (task->user_stop_count++ > 0) {
		/*
		 *	If the stop count was positive, the task is
		 *	already stopped and we can exit.
… more in source
release_task_hold source
static kern_return_t
release_task_hold(
	task_t          task,
	int                     mode)
{
	boolean_t release = FALSE;

	if (!task->active && !task_is_a_corpse(task)) {
		return KERN_FAILURE;
	}

	/* Return success for corpse task */
	if (task_is_a_corpse(task)) {
		return KERN_SUCCESS;
	}

	if (mode == TASK_HOLD_PIDSUSPEND) {
		if (task->pidsuspended == FALSE) {
			return KERN_FAILURE;
		}
		task->pidsuspended = FALSE;
	}

	if (task->user_stop_count > (task->pidsuspended ? 1 : 0)) {
#if MACH_ASSERT
		/*
		 * This is obviously not robust; if we suspend one task and then resume a different one,
		 * we'll fly under the radar. This is only meant to catch the common case of a crashed
		 * or buggy suspender.
		 */
… more in source
task_suspend source · task_suspend reference
task_suspend: Implement an (old-fashioned) user-level suspension on a task. Because the user isn't expecting to have to manage a suspension token, we'll track it for him in the kernel in the form of a naked send right to the task's resume port. All such send rights account for a single suspension against the task (unlike task_suspend2() where each caller gets a unique suspension count represented by a unique send-once right). Conditions: The caller holds a reference to the task
kern_return_t
task_suspend(
	task_t          task)
{
	kern_return_t           kr;
	mach_port_t             port;
	mach_port_name_t        name;

	if (task == TASK_NULL || task == kernel_task) {
		return KERN_INVALID_ARGUMENT;
	}

	/*
	 * place a legacy hold on the task.
	 */
	task_lock(task);
	kr = place_task_hold(task, TASK_HOLD_LEGACY);
	task_unlock(task);

	if (kr != KERN_SUCCESS) {
		return kr;
	}

	/*
	 * Claim a send right on the task resume port, and request a no-senders
	 * notification on that port (if none outstanding).
	 */
	itk_lock(task);
	port = task->itk_resume;
	if (port == IP_NULL) {
… more in source
task_resume source · task_resume reference
task_resume: Release a user hold on a task. Conditions: The caller holds a reference to the task
kern_return_t
task_resume(
	task_t  task)
{
	kern_return_t    kr;
	mach_port_name_t resume_port_name;
	ipc_entry_t              resume_port_entry;
	ipc_space_t              space = current_task()->itk_space;

	if (task == TASK_NULL || task == kernel_task) {
		return KERN_INVALID_ARGUMENT;
	}

	/* release a legacy task hold */
	task_lock(task);
	kr = release_task_hold(task, TASK_HOLD_LEGACY);
	task_unlock(task);

	itk_lock(task); /* for itk_resume */
	is_write_lock(space); /* spin lock */
	if (is_active(space) && IP_VALID(task->itk_resume) &&
	    ipc_hash_lookup(space, ip_to_object(task->itk_resume), &resume_port_name, &resume_port_entry) == TRUE) {
		/*
		 * We found a suspension token in the caller's IPC space. Release a send right to indicate that
		 * we are holding one less legacy hold on the task from this caller.  If the release failed,
		 * go ahead and drop all the rights, as someone either already released our holds or the task
		 * is gone.
		 */
		itk_unlock(task);
		if (kr == KERN_SUCCESS) {
… more in source
task_suspend_internal source
Suspend the target task. Making/holding a token/reference/port is the callers responsibility.
kern_return_t
task_suspend_internal(task_t task)
{
	kern_return_t    kr;

	if (task == TASK_NULL || task == kernel_task) {
		return KERN_INVALID_ARGUMENT;
	}

	task_lock(task);
	kr = place_task_hold(task, TASK_HOLD_NORMAL);
	task_unlock(task);
	return kr;
}
task_suspend2_grp source
Suspend the target task, and return a suspension token. The token represents a reference on the suspended task.
static kern_return_t
task_suspend2_grp(
	task_t                  task,
	task_suspension_token_t *suspend_token,
	task_grp_t              grp)
{
	kern_return_t    kr;

	kr = task_suspend_internal(task);
	if (kr != KERN_SUCCESS) {
		*suspend_token = TASK_NULL;
		return kr;
	}

	/*
	 * Take a reference on the target task and return that to the caller
	 * as a "suspension token," which can be converted into an SO right to
	 * the now-suspended task's resume port.
	 */
	task_reference_grp(task, grp);
	*suspend_token = task;

	return KERN_SUCCESS;
}
task_suspend2_mig source
kern_return_t
task_suspend2_mig(
	task_t                  task,
	task_suspension_token_t *suspend_token)
{
	return task_suspend2_grp(task, suspend_token, TASK_GRP_MIG);
}
task_resume_internal source
Resume the task (reference/token/port management is caller's responsibility).
kern_return_t
task_resume_internal(
	task_suspension_token_t         task)
{
	kern_return_t kr;

	if (task == TASK_NULL || task == kernel_task) {
		return KERN_INVALID_ARGUMENT;
	}

	task_lock(task);
	kr = release_task_hold(task, TASK_HOLD_NORMAL);
	task_unlock(task);
	return kr;
}
task_resume2_grp source
Resume the task using a suspension token. Consumes the token's ref.
static kern_return_t
task_resume2_grp(
	task_suspension_token_t         task,
	task_grp_t                      grp)
{
	kern_return_t kr;

	kr = task_resume_internal(task);
	task_suspension_token_deallocate_grp(task, grp);

	return kr;
}