导读
因为操作系统内核的不稳定性、时效性较差、完整性问题以及须要人工干预等诱因,Linux内核剪裁技术没有得到广泛的应用。了解了现有技术的局限性,尝试提出一个Linux内核剪裁框架,其实可以解决那些问题。
大概是在2000年的时侯,老码农还很年青,当时希望将Linux作为手机的操作系统,于是才有了进行内核裁切的看法并辅助实践,疗效尚好linux内核定制,早已能在PDA上执行手机的功能了。转眼20多年过去了,Linux早已有了太大的变化,内核裁切的技术和方法也有了较大的不同。
Linux的内核剪裁是为了降低目标应用中不须要的内核代码北京linux培训,在安全性和高性能(快速启动时间和降低显存占用)方面有着明显的用处。并且,现有的内核剪裁技术有其局限性,有没有内核剪裁的框架化方式呢?
1.关于内核剪裁
近些年来,Linux操作系统在复杂性和规模上都在下降。但是,一个应用程序一般只须要一部份OS功能,诸多的应用需求致使了Linux内核的膨胀。操作系统的内核膨胀同样引起了安全性隐患、启动时间变长和显存使用的降低。
随着服务化和微服务的流行,进一步提出了对内核裁切的需求。在这种场景中,虚拟机运行大型应用程序,每位应用程序常常是“微型”的,内核占用较小,一些虚拟化技术要为目标应用程序提供最简单的Linux内核。
鉴于操作系统的复杂性,通过手工选购内核特点来裁切内核有些不切实际。诸如,Linux有超过14,000+个配置选项(截止v4.14),每年还会引入数百个新选项。内核配置器(比如KConfig)只提供用于选择配置选项的用户界面。鉴于糟糕的可用性和文档的不完整性,用户很难选择最小且实用的内核配置。
现有的内核剪裁技术通常遵守三个步骤:
运行目标应用程序的工作负载并跟踪在应用程序运行期间执行的内核代码;剖析跟踪并确定目标应用程序所需的内核代码,组装一个只包含应用程序所需代码的内核剪裁。
配置驱动的是内核剪裁的通常方式,大多数现有的工具使用配置驱动技术,由于它们是为数不多的可以形成稳定内核的技术之一。配置驱动的内核重载按照功能特点降低了内核代码,配置选项对应于内核的功能,裁切后的内核只包含用于支持目标应用程序工作负载的功能。
但是,虽然内核剪裁技术在安全性和性能方面十分吸引人,但在实践中并没有得到广泛采用。这并不是由于缺少需求,实际上,许多云供应商手工编撰Linux内核来降低代码,但通常不如内核剪裁技术有效。
2.现有内核剪裁技术的限制
现有内核剪裁技术有五个主要的局限性。
在引导阶段不可见。现有技术只能在内核引导后启动,依赖于ftrace,因而未能观察在引导阶段加载了什么内核代码。假如内核中缺乏关键模块,内核一般难以启动,而大量的内核功能特点只能通过观察引导阶段来捕获。据悉,关于性能和安全性同样只在引导时加载(比如,用于多核支持的CONFIGSCHEDMC和CONFIGSECURITYNETWORK),造成了性能和安全性增加。
缺少对应用程序布署的快速支持。使用现有的工具,面向内核剪裁来布署一个新的应用程序须要完成跟踪、分析和组装这三个步骤。这个过程十分历时,有可能须要几个小时甚至几天,妨碍了应用布署的敏捷性。
细度较粗。使用ftrace只能在函数级跟踪内核代码,细度太粗,难以跟踪影响函数内代码的配置选项。
覆盖不完全。由于使用动态跟踪,所以须要应用程序工作负载来驱动内核的代码执行,以最大限度地扩大覆盖范围。但是,基准测试覆盖是具有挑战性的,但是,假若应用程序有在跟踪期间没有观察到的内核代码,这么裁切后的内核可能会在运行时崩溃。
没有分辨执行依赖,可能存在冗余。虽然实际上可能并不须要执行的代码,也可能包含在了内核功能特点中,比如,可能初始化了第二个文件系统。
前三个限制是可以克服的,可以通过改进设计和工具加以解决,而后两个限制是在所难免,须要在具体的技术之外做出努力。
3.Linux的内核配置
3.1配置选项
内核配置由一组配置选项组成。一个内核模块可以有多个选项,每位选项都控制什么代码将包含在最终的内核二补码文件中。
配置选项控制内核代码的不同细度,比如由C预处理器实现的句子和函数,以及基于Makefile实现的对象文件。C预处理器依据#ifdef/#ifndef选择代码块,配置选项用作宏定义,以确定是否在编译后的内核中包含这样条件的代码块linux内核定制,可以是句子细度或则函数细度。Makefile用于确定是否在编译后的内核中包含个别对象文件,比如,CONFIG_CACHEFILES就是Makefile中的配置选项。
句子级配置选项不能通过现有内核剪裁工具所使用的函数级跟踪来辨识。事实上,Linux4.14中30%左右的C预处理器是句子级选项。
随着内核代码和功能特点的快速下降,内核中的配置选项数目也在迅速降低,以Linux内核3.0以上版本都有1万多个配置选项。
3.2.配置语言
Linux内核使用KConfig配置语言来指示编译器在编译后的内核中包含什么代码,容许定义配置选项以及它们之间的依赖关系。
KConfig中配置选项的值可能是bool、tristate或constant。bool意味着代码要么被静态编译成内核二补码文件,要么被排除在外,而tristate容许代码被编译成一个可载入核心模组,即一个可以在运行时加载的独立对象。constant可以为内核代码变量提供字符串或数值。一个选项可以依赖于另一个选项,KConfig使用了一个递归过程linux模拟,通过递归选择和取消依赖项。最终的内核配置具有有效的依赖关系,但可能与用户输入不同。
3.3.配置模板
Linux内核附送了许多手工制做的配置模板。并且,因为配置模板的硬编码特点而且须要人工干预,它们不能适应不同的硬件平台,也不了解应用程序的需求。诸如,由tinyconfig建立的内核不能在标准硬件上启动,更不用说支持其他应用了。有些工具将localmodconfig视为最小化的配置,并且,localmodconfig与静态配置模板具有相同的局限性,它不会启动控制句子级或函数级C预处理器的配置选项,也不会处理可加载的内核模块。
kvmconfig和xenconfig模板是为在KVM和Xen上运行的内核而订制的。它们提供诸如底层虚拟化和硬件环境的领域知识。
3.4.云中的Linux内核配置
Linux是云服务中占主导地位的操作系统内核,云供应商都在一定程度上舍弃了普通的Linux内核。云厂商的订制一般是通过直接删掉可加载的内核模块来完成的,手工修剪内核模块二补码文件的问题是可能会违背依赖关系。重要的是,基于应用程序需求可以进一步剪裁内核。诸如,AmazonFireCracker内核是一个专门用于函数即服务的微型虚拟机,使用HTTPD作为目标应用程序,在保证功能和性能提高的同时,使内核剪裁实现了更大程度的最小化。
4.内核剪裁的思索
针对局限一,是否可以使用来自QEMU的指令级跟踪来实现引导阶段的可见性呢?这样,就可以跟踪内核代码并将其映射到内核配置选项。既然引导阶段对于生成可引导内核至关重要,使用hypervisor提供的跟踪特点来获得端到端的可观察性并生成稳定的内核。
针对局限二,按照在NLP深度学习中的经验,可以使用离线和在线结合的方式,给定一组目标应用程序,可以直接离线生成的App配置,再和基线配置组合成完整的内核配置,进而生成一个裁切后的内核。这些可组合性才能通过重用应用配置和先前建立的文件(比如内核模块)来增量地构建新内核。假如目标应用程序的配置已知,就可以在几十秒内完成内核剪裁。
针对局限三,使用指令级跟踪可以解决控制函数内部功能特点的内核配置选项,指令级跟踪的开支对于运行测试套件和性能基准来说是可以接受的。
针对局限四,使用基于动态跟踪的一个基本限制是测试套件和基准的不健全,许多开源应用程序测试套件的代码覆盖率较低。组合不同的工作负载来驱动应用程序可以在一定程度上缓解这些限制。
针对局限五,通过删掉在基线内核中执行但在实际布署运行时不须要的内核模块,可以使用特定于领域的信息进一步加载内核。以Xen和KVM为例,可以基于xenconfig和kvmconfig配置模板进一步降低内核大小。面向应用程序的内核裁切可以进一步降低内核大小甚至广泛地定做的内核代码。
5内核剪裁框架初探
内核剪裁框架的原理没有变,依然是跟踪目标应用工作负载的内核占用情况,以确定所需的内核选项。
5.1内核剪裁框架的核心特点
内核剪裁框架大约可以具备以下特点:
端到端的可见性。借助虚拟机监控程序的可见性来实现端到端的观察,可以跟踪内核引导阶段和应用程序工作负载,可以尝试在QEMU的基础上建造Linux内核的剪裁框架。
可组合性。一个核心思想是通过将内核配置界定为若干组配置集,使内核配置可以组合,用于在给定的布署环境上引导内核,也可以用于目标应用程序所需的配置选项。配置集分为两种:基线配置和应用配置。基线配置不一定是在特定硬件上引导所需的最小配置集,而是在引导阶段跟踪的一组配置选项。基线配置可以与一个或多个应用配置组合在一起,以生成最终的内核配置。
可重用性。基线配置和应用配置都可以储存在数据库中,而且只要布署环境和应用程序的二补码文件不变就可以重用。这些可重用性防止了重复跟踪工作负载的运行,致使配置集的创建成为一次性的工作。
支持快速应用布署。给定一个布署环境和目标应用程序,内核剪裁框架可以有效地检索基线配置和应用配置,并将它们组合成所需的内核配置,之后使用生成的配置完善废弃的内核。
细细度配置跟踪,基于程序计数器的跟踪来辨识基于低级代码模式的配置选项。
5.2内核剪裁框架的体系结构
内核剪裁框架应当同时具备离/在线系统,体系结构如右图所示:
通过离线系统,配置跟踪器用于跟踪布署环境和应用程序所需的配置选项,并记录出来。配置生成器将这种选项处理成基线配置和应用配置选项,并将它们储存在配置数据库中。
通过在线系统,配置组合器使用基线配置和应用配置来世成目标内核配置,之后,内核建立器生成裁切后的Linux内核.
5.3内核剪裁框架的实现可行性
配置跟踪
内核剪裁框架的配置跟踪器在目标应用程序驱动的内核执行期间跟踪配置选项,使用PC寄存器捕获正在执行的指令的地址。为了确保被跟踪的PC属于目标应用程序,而不是其他进程(比如,后台服务),可以使用了一个订制的init脚本,该脚本不启动任何其他应用程序,只挂载文件系统/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求解器解决它们之间的依赖关系。尝试将配置依赖性建模为一个布尔可满足性问题,有效配置是指满足配置选项之间所有指定依赖性的配置。由于KConfig并不确保包含所有选取的选项,而是取消选择未满足的依赖项,所以才要基于SAT求解器对内核配置进行建模。
内核建立
使用于Linux的KBuild基于组装后的配置选项建立剪裁内核,借助现代make的增量建立可以优化完善时间,也可以缓存曾经的建立结果(比如,目标文件和内核模块),以防止冗余的编译和链接。当发生配置修改时,只有对配置选项进行修改的模块重新建立,而其他文件可以重用。
6.小结
因为操作系统内核的不稳定性、时效性较差、完整性问题以及须要人工干预等诱因,Linux内核剪裁技术没有得到广泛的应用。了解了现有技术的局限性,尝试提出一个Linux内核剪裁框架,其实可以解决那些问题。
原文来自: