当前位置: 首页 > 科技观察

Linux内核裁剪框架初探

时间:2023-03-14 14:56:12 科技观察

2000年左右,老码农们还很年轻。当时他们希望用Linux作为手机的操作系统,于是萌生了内核裁剪辅助实践的想法。手机的功能可以在PDA上执行。20多年过去了,Linux已经发生了很大的变化,内核裁剪的技术和方法也有了很大的不同。Linux的内核修剪是为了减少目标应用程序中不需要的内核代码,这在安全性和高性能(快速启动时间和减少内存占用)方面具有显着的好处。然而,现有的内核裁剪技术有其局限性。是否有内核裁剪的框架方法?1.关于KernelTailoring近年来,Linux操作系统的复杂度和规模都在增长。然而,一个应用程序通常只需要操作系统的一部分功能,大量的应用程序需求导致了Linux内核的扩展。操作系统的内核膨胀还会导致安全问题、更长的启动时间和增加的内存使用量。随着服务化和微服务的普及,对内核裁剪的需求进一步增加。在这些场景中,虚拟机运行小型应用程序,每个应用程序都趋于“微小”,内核占用空间小,并且一些虚拟化技术为目标应用程序提供尽可能简单的Linux内核。鉴于操作系统的复杂性,通过手工挑选内核特性来定制内核有些不切实际。例如,Linux有超过14,000多个配置选项(从v4.14开始),并且每年都会引入数百个新选项。KConfig等内核配置器仅提供用于选择配置选项的用户界面。由于可用性差和文档不完整,用户很难选择一个最小且实用的内核配置。现有的内核剪枝技术一般遵循三个步骤:运行目标应用程序的工作负载,跟踪应用程序运行过程中执行的内核代码;分析跟踪并确定目标应用程序所需的内核代码,并组装所需代码的应用程序专用内核剪辑。配置驱动是内核修剪的一般方法,大多数现有工具都使用配置驱动技术,因为它们是少数可以产生稳定内核的技术之一。配置驱动的内核重载根据功能特性减少内核代码,配置选项与内核的功能相对应,修剪后的内核仅包含用于支持目标应用工作负载的功能。然而,尽管内核修剪技术在安全性和性能方面非常有吸引力,但在实践中并未得到广泛采用。这并不是因为缺乏需求,事实上,许多云提供商手工编写Linux内核以减少代码,但通常不如内核修剪技术有效。2.现有内核裁剪技术的局限性现有内核裁剪技术主要有五个局限性。在引导过程中不可见。现有技术只能在内核启动后启动,依赖于ftrace,因此无法观察到启动阶段加载了哪些内核代码。如果内核中缺少关键模块,内核往往无法启动,大量的内核特性只能通过观察启动阶段来捕捉。此外,关于性能和安全性,它们仅在启动时加载(例如用于多核支持的CONFIGSCHEDMC和CONFIGSECURITYNETWORK),导致性能和安全性降低。缺乏对应用程序部署的快速支持。使用现有工具,为内核定制部署新应用程序需要三个步骤:跟踪、分析和组装。这个过程非常耗时,可能需要数小时甚至数天,阻碍了应用部署的敏捷性。粒径较粗。使用ftrace只能在函数级别跟踪内核代码,并且粒度太粗,无法跟踪影响函数内代码的配置选项。覆盖不全。因为使用了动态跟踪,所以需要应用程序工作负载来驱动内核的代码执行以最大化覆盖率。然而,基准覆盖率具有挑战性,而且,如果应用程序具有在跟踪期间未观察到的内核代码,则裁剪后的内核可能会在运行时崩溃。执行依赖不区分,可能存在冗余。即使实际上可能不需要执行的代码也可能包含在内核功能中,例如,可能会初始化第二个文件系统。前三个限制是可以克服的,可以通过改进设计和工具来解决,而后两个限制是不可避免的,需要特定技术之外的努力。3.Linux内核配置3.1配置选项内核配置由一组配置选项组成。内核模块可以有多个选项,每个选项控制哪些代码将包含在最终的内核二进制文件中。配置选项控制内核代码的不同粒度,例如C预处理器实现的语句和函数,以及基于Makefiles的目标文件。C预处理器根据#ifdef/#ifndef选择代码块,配置选项作为宏定义来决定是否在编译后的内核中包含这样的条件代码块,可以是语句粒度也可以是函数粒度。Makefile用于决定是否在编译后的内核中包含某些目标文件,例如CONFIG_CACHEFILES是Makefile中的一个配置选项。现有内核裁剪工具使用的函数级跟踪无法识别语句级配置选项。事实上,Linux4.14中大约30%的C预处理器是语句级选项。随着内核代码和功能特性的快速增长,内核中的配置选项数量也在迅速增加。Linuxkernel3.0以上的版本有10000多个配置选项。3.2.配置语言Linux内核使用KConfig配置语言来指示编译器将哪些代码包含在已编译的内核中,从而允许定义配置选项以及它们之间的依赖关系。KConfig中配置选项的值可以是bool、tristate或constant。bool表示代码被静态编译成内核二进制文件或被排除,而tristate允许代码被编译成可加载的核心模块,即可以在运行时加载的独立对象。常量可以为内核代码变量提供字符串或值。一个选项可以依赖于另一个选项,KConfig通过递归地选择和取消依赖性来使用递归过程。最终的内核配置具有有效的依赖关系,但可能与用户输入不同。3.3.配置模板Linux内核带有许多手工制作的配置模板。然而,由于配置模板的硬编码特性和人工干预的需要,它们无法适应不同的硬件平台并且不了解应用程序的需求。例如,使用tinyconfig构建的内核无法在标准硬件上启动,更不用说支持其他应用程序了。一些工具将localmodconfig视为最小配置,但是,localmodconfig具有与静态配置模板相同的限制,它不启用控制语句级或函数级C预处理器的配置选项,也不处理可加载内核模块。kvmconfig和xenconfig模板是为在KVM和Xen上运行的内核定制的。他们提供领域知识,例如底层虚拟化和硬件环境。3.4.云中的Linux内核配置Linux是云服务中占主导地位的操作系统内核,云提供商在一定程度上已经放弃了通用的Linux内核。云厂商的定制通常是通过直接移除可加载的内核模块来完成的。手动修剪内核模块二进制文件的问题是可能会违反依赖关系。重要的是,可以根据应用程序需求进一步定制内核。例如,AmazonFireCracker内核是一个专用于功能即服务的微型虚拟机,它使用HTTPD作为目标应用程序,可以最大限度地减少内核裁剪,同时保持增强的功能和性能。4.关于内核修剪的思考对于限制一,是否可以使用QEMU的指令级跟踪来实现引导阶段的可见性?这样,可以跟踪内核代码并将其映射到内核配置选项。由于引导阶段对于生成可引导内核至关重要,因此请使用管理程序提供的跟踪功能来获得端到端的可观察性并生成稳定的内核。对于第二个限制,根据在NLP深度学习方面的经验,可以采用离线和在线相结合的方式。给定一组目标应用,可以直接离线生成App配置,然后与基线配置结合,形成完整的内核配置。生成裁剪内核。这种可组合性通过重用应用程序配置和以前构建的文件(例如内核模块)来实现新内核的增量构建。如果目标应用程序的配置是已知的,内核裁剪可以在几十秒内完成。对于限制三,使用指令级跟踪可以解决控制函数内部功能特性的内核配置选项,指令级跟踪的开销对于运行测试套件和性能基准测试来说是可以接受的。关于限制四,使用基于动态的跟踪的一个基本限制是测试套件和基准测试的不完善,许多开源应用程序测试套件的代码覆盖率较低。结合不同的工作负载来驱动应用程序可以在一定程度上缓解这种限制。对于限制五,可以通过删除在基线内核中执行但在实际部署运行时不需要的内核模块来进一步加载具有域特定信息的内核。以Xen和KVM为例,基于xenconfig和kvmconfig配置模板可以进一步减小内核大小。面向应用的内核剪裁可以进一步减小内核大小,甚至可以广泛定制内核代码。5内核裁剪框架初探内核裁剪框架的原理没有改变,仍然是跟踪目标应用工作负载的核心占用情况,以确定所需的内核选项。5.1内核裁剪框架的核心特性内核裁剪框架可能具有以下特性:端到端的可见性。利用虚拟机监控程序的可见性实现端到端的观察,可以跟踪内核启动阶段和应用程序工作负载,可以尝试构建基于QEMU的Linux内核裁剪框架。可组合性。一个核心思想是将内核配置分成几组配置集,这样内核配置可以组合起来用于在给定的部署环境上启动内核,也可以用于目标应用程序所需的配置选项。有两种类型的配置集:基线配置和应用程序配置。基线配置不一定是在特定硬件上启动所需的最少配置集,而是在启动阶段跟踪的一组配置选项。基线配置可以与一个或多个应用程序配置相结合,以生成最终的内核配置。可重用性。只要部署环境和应用程序二进制文件不变,基线和应用程序配置都可以存储在数据库中并重复使用。这种可重用性避免了重复跟踪工作负载运行,使配置集创建成为一次性工作。支持快速应用部署。给定部署环境和目标应用程序,内核裁剪框架可以高效地检索基线配置和应用程序配置,将它们组合成所需的内核配置,然后使用生成的配置构建废弃的内核。细粒度配置跟踪,基于程序计数器的跟踪以识别基于低级代码模式的配置选项。5.2KernelTailoringFramework架构KernelTailoringFramework应该有离线/在线系统,架构如下图所示:通过离线系统,配置跟踪器用于跟踪和记录部署环境所需的配置选项和应用程序。配置生成器将这些选项处理成基线配置和应用程序配置选项,并将它们存储在配置数据库中。通过在线系统,配置组合器使用基线配置和应用程序配置生成目标内核配置,然后内核构建器生成定制的Linux内核。5.3内核裁剪框架的实现可行性配置跟踪配置选项在应用驱动的内核执行过程中进行跟踪,使用PC寄存器捕获正在执行的指令的地址。为确保被跟踪的PC属于目标应用程序而不属于其他进程(例如后台服务),使用自定义初始化脚本,该脚本不会启动任何其他应用程序,只会挂载文件系统/tmp、/proc和/sys,启用网络接口(lo和eth0),最后在内核引导后直接启动应用程序。此外,可能需要禁用内核地址空间配置随机加载才能将地址??正确映射到源代码,但仍然可以在修整的内核中工作。然后,将PC映射到源代码语句。可加载内核模块需要额外处理,/proc/module可用于获取每个已加载内核模块的起始地址,将这些PC映射到内核模块二进制文件中的语句。另一种方法是利用localmodconfig,但是,localmodconfig仅提供模块粒度级别的信息。最后,将语句归于配置。对于基于C预处理器的模式,分析C源文件以提取预处理器指令,然后检查这些指令中的语句是否被执行。对于基于Makefile的模式,确定是否应在目标文件的粒度上选择配置选项。例如,如果使用任何相应的文件(bind.o、achefiles.o或daemon.o),则需要选择CONFIG_CACHEFILES。配置生成基线配置和应用配置在离线系统中生成。如何判断启动阶段结束?可以使用mmap将空存根函数映射到预定义的地址段。上面的初始化脚本在运行目标应用程序之前调用了调用存根函数,因此可以从PC跟踪中的预定义地址识别引导阶段的结束。内核裁剪框架从应用程序中获取配置选项,并过滤掉在引导阶段观察到的与硬件相关的选项。这些硬件特性是根据它们在内核源代码中的位置来定义的。不排除只有在应用程序执行期间才能观察到与硬件相关的选项的可能性,例如它根据需要加载新的设备驱动程序。配置组装将基线配置与一个或多个应用程序配置相结合,以生成用于构建内核的最终配置。首先,将所有配置选项组合成一个初始配置,然后使用SAT求解器解决它们之间的依赖关系。尝试将配置依赖性建模为布尔可满足性问题,有效配置是满足配置选项之间所有指定依赖性的配置。内核配置是基于SAT求解器建模的,因为KConfig不确保包含所有选择的选项,而是取消选择未满足的依赖项。内核构建使用KBuildforLinux根据组装的配置选项构建定制内核,使用现代make的增量构建可以优化构建时间,还可以缓存以前的构建结果(例如目标文件和内核模块)以避免冗余编译和链接。当发生配置更改时,仅重建对配置选项进行更改的模块,而其他文件可以重用。6.小结由于操作系统内核的不稳定、时效性差、完整性问题以及需要人工干预等问题,Linux内核裁剪技术一直没有得到广泛应用。在了解现有技术的局限性后,我们尝试提出一个Linux内核裁剪框架,或许能够解决这些问题。