mirror of
https://github.com/JakeHillion/scx.git
synced 2024-11-26 11:30:22 +00:00
scx_bpfland: prevent tasks from abusing interactive priority boost
The priority boost for interactive tasks can be exploited to render the system nearly unresponsive by creating numerous tasks that constantly switch between wait/wakeup states. For example, stress tests like `hackbench -l 10000` can significantly degrade system responsiveness. To mitigate this, limit the number of interactive tasks added to the priority queue to 4x the number of online CPUs. This simple approach appears to be a quite effective at identifying potential spam of "fake" interactive tasks, while still prioritizing legitimate interactive tasks. Additionally, periodically refresh the interactive status of the tasks based on their most recent average of voluntary context switches, preventing the interactive status from being too "sticky". Tested-by: Piotr Gorski <lucjan.lucjanov@gmail.com> Signed-off-by: Andrea Righi <righi.andrea@gmail.com>
This commit is contained in:
parent
1babb2b92d
commit
640bd562ff
@ -146,14 +146,27 @@ struct {
|
||||
__type(value, struct task_ctx);
|
||||
} task_ctx_stor SEC(".maps");
|
||||
|
||||
/* Return a local task context from a generic task */
|
||||
/*
|
||||
* Return a local task context from a generic task.
|
||||
*/
|
||||
struct task_ctx *try_lookup_task_ctx(const struct task_struct *p)
|
||||
{
|
||||
return bpf_task_storage_get(&task_ctx_stor,
|
||||
(struct task_struct *)p, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a local task context from a generic task, failing if it doesn't
|
||||
* exist.
|
||||
*/
|
||||
struct task_ctx *lookup_task_ctx(const struct task_struct *p)
|
||||
{
|
||||
struct task_ctx *tctx;
|
||||
|
||||
tctx = bpf_task_storage_get(&task_ctx_stor, (struct task_struct *)p, 0, 0);
|
||||
tctx = try_lookup_task_ctx(p);
|
||||
if (!tctx) {
|
||||
scx_bpf_error("Failed to lookup task ctx for %s", p->comm);
|
||||
scx_bpf_error("Failed to lookup task ctx for %d (%s)",
|
||||
p->pid, p->comm);
|
||||
return NULL;
|
||||
}
|
||||
return tctx;
|
||||
@ -167,6 +180,15 @@ static inline bool is_kthread(const struct task_struct *p)
|
||||
return !!(p->flags & PF_KTHREAD);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the system is capable of accepting more interactive tasks,
|
||||
* false otherwise.
|
||||
*/
|
||||
static bool is_interactive_avail(void)
|
||||
{
|
||||
return scx_bpf_dsq_nr_queued(PRIO_DSQ) < nr_online_cpus * 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Access a cpumask in read-only mode (typically to check bits).
|
||||
*/
|
||||
@ -393,6 +415,24 @@ out_put_cpumask:
|
||||
return cpu;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle synchronous wake-up event for a task.
|
||||
*/
|
||||
static void handle_sync_wakeup(struct task_struct *p)
|
||||
{
|
||||
struct task_ctx *tctx;
|
||||
|
||||
/*
|
||||
* If we are waking up a task set the task as interactive, so that it
|
||||
* can be dispatched as soon as possible on the first CPU available.
|
||||
*/
|
||||
tctx = lookup_task_ctx(p);
|
||||
if (!tctx)
|
||||
return;
|
||||
if (is_interactive_avail())
|
||||
tctx->is_interactive = true;
|
||||
}
|
||||
|
||||
s32 BPF_STRUCT_OPS(bpfland_select_cpu, struct task_struct *p, s32 prev_cpu, u64 wake_flags)
|
||||
{
|
||||
s32 cpu;
|
||||
@ -400,18 +440,8 @@ s32 BPF_STRUCT_OPS(bpfland_select_cpu, struct task_struct *p, s32 prev_cpu, u64
|
||||
/*
|
||||
* Try to prioritize newly awakened tasks.
|
||||
*/
|
||||
if (wake_flags & SCX_WAKE_SYNC) {
|
||||
struct task_ctx *tctx;
|
||||
|
||||
/*
|
||||
* If we are waking up a task and we can't use the current CPU
|
||||
* at least set the task as interactive, so that it can be
|
||||
* dispatched as soon as possible on the first CPU available.
|
||||
*/
|
||||
tctx = lookup_task_ctx(p);
|
||||
if (tctx)
|
||||
tctx->is_interactive = true;
|
||||
}
|
||||
if (wake_flags & SCX_WAKE_SYNC)
|
||||
handle_sync_wakeup(p);
|
||||
|
||||
cpu = pick_idle_cpu(p, prev_cpu, wake_flags);
|
||||
if (cpu >= 0 && !dispatch_direct_cpu(p, cpu, 0)) {
|
||||
@ -449,10 +479,15 @@ void BPF_STRUCT_OPS(bpfland_enqueue, struct task_struct *p, u64 enq_flags)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Always dispatch interactive tasks to the priority DSQ and regular
|
||||
* tasks to the shared DSQ.
|
||||
* Dispatch interactive tasks to the priority DSQ and regular tasks to
|
||||
* the shared DSQ.
|
||||
*
|
||||
* However, avoid queuing too many tasks to the priority DSQ: if we
|
||||
* have a storm of interactive tasks (more than 4x the amount of CPUs
|
||||
* that can consume them) we can just dispatch them to the shared DSQ
|
||||
* and simply rely on the vruntime logic.
|
||||
*/
|
||||
if (tctx->is_interactive) {
|
||||
if (tctx->is_interactive && is_interactive_avail()) {
|
||||
scx_bpf_dispatch_vtime(p, PRIO_DSQ, slice, vtime, enq_flags);
|
||||
__sync_fetch_and_add(&nr_prio_dispatches, 1);
|
||||
} else {
|
||||
@ -622,6 +657,7 @@ void BPF_STRUCT_OPS(bpfland_running, struct task_struct *p)
|
||||
void BPF_STRUCT_OPS(bpfland_stopping, struct task_struct *p, bool runnable)
|
||||
{
|
||||
u64 now = bpf_ktime_get_ns();
|
||||
s64 delta_t;
|
||||
struct task_ctx *tctx;
|
||||
|
||||
__sync_fetch_and_sub(&nr_running, 1);
|
||||
@ -655,9 +691,9 @@ void BPF_STRUCT_OPS(bpfland_stopping, struct task_struct *p, bool runnable)
|
||||
* Evaluate the average number of voluntary context switches per second
|
||||
* using an exponentially weighted moving average, see calc_avg().
|
||||
*/
|
||||
if (now - tctx->nvcsw_ts > NSEC_PER_SEC) {
|
||||
delta_t = (s64)(now - tctx->nvcsw_ts);
|
||||
if (nvcsw_thresh && delta_t > NSEC_PER_SEC) {
|
||||
u64 delta_nvcsw = p->nvcsw - tctx->nvcsw;
|
||||
u64 delta_t = MAX(now - tctx->nvcsw_ts, 1);
|
||||
u64 avg_nvcsw = delta_nvcsw * NSEC_PER_SEC / delta_t;
|
||||
|
||||
tctx->avg_nvcsw = calc_avg(tctx->avg_nvcsw, avg_nvcsw);
|
||||
@ -668,20 +704,35 @@ void BPF_STRUCT_OPS(bpfland_stopping, struct task_struct *p, bool runnable)
|
||||
"curr_avg_nvcsw=%llu avg_nvcsw=%llu",
|
||||
__func__, p->pid, p->comm, delta_nvcsw, delta_t,
|
||||
avg_nvcsw, tctx->avg_nvcsw);
|
||||
/*
|
||||
* Classify interactive tasks based on the average amount of their
|
||||
* voluntary context switches.
|
||||
*
|
||||
* A task can be promoted to interactive if the average of
|
||||
* voluntary context switches per second exceeds nvcsw_thresh.
|
||||
*
|
||||
* However, if the average of voluntarily context switches
|
||||
* drops to zero, the task will be demoted to regular.
|
||||
*/
|
||||
if (tctx->avg_nvcsw >= nvcsw_thresh)
|
||||
tctx->is_interactive = true;
|
||||
else if (tctx->avg_nvcsw == 0)
|
||||
tctx->is_interactive = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Classify interactive tasks based on the average amount of their
|
||||
* voluntary context switches.
|
||||
*/
|
||||
if (nvcsw_thresh && tctx->avg_nvcsw >= nvcsw_thresh)
|
||||
tctx->is_interactive = true;
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(bpfland_enable, struct task_struct *p)
|
||||
{
|
||||
struct task_ctx *tctx;
|
||||
|
||||
/* Initialize task's vruntime */
|
||||
p->scx.dsq_vtime = vtime_now;
|
||||
|
||||
/* Initialize voluntary context switch timestamp */
|
||||
tctx = lookup_task_ctx(p);
|
||||
if (!tctx)
|
||||
return;
|
||||
tctx->nvcsw_ts = bpf_ktime_get_ns();
|
||||
}
|
||||
|
||||
void BPF_STRUCT_OPS(bpfland_cpu_online, s32 cpu)
|
||||
|
Loading…
Reference in New Issue
Block a user