Linux|龙蜥开源Plugsched:首次实现 Linux kernel 调度器热升级( 二 )


【Linux|龙蜥开源Plugsched:首次实现 Linux kernel 调度器热升级】安装该调度器模块后 , 在系统中创建两个 cpu cgroup A 和 B , 并绑定同一个 CPU , 分别设置最高和最低优先级 , 然后各自创建一个 busy loop 任务 。 理论上 , 当 A 中有任务执行时 , B 中的任务会停止运行 。 此时用 top 工具查看该 CPU 利用率 , 发现只有一个利用率是 100% 的 busy loop 任务 , 说明模块中的 Group Identity 特性已生效;而动态卸载该模块后 , 出现了两个各占 50% CPU 的 busy loop 任务 , 说明模块已经失效 。
应用案例 2:与内核发布解耦及定制化调度器
阿里云某客户使用的旧版本内核 , 由于该内核调度器对 load 的统计算法不合理 , 导致 CPU 利用率过高 , 虽然修复补丁已经合入内核主线 , 但是新内核版本还未发布 , 而且业务方也不打算更换内核 , 因为集群中部署了大量的业务 , 升级内核成本较高 。
除此之外 , 客户的内核开发人员对其混部业务场景(Group Identity 调度特性)进行了针对性的优化 , 想将优化内容合入内核主线 。 但是 , 阿里云内核开发人员发现 , 该优化内容在其它场景中有性能回退 , 属于非通用优化 , 因此不允许将优化内容合入主线 。 于是 , 客户的内核开发人员使用 plugsched 将优化修复内容全部移植到调度器模块中 , 最后规模部署 。 该案例可以体现出 plugsched 的与内核发布解耦、定制化调度器的优势 。
那么 Plugsched 该如何使用? 目前 , plugsched 默认支持 Anolis OS 7(内核ANCK-4.19版本) 系统 , 其它操作系统需要调整边界配置 。 为了减轻搭建运行环境的复杂度 , 我们提供了容器镜像和 Dockerfile , 开发人员不需要自己去搭建开发环境 。 为了方便演示 , 这里购买了一台阿里云 ECS(64CPU + 128GB) , 并安装 Anolis OS 7.9 ANCK 系统发行版 , 我们将演示对其内核调度器进行热升级的过程 。
1、登陆云服务器后 , 先安装一些必要的基础软件包:
# yum install anolis-repos -y # yum install podman kernel-debuginfo-$(uname -r) kernel-devel-$(uname -r) --enablerepo=Plus-debuginfo --enablerepo=Plus -y 2、创建临时工作目录 , 下载系统内核的 SRPM 包:
# mkdir /tmp/work # uname -r 4.19.91-25.2.an7.x86_64 # cd /tmp/work # wget https://mirrors.openanolis.cn/anolis/7.9/Plus/source/Packages/kernel-4.19.91-25.2.an7.src.rpm 3、启动并进入容器:
# podman run -itd --name=plugsched -v /tmp/work:/tmp/work -v /usr/src/kernels:/usr/src/kernels -v /usr/lib/debug/lib/modules:/usr/lib/debug/lib/modules docker.io/plugsched/plugsched-sdk # podman exec -it plugsched bash # cd /tmp/work 4、提取 4.19.91-25.1.al7.x86_64 内核源码:
# plugsched-cli extract_src kernel-4.19.91-25.2.an7.src.rpm ./kernel 5、进行边界划分与提取:
# plugsched-cli init 4.19.91-25.2.an7.x86_64 ./kernel ./scheduler 6、提取后的调度器模块代码在 ./scheduler/kernel/sched/mod 中 , 简单修改 __schedule 函数 , 然后编译打包成调度器 rpm 包:
diff --git a/kernel/sched/mod/core.c b/kernel/sched/mod/core.cindex f337607..88fe861 100644--- a/kernel/sched/mod/core.c+++ b/kernel/sched/mod/core.c@@ -32356 +32358 @@ static void __sched notrace __schedule(bool preempt) struct rq *rq; int cpu; + printk_once(\"scheduler: Hi I am the new scheduler!\\");+ cpu = smp_processor_id(); rq = cpu_rq(cpu); prev = rq-curr; # plugsched-cli build /tmp/work/scheduler 7、将生成的 rpm 包拷贝到宿主机 , 退出容器 , 并安装调度器包 , 调度器日志显示新修改的调度器已经生效:
# cp /usr/local/lib/plugsched/rpmbuild/RPMS/x86_64/scheduler-xxx-4.19.91-25.2.an7.yyy.x86_64.rpm /tmp/work# exitexit# rpm -ivh /tmp/work/scheduler-xxx-4.19.91-25.2.an7.yyy.x86_64.rpm# dmesg | tail -n 10[ 878.915006
scheduler: total initialization time is 5780743 ns[ 878.915006
scheduler module is loading[ 878.915232
scheduler: Hi I am the new scheduler![ 878.915232
scheduler: Hi I am the new scheduler![ 878.915990
scheduler load: current cpu number is 64[ 878.915990
scheduler load: current thread number is 626[ 878.915991
scheduler load: stop machine time is 243138 ns[ 878.915991
scheduler load: stop handler time is 148542 ns[ 878.915992
scheduler load: stack check time is 86532 ns[ 878.915992
scheduler load: all the time is 982076 ns 我们通过以上知道了 Plugsched 是什么、应用案例 , 那它实现原理是什么?
调度器子系统在内核中并非是一个独立的模块 , 而是内嵌在内核中 , 与内核其它部分紧密相连 。 Plugsched 采用“模块化”的思想:它提供了边界划分程序 , 确定调度器子系统的边界 , 把调度器从内核代码中提取到独立的目录中 , 开发人员可对提取出的调度器代码进行修改 , 然后编译成新的调度器内核模块 , 动态替换内核中旧的调度器 。 对子系统进行边界划分和代码提取 , 需要处理函数和数据 , 而后生成一个独立的模块 。