Loading...
--- xnu/xnu-12377.121.6/bsd/pthread/pthread_workqueue.c
+++ xnu/xnu-8019.80.24/bsd/pthread/pthread_workqueue.c
@@ -41,7 +41,6 @@
#include <kern/thread.h>
#include <kern/thread_group.h>
#include <kern/zalloc.h>
-#include <kern/work_interval.h>
#include <mach/kern_return.h>
#include <mach/mach_param.h>
#include <mach/mach_port.h>
@@ -58,7 +57,6 @@
#include <machine/machine_routines.h>
#include <machine/smp.h>
#include <vm/vm_map.h>
-#include <vm/vm_fault_xnu.h>
#include <vm/vm_protos.h>
#include <sys/eventvar.h>
@@ -84,13 +82,6 @@
#include <os/log.h>
static void workq_unpark_continue(void *uth, wait_result_t wr) __dead2;
-
-static void workq_bound_thread_unpark_continue(void *uth, wait_result_t wr) __dead2;
-
-static void workq_bound_thread_initialize_and_unpark_continue(void *uth, wait_result_t wr) __dead2;
-
-static void workq_bound_thread_setup_and_run(struct uthread *uth, int setup_flags) __dead2;
-
static void workq_schedule_creator(proc_t p, struct workqueue *wq,
workq_kern_threadreq_flags_t flags);
@@ -98,8 +89,7 @@
workq_threadreq_t req);
static uint32_t workq_constrained_allowance(struct workqueue *wq,
- thread_qos_t at_qos, struct uthread *uth,
- bool may_start_timer, bool record_failed_allowance);
+ thread_qos_t at_qos, struct uthread *uth, bool may_start_timer);
static bool _wq_cooperative_queue_refresh_best_req_qos(struct workqueue *wq);
@@ -133,9 +123,9 @@
static LCK_GRP_DECLARE(workq_lck_grp, "workq");
os_refgrp_decl(static, workq_refgrp, "workq", NULL);
-static ZONE_DEFINE(workq_zone_workqueue, "workq.wq",
+static ZONE_DECLARE(workq_zone_workqueue, "workq.wq",
sizeof(struct workqueue), ZC_NONE);
-static ZONE_DEFINE(workq_zone_threadreq, "workq.threadreq",
+static ZONE_DECLARE(workq_zone_threadreq, "workq.threadreq",
sizeof(struct workq_threadreq_s), ZC_CACHING);
static struct mpsc_daemon_queue workq_deallocate_queue;
@@ -332,7 +322,7 @@
return os_atomic_load_wide(&wq->wq_thactive, relaxed);
}
-static inline uint8_t
+static inline int
_wq_bucket(thread_qos_t qos)
{
// Map both BG and MT to the same bucket by over-shifting down and
@@ -387,9 +377,7 @@
static inline wq_thactive_t
_wq_thactive_offset_for_qos(thread_qos_t qos)
{
- uint8_t bucket = _wq_bucket(qos);
- __builtin_assume(bucket < WORKQ_NUM_BUCKETS);
- return (wq_thactive_t)1 << (bucket * WQ_THACTIVE_BUCKET_WIDTH);
+ return (wq_thactive_t)1 << (_wq_bucket(qos) * WQ_THACTIVE_BUCKET_WIDTH);
}
static inline wq_thactive_t
@@ -434,7 +422,7 @@
*max_busycount = THREAD_QOS_LAST - qos;
}
- uint8_t i = _wq_bucket(qos);
+ int i = _wq_bucket(qos);
v >>= i * WQ_THACTIVE_BUCKET_WIDTH;
for (; i < WORKQ_NUM_QOS_BUCKETS; i++, v >>= WQ_THACTIVE_BUCKET_WIDTH) {
active = v & WQ_THACTIVE_BUCKET_MASK;
@@ -456,8 +444,6 @@
return count;
}
-/* The input qos here should be the requested QoS of the thread, not accounting
- * for any overrides */
static inline void
_wq_cooperative_queue_scheduled_count_dec(struct workqueue *wq, thread_qos_t qos)
{
@@ -465,8 +451,6 @@
assert(old_scheduled_count > 0);
}
-/* The input qos here should be the requested QoS of the thread, not accounting
- * for any overrides */
static inline void
_wq_cooperative_queue_scheduled_count_inc(struct workqueue *wq, thread_qos_t qos)
{
@@ -610,18 +594,11 @@
return false;
}
-/* Input thread must be self. Called during self override, resetting overrides
- * or while processing kevents
- *
- * Called with workq lock held. Sometimes also the thread mutex
- */
static void
workq_thread_update_bucket(proc_t p, struct workqueue *wq, struct uthread *uth,
struct uu_workq_policy old_pri, struct uu_workq_policy new_pri,
bool force_run)
{
- assert(uth == current_uthread());
-
thread_qos_t old_bucket = old_pri.qos_bucket;
thread_qos_t new_bucket = workq_pri_bucket(new_pri);
@@ -632,12 +609,11 @@
new_pri.qos_bucket = new_bucket;
uth->uu_workq_pri = new_pri;
- if (old_pri.qos_override != new_pri.qos_override) {
- thread_set_workq_override(get_machthread(uth), new_pri.qos_override);
- }
-
- if (wq->wq_reqcount &&
- (old_bucket > new_bucket || force_run)) {
+ if (workq_pri_override(old_pri) != new_bucket) {
+ thread_set_workq_override(get_machthread(uth), new_bucket);
+ }
+
+ if (wq->wq_reqcount && (old_bucket > new_bucket || force_run)) {
int flags = WORKQ_THREADREQ_CAN_CREATE_THREADS;
if (old_bucket > new_bucket) {
/*
@@ -686,11 +662,7 @@
}
}
-/*
- * This function is always called with the workq lock, except for the
- * permanently bound workqueue thread, which instead requires the kqlock.
- * See locking model for bound thread's uu_workq_flags.
- */
+/* Called with the workq lock held */
static void
workq_thread_reset_pri(struct workqueue *wq, struct uthread *uth,
workq_threadreq_t req, bool unpark)
@@ -718,7 +690,7 @@
uint32_t mgr_pri = wq->wq_event_manager_priority;
assert(trp.trp_value == 0); // manager qos and thread policy don't mix
- if (_pthread_priority_has_sched_pri(mgr_pri)) {
+ if (mgr_pri & _PTHREAD_PRIORITY_SCHED_PRI_FLAG) {
mgr_pri &= _PTHREAD_PRIORITY_SCHED_PRI_MASK;
thread_set_workq_pri(th, THREAD_QOS_UNSPECIFIED, mgr_pri,
POLICY_TIMESHARE);
@@ -741,12 +713,8 @@
#if CONFIG_PREADOPT_TG
if (req && (req->tr_flags & WORKQ_TR_FLAG_WORKLOOP)) {
/*
- * For kqwl permanently configured with a thread group, we can safely borrow
- * +1 ref from kqwl_preadopt_tg. A thread then takes additional +1 ref
- * for itself via thread_set_preadopt_thread_group.
- *
- * In all other cases, we cannot safely read and borrow the reference from the kqwl
- * since it can disappear from under us at any time due to the max-ing logic in
+ * We cannot safely read and borrow the reference from the kqwl since it
+ * can disappear from under us at any time due to the max-ing logic in
* kqueue_set_preadopted_thread_group.
*
* As such, we do the following dance:
@@ -768,8 +736,7 @@
int ret = 0;
again:
ret = os_atomic_rmw_loop(tg_loc, old_tg, new_tg, relaxed, {
- if ((!KQWL_HAS_VALID_PREADOPTED_TG(old_tg)) ||
- KQWL_HAS_PERMANENT_PREADOPTED_TG(old_tg)) {
+ if (!KQWL_HAS_VALID_PREADOPTED_TG(old_tg)) {
os_atomic_rmw_loop_give_up(break);
}
@@ -805,12 +772,8 @@
new_tg = thread_group_to_set;
});
} else {
- if (KQWL_HAS_PERMANENT_PREADOPTED_TG(old_tg)) {
- thread_set_preadopt_thread_group(th, KQWL_GET_PREADOPTED_TG(old_tg));
- } else {
- /* Nothing valid on the kqwl, just clear what's on the thread */
- thread_set_preadopt_thread_group(th, NULL);
- }
+ /* Nothing valid on the kqwl, just clear what's on the thread */
+ thread_set_preadopt_thread_group(th, NULL);
}
} else {
/* Not even a kqwl, clear what's on the thread */
@@ -959,13 +922,11 @@
struct workqueue *wq = proc_get_wqptr_fast(p);
workq_lock_spin(wq);
- if (!workq_thread_is_permanently_bound(uth)) {
- TAILQ_REMOVE(&wq->wq_thrunlist, uth, uu_workq_entry);
- if (uth->uu_workq_flags & UT_WORKQ_DYING) {
- WQ_TRACE_WQ(TRACE_wq_thread_terminate | DBG_FUNC_END,
- wq, wq->wq_thidlecount, 0, 0);
- workq_death_policy_evaluate(wq, 1);
- }
+ TAILQ_REMOVE(&wq->wq_thrunlist, uth, uu_workq_entry);
+ if (uth->uu_workq_flags & UT_WORKQ_DYING) {
+ WQ_TRACE_WQ(TRACE_wq_thread_terminate | DBG_FUNC_END,
+ wq, wq->wq_thidlecount, 0, 0);
+ workq_death_policy_evaluate(wq, 1);
}
if (wq->wq_nthreads-- == wq_max_threads) {
/*
@@ -1061,13 +1022,8 @@
* - dropped and retaken around thread creation
* - return with workq lock held
*/
-static kern_return_t
-workq_add_new_idle_thread(
- proc_t p,
- struct workqueue *wq,
- thread_continue_t continuation,
- bool is_permanently_bound,
- thread_t *new_thread)
+static bool
+workq_add_new_idle_thread(proc_t p, struct workqueue *wq)
{
mach_vm_offset_t th_stackaddr;
kern_return_t kret;
@@ -1077,7 +1033,7 @@
workq_unlock(wq);
- vm_map_t vmap = get_task_map(proc_task(p));
+ vm_map_t vmap = get_task_map(p->task);
kret = pthread_functions->workq_create_threadstack(p, vmap, &th_stackaddr);
if (kret != KERN_SUCCESS) {
@@ -1086,10 +1042,7 @@
goto out;
}
- kret = thread_create_workq_waiting(proc_task(p),
- continuation,
- &th,
- is_permanently_bound);
+ kret = thread_create_workq_waiting(p->task, workq_unpark_continue, &th);
if (kret != KERN_SUCCESS) {
WQ_TRACE_WQ(TRACE_wq_thread_create_failed | DBG_FUNC_NONE, wq,
kret, 0, 0);
@@ -1101,20 +1054,14 @@
// on success, because it calls workq_thread_init_and_wq_lock() above
struct uthread *uth = get_bsdthread_info(th);
+
+ wq->wq_creations++;
+ wq->wq_thidlecount++;
uth->uu_workq_stackaddr = (user_addr_t)th_stackaddr;
-
- wq->wq_creations++;
- if (!is_permanently_bound) {
- wq->wq_thidlecount++;
- TAILQ_INSERT_TAIL(&wq->wq_thnewlist, uth, uu_workq_entry);
- }
-
- if (new_thread) {
- *new_thread = th;
- }
+ TAILQ_INSERT_TAIL(&wq->wq_thnewlist, uth, uu_workq_entry);
WQ_TRACE_WQ(TRACE_wq_thread_create | DBG_FUNC_NONE, wq, 0, 0, 0);
- return kret;
+ return true;
out:
workq_lock_spin(wq);
@@ -1124,7 +1071,7 @@
* to do so when it fails.
*/
wq->wq_nthreads--;
- return kret;
+ return false;
}
static inline bool
@@ -1136,20 +1083,13 @@
static inline bool
workq_thread_is_nonovercommit(struct uthread *uth)
{
- return (uth->uu_workq_flags & (UT_WORKQ_OVERCOMMIT |
- UT_WORKQ_COOPERATIVE)) == 0;
+ return (uth->uu_workq_flags & (UT_WORKQ_OVERCOMMIT | UT_WORKQ_COOPERATIVE)) == 0;
}
static inline bool
workq_thread_is_cooperative(struct uthread *uth)
{
return (uth->uu_workq_flags & UT_WORKQ_COOPERATIVE) != 0;
-}
-
-bool
-workq_thread_is_permanently_bound(struct uthread *uth)
-{
- return (uth->uu_workq_flags & UT_WORKQ_PERMANENT_BIND) != 0;
}
static inline void
@@ -1197,7 +1137,7 @@
uint32_t flags = WQ_FLAG_THREAD_NEWSPI | qos | WQ_FLAG_THREAD_PRIO_QOS;
thread_t th = get_machthread(uth);
- vm_map_t vmap = get_task_map(proc_task(p));
+ vm_map_t vmap = get_task_map(p->task);
if (!first_use) {
flags |= WQ_FLAG_THREAD_REUSE;
@@ -1252,7 +1192,7 @@
if (workq_thread_is_cooperative(uth)) {
assert(!is_creator);
- thread_qos_t thread_qos = uth->uu_workq_pri.qos_req;
+ thread_qos_t thread_qos = uth->uu_workq_pri.qos_bucket;
_wq_cooperative_queue_scheduled_count_dec(wq, thread_qos);
/* Before we get here, we always go through
@@ -1362,9 +1302,7 @@
static inline bool
workq_tr_is_nonovercommit(workq_tr_flags_t tr_flags)
{
- return (tr_flags & (WORKQ_TR_FLAG_OVERCOMMIT |
- WORKQ_TR_FLAG_COOPERATIVE |
- WORKQ_TR_FLAG_PERMANENT_BIND)) == 0;
+ return (tr_flags & (WORKQ_TR_FLAG_OVERCOMMIT | WORKQ_TR_FLAG_COOPERATIVE)) == 0;
}
static inline bool
@@ -1404,39 +1342,35 @@
}
}
+
/* Calculates the number of threads scheduled >= the input QoS */
static uint64_t
-workq_num_cooperative_threads_scheduled_to_qos_internal(struct workqueue *wq, thread_qos_t qos)
-{
+workq_num_cooperative_threads_scheduled_to_qos(struct workqueue *wq, thread_qos_t qos)
+{
+ workq_lock_held(wq);
+
uint64_t num_cooperative_threads = 0;
for (thread_qos_t cur_qos = WORKQ_THREAD_QOS_MAX; cur_qos >= qos; cur_qos--) {
- uint8_t bucket = _wq_bucket(cur_qos);
+ int bucket = _wq_bucket(cur_qos);
num_cooperative_threads += wq->wq_cooperative_queue_scheduled_count[bucket];
}
return num_cooperative_threads;
-}
-
-/* Calculates the number of threads scheduled >= the input QoS */
-static uint64_t
-workq_num_cooperative_threads_scheduled_to_qos_locked(struct workqueue *wq, thread_qos_t qos)
-{
- workq_lock_held(wq);
- return workq_num_cooperative_threads_scheduled_to_qos_internal(wq, qos);
}
static uint64_t
workq_num_cooperative_threads_scheduled_total(struct workqueue *wq)
{
- return workq_num_cooperative_threads_scheduled_to_qos_locked(wq, WORKQ_THREAD_QOS_MIN);
-}
-
+ return workq_num_cooperative_threads_scheduled_to_qos(wq, WORKQ_THREAD_QOS_MIN);
+}
+
+#if DEBUG || DEVELOPMENT
static bool
workq_has_cooperative_thread_requests(struct workqueue *wq)
{
for (thread_qos_t qos = WORKQ_THREAD_QOS_MAX; qos >= WORKQ_THREAD_QOS_MIN; qos--) {
- uint8_t bucket = _wq_bucket(qos);
+ int bucket = _wq_bucket(qos);
if (!STAILQ_EMPTY(&wq->wq_cooperative_queue[bucket])) {
return true;
}
@@ -1444,6 +1378,7 @@
return false;
}
+#endif
/*
* Determines the next QoS bucket we should service next in the cooperative
@@ -1493,7 +1428,7 @@
int scheduled_count_till_qos = 0;
for (thread_qos_t qos = WORKQ_THREAD_QOS_MAX; qos >= WORKQ_THREAD_QOS_MIN; qos--) {
- uint8_t bucket = _wq_bucket(qos);
+ int bucket = _wq_bucket(qos);
uint8_t scheduled_count_for_bucket = wq->wq_cooperative_queue_scheduled_count[bucket];
scheduled_count_till_qos += scheduled_count_for_bucket;
@@ -1528,7 +1463,7 @@
wq->wq_cooperative_queue_best_req_qos = highest_qos_req;
}
-#if MACH_ASSERT
+#if DEBUG || DEVELOPMENT
/* Assert that if we are showing up the next best req as UN, then there
* actually is no thread request in the cooperative pool buckets */
if (wq->wq_cooperative_queue_best_req_qos == THREAD_QOS_UNSPECIFIED) {
@@ -1566,11 +1501,11 @@
bool exclude_thread_as_scheduled = false;
bool passed_admissions = false;
- uint8_t bucket = _wq_bucket(qos);
+ int bucket = _wq_bucket(qos);
if (uth && workq_thread_is_cooperative(uth)) {
exclude_thread_as_scheduled = true;
- _wq_cooperative_queue_scheduled_count_dec(wq, uth->uu_workq_pri.qos_req);
+ _wq_cooperative_queue_scheduled_count_dec(wq, uth->uu_workq_pri.qos_bucket);
}
/*
@@ -1604,7 +1539,7 @@
* If number of threads at the QoS bucket >= input QoS exceeds the max we want
* for the pool, deny this thread
*/
- uint64_t aggregate_down_to_qos = workq_num_cooperative_threads_scheduled_to_qos_locked(wq, qos);
+ uint64_t aggregate_down_to_qos = workq_num_cooperative_threads_scheduled_to_qos(wq, qos);
passed_admissions = (aggregate_down_to_qos < wq_cooperative_queue_max_size(wq));
WQ_TRACE(TRACE_wq_cooperative_admission | DBG_FUNC_NONE, aggregate_down_to_qos,
qos, passed_admissions, WQ_COOPERATIVE_POOL_SATURATED_UP_TO_QOS);
@@ -1615,7 +1550,7 @@
out:
if (exclude_thread_as_scheduled) {
- _wq_cooperative_queue_scheduled_count_inc(wq, uth->uu_workq_pri.qos_req);
+ _wq_cooperative_queue_scheduled_count_inc(wq, uth->uu_workq_pri.qos_bucket);
}
return passed_admissions;
}
@@ -1707,10 +1642,7 @@
if (workq_threadreq_is_cooperative(req)) {
assert(req->tr_qos != WORKQ_THREAD_QOS_MANAGER);
assert(req->tr_qos != WORKQ_THREAD_QOS_ABOVEUI);
- /* Account for the fact that BG and MT are coalesced when
- * calculating best request for cooperative pool
- */
- assert(_wq_bucket(req->tr_qos) == _wq_bucket(wq->wq_cooperative_queue_best_req_qos));
+ assert(req->tr_qos == wq->wq_cooperative_queue_best_req_qos);
struct workq_threadreq_tailq *bucket = &wq->wq_cooperative_queue[_wq_bucket(req->tr_qos)];
__assert_only workq_threadreq_t head = STAILQ_FIRST(bucket);
@@ -2323,8 +2255,7 @@
bsdthread_part_of_cooperative_workqueue(struct uthread *uth)
{
return (workq_thread_is_cooperative(uth) || workq_thread_is_nonovercommit(uth)) &&
- (uth->uu_workq_pri.qos_bucket != WORKQ_THREAD_QOS_MANAGER) &&
- (!workq_thread_is_permanently_bound(uth));
+ (uth->uu_workq_pri.qos_bucket != WORKQ_THREAD_QOS_MANAGER);
}
static bool
@@ -2351,7 +2282,6 @@
int unbind_rv = 0, qos_rv = 0, voucher_rv = 0, fixedpri_rv = 0;
bool is_wq_thread = (thread_get_tag(th) & THREAD_TAG_WORKQUEUE);
- assert(th == current_thread());
if (flags & WORKQ_SET_SELF_WQ_KEVENT_UNBIND) {
if (!is_wq_thread) {
unbind_rv = EINVAL;
@@ -2378,38 +2308,12 @@
}
qos:
- if (flags & (WORKQ_SET_SELF_QOS_FLAG | WORKQ_SET_SELF_QOS_OVERRIDE_FLAG)) {
- assert(flags & WORKQ_SET_SELF_QOS_FLAG);
-
+ if (flags & WORKQ_SET_SELF_QOS_FLAG) {
thread_qos_policy_data_t new_policy;
- thread_qos_t qos_override = THREAD_QOS_UNSPECIFIED;
if (!_pthread_priority_to_policy(priority, &new_policy)) {
qos_rv = EINVAL;
goto voucher;
- }
-
- if (flags & WORKQ_SET_SELF_QOS_OVERRIDE_FLAG) {
- /*
- * If the WORKQ_SET_SELF_QOS_OVERRIDE_FLAG is set, we definitely
- * should have an override QoS in the pthread_priority_t and we should
- * only come into this path for cooperative thread requests
- */
- if (!_pthread_priority_has_override_qos(priority) ||
- !_pthread_priority_is_cooperative(priority)) {
- qos_rv = EINVAL;
- goto voucher;
- }
- qos_override = _pthread_priority_thread_override_qos(priority);
- } else {
- /*
- * If the WORKQ_SET_SELF_QOS_OVERRIDE_FLAG is not set, we definitely
- * should not have an override QoS in the pthread_priority_t
- */
- if (_pthread_priority_has_override_qos(priority)) {
- qos_rv = EINVAL;
- goto voucher;
- }
}
if (!is_wq_thread) {
@@ -2456,37 +2360,10 @@
struct uu_workq_policy old_pri, new_pri;
bool force_run = false;
- if (qos_override) {
- /*
- * We're in the case of a thread clarifying that it is for eg. not IN
- * req QoS but rather, UT req QoS with IN override. However, this can
- * race with a concurrent override happening to the thread via
- * workq_thread_add_dispatch_override so this needs to be
- * synchronized with the thread mutex.
- */
- thread_mtx_lock(th);
- }
-
workq_lock_spin(wq);
old_pri = new_pri = uth->uu_workq_pri;
new_pri.qos_req = (thread_qos_t)new_policy.qos_tier;
-
- if (old_pri.qos_override < qos_override) {
- /*
- * Since this can race with a concurrent override via
- * workq_thread_add_dispatch_override, only adjust override value if we
- * are higher - this is a saturating function.
- *
- * We should not be changing the final override values, we should simply
- * be redistributing the current value with a different breakdown of req
- * vs override QoS - assert to that effect. Therefore, buckets should
- * not change.
- */
- new_pri.qos_override = qos_override;
- assert(workq_pri_override(new_pri) == workq_pri_override(old_pri));
- assert(workq_pri_bucket(new_pri) == workq_pri_bucket(old_pri));
- }
/* Adjust schedule counts for various types of transitions */
@@ -2502,24 +2379,17 @@
/* cooperative -> cooperative */
} else if (workq_thread_is_cooperative(uth)) {
- _wq_cooperative_queue_scheduled_count_dec(wq, old_pri.qos_req);
- _wq_cooperative_queue_scheduled_count_inc(wq, new_pri.qos_req);
+ _wq_cooperative_queue_scheduled_count_dec(wq, old_pri.qos_bucket);
+ _wq_cooperative_queue_scheduled_count_inc(wq, workq_pri_bucket(new_pri));
/* We're changing schedule counts within cooperative pool, we
* need to refresh best cooperative QoS logic again */
force_run = _wq_cooperative_queue_refresh_best_req_qos(wq);
}
- /*
- * This will set up an override on the thread if any and will also call
- * schedule_creator if needed
- */
+ /* This will also call schedule_creator if needed */
workq_thread_update_bucket(p, wq, uth, old_pri, new_pri, force_run);
workq_unlock(wq);
-
- if (qos_override) {
- thread_mtx_unlock(th);
- }
if (workq_thread_is_overcommit(uth)) {
thread_disarm_workqueue_quantum(th);
@@ -2623,7 +2493,7 @@
return ESRCH;
}
- int rv = proc_thread_qos_add_override(proc_task(p), th, 0, qos, TRUE,
+ int rv = proc_thread_qos_add_override(p->task, th, 0, qos, TRUE,
resource, THREAD_QOS_OVERRIDE_TYPE_PTHREAD_EXPLICIT_OVERRIDE);
thread_deallocate(th);
@@ -2640,7 +2510,7 @@
return ESRCH;
}
- int rv = proc_thread_qos_remove_override(proc_task(p), th, 0, resource,
+ int rv = proc_thread_qos_remove_override(p->task, th, 0, resource,
THREAD_QOS_OVERRIDE_TYPE_PTHREAD_EXPLICIT_OVERRIDE);
thread_deallocate(th);
@@ -2679,9 +2549,13 @@
if (ulock_addr) {
uint32_t val;
int rc;
- vm_fault_disable();
+ /*
+ * Workaround lack of explicit support for 'no-fault copyin'
+ * <rdar://problem/24999882>, as disabling preemption prevents paging in
+ */
+ disable_preemption();
rc = copyin_atomic32(ulock_addr, &val);
- vm_fault_enable();
+ enable_preemption();
if (rc == 0 && ulock_owner_value_to_port_name(val) != kport) {
goto out;
}
@@ -2724,20 +2598,11 @@
WQ_TRACE_WQ(TRACE_wq_override_reset | DBG_FUNC_NONE, wq, 0, 0, 0);
- /*
- * workq_thread_add_dispatch_override takes the thread mutex before doing the
- * copyin to validate the drainer and apply the override. We need to do the
- * same here. See rdar://84472518
- */
- thread_mtx_lock(thread);
-
workq_lock_spin(wq);
old_pri = new_pri = uth->uu_workq_pri;
new_pri.qos_override = THREAD_QOS_UNSPECIFIED;
workq_thread_update_bucket(p, wq, uth, old_pri, new_pri, false);
workq_unlock(wq);
-
- thread_mtx_unlock(thread);
return 0;
}
@@ -2759,20 +2624,6 @@
}
static int
-workq_allow_sigmask(proc_t p, sigset_t mask)
-{
- if (mask & workq_threadmask) {
- return EINVAL;
- }
-
- proc_lock(p);
- p->p_workq_allow_sigmask |= mask;
- proc_unlock(p);
-
- return 0;
-}
-
-static int
bsdthread_get_max_parallelism(thread_qos_t qos, unsigned long flags,
int *retval)
{
@@ -2787,12 +2638,10 @@
return EINVAL;
}
-#if !HAS_ARM_FEAT_SME
/* No units are present */
if (flags & QOS_PARALLELISM_CLUSTER_SHARED_RESOURCE) {
return ENOTSUP;
}
-#endif /* !HAS_ARM_FEAT_SME */
if (flags & QOS_PARALLELISM_REALTIME) {
if (qos) {
@@ -2868,8 +2717,6 @@
return bsdthread_dispatch_apply_attr(p, current_thread(),
(unsigned long)uap->arg1, (uint64_t)uap->arg2,
(uint64_t)uap->arg3);
- case BSDTHREAD_CTL_WORKQ_ALLOW_SIGMASK:
- return workq_allow_sigmask(p, (int)uap->arg1);
case BSDTHREAD_CTL_SET_QOS:
case BSDTHREAD_CTL_QOS_DISPATCH_ASYNCHRONOUS_OVERRIDE_ADD:
case BSDTHREAD_CTL_QOS_DISPATCH_ASYNCHRONOUS_OVERRIDE_RESET:
@@ -2983,7 +2830,7 @@
assert(!workq_threadreq_is_cooperative(req));
if (workq_threadreq_is_nonovercommit(req)) {
- unpaced = workq_constrained_allowance(wq, qos, NULL, false, true);
+ unpaced = workq_constrained_allowance(wq, qos, NULL, false);
if (unpaced >= reqcount - 1) {
unpaced = reqcount - 1;
}
@@ -3024,8 +2871,7 @@
reqcount--;
}
} while (unpaced && wq->wq_nthreads < wq_max_threads &&
- (workq_add_new_idle_thread(p, wq, workq_unpark_continue,
- false, NULL) == KERN_SUCCESS));
+ workq_add_new_idle_thread(p, wq));
if (_wq_exiting(wq)) {
goto unlock_and_exit;
@@ -3056,18 +2902,7 @@
struct uthread *uth = NULL;
assert(req->tr_flags & (WORKQ_TR_FLAG_WORKLOOP | WORKQ_TR_FLAG_KEVENT));
- /*
- * Thread bound kqworkloops overlay the priority queue entry with other
- * data (the thread_t and work interval), so should never have their
- * threadreq passed here.
- */
- assert(!(req->tr_flags & WORKQ_TR_FLAG_PERMANENT_BIND));
-
- /*
- * For any new initialization changes done to workqueue thread request below,
- * please also consider if they are relevant to permanently bound thread
- * request. See workq_kern_threadreq_permanent_bind.
- */
+
if (req->tr_flags & WORKQ_TR_FLAG_WL_OUTSIDE_QOS) {
workq_threadreq_param_t trp = kqueue_threadreq_workloop_param(req);
qos = thread_workq_qos_for_pri(trp.trp_pri);
@@ -3254,34 +3089,6 @@
}
void
-workq_kern_bound_thread_reset_pri(workq_threadreq_t req, struct uthread *uth)
-{
- assert(workq_thread_is_permanently_bound(uth));
-
- if (req && (req->tr_flags & WORKQ_TR_FLAG_WL_OUTSIDE_QOS)) {
- /*
- * For requests outside-of-QoS, we set the scheduling policy and
- * absolute priority for the bound thread right at the initialization
- * time. See workq_kern_threadreq_permanent_bind.
- */
- return;
- }
-
- struct workqueue *wq = proc_get_wqptr_fast(current_proc());
- if (req) {
- assert(req->tr_qos != WORKQ_THREAD_QOS_MANAGER);
- workq_thread_reset_pri(wq, uth, req, /*unpark*/ true);
- } else {
- thread_qos_t qos = workq_pri_override(uth->uu_workq_pri);
- if (qos > WORKQ_THREAD_QOS_CLEANUP) {
- workq_thread_reset_pri(wq, uth, NULL, /*unpark*/ true);
- } else {
- uth->uu_save.uus_workq_park_data.qos = qos;
- }
- }
-}
-
-void
workq_kern_threadreq_lock(struct proc *p)
{
workq_lock_spin(proc_get_wqptr_fast(p));
@@ -3307,7 +3114,7 @@
if (req->tr_state == WORKQ_TR_STATE_BINDING) {
kqueue_threadreq_bind(p, req, req->tr_thread,
- KQUEUE_THREADREQ_BIND_NO_INHERITOR_UPDATE);
+ KQUEUE_THREADERQ_BIND_NO_INHERITOR_UPDATE);
return;
}
@@ -3330,223 +3137,6 @@
workq_perform_turnstile_operation_locked(wq, ^{
turnstile_update_inheritor(wl_ts, inheritor, flags);
});
-}
-
-/*
- * An entry point for kevent to request a newly created workqueue thread
- * and bind it permanently to the given workqueue thread request.
- *
- * It currently only supports fixed scheduler priority thread requests.
- *
- * The newly created thread counts towards wq_nthreads. This function returns
- * an error if we are above that limit. There is no concept of delayed thread
- * creation for such specially configured kqworkloops.
- *
- * If successful, the newly created thread will be parked in
- * workq_bound_thread_initialize_and_unpark_continue waiting for
- * new incoming events.
- */
-kern_return_t
-workq_kern_threadreq_permanent_bind(struct proc *p, struct workq_threadreq_s *kqr)
-{
- kern_return_t ret = 0;
- thread_t new_thread = NULL;
- struct workqueue *wq = proc_get_wqptr_fast(p);
-
- workq_lock_spin(wq);
-
- if (wq->wq_nthreads >= wq_max_threads) {
- ret = EDOM;
- } else {
- if (kqr->tr_flags & WORKQ_TR_FLAG_WL_OUTSIDE_QOS) {
- workq_threadreq_param_t trp = kqueue_threadreq_workloop_param(kqr);
- /*
- * For requests outside-of-QoS, we fully initialize the thread
- * request here followed by preadopting the scheduling properties
- * on the newly created bound thread.
- */
- thread_qos_t qos = thread_workq_qos_for_pri(trp.trp_pri);
- if (qos == THREAD_QOS_UNSPECIFIED) {
- qos = WORKQ_THREAD_QOS_ABOVEUI;
- }
- kqr->tr_qos = qos;
- }
- kqr->tr_count = 1;
-
- /* workq_lock dropped and retaken around thread creation below. */
- ret = workq_add_new_idle_thread(p, wq,
- workq_bound_thread_initialize_and_unpark_continue,
- true, &new_thread);
- if (ret == KERN_SUCCESS) {
- struct uthread *uth = get_bsdthread_info(new_thread);
- if (kqr->tr_flags & WORKQ_TR_FLAG_WL_OUTSIDE_QOS) {
- workq_thread_reset_pri(wq, uth, kqr, /*unpark*/ true);
- }
- /*
- * The newly created thread goes through a full bind to the kqwl
- * right upon creation.
- * It then falls back to soft bind/unbind upon wakeup/park.
- */
- kqueue_threadreq_bind_prepost(p, kqr, uth);
- uth->uu_workq_flags |= UT_WORKQ_PERMANENT_BIND;
- }
- }
-
- workq_unlock(wq);
-
- if (ret == KERN_SUCCESS) {
- kqueue_threadreq_bind_commit(p, new_thread);
- }
- return ret;
-}
-
-/*
- * Called with kqlock held. It does not need to take the process wide
- * global workq lock -> making it faster.
- */
-void
-workq_kern_bound_thread_wakeup(struct workq_threadreq_s *kqr)
-{
- struct uthread *uth = get_bsdthread_info(kqr->tr_thread);
- workq_threadreq_param_t trp = kqueue_threadreq_workloop_param(kqr);
-
- /*
- * See "Locking model for accessing uu_workq_flags" for more information
- * on how access to uu_workq_flags for the bound thread is synchronized.
- */
- assert((uth->uu_workq_flags & (UT_WORKQ_RUNNING | UT_WORKQ_DYING)) == 0);
-
- if (trp.trp_flags & TRP_RELEASED) {
- uth->uu_workq_flags |= UT_WORKQ_DYING;
- } else {
- uth->uu_workq_flags |= UT_WORKQ_RUNNING;
- }
-
- workq_thread_wakeup(uth);
-}
-
-/*
- * Called with kqlock held. Dropped before parking.
- * It does not need to take process wide global workqueue
- * lock -> making it faster.
- */
-__attribute__((noreturn, noinline))
-void
-workq_kern_bound_thread_park(struct workq_threadreq_s *kqr)
-{
- struct uthread *uth = get_bsdthread_info(kqr->tr_thread);
- assert(uth == current_uthread());
-
- /*
- * See "Locking model for accessing uu_workq_flags" for more information
- * on how access to uu_workq_flags for the bound thread is synchronized.
- */
- uth->uu_workq_flags &= ~(UT_WORKQ_RUNNING);
-
- thread_disarm_workqueue_quantum(get_machthread(uth));
-
- /*
- * TODO (pavhad) We could do the reusable userspace stack performance
- * optimization here.
- */
-
- kqworkloop_bound_thread_park_prepost(kqr);
- /* KQ_SLEEP bit is set and kqlock is dropped. */
-
- __assert_only kern_return_t kr;
- kr = thread_set_voucher_name(MACH_PORT_NULL);
- assert(kr == KERN_SUCCESS);
-
- /*
- * Bound threads park (and unpark) with the scheduler callback cleared and
- * not counting towards thactive. If we unpark to do work (as opposed to
- * waking up to thread exit), the scheduler callback is added via
- * workq_setup_and_run, and thactive is incremented in
- * workq_bound_thread_setup_and_run.
- */
- thread_sched_call(get_machthread(uth), NULL);
- proc_t p = current_proc();
- struct workqueue *wq = proc_get_wqptr_fast(p);
- _wq_thactive_dec(wq, uth->uu_workq_pri.qos_bucket);
-
- kqworkloop_bound_thread_park_commit(kqr,
- workq_parked_wait_event(uth), workq_bound_thread_unpark_continue);
-
- __builtin_unreachable();
-}
-
-/*
- * To terminate the permenantly bound workqueue thread. It unbinds itself
- * with the kqwl during uthread_cleanup -> kqueue_threadreq_unbind.
- * It is also when it will release its reference on the kqwl.
- */
-__attribute__((noreturn, noinline))
-void
-workq_kern_bound_thread_terminate(struct workq_threadreq_s *kqr)
-{
- proc_t p = current_proc();
- struct uthread *uth = get_bsdthread_info(kqr->tr_thread);
- uint16_t uu_workq_flags_orig;
-
- assert(uth == current_uthread());
-
- /*
- * See "Locking model for accessing uu_workq_flags" for more information
- * on how access to uu_workq_flags for the bound thread is synchronized.
- */
- kqworkloop_bound_thread_terminate(kqr, &uu_workq_flags_orig);
-
- if (uu_workq_flags_orig & UT_WORKQ_WORK_INTERVAL_JOINED) {
- __assert_only kern_return_t kr;
- kr = kern_work_interval_join(get_machthread(uth), MACH_PORT_NULL);
- /* The bound thread un-joins the work interval and drops its +1 ref. */
- assert(kr == KERN_SUCCESS);
- }
-
- /*
- * Drop the voucher now that we are on our way to termination.
- */
- __assert_only kern_return_t kr;
- kr = thread_set_voucher_name(MACH_PORT_NULL);
- assert(kr == KERN_SUCCESS);
-
- uint32_t upcall_flags = WQ_FLAG_THREAD_NEWSPI;
- upcall_flags |= uth->uu_save.uus_workq_park_data.qos |
- WQ_FLAG_THREAD_PRIO_QOS;
-
- thread_t th = get_machthread(uth);
- vm_map_t vmap = get_task_map(proc_task(p));
-
- uint32_t setup_flags = WQ_SETUP_EXIT_THREAD;
-
- if ((uu_workq_flags_orig & UT_WORKQ_NEW) == 0) {
- upcall_flags |= WQ_FLAG_THREAD_REUSE;
- } else {
- /*
- * The bound thread is exiting before ever having gone to userspace. We
- * need pthread to perform initial setup before calling _pthread_exit.
- * TODO(aaron): We should investigate an optimization both here and in
- * workq_unpark_for_death_and_unlock to never send these threads to
- * userspace, instead processing ASTs and calling destroying the stack.
- */
- setup_flags |= WQ_SETUP_FIRST_USE;
-
- /*
- * Typically we'd set the workq_thport in workq_setup_and_run, but we
- * haven't been through that path yet.
- */
- assert(uth->uu_workq_thport == MACH_PORT_NULL);
- /* convert_thread_to_port_immovable() consumes a reference */
- thread_reference(th);
- /* Convert to immovable thread port, then pin the entry */
- uth->uu_workq_thport = ipc_port_copyout_send_pinned(
- convert_thread_to_port_immovable(th),
- get_task_ipcspace(proc_task(p)));
- }
-
- pthread_functions->workq_setup_thread(p, th, vmap, uth->uu_workq_stackaddr,
- uth->uu_workq_thport, 0, setup_flags, upcall_flags);
- __builtin_unreachable();
}
void
@@ -3612,7 +3202,7 @@
flags |= PTHREAD_WQ_QUANTUM_EXPIRY_SHUFFLE;
}
} else if (workq_thread_is_nonovercommit(uth)) {
- if (!workq_constrained_allowance(wq, qos, uth, false, false)) {
+ if (!workq_constrained_allowance(wq, qos, uth, false)) {
flags |= PTHREAD_WQ_QUANTUM_EXPIRY_NARROW;
}
}
@@ -3657,14 +3247,10 @@
return EINVAL;
}
- /*
- * Reset signal mask on the workqueue thread to default state,
- * but do not touch any signals that are marked for preservation.
- */
- sigset_t resettable = uth->uu_sigmask & ~p->p_workq_allow_sigmask;
- if (resettable != (sigset_t)~workq_threadmask) {
+ /* reset signal mask on the workqueue thread to default state */
+ if (uth->uu_sigmask != (sigset_t)(~workq_threadmask)) {
proc_lock(p);
- uth->uu_sigmask |= ~workq_threadmask & ~p->p_workq_allow_sigmask;
+ uth->uu_sigmask = ~workq_threadmask;
proc_unlock(p);
}
@@ -3674,10 +3260,6 @@
* the kqr from this thread.
*/
trp = kqueue_threadreq_workloop_param(kqr);
- }
-
- if (kqr && kqr->tr_flags & WORKQ_TR_FLAG_PERMANENT_BIND) {
- goto handle_stack_events;
}
/*
@@ -3688,8 +3270,6 @@
* - or we proceed to workq_select_threadreq_or_park_and_unlock() who will.
*/
thread_freeze_base_pri(th);
-
-handle_stack_events:
if (kqr) {
uint32_t upcall_flags = WQ_FLAG_THREAD_NEWSPI | WQ_FLAG_THREAD_REUSE;
@@ -3712,7 +3292,7 @@
}
}
error = pthread_functions->workq_handle_stack_events(p, th,
- get_task_map(proc_task(p)), uth->uu_workq_stackaddr,
+ get_task_map(p->task), uth->uu_workq_stackaddr,
uth->uu_workq_thport, eventlist, nevents, upcall_flags);
if (error) {
assert(uth->uu_kqr_bound == kqr);
@@ -3808,7 +3388,7 @@
/*
* Normalize the incoming priority so that it is ordered numerically.
*/
- if (_pthread_priority_has_sched_pri(pri)) {
+ if (pri & _PTHREAD_PRIORITY_SCHED_PRI_FLAG) {
pri &= (_PTHREAD_PRIORITY_SCHED_PRI_MASK |
_PTHREAD_PRIORITY_SCHED_PRI_FLAG);
} else {
@@ -3859,7 +3439,7 @@
break;
}
workq_lock_spin(wq);
- bool should_narrow = !workq_constrained_allowance(wq, qos, uth, false, false);
+ bool should_narrow = !workq_constrained_allowance(wq, qos, uth, false);
workq_unlock(wq);
*retval = should_narrow;
@@ -3950,7 +3530,7 @@
*/
if (!uth->uu_save.uus_workq_park_data.has_stack) {
pthread_functions->workq_markfree_threadstack(p,
- get_machthread(uth), get_task_map(proc_task(p)),
+ get_machthread(uth), get_task_map(p->task),
uth->uu_workq_stackaddr);
}
@@ -4015,13 +3595,11 @@
(uth && uth->uu_workq_pri.qos_bucket == WORKQ_THREAD_QOS_MANAGER);
}
-/* Called with workq lock held. */
static uint32_t
workq_constrained_allowance(struct workqueue *wq, thread_qos_t at_qos,
- struct uthread *uth, bool may_start_timer, bool record_failed_allowance)
+ struct uthread *uth, bool may_start_timer)
{
assert(at_qos != WORKQ_THREAD_QOS_MANAGER);
- uint32_t allowance_passed = 0;
uint32_t count = 0;
uint32_t max_count = wq->wq_constrained_threads_scheduled;
@@ -4040,8 +3618,7 @@
* we need 1 or more constrained threads to return to the kernel before
* we can dispatch additional work
*/
- allowance_passed = 0;
- goto out;
+ return 0;
}
max_count -= wq_max_constrained_threads;
@@ -4078,12 +3655,10 @@
count -= thactive_count + busycount;
WQ_TRACE_WQ(TRACE_wq_constrained_admission | DBG_FUNC_NONE, wq, 2,
thactive_count, busycount);
- allowance_passed = MIN(count, max_count);
- goto out;
+ return MIN(count, max_count);
} else {
WQ_TRACE_WQ(TRACE_wq_constrained_admission | DBG_FUNC_NONE, wq, 3,
thactive_count, busycount);
- allowance_passed = 0;
}
if (may_start_timer) {
@@ -4094,11 +3669,7 @@
workq_schedule_delayed_thread_creation(wq, 0);
}
-out:
- if (record_failed_allowance) {
- wq->wq_exceeded_active_constrained_thread_limit = !allowance_passed;
- }
- return allowance_passed;
+ return 0;
}
static bool
@@ -4112,7 +3683,7 @@
return workq_cooperative_allowance(wq, req->tr_qos, uth, true);
}
if (workq_threadreq_is_nonovercommit(req)) {
- return workq_constrained_allowance(wq, req->tr_qos, uth, true, true);
+ return workq_constrained_allowance(wq, req->tr_qos, uth, true);
}
return true;
@@ -4134,11 +3705,11 @@
* requires us to reeevaluate the next best request for it.
*/
if (uth && workq_thread_is_cooperative(uth)) {
- _wq_cooperative_queue_scheduled_count_dec(wq, uth->uu_workq_pri.qos_req);
+ _wq_cooperative_queue_scheduled_count_dec(wq, uth->uu_workq_pri.qos_bucket);
(void) _wq_cooperative_queue_refresh_best_req_qos(wq);
- _wq_cooperative_queue_scheduled_count_inc(wq, uth->uu_workq_pri.qos_req);
+ _wq_cooperative_queue_scheduled_count_inc(wq, uth->uu_workq_pri.qos_bucket);
} else {
/*
* The old value that was already precomputed should be safe to use -
@@ -4157,7 +3728,7 @@
assert(qos != WORKQ_THREAD_QOS_ABOVEUI);
assert(qos != WORKQ_THREAD_QOS_MANAGER);
- uint8_t bucket = _wq_bucket(qos);
+ int bucket = _wq_bucket(qos);
assert(!STAILQ_EMPTY(&wq->wq_cooperative_queue[bucket]));
return STAILQ_FIRST(&wq->wq_cooperative_queue[bucket]);
@@ -4242,7 +3813,7 @@
return req_pri;
}
- if (workq_constrained_allowance(wq, req_tmp->tr_qos, NULL, true, true)) {
+ if (workq_constrained_allowance(wq, req_tmp->tr_qos, NULL, true)) {
/*
* If the constrained thread request is the best one and passes
* the admission check, pick it.
@@ -4305,7 +3876,7 @@
*
* Note that the creator thread is an overcommit thread.
*/
- thread_qos_t new_thread_qos = uth->uu_workq_pri.qos_req;
+ thread_qos_t new_thread_qos = uth->uu_workq_pri.qos_bucket;
/*
* Anytime a cooperative bucket's schedule count changes, we need to
@@ -4451,7 +4022,7 @@
return req_pri;
}
- if (workq_constrained_allowance(wq, req_tmp->tr_qos, uth, true, true)) {
+ if (workq_constrained_allowance(wq, req_tmp->tr_qos, uth, true)) {
/*
* If the constrained thread request is the best one and passes
* the admission check, pick it.
@@ -4574,8 +4145,7 @@
} else if (!(flags & WORKQ_THREADREQ_CAN_CREATE_THREADS)) {
/* This can drop the workqueue lock, and take it again */
workq_schedule_immediate_thread_creation(wq);
- } else if ((workq_add_new_idle_thread(p, wq,
- workq_unpark_continue, false, NULL) == KERN_SUCCESS)) {
+ } else if (workq_add_new_idle_thread(p, wq)) {
goto again;
} else {
workq_schedule_delayed_thread_creation(wq, 0);
@@ -4629,7 +4199,7 @@
goto park;
}
- struct uu_workq_policy old_pri = uth->uu_workq_pri;
+ thread_qos_t old_thread_bucket = uth->uu_workq_pri.qos_bucket;
uint8_t tr_flags = req->tr_flags;
struct turnstile *req_ts = kqueue_threadreq_get_turnstile(req);
@@ -4664,8 +4234,8 @@
wq->wq_creator = NULL;
_wq_thactive_inc(wq, req->tr_qos);
wq->wq_thscheduled_count[_wq_bucket(req->tr_qos)]++;
- } else if (old_pri.qos_bucket != req->tr_qos) {
- _wq_thactive_move(wq, old_pri.qos_bucket, req->tr_qos);
+ } else if (old_thread_bucket != req->tr_qos) {
+ _wq_thactive_move(wq, old_thread_bucket, req->tr_qos);
}
workq_thread_reset_pri(wq, uth, req, /*unpark*/ true);
@@ -4677,7 +4247,7 @@
*/
bool cooperative_sched_count_changed =
workq_adjust_cooperative_constrained_schedule_counts(wq, uth,
- old_pri.qos_req, tr_flags);
+ old_thread_bucket, tr_flags);
if (workq_tr_is_overcommit(tr_flags)) {
workq_thread_set_type(uth, UT_WORKQ_OVERCOMMIT);
@@ -4697,21 +4267,6 @@
});
}
WQ_TRACE_WQ(TRACE_wq_select_threadreq | DBG_FUNC_NONE, wq, 3, 0, 0);
-
- /*
- * If a cooperative thread was the one which picked up the manager
- * thread request, we need to reevaluate the cooperative pool before
- * it goes and parks.
- *
- * For every other of thread request that it picks up, the logic in
- * workq_threadreq_select should have done this refresh.
- * See workq_push_idle_thread.
- */
- if (cooperative_sched_count_changed) {
- if (req->tr_qos == WORKQ_THREAD_QOS_MANAGER) {
- _wq_cooperative_queue_refresh_best_req_qos(wq);
- }
- }
goto park_thawed;
}
@@ -4867,7 +4422,7 @@
return true;
}
- for (uint8_t i = _wq_bucket(qos); i < WORKQ_NUM_QOS_BUCKETS; i++) {
+ for (int i = _wq_bucket(qos); i < WORKQ_NUM_QOS_BUCKETS; i++) {
cnt += wq->wq_thscheduled_count[i];
}
if (conc <= cnt) {
@@ -4881,7 +4436,7 @@
}
/**
- * parked idle thread wakes up
+ * parked thread wakes up
*/
__attribute__((noreturn, noinline))
static void
@@ -4941,7 +4496,7 @@
workq_setup_and_run(proc_t p, struct uthread *uth, int setup_flags)
{
thread_t th = get_machthread(uth);
- vm_map_t vmap = get_task_map(proc_task(p));
+ vm_map_t vmap = get_task_map(p->task);
if (setup_flags & WQ_SETUP_CLEAR_VOUCHER) {
/*
@@ -4985,12 +4540,12 @@
}
if (uth->uu_workq_thport == MACH_PORT_NULL) {
- /* convert_thread_to_port_immovable() consumes a reference */
+ /* convert_thread_to_port_pinned() consumes a reference */
thread_reference(th);
- /* Convert to immovable thread port, then pin the entry */
- uth->uu_workq_thport = ipc_port_copyout_send_pinned(
- convert_thread_to_port_immovable(th),
- get_task_ipcspace(proc_task(p)));
+ /* Convert to immovable/pinned thread port, but port is not pinned yet */
+ ipc_port_t port = convert_thread_to_port_pinned(th);
+ /* Atomically, pin and copy out the port */
+ uth->uu_workq_thport = ipc_port_copyout_send_pinned(port, get_task_ipcspace(p->task));
}
/* Thread has been set up to run, arm its next workqueue quantum or disarm
@@ -5017,166 +4572,6 @@
pthread_functions->workq_setup_thread(p, th, vmap, uth->uu_workq_stackaddr,
uth->uu_workq_thport, 0, setup_flags, upcall_flags);
- __builtin_unreachable();
-}
-
-/**
- * A wrapper around workq_setup_and_run for permanently bound thread.
- */
-__attribute__((noreturn, noinline))
-static void
-workq_bound_thread_setup_and_run(struct uthread *uth, int setup_flags)
-{
- struct workq_threadreq_s * kqr = uth->uu_kqr_bound;
-
- uint32_t upcall_flags = (WQ_FLAG_THREAD_NEWSPI |
- WQ_FLAG_THREAD_WORKLOOP | WQ_FLAG_THREAD_KEVENT);
- if (workq_tr_is_overcommit(kqr->tr_flags)) {
- workq_thread_set_type(uth, UT_WORKQ_OVERCOMMIT);
- upcall_flags |= WQ_FLAG_THREAD_OVERCOMMIT;
- }
- uth->uu_save.uus_workq_park_data.upcall_flags = upcall_flags;
-
- /*
- * Increment thactive since we've decided this thread should go to
- * userspace. The scheduler callback is set in workq_setup_and_run.
- */
- proc_t p = current_proc();
- struct workqueue *wq = proc_get_wqptr_fast(p);
- _wq_thactive_inc(wq, uth->uu_workq_pri.qos_bucket);
-
- workq_setup_and_run(p, uth, setup_flags);
- __builtin_unreachable();
-}
-
-/**
- * A parked bound thread wakes up for the first time.
- */
-__attribute__((noreturn, noinline))
-static void
-workq_bound_thread_initialize_and_unpark_continue(void *parameter __unused,
- wait_result_t wr)
-{
- /*
- * Locking model for accessing uu_workq_flags :
- *
- * The concurrent access to uu_workq_flags is synchronized with workq lock
- * until a thread gets permanently bound to a kqwl. Post that, kqlock
- * is used for subsequent synchronizations. This gives us a significant
- * benefit by avoiding having to take a process wide workq lock on every
- * wakeup of the bound thread.
- * This flip in locking model is tracked with UT_WORKQ_PERMANENT_BIND flag.
- *
- * There is one more optimization we can perform for when the thread is
- * awakened for running (i.e THREAD_AWAKENED) until it parks.
- * During this window, we know KQ_SLEEP bit is reset so there should not
- * be any concurrent attempts to modify uu_workq_flags by
- * kqworkloop_bound_thread_wakeup because the thread is already "awake".
- * So we can safely access uu_workq_flags within this window without having
- * to take kqlock. This KQ_SLEEP is later set by the bound thread under
- * kqlock on its way to parking.
- */
- struct uthread *uth = get_bsdthread_info(current_thread());
-
- if (__probable(wr == THREAD_AWAKENED)) {
- /* At most one flag. */
- assert((uth->uu_workq_flags & (UT_WORKQ_RUNNING | UT_WORKQ_DYING))
- != (UT_WORKQ_RUNNING | UT_WORKQ_DYING));
-
- assert(workq_thread_is_permanently_bound(uth));
-
- if (uth->uu_workq_flags & UT_WORKQ_RUNNING) {
- assert(uth->uu_workq_flags & UT_WORKQ_NEW);
- uth->uu_workq_flags &= ~UT_WORKQ_NEW;
-
- struct workq_threadreq_s * kqr = uth->uu_kqr_bound;
- if (kqr->tr_work_interval) {
- kern_return_t kr;
- kr = kern_work_interval_explicit_join(get_machthread(uth),
- kqr->tr_work_interval);
- /*
- * The work interval functions requires to be called on the
- * current thread. If we fail here, we record the fact and
- * continue.
- * In the future, we can preflight checking that this join will
- * always be successful when the paird kqwl is configured; but,
- * for now, this should be a rare case (e.g. if you have passed
- * invalid arguments to the join).
- */
- if (kr == KERN_SUCCESS) {
- uth->uu_workq_flags |= UT_WORKQ_WORK_INTERVAL_JOINED;
- /* Thread and kqwl both have +1 ref on the work interval. */
- } else {
- uth->uu_workq_flags |= UT_WORKQ_WORK_INTERVAL_FAILED;
- }
- }
- workq_thread_reset_cpupercent(kqr, uth);
- workq_bound_thread_setup_and_run(uth, WQ_SETUP_FIRST_USE);
- __builtin_unreachable();
- } else {
- /*
- * The permanently bound kqworkloop is getting destroyed so we
- * are woken up to cleanly unbind ourselves from it and terminate.
- * See KQ_WORKLOOP_DESTROY -> workq_kern_bound_thread_wakeup.
- *
- * The actual full unbind happens from
- * uthread_cleanup -> kqueue_threadreq_unbind.
- */
- assert(uth->uu_workq_flags & UT_WORKQ_DYING);
- }
- } else {
- /*
- * The process is getting terminated so we are woken up to die.
- * E.g. SIGKILL'd.
- */
- assert(wr == THREAD_INTERRUPTED);
- /*
- * It is possible we started running as the process is aborted
- * due to termination; but, workq_kern_threadreq_permanent_bind
- * has not had a chance to bind us to the kqwl yet.
- *
- * We synchronize with it using workq lock.
- */
- proc_t p = current_proc();
- struct workqueue *wq = proc_get_wqptr_fast(p);
- workq_lock_spin(wq);
- assert(workq_thread_is_permanently_bound(uth));
- workq_unlock(wq);
-
- /*
- * We do the bind commit ourselves if workq_kern_threadreq_permanent_bind
- * has not done it for us yet so our state is aligned with what the
- * termination path below expects.
- */
- kqueue_threadreq_bind_commit(p, get_machthread(uth));
- }
- workq_kern_bound_thread_terminate(uth->uu_kqr_bound);
- __builtin_unreachable();
-}
-
-/**
- * A parked bound thread wakes up. Not the first time.
- */
-__attribute__((noreturn, noinline))
-static void
-workq_bound_thread_unpark_continue(void *parameter __unused, wait_result_t wr)
-{
- struct uthread *uth = get_bsdthread_info(current_thread());
- assert(workq_thread_is_permanently_bound(uth));
-
- if (__probable(wr == THREAD_AWAKENED)) {
- /* At most one flag. */
- assert((uth->uu_workq_flags & (UT_WORKQ_RUNNING | UT_WORKQ_DYING))
- != (UT_WORKQ_RUNNING | UT_WORKQ_DYING));
- if (uth->uu_workq_flags & UT_WORKQ_RUNNING) {
- workq_bound_thread_setup_and_run(uth, WQ_SETUP_NONE);
- } else {
- assert(uth->uu_workq_flags & UT_WORKQ_DYING);
- }
- } else {
- assert(wr == THREAD_INTERRUPTED);
- }
- workq_kern_bound_thread_terminate(uth->uu_kqr_bound);
__builtin_unreachable();
}
@@ -5224,17 +4619,6 @@
pwqinfo->pwq_state |= WQ_EXCEEDED_TOTAL_THREAD_LIMIT;
}
- uint64_t total_cooperative_threads;
- total_cooperative_threads = workq_num_cooperative_threads_scheduled_total(wq);
- if ((total_cooperative_threads == wq_cooperative_queue_max_size(wq)) &&
- workq_has_cooperative_thread_requests(wq)) {
- pwqinfo->pwq_state |= WQ_EXCEEDED_COOPERATIVE_THREAD_LIMIT;
- }
-
- if (wq->wq_exceeded_active_constrained_thread_limit) {
- pwqinfo->pwq_state |= WQ_EXCEEDED_ACTIVE_CONSTRAINED_THREAD_LIMIT;
- }
-
workq_unlock(wq);
return error;
}
@@ -5265,22 +4649,16 @@
return TRUE;
}
-uint64_t
-workqueue_get_task_ss_flags_from_pwq_state_kdp(void * v)
+uint32_t
+workqueue_get_pwq_state_kdp(void * v)
{
static_assert((WQ_EXCEEDED_CONSTRAINED_THREAD_LIMIT << 17) ==
kTaskWqExceededConstrainedThreadLimit);
static_assert((WQ_EXCEEDED_TOTAL_THREAD_LIMIT << 17) ==
kTaskWqExceededTotalThreadLimit);
static_assert((WQ_FLAGS_AVAILABLE << 17) == kTaskWqFlagsAvailable);
- static_assert(((uint64_t)WQ_EXCEEDED_COOPERATIVE_THREAD_LIMIT << 34) ==
- (uint64_t)kTaskWqExceededCooperativeThreadLimit);
- static_assert(((uint64_t)WQ_EXCEEDED_ACTIVE_CONSTRAINED_THREAD_LIMIT << 34) ==
- (uint64_t)kTaskWqExceededActiveConstrainedThreadLimit);
static_assert((WQ_FLAGS_AVAILABLE | WQ_EXCEEDED_TOTAL_THREAD_LIMIT |
- WQ_EXCEEDED_CONSTRAINED_THREAD_LIMIT |
- WQ_EXCEEDED_COOPERATIVE_THREAD_LIMIT |
- WQ_EXCEEDED_ACTIVE_CONSTRAINED_THREAD_LIMIT) == 0x1F);
+ WQ_EXCEEDED_CONSTRAINED_THREAD_LIMIT) == 0x7);
if (v == NULL) {
return 0;
@@ -5293,29 +4671,17 @@
return 0;
}
- uint64_t ss_flags = kTaskWqFlagsAvailable;
+ uint32_t pwq_state = WQ_FLAGS_AVAILABLE;
if (wq->wq_constrained_threads_scheduled >= wq_max_constrained_threads) {
- ss_flags |= kTaskWqExceededConstrainedThreadLimit;
+ pwq_state |= WQ_EXCEEDED_CONSTRAINED_THREAD_LIMIT;
}
if (wq->wq_nthreads >= wq_max_threads) {
- ss_flags |= kTaskWqExceededTotalThreadLimit;
- }
-
- uint64_t total_cooperative_threads;
- total_cooperative_threads = workq_num_cooperative_threads_scheduled_to_qos_internal(wq,
- WORKQ_THREAD_QOS_MIN);
- if ((total_cooperative_threads == wq_cooperative_queue_max_size(wq)) &&
- workq_has_cooperative_thread_requests(wq)) {
- ss_flags |= kTaskWqExceededCooperativeThreadLimit;
- }
-
- if (wq->wq_exceeded_active_constrained_thread_limit) {
- ss_flags |= kTaskWqExceededActiveConstrainedThreadLimit;
- }
-
- return ss_flags;
+ pwq_state |= WQ_EXCEEDED_TOTAL_THREAD_LIMIT;
+ }
+
+ return pwq_state;
}
void