Linux任务调度(1)

分享:  

背景

任务调度是计算机通识课程中的必讲内容,我印象中还有相关的大作业让学生自己实现一个简单的进程调度功能,当然并不是直接在操作系统中去实现,而是用户态模拟进程的状态切换及过程中涉及到的调度逻辑。那为什么工作多年对这个认识也比较深入了,反而又准备写这样跟调度器相关的一个内容呢?因为调度器确实比较有意思,而且我敢说我们并没有挖掘出调度器的所有潜力,多数时候我们只是用了内核提供的默认的调度能力,还是有些可以挖掘来优化服务质量的地方,于是有此文。

ps:联想到当年操作系统老师布置的题目,我写了个demo然后上去讲,情商有点低,讲完还说老师出的题目不太好,老师有点小肚鸡肠直接让我下来,当时愣是没下来还大声问同学们有没有问题,笑死 :)

一个导火索

先抛个有趣的问题,是这样的:一个go线上服务,与其他一些服务混部在16核32GB的机器上,没有用户请求的情况下CPU开销到了6%,perf top可以看到进程主要是在做go runtime work-stealing的事情(真实采样数据现在不好拿到了),大致如下所示吧:

Samples: 800  of event 'cpu-clock:uhpppH', 4000 Hz, Event count (approx.): 125918164 lost: 0/0 drop: 0/0
Overhead  Shared  Symbol
  30.08%  main    [.] runtime.stealWork
   5.76%  main    [.] runtime.futex.abi0
   5.37%  main    [.] runtime.findRunnable
   4.79%  [vdso]  [.] __vdso_clock_gettime
   ...

runtime.stealwork频繁被采样到,说明当前线上服务确实没啥业务逻辑执行,遇到这种情况自然联想到有些代码逻辑导致了频繁的go runtime schedule的操作,那又没有业务请求过来、也没有IO可能阻塞部分协程导致shedule发生的网络事件,那可能是什么呢?

  • 有用户自定义的写成频繁阻塞、唤醒吗?
  • 有用户自定义的定时器处理逻辑吗?

带着这些问题,去了解,最后发现是因为用到的sdk代码里用到了一个1ms触发一次的定时器,至于为什么是1ms,虽然设计上是有必要,但是其sdk内部没有按需创建该timer,导致即使在没有用户请求情况下,sdk代码也在频繁启停timer、导致了go runtime schedule事件的不断触发……事实证明,vsdo_clock_gettime虽然是通过rdtsc优化后的,但是其开销依然不能忽视。

ps:也验证了,sdk内部的1ms定时器调大触发间隔,如1s,CPU开销降为0.3%上下。

引出大问题

上面这个问题,导致部分服务空闲时CPU开销也比较高,这就令人警惕了,这个服务会不会影响其他服务呢?当然这个服务没有明显BUG,空闲时CPU开销高点事后也查清楚了是一个固定的开销,不会因为用户请求量增大就会导致CPU开销也成比例上涨。

但是还是值得更加慎重些:

  • 万一某个用户1创建了大量进程、线程,而另外一个用户2创建了少量进程、线程,内核会如何调度用户1的任务以及用户2的任务呢?会保证调度时用户层级的公平性吗?

    可以做到吗?

  • 万一某个用户下启动了不少服务进程,但是其中一个进程有bug导致了大量的线程创建,那操作系统有能力解决保证进程层级的公平性吗?比如整体来看优先级相同的A进程和B进程,尽管他们线程数不同,但是从进程视角来看它们能获得近似的执行时间。

    可以做到吗?

  • 万一某个用户启动了一组多媒体进程,同时又启动了一组编译测试进程,如何人为地赋予这两组进程在组级别的公平性。

    可以做到吗?

考虑这些问题的原因,是因为我们的测试环境大量使用了混部方案:

  • 混部情况下进程之间容易相互影响,如果一台机器大量占用CPU资源(恶意创建更多进程、线程),会不会影响到其他进程的正常执行呢,这个是肯定的。

  • 那如果我们不混部呢?其实这样并没有实质的解决问题,因为不混部可能存在严重浪费,混部涉及到物理机、虚拟机与容器的关系,仍然需要测算数据确定合理值。

操作系统是如何尝试解决这个问题的呢,能不能解决呢?能解决,那就是schedulers。

任务调度器

现在终于可以言归正传了,这个解决方案就是任务调度器啊!

在接下来的几篇文章里,我们将详细介绍下Linux schedulers是如何演进和变化的,主要内容包括:

然后我们在介绍完这些调度器基本内容后,我们再通过demo来手把手演示下CFS调度器的工作效果。

本文总结

本文从线上问题出发,引出了一个在日常混部服务的过程中的一点担忧,并整理了令人担心的可能会发生的问题,最后回到调度器本身如何应对这个挑战。我们列举了调度器目前曾经出现过的那些具有代表性的Linux schedulers实现,接下来将会介绍给大家。