一个Linux系统开机时间优化实例
从按下电源到进入工作状态(PowerKey—>Idle)是使用设备必经之路,开机时间的优化也就必不可少。
如果将从按下Power键到进入Idle状态(系统可用)作为度量标准,大部分的系统可以分为BootROM(芯片固化Firmware)、Bootloader(用于加载Kernel)、Kernel和用户空间初始化(文件系统、后台服务、Idle进程等等)。由于BootROM固化、Bootloader种类繁多,不太容易有通用的方法去度量。
优化主要通过Kernel和用户空间来进行,Linux开机时间度量手段有: systemd(需要systemd支持)、BootChart(修改init支持,主要分析用户空间启动)、分析dmesg(使用analyze_boot.py进行分析)等。
用户空间的优化涉及到的变数相对于内核要大点,比如Android和普通的Busybox,Android可以通过Bootchart快速得到结果,而普通Busybox需要特定版本支持Bootchart。
TODO:在Busybox下使能Bootchart可以作为下一步计划。
下面按照一个有效度量工具(快速迭代、低离散,分阶段列出设备耗时,可进行对比)、可能的优化方向、优化实战。
Linux Kernel版本:v3.4
Linux开机优化方向
1.来自于Android Booting的Sequence of boot steps on ADP
the kernel boots
- core kernel initialization
memory and I/O are initialized
interrupts are started, and the process table is initialized- driver initialization
- kernel daemons(threads) are started
- root file system is mounted
- the first user-space process is started
usually /init(note that other Linux system usually start /sbin/init)
2.减少log显示内容,内核参数Quiet
修该kernel/printk.c中的console显示等级,极大降低log输出量。
Index: printk.c
===================================================================
--- printk.c (revision 1173)
+++ printk.c (working copy)
@@ -79,10 +79,10 @@
DECLARE_WAIT_QUEUE_HEAD(log_wait);
int console_printk[4] = {
- DEFAULT_CONSOLE_LOGLEVEL, /* console_loglevel */
+ MINIMUM_CONSOLE_LOGLEVEL, /* console_loglevel */
DEFAULT_MESSAGE_LOGLEVEL, /* default_message_loglevel */
MINIMUM_CONSOLE_LOGLEVEL, /* minimum_console_loglevel */
- DEFAULT_CONSOLE_LOGLEVEL, /* default_console_loglevel */
+ MINIMUM_CONSOLE_LOGLEVEL, /* default_console_loglevel */
};
/*
3.通过1中的阶段划分:A.删除不需要的模块;B.尽量优化耗时Top 20模块。
4.一份Update on boot time reduction techniques, with figures,这里备份。
- Boot time reductin methodology:Measure time->Remove unnecessary functionality->Postpone, parallelize, recorder(推迟、并行、重新排序)->Optimize necessary functionality。
- Generic boot sequence:
- 一些注意点:
不要先优化影响到测试结果或者其他优化项
从优化应用和启动脚本开始
优化Busybox,却除不使用或较少使用的命令
然后开始简化和优化内核
最后优化bootloader选项(所有优化顺序是:用户空间->内核->Bootloader) - 如何使用grabserial工具
- Filesystem optimizations
- Init scripts(Measuring bootchart/systemd)
- Applications(Tracubg applications: strace/oprofile/perf)
- Kernel optimizations
Measure-Kernel initialization functions: initcall_debug
Kernel boot graph: bootgraph.pl、inkscape,Remove unnecessary functionality、Postpone、Optimize necessary functionality
Reduce kernel size:Compile not needed at boot time as a module、Remove features not needed in your system、Kernel compression。
Turning off console output:Passing quietargument on the kernel command line、Still be able to use dmesg to get the kernel messages。 - Preset loops per jiffy: add lpj=<value> to the command line
- Bootloader optimization: uboot
- Optimize kernel for size: CONFIG_CC_OPTIMIZE_FOR_SIZE, possibility to compile the kernel with gcc –Os instead of gcc –O2
测量时间准备
修改内核(基于dmesg、/proc/kmsg获取数据)
- 修改.config文件,project/prj_phone/config/normal/common/config.linux。
CONFIG_PRINTK_TIME=y 打印printk的时候带上timestamp
CONFIG_DEFAULT_MESSAGE_LOGLEVEL=7 这样子会有副作用,造成相关log特别多。
Index: config.linux
===================================================================
--- config.linux (revision 1142)
+++ config.linux (working copy)
@@ -72,7 +72,7 @@
# CONFIG_TREE_RCU_TRACE is not set
CONFIG_IKCONFIG=y
# CONFIG_IKCONFIG_PROC is not set
-CONFIG_LOG_BUF_SHIFT=16
+CONFIG_LOG_BUF_SHIFT=18 修改kmsg内存大小
# CONFIG_CGROUPS is not set - 在main.c中修改initcall_debug为true或修改Kernel command line。
- 修改所有initcall_debug下的打印等级为KERN_ALERT。
- 修改free_area中的打印等级,作为Kernel启动结束标志。
Index: init.c
===================================================================
--- init.c (revision 1173)
+++ init.c (working copy)
、@@ -425,7 +425,7 @@
}
if (size && s)
- printk(KERN_INFO "Freeing %s memory: %dK\n", s, size);
+ printk(KERN_ALERT "Freeing %s memory: %dK\n", s, size);
return pages;
} - TODO:更详细划分内核启动阶段?如何有效的划分?
工具准备
主要对比analyze_boot.py和bootgraph.pl两个工具。
analyze_boot.py
./analyze_boot.py -dmesg dmesg.txt |
通过分析dmesg得出Timeline,现包括内核的所有initcall调用时间。从”Booting Linux on XXX”开始,到”Freeing init memoryXXX”结束。
TODO:为了更方便的摘出耗时Top 20的模块,还需要生成csv文件。DONE!
在生成Timeline图标的同时生成每个设备的运行时间,然后在Excel中过滤出Top20如下,可以有针对性的进行优化。
延伸阅读:关于probe是否在initcall中调用?
函数调用关系如下:
dwc_otg_driver_init
->platform_driver_register
->platform_driver_register
->driver_register
->bus_add_driver
->driver_attach (在drivers_autoprobe为1的情况下)
->__driver_attach
->driver_match_device (调用bus的match函数进行配对,具体到platform bus对应的是platform_match)
->driver_probe_device
->really_probe (在really_probe中,如果存在dev->bus->probe则优先使用,否则调用drv->probe)
->ret = drv->probe(dev); (drv->probe具体指向哪里?这里的drv是device_driver类型。)由platform_driver_register可知,这里的drv->probe指向platform_drv_probe。
再看platform_drv_probe,可知调用drv->probe这里的drv是platform_driver,所以对应函数是dwc_otg_driver_probe。
在platform_driver_register中:
int platform_driver_register(struct platform_driver *drv)
{
printk("%s line=%d\n", __func__, __LINE__);
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe; 这里的drv是platform_driver类型,driver才是device_driver类型。
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;return driver_register(&drv->driver);
}bus_register
->__bus_register
->priv->drivers_autoprobe = 1;platform总线的match函数:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */
printk("%s %s %s line=%d\n", __func__, pdev->name, drv->name, __LINE__);
return (strcmp(pdev->name, drv->name) == 0);
}
下面是log信息,可以看出probe在initcall中进行了处理。
<1>[ 0.172119] calling dwc_otg_driver_init+0x0/0x90 @ 1
<6>[ 0.172119] zx29_hsotg: version 3.10b 20-MAY-2013
<1>[ 0.172149] platform_driver_register line=473
<1>[ 0.172149] driver_register line=170
<1>[ 0.172180] bus_add_driver line=699
<1>[ 0.172180] driver_attach line=481
<1>[ 0.172210] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.172210] platform_match zx29_gpio zx29_hsotg line=676
<1>[ 0.172210] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.172241] platform_match zx29_uart zx29_hsotg line=676
<1>[ 0.172241] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.172271] platform_match zx29_uart zx29_hsotg line=676
<1>[ 0.172271] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.172271] platform_match zx29_uart zx29_hsotg line=676
<1>[ 0.172302] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.172302] platform_match zx29_hsotg zx29_hsotg line=676
<1>[ 0.172302] driver_probe_device line=373
<1>[ 0.172332] really_probe line=269
<1>[ 0.172332] platform_drv_probe line=441
<1>[ 0.172363] dwc_otg_driver_probe line=781
<1>[ 0.584167] Core Release: 3.20a
<1>[ 0.584167] Setting default values for core params
<1>[ 0.588439] Using Descriptor DMA mode
<1>[ 0.588439] Periodic Transfer Interrupt Enhancement - disabled
<1>[ 0.588470] Multiprocessor Interrupt Enhancement - disabled
<1>[ 0.588470] OTG VER PARAM: 0, OTG VER FLAG: 0
<1>[ 0.588562] Dedicated Tx FIFOs mode
<1>[ 0.589416] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589416] platform_match spi-nand-dt zx29_hsotg line=676
<1>[ 0.589447] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589447] platform_match zx29_dma zx29_hsotg line=676
<1>[ 0.589447] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589477] platform_match zx29_sd zx29_hsotg line=676
<1>[ 0.589477] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589508] platform_match zx29_sd zx29_hsotg line=676
<1>[ 0.589508] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589508] platform_match zx29_i2c zx29_hsotg line=676
<1>[ 0.589538] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589538] platform_match zx29_i2c zx29_hsotg line=676
<1>[ 0.589538] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589569] platform_match zx29_ssp zx29_hsotg line=676
<1>[ 0.589569] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589599] platform_match icp zx29_hsotg line=676
<1>[ 0.589599] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589599] platform_match icp zx29_hsotg line=676
<1>[ 0.589630] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589630] platform_match zx29_ap_wdt zx29_hsotg line=676
<1>[ 0.589630] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589660] platform_match zx234290-regulators zx29_hsotg line=676
<1>[ 0.589660] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589691] platform_match zx234290-rtc zx29_hsotg line=676
<1>[ 0.589691] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589691] platform_match zx234290-gpadc zx29_hsotg line=676
<1>[ 0.589721] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589721] platform_match regulatory zx29_hsotg line=676
<1>[ 0.589752] __driver_attach (null) zx29_hsotg platform line=453
<1>[ 0.589752] platform_match alarmtimer zx29_hsotg line=676
<1>[ 0.589935] initcall dwc_otg_driver_init+0x0/0x90 returned 0 after 407813 usecs
延伸阅读:Linux下platform_device注册时机?
为什么要了解Linux下platform_device的注册时机?如果在do_initcall之前已经注册,则probe会在platform_driver注册的时候执行。
一般platform_device通过platform_add_devices来添加。
举一个实例:
board_init
->platform_add_devices(zx29_device_table, zx29_device_table_num)
->zx297520v2_usb0_device
->zx29_hsotgboard_init在MACHINE_START的init_machine成员,可以看出MACHINE_START定义了一个struct machine_desc类型的结构体变量,这个变量放在.arch.info.init中,内核启动之后会被丢弃。
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,#define MACHINE_END \
};
MACHINE_START(ZX297520V3, "ZTE-TSP ZX297520V3")
.atag_offset = 0x100,
#if NEW_LINUX_FRAME
.init_time = zx29_timer_init,
#else
.timer = &zx29_timer,
#endif
.map_io = zx29_map_io,
.init_early = board_init_early,
.init_irq = zx29_init_irq,
.init_machine = board_init,
.restart = zx29_restart,
MACHINE_END那么谁去获取这个变量呢?
start_kernel
->setup_arch(&command_line)
->mdesc = setup_machine_fdt(__atags_pointer); (然后将machine_desc = mdesc;)在setup_arch中还可以看到其他成员函数的使用情况:
arm_pm_restart = mdesc->restart;
handle_arch_irq = mdesc->handle_irq;
mdesc->init_early();然后谁去调用init_machine的呢?
可以看到board_init会在arch_initcall这个级别的initcall中调用。
static int __init customize_machine(void)
{
/* customizes platform devices, or adds new ones */
if (machine_desc->init_machine)
machine_desc->init_machine();
return 0;
}
arch_initcall(customize_machine);由上述分析可知,struct machine_desc类型的变量在setup_arch中获取。然后在arch_initcall中调用init_machine进行platform_device的注册。
arch_initcall等级是3,在pure_initcall、core_initcall、postcore_initcall之后,但在subsys_initcall、fs_initcall、rootfs_initcall、device_initcall(也即module_init)、late_initcall之前。所以platform_device还是在大部分驱动之前注册的。
具体信息参照:include/linux/init.h。
bootgraph.pl
perl bootgraph.pl dmesg.txt > output.svg |
使用inkscape打开如下:
总结
经过实际使用,analyze_boot.py要比bootgraph.pl生成的结果可读性更高,并且适量缩放、单个查看更方便,直接在浏览器中查看。
同时还需要对analyze_boot.py进行扩展,提供生成csv格式的文档。
优化实例
1. 最大限度降低console输出
Index: linux/linux-3.4.x/kernel/printk.c |
对比一下前后结果:
可以看出效果很显著,整个Kernel启动时间从8008ms降低到1847ms,提高了6s多。
分析一下优化后的整个重点关注两部分:1.initcall部分,可以挑出Top20逐个击破;2.prepare_namespace部分。
TODO:同样的措施可否在Bootloader、UserSpace采取。
2.initcall Top20
列出initcallTop20如下,可以看出能把Top10很好的解决,就很显著了。整个initcall耗时610ms左右,一个dwc_otg_driver_init就占到2/3。
3.prepare_namespace
其他注意点
将所有的相关log都是用一个Kernel command line来控制起来,类似于initcall_debug
将其集成到每日集成中,每次开机获取一个报告。可以清晰看出每次regression。
给驱动人员提出建议,清理不需要模块,增加模块要谨慎。