|
@@ -41,6 +41,7 @@
|
|
|
#include <linux/debug_locks.h>
|
|
|
#include <linux/lockdep.h>
|
|
|
#include <linux/idr.h>
|
|
|
+#include <linux/jhash.h>
|
|
|
#include <linux/hashtable.h>
|
|
|
#include <linux/rculist.h>
|
|
|
|
|
@@ -80,6 +81,7 @@ enum {
|
|
|
|
|
|
NR_STD_WORKER_POOLS = 2, /* # standard pools per cpu */
|
|
|
|
|
|
+ UNBOUND_POOL_HASH_ORDER = 6, /* hashed by pool->attrs */
|
|
|
BUSY_WORKER_HASH_ORDER = 6, /* 64 pointers */
|
|
|
|
|
|
MAX_IDLE_WORKERS_RATIO = 4, /* 1/4 of busy can be idle */
|
|
@@ -149,6 +151,8 @@ struct worker_pool {
|
|
|
struct ida worker_ida; /* L: for worker IDs */
|
|
|
|
|
|
struct workqueue_attrs *attrs; /* I: worker attributes */
|
|
|
+ struct hlist_node hash_node; /* R: unbound_pool_hash node */
|
|
|
+ int refcnt; /* refcnt for unbound pools */
|
|
|
|
|
|
/*
|
|
|
* The current concurrency level. As it's likely to be accessed
|
|
@@ -156,6 +160,12 @@ struct worker_pool {
|
|
|
* cacheline.
|
|
|
*/
|
|
|
atomic_t nr_running ____cacheline_aligned_in_smp;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Destruction of pool is sched-RCU protected to allow dereferences
|
|
|
+ * from get_work_pool().
|
|
|
+ */
|
|
|
+ struct rcu_head rcu;
|
|
|
} ____cacheline_aligned_in_smp;
|
|
|
|
|
|
/*
|
|
@@ -218,6 +228,11 @@ struct workqueue_struct {
|
|
|
|
|
|
static struct kmem_cache *pwq_cache;
|
|
|
|
|
|
+/* hash of all unbound pools keyed by pool->attrs */
|
|
|
+static DEFINE_HASHTABLE(unbound_pool_hash, UNBOUND_POOL_HASH_ORDER);
|
|
|
+
|
|
|
+static struct workqueue_attrs *unbound_std_wq_attrs[NR_STD_WORKER_POOLS];
|
|
|
+
|
|
|
struct workqueue_struct *system_wq __read_mostly;
|
|
|
EXPORT_SYMBOL_GPL(system_wq);
|
|
|
struct workqueue_struct *system_highpri_wq __read_mostly;
|
|
@@ -1742,7 +1757,7 @@ static struct worker *create_worker(struct worker_pool *pool)
|
|
|
worker->pool = pool;
|
|
|
worker->id = id;
|
|
|
|
|
|
- if (pool->cpu != WORK_CPU_UNBOUND)
|
|
|
+ if (pool->cpu >= 0)
|
|
|
worker->task = kthread_create_on_node(worker_thread,
|
|
|
worker, cpu_to_node(pool->cpu),
|
|
|
"kworker/%d:%d%s", pool->cpu, id, pri);
|
|
@@ -3161,16 +3176,68 @@ fail:
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
+static void copy_workqueue_attrs(struct workqueue_attrs *to,
|
|
|
+ const struct workqueue_attrs *from)
|
|
|
+{
|
|
|
+ to->nice = from->nice;
|
|
|
+ cpumask_copy(to->cpumask, from->cpumask);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Hacky implementation of jhash of bitmaps which only considers the
|
|
|
+ * specified number of bits. We probably want a proper implementation in
|
|
|
+ * include/linux/jhash.h.
|
|
|
+ */
|
|
|
+static u32 jhash_bitmap(const unsigned long *bitmap, int bits, u32 hash)
|
|
|
+{
|
|
|
+ int nr_longs = bits / BITS_PER_LONG;
|
|
|
+ int nr_leftover = bits % BITS_PER_LONG;
|
|
|
+ unsigned long leftover = 0;
|
|
|
+
|
|
|
+ if (nr_longs)
|
|
|
+ hash = jhash(bitmap, nr_longs * sizeof(long), hash);
|
|
|
+ if (nr_leftover) {
|
|
|
+ bitmap_copy(&leftover, bitmap + nr_longs, nr_leftover);
|
|
|
+ hash = jhash(&leftover, sizeof(long), hash);
|
|
|
+ }
|
|
|
+ return hash;
|
|
|
+}
|
|
|
+
|
|
|
+/* hash value of the content of @attr */
|
|
|
+static u32 wqattrs_hash(const struct workqueue_attrs *attrs)
|
|
|
+{
|
|
|
+ u32 hash = 0;
|
|
|
+
|
|
|
+ hash = jhash_1word(attrs->nice, hash);
|
|
|
+ hash = jhash_bitmap(cpumask_bits(attrs->cpumask), nr_cpu_ids, hash);
|
|
|
+ return hash;
|
|
|
+}
|
|
|
+
|
|
|
+/* content equality test */
|
|
|
+static bool wqattrs_equal(const struct workqueue_attrs *a,
|
|
|
+ const struct workqueue_attrs *b)
|
|
|
+{
|
|
|
+ if (a->nice != b->nice)
|
|
|
+ return false;
|
|
|
+ if (!cpumask_equal(a->cpumask, b->cpumask))
|
|
|
+ return false;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* init_worker_pool - initialize a newly zalloc'd worker_pool
|
|
|
* @pool: worker_pool to initialize
|
|
|
*
|
|
|
* Initiailize a newly zalloc'd @pool. It also allocates @pool->attrs.
|
|
|
- * Returns 0 on success, -errno on failure.
|
|
|
+ * Returns 0 on success, -errno on failure. Even on failure, all fields
|
|
|
+ * inside @pool proper are initialized and put_unbound_pool() can be called
|
|
|
+ * on @pool safely to release it.
|
|
|
*/
|
|
|
static int init_worker_pool(struct worker_pool *pool)
|
|
|
{
|
|
|
spin_lock_init(&pool->lock);
|
|
|
+ pool->id = -1;
|
|
|
+ pool->cpu = -1;
|
|
|
pool->flags |= POOL_DISASSOCIATED;
|
|
|
INIT_LIST_HEAD(&pool->worklist);
|
|
|
INIT_LIST_HEAD(&pool->idle_list);
|
|
@@ -3187,12 +3254,136 @@ static int init_worker_pool(struct worker_pool *pool)
|
|
|
mutex_init(&pool->assoc_mutex);
|
|
|
ida_init(&pool->worker_ida);
|
|
|
|
|
|
+ INIT_HLIST_NODE(&pool->hash_node);
|
|
|
+ pool->refcnt = 1;
|
|
|
+
|
|
|
+ /* shouldn't fail above this point */
|
|
|
pool->attrs = alloc_workqueue_attrs(GFP_KERNEL);
|
|
|
if (!pool->attrs)
|
|
|
return -ENOMEM;
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static void rcu_free_pool(struct rcu_head *rcu)
|
|
|
+{
|
|
|
+ struct worker_pool *pool = container_of(rcu, struct worker_pool, rcu);
|
|
|
+
|
|
|
+ ida_destroy(&pool->worker_ida);
|
|
|
+ free_workqueue_attrs(pool->attrs);
|
|
|
+ kfree(pool);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * put_unbound_pool - put a worker_pool
|
|
|
+ * @pool: worker_pool to put
|
|
|
+ *
|
|
|
+ * Put @pool. If its refcnt reaches zero, it gets destroyed in sched-RCU
|
|
|
+ * safe manner.
|
|
|
+ */
|
|
|
+static void put_unbound_pool(struct worker_pool *pool)
|
|
|
+{
|
|
|
+ struct worker *worker;
|
|
|
+
|
|
|
+ spin_lock_irq(&workqueue_lock);
|
|
|
+ if (--pool->refcnt) {
|
|
|
+ spin_unlock_irq(&workqueue_lock);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* sanity checks */
|
|
|
+ if (WARN_ON(!(pool->flags & POOL_DISASSOCIATED)) ||
|
|
|
+ WARN_ON(!list_empty(&pool->worklist))) {
|
|
|
+ spin_unlock_irq(&workqueue_lock);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* release id and unhash */
|
|
|
+ if (pool->id >= 0)
|
|
|
+ idr_remove(&worker_pool_idr, pool->id);
|
|
|
+ hash_del(&pool->hash_node);
|
|
|
+
|
|
|
+ spin_unlock_irq(&workqueue_lock);
|
|
|
+
|
|
|
+ /* lock out manager and destroy all workers */
|
|
|
+ mutex_lock(&pool->manager_arb);
|
|
|
+ spin_lock_irq(&pool->lock);
|
|
|
+
|
|
|
+ while ((worker = first_worker(pool)))
|
|
|
+ destroy_worker(worker);
|
|
|
+ WARN_ON(pool->nr_workers || pool->nr_idle);
|
|
|
+
|
|
|
+ spin_unlock_irq(&pool->lock);
|
|
|
+ mutex_unlock(&pool->manager_arb);
|
|
|
+
|
|
|
+ /* shut down the timers */
|
|
|
+ del_timer_sync(&pool->idle_timer);
|
|
|
+ del_timer_sync(&pool->mayday_timer);
|
|
|
+
|
|
|
+ /* sched-RCU protected to allow dereferences from get_work_pool() */
|
|
|
+ call_rcu_sched(&pool->rcu, rcu_free_pool);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * get_unbound_pool - get a worker_pool with the specified attributes
|
|
|
+ * @attrs: the attributes of the worker_pool to get
|
|
|
+ *
|
|
|
+ * Obtain a worker_pool which has the same attributes as @attrs, bump the
|
|
|
+ * reference count and return it. If there already is a matching
|
|
|
+ * worker_pool, it will be used; otherwise, this function attempts to
|
|
|
+ * create a new one. On failure, returns NULL.
|
|
|
+ */
|
|
|
+static struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs)
|
|
|
+{
|
|
|
+ static DEFINE_MUTEX(create_mutex);
|
|
|
+ u32 hash = wqattrs_hash(attrs);
|
|
|
+ struct worker_pool *pool;
|
|
|
+ struct worker *worker;
|
|
|
+
|
|
|
+ mutex_lock(&create_mutex);
|
|
|
+
|
|
|
+ /* do we already have a matching pool? */
|
|
|
+ spin_lock_irq(&workqueue_lock);
|
|
|
+ hash_for_each_possible(unbound_pool_hash, pool, hash_node, hash) {
|
|
|
+ if (wqattrs_equal(pool->attrs, attrs)) {
|
|
|
+ pool->refcnt++;
|
|
|
+ goto out_unlock;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ spin_unlock_irq(&workqueue_lock);
|
|
|
+
|
|
|
+ /* nope, create a new one */
|
|
|
+ pool = kzalloc(sizeof(*pool), GFP_KERNEL);
|
|
|
+ if (!pool || init_worker_pool(pool) < 0)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ copy_workqueue_attrs(pool->attrs, attrs);
|
|
|
+
|
|
|
+ if (worker_pool_assign_id(pool) < 0)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ /* create and start the initial worker */
|
|
|
+ worker = create_worker(pool);
|
|
|
+ if (!worker)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ spin_lock_irq(&pool->lock);
|
|
|
+ start_worker(worker);
|
|
|
+ spin_unlock_irq(&pool->lock);
|
|
|
+
|
|
|
+ /* install */
|
|
|
+ spin_lock_irq(&workqueue_lock);
|
|
|
+ hash_add(unbound_pool_hash, &pool->hash_node, hash);
|
|
|
+out_unlock:
|
|
|
+ spin_unlock_irq(&workqueue_lock);
|
|
|
+ mutex_unlock(&create_mutex);
|
|
|
+ return pool;
|
|
|
+fail:
|
|
|
+ mutex_unlock(&create_mutex);
|
|
|
+ if (pool)
|
|
|
+ put_unbound_pool(pool);
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
static int alloc_and_link_pwqs(struct workqueue_struct *wq)
|
|
|
{
|
|
|
bool highpri = wq->flags & WQ_HIGHPRI;
|
|
@@ -3217,7 +3408,12 @@ static int alloc_and_link_pwqs(struct workqueue_struct *wq)
|
|
|
if (!pwq)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
- pwq->pool = get_std_worker_pool(WORK_CPU_UNBOUND, highpri);
|
|
|
+ pwq->pool = get_unbound_pool(unbound_std_wq_attrs[highpri]);
|
|
|
+ if (!pwq->pool) {
|
|
|
+ kmem_cache_free(pwq_cache, pwq);
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
list_add_tail_rcu(&pwq->pwqs_node, &wq->pwqs);
|
|
|
}
|
|
|
|
|
@@ -3395,6 +3591,15 @@ void destroy_workqueue(struct workqueue_struct *wq)
|
|
|
kfree(wq->rescuer);
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ * We're the sole accessor of @wq at this point. Directly access
|
|
|
+ * the first pwq and put its pool.
|
|
|
+ */
|
|
|
+ if (wq->flags & WQ_UNBOUND) {
|
|
|
+ pwq = list_first_entry(&wq->pwqs, struct pool_workqueue,
|
|
|
+ pwqs_node);
|
|
|
+ put_unbound_pool(pwq->pool);
|
|
|
+ }
|
|
|
free_pwqs(wq);
|
|
|
kfree(wq);
|
|
|
}
|
|
@@ -3857,19 +4062,14 @@ static int __init init_workqueues(void)
|
|
|
hotcpu_notifier(workqueue_cpu_down_callback, CPU_PRI_WORKQUEUE_DOWN);
|
|
|
|
|
|
/* initialize CPU pools */
|
|
|
- for_each_wq_cpu(cpu) {
|
|
|
+ for_each_possible_cpu(cpu) {
|
|
|
struct worker_pool *pool;
|
|
|
|
|
|
i = 0;
|
|
|
for_each_std_worker_pool(pool, cpu) {
|
|
|
BUG_ON(init_worker_pool(pool));
|
|
|
pool->cpu = cpu;
|
|
|
-
|
|
|
- if (cpu != WORK_CPU_UNBOUND)
|
|
|
- cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu));
|
|
|
- else
|
|
|
- cpumask_setall(pool->attrs->cpumask);
|
|
|
-
|
|
|
+ cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu));
|
|
|
pool->attrs->nice = std_nice[i++];
|
|
|
|
|
|
/* alloc pool ID */
|
|
@@ -3878,14 +4078,13 @@ static int __init init_workqueues(void)
|
|
|
}
|
|
|
|
|
|
/* create the initial worker */
|
|
|
- for_each_online_wq_cpu(cpu) {
|
|
|
+ for_each_online_cpu(cpu) {
|
|
|
struct worker_pool *pool;
|
|
|
|
|
|
for_each_std_worker_pool(pool, cpu) {
|
|
|
struct worker *worker;
|
|
|
|
|
|
- if (cpu != WORK_CPU_UNBOUND)
|
|
|
- pool->flags &= ~POOL_DISASSOCIATED;
|
|
|
+ pool->flags &= ~POOL_DISASSOCIATED;
|
|
|
|
|
|
worker = create_worker(pool);
|
|
|
BUG_ON(!worker);
|
|
@@ -3895,6 +4094,18 @@ static int __init init_workqueues(void)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /* create default unbound wq attrs */
|
|
|
+ for (i = 0; i < NR_STD_WORKER_POOLS; i++) {
|
|
|
+ struct workqueue_attrs *attrs;
|
|
|
+
|
|
|
+ BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
|
|
|
+
|
|
|
+ attrs->nice = std_nice[i];
|
|
|
+ cpumask_setall(attrs->cpumask);
|
|
|
+
|
|
|
+ unbound_std_wq_attrs[i] = attrs;
|
|
|
+ }
|
|
|
+
|
|
|
system_wq = alloc_workqueue("events", 0, 0);
|
|
|
system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
|
|
|
system_long_wq = alloc_workqueue("events_long", 0, 0);
|