最近发现了github上有个作者仓库中竟然有多个调度器,分了下源代码。
作者很牛逼。
作者称,baby scheduler是一个很基本且轻量级但有高性能的CPU调度器,可以用于学习Linux内核调度器;也正是因为其是basic的,所以很多features是没有的。
主要分三种:
Deadline Scheduling (dl) - main
Virtual Run Time Scheduling (vrt)
Round Robin Scheduling (rr)
baby调度器的throughput较高,因为其负载均衡只在一个CPU(CPU0)上进行扫描其他CPUs,然后每次迁移至迁移一个task(每个tick周期)。三种调度器的主要区别就是pick next的选择算法不同(排列顺序本身就不同)。
该调度器中CFS的痕迹很多,基本上就是沿用CFS。主要优化点在于负载均衡。
该调度器也是一组CFS补丁,主要专注于interactivity score机制,受ULE调度器(freebsd)启发得来。
主要特征:
- 每个CPU都有自己的runqueue(这个CFS中也是)
- NORMAL runqueue是link-list链表形式的(不是CFS中的红黑树)
- RT和其他runqueue和Linux中已有的类似
- wake up之后preempt抢占的依据是其interactive score得分更高就可以抢占
cacule中的ineractive score就是源自ULE中,这个我分析过。
可以参考ULE源代码。
CacULE调度器并不是为了替换CFS,主要就是改变了pick next的算法,根据interactive score来选择下一个任务。
task type调度器,也是基于CFS打补丁,不过其将task分类,分了5大类,且这种分类是根据task的行为来实时判断:
#define TT_REALTIME 0
#define TT_INTERACTIVE 1
#define TT_NO_TYPE 2
#define TT_CPU_BOUND 3
#define TT_BATCH 4
那么它是如何判断task type的:
判断实时进程:burst before sleep is <= 4ms
#define INTERACTIVE_HRRN 2U
#define RT_WAIT_DELTA 800000U //8ms
#define RT_BURST_DELTA 2000000U //2ms
#define RT_BURST_MAX 4000000U //4ms
if (LEQ(ttn->burst, RT_BURST_MAX) &&
LEQ(ttn->curr_burst, RT_BURST_MAX))
return true;
判断cpu_bound类型:
根据其运行时间占总时间(wait time + vruntime)的百分比,超过80%则是cpu_bound类型
_hrrn_percent = ttn->vruntime * 100ULL;
_hrrn_percent /= ttn->wait_time + ttn->vruntime;
return (GEQ(_hrrn_percent, 80ULL));
判断batch类型:
根据vruntime占总时间百分比,占比50%~80%之间是batch类型
_hrrn = (ttn->wait_time + ttn->vruntime) / ttn->vruntime;
static inline bool is_batch(struct tt_node *ttn, u64 _hrrn)
{
// HRRN > 50%
return (LES(_hrrn, 2ULL));
}
判断interactive类型:
根据vruntime占总时间百分比,小于50%
_hrrn = (ttn->wait_time + ttn->vruntime) / ttn->vruntime;
static inline bool is_interactive(struct tt_node *ttn, u64 now, u64 _hrrn)
{
u64 wait;
if (LES(_hrrn, (u64) INTERACTIVE_HRRN))
return false;
wait = now - se_of(ttn)->exec_start;
if (wait && EQ_D(wait, ttn->prev_wait_time, RT_WAIT_DELTA))
return false;
return true;
}
分出了task type后又是如何干扰调度器的:
tt scheduler中维护了以个candidate候选队列:但只有realtime和interactive两种类型的任务才能进入候选队列。
struct global_candidate {
struct rq *rq;
struct tt_node *candidate;
u64 hrrn;
// for update
raw_spinlock_t lock;
};
然后再newidle_balance或者nohz_idle_balance的时候,idle空闲cpu就能够从candidate候选队列中拉取任务来执行
int idle_pull_global_candidate(struct rq *dist_rq);
此外:tt scheduler在负载均衡是选择cpu时也是根据rq的nr_running来判断load的,并没有CFS的load_avg那么复杂。