Alibaba Cloud Linux 2 (原 Aliyun Linux 2)是阿里云操作系统团队基于社区版 4.19 LTS 内核打造的一款针对云产品优化的下一代 Linux 操作系统发行版,不仅提供 Linux 社区的最新增强功能,也提供了云上最佳用户体验并针对阿里云基础设施做了深度的优化。今年 3 月 26 日 Alibaba Cloud Linux 2 LTS 正式发布,这是一个重要的里程碑。在 LTS 版本中,阿里云操作系统团队将提供长期的技术支持、稳定的安全更新以及持续的特性与优化。
阿里云同时推出了基于 Alibaba Cloud Linux 2 LTS 的快速启动版镜像,当前正在火热公测中,用户可以在北京、杭州、张家口、香港等可用区的控制台中购买试用。Alibaba Cloud Linux 2 LTS 为快速启动版做了大量优化工作,本文将深度披露相关操作系统侧的优化技术细节。
镜像目前在北京、杭州、张家口、香港等可用区公测,欢迎购买试用。
1、Linux 系统启动流程简介
我们首先需要定义 Linux 系统启动 ,这里我们定义为从系统上电到用户能够登录终端的时间为启动时长,对于云上用户来说,从 Guest OS 启动到用户可以通过 ssh 登录的阶段,为系统启动阶段。通用 Linux 操作系统启动大致分为三个阶段:引导阶段(第一阶段),内核启动阶段(第二阶段)及用户态启动阶段(第三阶段),相关流程如下图所示:
下面来看看各阶段大致的启动流程。
第一阶段
Bootloader 是位于系统引导扇区的一段独立的系统程序,用于系统启动初期的硬件初始化,系统分区识别,系统内核加载及跳转执行。目前应用比较广泛的 bootloader 是用于通用系统的 Grub2 和嵌入式系统的 uboot。Grub2 是多重引导器(multiboot),提供交互界面,默认配置下 Grub2 有 5 秒交互超时时间,启动耗时较长。
第二阶段
Bootloader 加载 Linux 内核(一般为压缩内核 vmlinuz)到内存,并运行内核自解压缩程序,解压后跳转至start_kernel()
,开始内核初始化流程:
第三阶段
Linux 内核完成一系列初始化动作之后,开始运行 init 程序,创建 PID 为 1 的用户态进程,将系统控制权从内核态跳转到用户态。init 程序会继续进行用户态启动流程,开启各种必要的或是预先配置的系统服务,最后启动登陆服务,完成整个系统的启动。
Initrd 与 Switch Root
init 是用户态程序,存放在系统根文件系统(rootfs)里。内核需要先挂载 rootfs,才能运行 init 程序。通用 Linux 发行版需要支持多种磁盘设备及文件系统,这需要内核预加载多种可能的磁盘设备驱动及文件系统相关用户态工具软件才能正确识别 rootfs。而这些驱动及用户态工具一般都存放在 rootfs 中,形成一个循环依赖。
为解决这个问题,initrd 应运而生,将挂载 rootfs 必要的驱动、用户态工具以及其他需要预加载的代码从 rootfs 中抽取出来,并依照 rootfs 的文件结构,打包成一个小的 rootfs,做成一个内存盘(ram disk)。内核在挂载最终的 rootfs 之前,先从内存中挂载 initrd,加载必要的驱动后,先运行 initrd 中的 init 程序,挂载最终的 rootfs。然后执行 switch root 动作,切换至最终的 rootfs。
Alibaba Cloud Linux 2 采用 systemd 来管理用户空间启动流程,systemd 就是 init 程序,initrd 使用压缩格式的 initramfs 文件。因此在加载 initrd 之前,内核需要先解压缩 initramfs。
cloud-init
在阿里云 ECS 环境中,cloud-init 是一个必不可少的初始化配置工具。在实例启动阶段能从多种数据源读取相关数据并据此对虚拟机进行配置,如用户密码,主机名,网络,用户数据等等一些配置。
2、工欲善其事必先利其器
优化系统启动时间,自然需要先对系统启动画像,了解启动时间分布情况,找出系统启动耗时热点。
启动时间测量
Linux 操作系统有如下常见的启动时间测量统计方法:
systemd-analyze : systemd 自带的启动分析工具,能够给出总的启动时间消耗,已经用户态服务启动耗时统计。
dmesg : dmesg 输出内核启动日志,时间戳能够帮助分析内核初始化各阶段耗时情况。配合
-d
选项计算出日志间的时间差,方便快速定位内核启动过程中耗时热点。
initcall_debug : 内核启动参数,开启后会统计内核各初始化函数的耗时情况,相比
dmesg -d
更加精确。printk/trace_printk : 要分析一些启动热点的细化耗时情况时,手动增加一些 printk/trace_printk 探针能够帮助获取时间统计信息。
ftrace : 必要时也可开启内核早期 ftrace 功能,帮助分析热点耗时。不过需要注意开启 ftrace 后可能会导致函数延时增加,因此不宜参考 ftrace 得出函数绝对耗时,可以参照 trace 结果帮助分析热点函数的耗时逻辑。
还有其他一些时间测试方法,以及图形化画像工具,这里不一一介绍。
启动耗时热点分析
对早期 Alibaba Cloud Linux 2 启动进行测量画像后,按耗时排序,得到如下耗时热点:
采样机器:ecs.g6.large 2C8G 实例
可见:
内核启动阶段,鼠标探测耗时占比较高。
总体启动耗时中,一半以上的时间消耗在用户态 cloud-init 进程上;
3、 快速启动优化
常用启动优化方法
常用的启动优化方法大致如下:
瘦身
移除不必要的代码,如模块,服务等,缩减启动初始化步骤;
移除不必要的测试,调式及打印;
精简共享库;
异步、并行
将耗时动作从关键路径移除,延后执行;
将顺序动作并行化执行 ;
原地执行(XIP) : 多用于嵌入式系统;
定制化 : 将通用初始化程序定制化 ;
算法优化 : 改进算法,加速初始化时间。
我们的优化策略
去 initrd
从前面的启动耗时热点分析结果可以看出,initrd 解压缩及 initrd systemd 耗时占了较大比率。
Alibaba Cloud Linux 2 只运行于阿里云云服务器上,系统盘设备基本固定为 virtio-blk 设备,根文件系统格式基本固定为 ext4 文件系统,去掉 initrd 方案应该可行,理论上会优化掉 initramfs unpack(270) + initrdsystemd(560) ~ 800ms 的启动耗时。于是开搞:
可见 initrd systemd 时间确实优化掉了,但是! 总的启动时间并没有理论优化收益 。
继续探索原因,发现是内核启动耗时增加了约 400ms。进一步分析发现,启动耗时热点之一的 mouse probe(600ms),去 initrd 之前是与 initrd systemd 并行执行的。
去掉 initrd 后,这部分时间就直接计入内核启动时间了。抵去优化掉的 initramfs unpacking 的 200ms,内核实际增加了 400ms 左右。
因此,要最大化去 initrd 的优化收益,必须同时解决 mouse probe 的耗时。
延迟 probe
通用 Linux 系统需要支持多种 IO 设备,而鼠标键盘是比较常用的输入设备,特别是鼠标,产品繁多,接口多样。系统启动过程中加载鼠标驱动后,需要扫描多种 IO 总线来探测鼠标设备,这一过程非常耗时。
依据前面提到的优化方法,我们有两种方案:
对云环境定制鼠标驱动,固定探测 virtio 设备;
将鼠标探测从启动关键路径剥离,延迟探测,与后面系统启动服务并行;
第一种方案需要重构相关代码,成本较高;而且定制化限制较多,无法与开源社区协作。因此需要思考第二种方法:延迟探测。一种简单可行的方法是将原本内置(built-in)的设备驱动重新编译为内核模块(kernel module),因内核模块存放在根文件系统,所以加载时机被动推迟到根文件系统挂载之后,此时内核已经启动完成,自然与用户态初始化进程并行执行。
测试结果如下:
带 initrd 启动
不带 initrd 启动
可见,内核启动时间缩减约 200ms,优化掉 initrd systemd 时间;鼠标设备探测延后至 userspace 初始化阶段,导致 userspace 启动时间略有增加。获得预期的启动时间优化。
内存初始化优化
内存初始化也是内核启动热点之一,特别是在大规格实例上,内存初始化耗时占比较高。图中为 750GB 实例内存初始化耗时:
mem init 耗时近 2s
buddy init 耗时 1.8s
内存初始化动作是在内核启动的关键路径上,优化思路是并行初始化。因内存初始化时机较早,系统多 CPU 还未初始化完成,所以需要将内存初始化延后至 CPU 初始化完成之后,采用多线程并行执行内存初始化。这部分工作社区已经完成,通过内核配置deferred struct page init
特性来开启。
开启后,内存初始化延后,按 NUMA node 并行执行:
前半部耗时约 0.2s
后半部耗时约 1.3s
free initmem 修复
早期 Alibaba Cloud Linux 2 启动优化前有一个概率性的启动热点,free initmem 到 buddy 系统时,会大概率出现 200ms 以上延时,dmesg 日志显示如下,耗时超过 200ms:
经分析,发现是社区已知问题,并在新内核已经修复,移植修复后耗时约 5ms,基本消除这部分延时:
ORC unwind 初始化
内核中有一些静态表,需要在内核初始化阶段排序,有些表体积较大,初始化耗时占比也不容小觑。如 ORC unwind 表格初始化排序,耗时约 90ms:
这些静态表格是在内核构建阶段生成,因此可以将排序动作从内核初始化阶段移除,放到内核构建阶段完成,以节省内核初始化时间。经调查发现社区已经有类似的优化方案,异常处理表(exception table)排序移植到了内核构建阶段完成。于是笔者对异常处理表改进,增加了 ORC unwind 表格构建阶段排序优化,相关系列补丁已经被社区接受,合入主线。
优化后基本削减了这部分耗时:
其它优化
另外,启动阶段的 console 输出也是一个相对耗时的动作,因为串行口的波特率是固定的,大量是输出会形成阻塞导致 console enabled 延时较大。例如:
开启 console output, console 耗时 2.6s
配置内核参数
quiet
, 关闭 console output
此外,从前面的数据里可以看到,cloud-init
等服务启动时间占了总启动时间的很大比例。操作系统团队联合阿里云虚拟化、镜像等团队,从底层云基础设施系统到用户态程序都做了大量优化,这些优化极大地提升了用户整体端到端启动的体验。在 Alibaba Cloud Linux 2 快速启动版中,用户可以更直观地感受到这些启动优化带来的效果。
4、下一步工作
在操作系统层面,虽然 Alibaba Cloud Linux 2 LTS 在启动优化已经取得了不错的效果,启动性能得到进一步提升,但仍然还有进一步挖掘的空间。特别是内存初始化这块,仍然是大规格实例启动热点。即便已经开启的 deferred page init 特性,但内存初始仍然限于 node 间并行,而 node 内并行初始化值得进一步挖掘,特别对当前 ECS 实例大都为单 node 实例(NUMA 关闭)的场景下,理论上有更大的收益。
作者介绍:
阿里云操作系统团队,飞能
本文转载自公众号云巅论剑。
原文链接:
评论