Task suspension, resume tokens, and thread enumeration
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; }
task_resume2_mig source
kern_return_t task_resume2_mig( task_suspension_token_t task) { return task_resume2_grp(task, TASK_GRP_MIG); }