Linux启动时间的极限优化

在上次完成嵌入式应用的Linux裁减后,Linux的启动时间仍需要 7s 左右,虽然勉强可以接受,但仍然没有达到我个人所追求的目标——2s 以内。况且,在实际的商用环境中,设备可靠性的要求可是“5个9”(99.999%,即OOS时间低于5分钟/年),这就意味着每减少一秒钟Linux启动(设备复位)时间,对可靠性都是一个明显的提升。

言归正传,如何着手对Linux的启动时间进行优化呢?

  CELF(The Consumer Electronics Linux Forum)论坛为我们指引了一个方向。

(1)首先是对Linux启动过程的跟踪和分析,生成详细的启动时间报告。

较为简单可行的方式是通过PrintkTime功能为启动过程的所有内核信息增加时间戳,便于汇总分析。PrintkTime最早为CELF所提供的一个内核补丁,在后来的Kernel 2.6.11版本中正式纳入标准内核。所以大家可能在新版本的内核中直接启用该功能。如果你的Linux内核因为某些原因不能更新为2.6.11之后的版本,那么可以参考CELF提供的方法修改或直接下载它们提供的补丁:http://tree.celinuxforum.org/CelfPubWiki/PrintkTimes

开启PrintkTime功能的方法很简单,只需在内核启动参数中增加“time”即可。当然,你也可以选择在编译内核时直接指定“Kernel hacking”中的“Show timing information on printks”来强制每次启动均为内核信息增加时间戳。这一种方式还有另一个好处:你可以得到内核在解析启动参数前所有信息的时间。因此,我选择后一种方式。

当完成上述配置后,重新启动Linux,然后通过以下命令将内核启动信息输出到文件:

dmesg -s 131072 > ktime

然后利用一个脚本“show_delta”(位于Linux源码的scripts文件夹下)将上述输出的文件转换为时间增量显示格式:

/usr/src/linux-x.xx.xx/scripts/show_delta ktime > dtime

这样,你就得到了一份关于Linux启动时间消耗的详细报告。

(2)然后,我们就来通过这份报告,找出启动中相对耗时的过程。

必须明确一点:报告中的时间增量和内核信息之间没有必然的对应关系,真正的时间消耗必须从内核源码入手分析。

这一点对于稍微熟悉编程的朋友来说都不难理解,因为时间增量只是两次调用printk之间的时间差值。通常来说,内核启动过程中在完成一些耗时的任务,如创建hash索引、probe硬件设备等操作后会通过printk将结果打印出来,这种情况下,时间增量往往反映的是信息对应过程的耗时;但有些时候,内核是在调用printk输出信息后才开始相应的过程,那么报告中内核信息相应过程的时间消耗对应的是其下一行的时间增量;还有一些时候,时间消耗在了两次内核信息输出之间的某个不确定的时段,这样时间增量可能就完全无法通过内核信息反应出来了。

所以,为了准确判断真正的时间消耗,我们需要结合内核源码进行分析。必要的时候,例如上述第三种情形下,还得自己在源码中插入printk打印,以进一步确定实际的时间消耗过程。

以下是我上次裁减后Linux内核的启动分析:

内核启动总时间: 6.188s

关键的耗时部分:
1) 0.652s – Timer,IRQ,Cache,Mem Pages等核心部分的初始化
2) 0.611s – 内核与RTC时钟同步
3) 0.328s – 计算Calibrating Delay(4个CPU核心的总消耗)
4) 0.144s – 校准APIC时钟
5) 0.312s – 校准Migration Cost
6) 3.520s – Intel E1000网卡初始化

下面,将针对上述各部分进行逐一分析和化解。

(3)接下来,进行具体的分项优化。

CELF已经提出了一整套针对消费类电子产品所使用的嵌入式Linux的启动优化方案,但是由于面向不同应用,所以我们只能部分借鉴他们的经验,针对自己面对的问题作出具体的分析和尝试。

内核关键部分(Timer、IRQ、Cache、Mem Pages……)的初始化目前暂时没有比较可靠和可行的优化方案,所以暂不考虑。

对于上面分析结果中的 2、3 两项,CELF已有专项的优化方案:“RTCNoSync”和“PresetLPJ”。

前者通过屏蔽启动过程中所进行的RTC时钟同步或者将这一过程放到启动后进行(视具体应用对时钟精度的需求而定),实现起来比较容易,但需要为内核打补丁。似乎CELF目前的工作仅仅是去掉了该过程,而没有实现所提到的“延后”处理RTC时钟的同步。考虑到这个原因,我的方案中暂时没有引入这一优化(毕竟它所带来的时间漂移已经达到了“秒”级),继续关注中。

后者是通过在启动参数中强制指定LPJ值而跳过实际的计算过程,这是基于LPJ值在硬件条件不变的情况下不会变化的考虑。所以在正常启动后记录下内核信息中的“Calibrating Delay”数值后就可以在启动参数中以下面的形式强制指定LPJ值了:

lpj=9600700

上面分析结果中的 4、5 两项都是SMP初始化的一部分,因此不在CELF研究的范畴(或许将来会有采用多核的MP4出现?……),只能自力更生了。研究了一下SMP的初始化代码,发现“Migration Cost”其实也可以像“Calibrating Delay”采用预置的方式跳过校准时间。方法类似,最后在内核启动参数中增加:

migration_cost=4000,4000

