1. Linux内核调度器介绍
Linux内核调度器是操作系统中一个非常重要的组件,它负责决定哪个进程在何时执行。调度器的主要目标是提高系统的效率和吞吐量,确保资源的合理利用和公平分配。在多核系统中,调度器还负责将进程合理地分配到不同的核心上,以充分利用系统的计算能力。
1.1 调度器的工作原理
Linux内核调度器根据一系列策略来选择下一个要执行的进程。其中最常用的策略是时间片轮转调度(Round-Robin Scheduling)和优先级调度。
struct task_struct {
// 进程的优先级
int static_prio;
// 时间片
int time_slice;
// ...
};
void schedule() {
struct task_struct *next_task = pick_next_task();
switch_to(next_task);
}
上述代码简要展示了调度器的核心工作流程。调度器首先会从就绪队列中选择下一个要执行的进程(pick_next_task()函数),然后通过调用switch_to()函数切换到该进程的上下文。
1.2 CFS调度器
Linux内核中最流行的调度器是完全公平调度(Completely Fair Scheduler,CFS)。CFS调度器以时间片轮转调度为基础,但引入了一种更加公平的调度机制。
struct sched_entity {
// ...
// 该进程已经运行的虚拟时间
u64 vruntime;
// 该进程的调度实体在红黑树上的位置
struct rb_node run_node;
// 该进程的子进程队列
struct list_head children;
// ...
};
void enqueue_task(struct rq *rq, struct task_struct *p) {
unsigned long delta_exec;
struct sched_entity *se = &p->se;
// 公平进程运行时间的计算
delta_exec = account_cfs_rq_runtime(rq, p);
// 调度实体插入红黑树
p->on_rq = 1;
rb_link_node(&se->run_node, rq-> cfs. tasks_timeline.rb_root,
rb_last_explicit(&se->run_node, __run_node_cmp), &se->run_node);
rb_insert_color(&se->run_node, rq-> cfs. tasks_timeline.rb_root);
}
CFS调度器通过红黑树来保存调度实体(sched_entity),它包含了进程的运行状态、优先级以及已经运行的虚拟时间。调度器根据进程的优先级和运行时间来确定下一个要执行的进程。
2. CFS调度器的优点
相比于传统的时间片轮转调度,CFS调度器具有以下几个明显的优点:
2.1 更好的响应能力
传统的时间片轮转调度可能会出现某些进程长时间占用CPU,导致其他进程无法及时响应。而CFS调度器通过公平调度机制,可以使每个进程获得公平的运行时间,提高了系统的响应能力。
2.2 更好的系统负载均衡
CFS调度器具有更好的负载均衡能力,它可以根据进程的优先级和运行时间,将进程动态地分配到不同的核心上。这使得系统的计算能力得到了充分利用,提高了系统的整体性能。
3. 多核调度的挑战
在多核系统中,调度器需要面对更多的挑战。一个最主要的挑战是如何合理地将进程分配到不同的核心上,以充分利用系统的计算资源。
为了解决这个问题,CFS调度器引入了一种负载平衡机制。它通过定期检查各个核心上的调度实体数量,将负载较重的核心上的进程迁移到负载较轻的核心上。
struct load_balance_data {
// ...
// 红黑树根节点
struct rb_root tasks_root;
// ...
};
static inline
struct task_struct *load_balance(struct rq *this_rq, int this_cpu);
{
struct rq *busiest_rq, *idle_rq, *src_rq;
struct task_struct *p;
busiest_rq = find_busiest_queue(this_rq, &src_rq);
idle_rq = find_idlest_queue(this_rq);
spin_unlock(&src_rq->lock);
p = pick_next_task(busiest_rq, prev_task);
if (p) {
task_rq_lock(busiest_rq, p, &flags)
if (likely(task_cpu(p) != cpu)) {
retune->flags = LBF_NONE;
goto out;
}
retune->type = LBF_ALL;
retune->dest = cpu;
retune->src = cpu;
retune->p = p;
}
return p;
}
上述代码展示了CFS调度器中实现负载均衡的部分。在每次调度时,调度器会检查各个核心上的任务数量,并选择负载最重的核心和负载最轻的核心。然后将任务重新分配到较轻的核心。这样可以保持各个核心的负载均衡。