struct preemption_info {
u64 stopping_tm_est_ns;
u64 lat_cri;
struct cpu_ctx *cpuc;
};
static u64 get_est_stopping_time(struct task_ctx *taskc, u64 now)
{
return now + taskc->avg_runtime;
}
static bool can_a_preempt_b(struct preemption_info *prm_a,
struct preemption_info *prm_b)
{
if ((prm_a->lat_cri > prm_b->lat_cri) &&
(prm_a->stopping_tm_est_ns < prm_b->stopping_tm_est_ns))
return true;
return false;
}
static bool can_task1_kick_task2(struct preemption_info *prm_task1,
struct preemption_info *prm_task2)
{
return can_a_preempt_b(prm_task1, prm_task2);
}
static bool can_cpu1_kick_cpu2(struct preemption_info *prm_cpu1,
struct preemption_info *prm_cpu2,
struct cpu_ctx *cpuc2)
{
prm_cpu2->stopping_tm_est_ns = cpuc2->stopping_tm_est_ns;
prm_cpu2->lat_cri = cpuc2->lat_cri;
prm_cpu2->cpuc = cpuc2;
if (prm_cpu2->cpuc->lock_holder)
return false;
return can_a_preempt_b(prm_cpu1, prm_cpu2);
}
static bool is_worth_kick_other_task(struct task_ctx *taskc)
{
return (taskc->lat_cri >= sys_stat.thr_lat_cri);
}
static bool can_cpu_be_kicked(u64 now, struct cpu_ctx *cpuc)
{
return cpuc->is_online;
}
static struct cpu_ctx *find_victim_cpu(const struct cpumask *cpumask,
struct task_ctx *taskc, u64 now)
{
struct cpu_ctx *cpuc;
struct preemption_info prm_task, prm_cpus[2], *victim_cpu;
int cpu, nr_cpus;
int i, v = 0, cur_cpu;
int ret;
prm_task.stopping_tm_est_ns = get_est_stopping_time(taskc, now);
prm_task.lat_cri = taskc->lat_cri;
prm_task.cpuc = cpuc = get_cpu_ctx();
if (!cpuc) {
scx_bpf_error("Failed to lookup the current cpu_ctx");
goto null_out;
}
cur_cpu = cpuc->cpu_id;
barrier();
nr_cpus = bpf_cpumask_weight(cpumask);
bpf_for(i, 0, nr_cpus) {
cpu = bpf_cpumask_any_distribute(cpumask);
if (cpu >= nr_cpu_ids || cur_cpu == cpu)
continue;
cpuc = get_cpu_ctx_id(cpu);
if (!cpuc) {
scx_bpf_error("Failed to lookup cpu_ctx: %d", cpu);
goto null_out;
}
if (!can_cpu_be_kicked(now, cpuc))
continue;
ret = can_cpu1_kick_cpu2(&prm_task, &prm_cpus[v], cpuc);
if (ret == true && ++v >= 2)
break;
}
switch(v) {
case 2:
victim_cpu = can_task1_kick_task2(&prm_cpus[0], &prm_cpus[1]) ?
&prm_cpus[0] : &prm_cpus[1];
goto bingo_out;
case 1:
victim_cpu = &prm_cpus[0];
goto bingo_out;
case 0:
goto null_out;
default:
goto null_out;
}
bingo_out:
return victim_cpu->cpuc;
null_out:
return NULL;
}
static void ask_cpu_yield(struct cpu_ctx *victim_cpuc)
{
struct rq *victim_rq;
struct task_struct *victim_p;
victim_rq = scx_bpf_cpu_rq(victim_cpuc->cpu_id);
if (victim_rq && (victim_p = victim_rq->curr)) {
u64 old = victim_cpuc->stopping_tm_est_ns;
if (old) {
bool ret = __sync_bool_compare_and_swap(
&victim_cpuc->stopping_tm_est_ns, old, 0);
if (ret)
WRITE_ONCE(victim_p->scx.slice, 1);
}
}
}
static void try_find_and_kick_victim_cpu(struct task_struct *p,
struct task_ctx *taskc, u64 dsq_id)
{
struct bpf_cpumask *cd_cpumask, *cpumask;
struct cpdom_ctx *cpdomc;
struct cpu_ctx *victim_cpuc;
struct cpu_ctx *cpuc_cur;
u64 now;
if (!is_eligible(taskc))
return;
if (!is_worth_kick_other_task(taskc))
return;
cpuc_cur = get_cpu_ctx();
if (!cpuc_cur)
return;
cpumask = cpuc_cur->tmp_t_mask;
cpdomc = MEMBER_VPTR(cpdom_ctxs, [dsq_id]);
cd_cpumask = MEMBER_VPTR(cpdom_cpumask, [dsq_id]);
if (!cpdomc || !cd_cpumask || !cpumask)
return;
bpf_cpumask_and(cpumask, cast_mask(cd_cpumask), p->cpus_ptr);
now = scx_bpf_now();
victim_cpuc = find_victim_cpu(cast_mask(cpumask), taskc, now);
if (victim_cpuc) {
ask_cpu_yield(victim_cpuc);
cpuc_cur->nr_preempt++;
}
}
static void reset_cpu_preemption_info(struct cpu_ctx *cpuc, bool released)
{
if (released) {
cpuc->lat_cri = SCX_SLICE_INF;
cpuc->stopping_tm_est_ns = 0;
} else {
cpuc->lat_cri = 0;
cpuc->stopping_tm_est_ns = SCX_SLICE_INF;
}
}