【Linux内核】trigger_load_balance(struct rq *rq)内核函数
trigger_load_balance(struct rq *rq)
是 Linux 内核中用于触发负载均衡的关键函数,主要在多处理器(SMP)环境下工作。其主要目的是确保系统中各个 CPU 的负载尽可能均匀,从而提高整体性能和响应能力。以下是对该函数的详细解析。
触发时机
在 Linux 内核中,负载均衡通常在以下情况下被触发:
- 时钟中断:每当 CPU 接收到时钟中断时,scheduler_tick() 函数会被调用。在这个函数中,如果系统配置了 CONFIG_SMP,则会调用 trigger_load_balance(rq) 来检查是否需要进行负载均衡。
- 条件判断:在调用 trigger_load_balance() 之前,会检查当前 CPU 是否处于空闲状态,以及是否附加到 NULL 域。如果满足这些条件,则不会进行负载均衡
/*
* Trigger the SCHED_SOFTIRQ if it is time to do periodic load balancing.
*/
void trigger_load_balance(struct rq *rq)
{
/*
* Don't need to rebalance while attached to NULL domain or
* runqueue CPU is not active
*/
if (unlikely(on_null_domain(rq) || !cpu_active(cpu_of(rq))))
return;
if (time_after_eq(jiffies, rq->next_balance))
raise_softirq(SCHED_SOFTIRQ);
nohz_balancer_kick(rq);
}
功能解析
检查条件:首先,函数会检查当前的运行队列(runqueue)是否处于有效状态。如果 CPU 处于空闲状态或不活跃,则直接返回,不进行负载均衡
触发软中断:如果当前时间已经超过了下次负载均衡的预定时间(rq->next_balance),则通过 raise_softirq(SCHED_SOFTIRQ) 触发一个软中断,通知系统执行负载均衡24。
无时钟负载均衡:接着,调用 nohz_balancer_kick(rq) 来处理无时钟(NOHZ)负载均衡的逻辑。这一部分主要用于在 CPU 处于空闲状态时,通过其他活跃 CPU 来唤醒空闲 CPU,以便分担任务13.
负载均衡策略
在 Linux 内核中,负载均衡的策略主要包括:
- 拉取(Pull):轻负载的 CPU 从重负载的 CPU 中拉取任务,这是最常用的方式。
- 推送(Push):重负载的 CPU 向轻负载的 CPU 推送任务,这种方式也被称为主动负载均衡(active load balance)
周期性与空闲平衡
- 周期性负载均衡:通过定期检查和触发 trigger_load_balance() 来实现,确保在有任务运行时进行平衡。
- 空闲平衡:当某个 CPU 进入空闲状态时,其他 CPU 会尝试唤醒它并分配任务,以避免某些 CPU 长时间处于空闲状态而其他 CPU 却过于繁忙
代码解析
on_null_domain(rq)
和 !cpu_active(cpu_of(rq)) 这
两个判断逻辑函数用于检查特定条件,以决定是否进行负载均衡。
以下是对这两个函数的详细解析。
on_null_domain(rq)
#if NR_CPUS > 1
/**
* num_online_cpus() - Read the number of online CPUs
*
* Despite the fact that __num_online_cpus is of type atomic_t, this
* interface gives only a momentary snapshot and is not protected against
* concurrent CPU hotplug operations unless invoked from a cpuhp_lock held
* region.
*/
static inline int on_null_domain(struct rq *rq)
{
return unlikely(!rcu_dereference_sched(rq->sd));
}
/**
* rcu_dereference_sched() - fetch RCU-sched-protected pointer for dereferencing
* @p: The pointer to read, prior to dereferencing
*
* Makes rcu_dereference_check() do the dirty work.
*/
#define rcu_dereference_sched(p) rcu_dereference_sched_check(p, 0)
/**
* rcu_dereference_sched_check() - rcu_dereference_sched with debug checking
* @p: The pointer to read, prior to dereferencing
* @c: The conditions under which the dereference will take place
*
* This is the RCU-sched counterpart to rcu_dereference_check().
* However, please note that starting in v5.0 kernels, vanilla RCU grace
* periods wait for preempt_disable() regions of code in addition to
* regions of code demarked by rcu_read_lock() and rcu_read_unlock().
* This means that synchronize_rcu(), call_rcu, and friends all take not
* only rcu_read_lock() but also rcu_read_lock_sched() into account.
*/
#define rcu_dereference_sched_check(p, c) \
__rcu_dereference_check((p), __UNIQUE_ID(rcu), \
(c) || rcu_read_lock_sched_held(), \
__rcu)
函数用于检查当前运行队列(runqueue)是否处于“空域”状态。具体来说,它判断当前 CPU 是否不属于任何有效的调度域。
空域(Null Domain):在多核系统中,调度域用于组织和管理 CPU 之间的负载均衡。当一个 CPU 被标记为在空域时,意味着它不参与任何负载均衡策略。这通常发生在 CPU 被禁用或未被配置为参与调度时。
影响:如果 CPU 在空域中,则不需要进行负载均衡,因为没有其他 CPU 可以与之进行任务分配。
!cpu_active(cpu_of(rq))
#else
static inline bool cpu_active(unsigned int cpu)
{
return cpumask_test_cpu(cpu, cpu_active_mask);
}
static inline bool cpu_active(unsigned int cpu)
{
return cpu == 0;
}
功能
!cpu_active(cpu_of(rq))
用于检查当前运行队列所对应的 CPU 是否处于活动状态。这个判断确保只有在 CPU 活跃的情况下才会进行负载均衡。
逻辑
CPU 活动状态:Linux 内核维护了一个关于每个 CPU 状态的位图,其中包括“活动”(active)状态。只有处于活动状态的 CPU 才能接收和执行任务。
影响:如果对应的 CPU 不活跃(即没有正在运行的进程),则负载均衡操作将被跳过,因为没有必要将任务分配给一个不活跃的 CPU。
综合分析
这两个判断逻辑结合在一起,确保了系统在进行负载均衡时的高效性和准确性:
- 避免无效操作:如果当前 CPU 在空域或不活跃,则触发负载均衡是没有意义的,这样可以减少不必要的调度开销。
- 优化资源利用:通过确保只有活跃且有效的 CPU 参与负载均衡,可以更好地利用系统资源,提高整体性能。