On 4 August 2026 11:19:24 BST, Tvrtko Ursulin <[email protected]>
wrote:
>For use cases such as the DRM scheduler submitting work to the GPU on
>behalf of low latency userspace applications, where latter have sufficient
>privileges to have had successfully obtained realtime Vulkan global
>priority, competing with random background CPU load can create large
>latency spikes which gets in the way of a smooth user experience.
>
>For these situations the existing WQ_HIGHPRI does not bring a noticeable
>improvement and a stronger hint is needed.
>
>Lets add WQ_RTPRI which creates workers with a SCHED_FIFO scheduling class
>to improve this.
>
>We use a minimum priority level since we only care about winning the
>contest against normal background CPU load.
>
>Signed-off-by: Tvrtko Ursulin <[email protected]>
>Cc: Boris Brezillon <[email protected]>
>Cc: Bradley Morgan <[email protected]>
>Cc: Chia-I Wu <[email protected]>
>Cc: Liviu Dudau <[email protected]>
>Cc: Matthew Brost <[email protected]>
>Cc: Steven Price <[email protected]>
>Cc: Tejun Heo <[email protected]>
>---
>v2:
> * Limit WQ_RTPRI to unbound workqueues and make it have strict CPU
> affinitity. (Tejun)
> * Fixed commit message typos. (AI)
> * Fixed sysfs handling, max_active setting and user modified nice
> application. (AI)
>
>v3:
> * Fix worker->pool null pointer dereference race by moving the
> global decrement to detach_dying_workers().
> * Rebase for upstream changes.
>
>v4:
> * Fixed onion unwind.
> * Moved affinity setting to default attributes.
>
>v5:
> * Dropped global and local limits.
> * Documented in workqueue.rst.
> * Added NR_WQ_ATTRIBUTES.
> * Reverted BH handling changes.
>---
> Documentation/core-api/workqueue.rst | 5 ++
> include/linux/workqueue.h | 23 ++++++--
> kernel/workqueue.c | 80 ++++++++++++++++++++++------
> 3 files changed, 86 insertions(+), 22 deletions(-)
>
>diff --git a/Documentation/core-api/workqueue.rst
>b/Documentation/core-api/workqueue.rst
>index 411e1b28b8de..6b6179e0a38a 100644
>--- a/Documentation/core-api/workqueue.rst
>+++ b/Documentation/core-api/workqueue.rst
>@@ -225,6 +225,11 @@ resources, scheduled and executed.
> each other. Each maintains its separate pool of workers and
> implements concurrency management among its workers.
>
>+``WQ_RTPRI``
>+ Real time priority workqueues must be created as unbound and have the strict
>+ CPU affinity set. Their worker threads use the FIFO scheduling policy with
>+ the lowest priority.
>+
> ``WQ_CPU_INTENSIVE``
> Work items of a CPU intensive wq do not contribute to the
> concurrency level. In other words, runnable CPU intensive
>diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h
>index a283766a192a..e9ab53568e0c 100644
>--- a/include/linux/workqueue.h
>+++ b/include/linux/workqueue.h
>@@ -140,6 +140,13 @@ enum wq_affn_scope {
> WQ_AFFN_NR_TYPES,
> };
>
>+enum wq_priority {
>+ WQ_PRIO_NORMAL = 0,
>+ WQ_PRIO_HIGH = 1,
>+ WQ_PRIO_RT = 2,
>+ NUM_WQ_PRIO, /* Keep last */
>+};
>+
> /**
> * struct workqueue_attrs - A struct for workqueue attributes.
> *
>@@ -147,7 +154,12 @@ enum wq_affn_scope {
> */
> struct workqueue_attrs {
> /**
>- * @nice: nice level
>+ * @prio: priority level
>+ */
>+ enum wq_priority prio;
>+
>+ /**
>+ * @nice: nice level for WQ_PRIO_HIGH
> */
> int nice;
>
>@@ -374,8 +386,9 @@ enum wq_flags {
> WQ_FREEZABLE = 1 << 2, /* freeze during suspend */
> WQ_MEM_RECLAIM = 1 << 3, /* may be used for memory reclaim */
> WQ_HIGHPRI = 1 << 4, /* high priority */
>- WQ_CPU_INTENSIVE = 1 << 5, /* cpu intensive workqueue */
>- WQ_SYSFS = 1 << 6, /* visible in sysfs, see
>workqueue_sysfs_register() */
>+ WQ_RTPRI = 1 << 5, /* real-time priority, valid only
>with WQ_UNBOUND */
>+ WQ_CPU_INTENSIVE = 1 << 6, /* cpu intensive workqueue */
>+ WQ_SYSFS = 1 << 7, /* visible in sysfs, see
>workqueue_sysfs_register() */
>
> /*
> * Per-cpu workqueues are generally preferred because they tend to
>@@ -402,8 +415,8 @@ enum wq_flags {
> *
> * http://thread.gmane.org/gmane.linux.kernel/1480396
> */
I feel we could clean this link up.
The link doesn't even resolve, smh man. + Its http and all of that nonsense
>- WQ_POWER_EFFICIENT = 1 << 7,
>- WQ_PERCPU = 1 << 8, /* bound to a specific cpu */
>+ WQ_POWER_EFFICIENT = 1 << 8,
>+ WQ_PERCPU = 1 << 9, /* bound to a specific cpu */
>
> __WQ_DESTROYING = 1 << 15, /* internal: workqueue is destroying
> */
> __WQ_DRAINING = 1 << 16, /* internal: workqueue is draining */
>diff --git a/kernel/workqueue.c b/kernel/workqueue.c
>index 78068ae8f28a..6b60380a8742 100644
>--- a/kernel/workqueue.c
>+++ b/kernel/workqueue.c
>@@ -105,6 +105,7 @@ enum work_cancel_flags {
>
> enum wq_internal_consts {
> NR_STD_WORKER_POOLS = 2, /* # standard pools per cpu */
>+ NR_WQ_ATTRIBUTES = NUM_WQ_PRIO, /* # of attributes */
>
> UNBOUND_POOL_HASH_ORDER = 6, /* hashed by pool->attrs */
> BUSY_WORKER_HASH_ORDER = 6, /* 64 pointers */
>@@ -509,10 +510,10 @@ static DEFINE_IDR(worker_pool_idr); /* PR: idr of
>all pools */
> static DEFINE_HASHTABLE(unbound_pool_hash, UNBOUND_POOL_HASH_ORDER);
>
> /* I: attributes used when instantiating standard unbound pools on demand
> */
>-static struct workqueue_attrs *unbound_std_wq_attrs[NR_STD_WORKER_POOLS];
>+static struct workqueue_attrs *unbound_std_wq_attrs[NR_WQ_ATTRIBUTES];
>
> /* I: attributes used when instantiating ordered pools on demand */
>-static struct workqueue_attrs *ordered_wq_attrs[NR_STD_WORKER_POOLS];
>+static struct workqueue_attrs *ordered_wq_attrs[NR_WQ_ATTRIBUTES];
>
> /*
> * I: kthread_worker to release pwq's. pwq release needs to be bounced to a
>@@ -2871,7 +2872,11 @@ static struct worker *create_worker(struct worker_pool
>*pool)
> goto fail;
> }
>
>- set_user_nice(worker->task, pool->attrs->nice);
>+ if (pool->attrs->prio == WQ_PRIO_RT)
>+ sched_set_fifo_low(worker->task);
>+ else
>+ set_user_nice(worker->task, pool->attrs->nice);
>+
> kthread_bind_mask(worker->task, pool_allowed_cpus(pool));
> }
>
>@@ -4780,6 +4785,7 @@ struct workqueue_attrs
>*alloc_workqueue_attrs_noprof(void)
> static void copy_workqueue_attrs(struct workqueue_attrs *to,
> const struct workqueue_attrs *from)
> {
>+ to->prio = from->prio;
> to->nice = from->nice;
> cpumask_copy(to->cpumask, from->cpumask);
> cpumask_copy(to->__pod_cpumask, from->__pod_cpumask);
>@@ -4811,6 +4817,7 @@ static u32 wqattrs_hash(const struct workqueue_attrs
>*attrs)
> {
> u32 hash = 0;
>
>+ hash = jhash_1word(attrs->prio, hash);
> hash = jhash_1word(attrs->nice, hash);
> hash = jhash_1word(attrs->affn_strict, hash);
> hash = jhash(cpumask_bits(attrs->__pod_cpumask),
>@@ -4825,6 +4832,8 @@ static u32 wqattrs_hash(const struct workqueue_attrs
>*attrs)
> static bool wqattrs_equal(const struct workqueue_attrs *a,
> const struct workqueue_attrs *b)
> {
>+ if (a->prio != b->prio)
>+ return false;
> if (a->nice != b->nice)
> return false;
> if (a->affn_strict != b->affn_strict)
>@@ -5601,11 +5610,17 @@ static void unbound_wq_update_pwq(struct
>workqueue_struct *wq, int cpu)
>
> static int alloc_and_link_pwqs(struct workqueue_struct *wq)
> {
>- bool highpri = wq->flags & WQ_HIGHPRI;
>- int cpu, ret;
>+ int prio, cpu, ret;
>
> lockdep_assert_held(&wq_pool_mutex);
>
>+ if (wq->flags & WQ_RTPRI)
>+ prio = WQ_PRIO_RT;
>+ else if (wq->flags & WQ_HIGHPRI)
>+ prio = WQ_PRIO_HIGH;
>+ else
>+ prio = WQ_PRIO_NORMAL;
>+
> wq->cpu_pwq = alloc_percpu(struct pool_workqueue *);
> if (!wq->cpu_pwq)
> goto enomem;
>@@ -5622,7 +5637,7 @@ static int alloc_and_link_pwqs(struct workqueue_struct
>*wq)
> struct pool_workqueue **pwq_p;
> struct worker_pool *pool;
>
>- pool = &(per_cpu_ptr(pools, cpu)[highpri]);
>+ pool = &(per_cpu_ptr(pools, cpu)[prio]);
> pwq_p = per_cpu_ptr(wq->cpu_pwq, cpu);
>
> *pwq_p = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL,
>@@ -5642,14 +5657,14 @@ static int alloc_and_link_pwqs(struct workqueue_struct
>*wq)
> if (wq->flags & __WQ_ORDERED) {
> struct pool_workqueue *dfl_pwq;
>
>- ret = apply_workqueue_attrs_locked(wq,
>ordered_wq_attrs[highpri]);
>+ ret = apply_workqueue_attrs_locked(wq, ordered_wq_attrs[prio]);
> /* there should only be single pwq for ordering guarantee */
> dfl_pwq = rcu_access_pointer(wq->dfl_pwq);
> WARN(!ret && (wq->pwqs.next != &dfl_pwq->pwqs_node ||
> wq->pwqs.prev != &dfl_pwq->pwqs_node),
> "ordering guarantee broken for workqueue %s\n", wq->name);
> } else {
>- ret = apply_workqueue_attrs_locked(wq,
>unbound_std_wq_attrs[highpri]);
>+ ret = apply_workqueue_attrs_locked(wq,
>unbound_std_wq_attrs[prio]);
> }
>
> if (ret)
>@@ -5814,6 +5829,12 @@ static struct workqueue_struct *__alloc_workqueue(const
>char *fmt,
> return NULL;
> }
>
>+ if (flags & WQ_RTPRI) {
>+ if (WARN_ON_ONCE((flags & (WQ_HIGHPRI | WQ_UNBOUND)) !=
>+ WQ_UNBOUND))
>+ return NULL;
>+ }
>+
> /* see the comment above the definition of WQ_POWER_EFFICIENT */
> if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
> flags = (flags & ~WQ_PERCPU) | WQ_UNBOUND;
>@@ -7302,7 +7323,11 @@ static ssize_t wq_nice_show(struct device *dev, struct
>device_attribute *attr,
> int written;
>
> mutex_lock(&wq->mutex);
>- written = scnprintf(buf, PAGE_SIZE, "%d\n", wq->unbound_attrs->nice);
>+ if (wq->unbound_attrs->prio != WQ_PRIO_RT)
>+ written = scnprintf(buf, PAGE_SIZE, "%d\n",
>+ wq->unbound_attrs->nice);
>+ else
>+ written = -EINVAL;
> mutex_unlock(&wq->mutex);
>
> return written;
>@@ -7328,13 +7353,20 @@ static ssize_t wq_nice_store(struct device *dev,
>struct device_attribute *attr,
> {
> struct workqueue_struct *wq = dev_to_wq(dev);
> struct workqueue_attrs *attrs;
>- int ret = -ENOMEM;
>+ int ret;
>
> mutex_lock(&wq_pool_mutex);
>
> attrs = wq_sysfs_prep_attrs(wq);
>- if (!attrs)
>+ if (!attrs) {
>+ ret = -ENOMEM;
> goto out_unlock;
>+ }
>+
>+ if (attrs->prio == WQ_PRIO_RT) {
>+ ret = -EINVAL;
>+ goto out_unlock;
>+ }
>
> if (sscanf(buf, "%d", &attrs->nice) == 1 &&
> attrs->nice >= MIN_NICE && attrs->nice <= MAX_NICE)
>@@ -7942,12 +7974,13 @@ static void __init restrict_unbound_cpumask(const char
>*name, const struct cpuma
> cpumask_and(wq_unbound_cpumask, wq_unbound_cpumask, mask);
> }
>
>-static void __init init_cpu_worker_pool(struct worker_pool *pool, int cpu,
>int nice)
>+static void __init init_cpu_worker_pool(struct worker_pool *pool, int cpu,
>enum wq_priority prio, int nice)
> {
> BUG_ON(init_worker_pool(pool));
> pool->cpu = cpu;
> cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu));
> cpumask_copy(pool->attrs->__pod_cpumask, cpumask_of(cpu));
>+ pool->attrs->prio = prio;
> pool->attrs->nice = nice;
> pool->attrs->affn_strict = true;
> pool->node = cpu_to_node(cpu);
>@@ -7971,7 +8004,8 @@ static void __init init_cpu_worker_pool(struct
>worker_pool *pool, int cpu, int n
> void __init workqueue_init_early(void)
> {
> struct wq_pod_type *pt = &wq_pod_types[WQ_AFFN_SYSTEM];
>- int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
>+ int std_prio[NR_WQ_ATTRIBUTES] = { 0, WQ_PRIO_HIGH, WQ_PRIO_RT };
>+ int std_nice[NR_WQ_ATTRIBUTES] = { 0, HIGHPRI_NICE_LEVEL, 0 };
> void (*irq_work_fns[NR_STD_WORKER_POOLS])(struct irq_work *) =
> { bh_pool_kick_normal, bh_pool_kick_highpri };
> int i, cpu;
>@@ -8023,23 +8057,34 @@ void __init workqueue_init_early(void)
>
> i = 0;
> for_each_bh_worker_pool(pool, cpu) {
>- init_cpu_worker_pool(pool, cpu, std_nice[i]);
>+ init_cpu_worker_pool(pool, cpu, std_prio[i],
>std_nice[i]);
> pool->flags |= POOL_BH;
> init_irq_work(bh_pool_irq_work(pool), irq_work_fns[i]);
> i++;
> }
>
> i = 0;
>- for_each_cpu_worker_pool(pool, cpu)
>- init_cpu_worker_pool(pool, cpu, std_nice[i++]);
>+ for_each_cpu_worker_pool(pool, cpu) {
>+ init_cpu_worker_pool(pool, cpu, std_prio[i],
>std_nice[i]);
>+ i++;
>+ }
> }
>
> /* create default unbound and ordered wq attrs */
>- for (i = 0; i < NR_STD_WORKER_POOLS; i++) {
>+ for (i = 0; i < NR_WQ_ATTRIBUTES; i++) {
> struct workqueue_attrs *attrs;
>
> BUG_ON(!(attrs = alloc_workqueue_attrs()));
>+ attrs->prio = std_prio[i];
> attrs->nice = std_nice[i];
>+ if (i == WQ_PRIO_RT) {
>+ /*
>+ * RT workqueues have strict CPU affinity for low
>+ * latency execution.
>+ */
>+ attrs->affn_scope = WQ_AFFN_CPU;
>+ attrs->affn_strict = true;
>+ }
> unbound_std_wq_attrs[i] = attrs;
>
> /*
>@@ -8047,6 +8092,7 @@ void __init workqueue_init_early(void)
> * guaranteed by max_active which is enforced by pwqs.
> */
> BUG_ON(!(attrs = alloc_workqueue_attrs()));
>+ attrs->prio = std_prio[i];
> attrs->nice = std_nice[i];
> attrs->ordered = true;
> ordered_wq_attrs[i] = attrs;
>
Thanks!