而Intel的网卡驱动初始化优化起来就比较麻烦了,虽然也是开源,但读硬件驱动完全不比读一般的C代码,况且建立在如此肤浅理解基础上的“优化”修改也实在难保万全。基于可靠性的考虑,我最终在两次尝试均告失败后放弃了这一条路。那么,换一个思维角度,可以借鉴CELF在“ParallelRCScripts”方案中的“并行初始化”思想,将网卡驱动独立编译为模块,放在初始化脚本中与其它模块和应用同步加载,从而消除Probe阻塞对启动时间的影响。考虑到应用初始化也可能使用到网络,而在我们的实际硬件环境中,只有eth0是供应用使用的,因此需要将第一个网口初始化的0.3s时间计算在内。

除了在我的方案中所遇到的上述各优化点,CELF还提出了一些你可能会感兴趣的有特定针对性的专项优化,如:

ShortIDEDelays – 缩短IDE探测时长(我的应用场景中不包含硬盘,所以用不上)
KernelXIP – 直接在ROM或Flash中运行内核(考虑到兼容性因素,未采用)
IDENoProbe – 跳过未连接设备的IDE口
OptimizeRCScripts – 优化initrd中的linuxrc脚本(我采用了BusyBox更简洁的linuxrc)

以及其它一些尚处于设想阶段的优化方案,感兴趣的朋友可以访问CELF Developer Wiki了解详情。

(4)优化结果

经过上述专项优化,以及对inittab、rcS脚本的冗余裁减,整个Linux内核的启动时间从优化前的 6.188s 下降到了最终的 2.016s,如果不包含eth0的初始化,则仅需 1.708s(eth0初始化可以和系统中间件及部分应用加载并行),基本达到了既定目标。与Kexec配合,可以大大降低软件故障导致的复位时间,有效的提升了产品的可靠性。

大家如果对内核启动时间的优化还有什么建议或疑惑,都欢迎与我探讨。:)

《Linux启动时间的极限优化》有17个想法

  1. 您好:
    最近在做关于LINUX快速启动的工作,刚开始学习,根据CELF的方法进行了一些缩减。对于您的方法,有一些不明白之处,请不吝赐教。
    1、OptimizeRCScripts – 优化initrd中的linuxrc脚本.我编译配置编译了BusyBox,但不懂之处在于对于_INSTALL下的linuxrc,怎么替换原来的呢?
    2、IDENoProbe ,这个办法我用了怎么不起作用呢?反而时间长了50MS?
    斑竹可否把您的优化过程整理的详细点儿,便于我们菜鸟借鉴。多谢!

  2. linuxrc是供initrd使用的。直接覆盖initrd的root中同名文件即可,当然记得需要有执行权限。一般可以在BusyBox的配置中直接将INSTALL路径指向mount之后的initrd,这样make install就可以直接安装至initrd中。

    IDENoProbe我没有实际测试过,只是引用CELF的方案,因为我们的Linux环境没有使用IDE设备。涉及到的优化内容文中都已详述,余下的一些优化方案因为硬件条件的限制,我也没有实际操作。

  3. 呵呵,不好意思,我是新手!那个KLOG日志在什么地方能找到?
    我看了一下/proc/kmsg ,里面什么也没有。

  4. 您好:
    我看了那个KLOG,没有发现migration_cost这个变量啊!我用的是2。6。9的内核。
    我主要是想优化一下整体启动时间在2秒左右,现在没有加任何服务,从内核启动到六个虚拟终端起来,总共8S,如果把网络部分去掉,把YAFFS文件系统编译为模块,可以减为3S左右。不知道还有什么可以优化的。因为没有加服务,所以启动文本优化暂时无需进行,我采用了LPJ;DISABLE CONSOLE;把IDE方面的删除,IDENOPROBE/SHORT IDE DELAY都没用;RTCNOSYNC因为ARM上没有相应补丁,没有采用。其他除了migration cost,感觉跟您采取的措施没多大差别了啊!望楼主根据您的优化经验指点小弟迷津!不胜感激!

  5. migration_cost只会出现在SMP结构(多CPU或多核)的系统中,同时还需要在内核配置中开启SMP。按照你的描述来看,是ARM的CPU,应该不需要用到这一优化。:)

  6. 您好:
    小弟最近又遇到一个问题:
    如何生成非压缩状态的内核映像并顺利被BLOB装载
    小弟为了提高启动速度,试图消除解压缩所带来的时间消耗,需要使用非压缩状态的内核映像,并用BLOB顺利载入并启动内核,但现在没有成功,请版主指教。
    我用的是博创的PXA270实验板,原来是压缩内核启动。现在我用非压缩的Image内核装入,BLOB引导到出现START KERNEL……就不动了。
    请版主指点一二!

  7. 没有接触过BLOB,抱歉帮不上忙。我的理解是内核压缩与否和BootLoader没有直接关系,因为真正完成解压操作的其实是内核本身。如你所遇到的未压缩反而不能启动的情况倒是比较少见,建议先尝试用其他BootLoader加载此内核映像,看看能否启动,首先确定问题在映像还是BLOB上。

  8. 对,quiet参数是漏掉了,视不同的console环境,最多可能达到数百豪秒的优化效果。 🙂

    欢迎作链接!不过老实说,我这人有点懒,更新blog比较慢。

  9. 您好!

    我做了一款嵌入式linux下的控制产品,但是linux启动时间过长,大约在15秒-20秒左右,不知道您能否帮助我把启动时间缩短在5秒以内,谢谢您!

    我的联系方式:thxmaple@yahoo.com.cn, 必有酬谢!

发表评论

电子邮件地址不会被公开。 必填项已用*标注