LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

一个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
      image
    • Generic boot sequence:
      image
    • 一些注意点:
      不要先优化影响到测试结果或者其他优化项
      从优化应用和启动脚本开始
      优化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获取数据)

  1. 修改.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

  2. 在main.c中修改initcall_debug为true或修改Kernel command line。
  3. 修改所有initcall_debug下的打印等级为KERN_ALERT。
  4. 修改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;
    }

  5. 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”结束。

image

TODO:为了更方便的摘出耗时Top 20的模块,还需要生成csv文件。DONE!

在生成Timeline图标的同时生成每个设备的运行时间,然后在Excel中过滤出Top20如下,可以有针对性的进行优化。

image

延伸阅读:关于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_hsotg

board_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打开如下:

image

总结

经过实际使用,analyze_boot.py要比bootgraph.pl生成的结果可读性更高,并且适量缩放、单个查看更方便,直接在浏览器中查看。

同时还需要对analyze_boot.py进行扩展,提供生成csv格式的文档。

优化实例

1. 最大限度降低console输出

Index: linux/linux-3.4.x/kernel/printk.c
===================================================================
--- linux/linux-3.4.x/kernel/printk.c    (revision 1173)
+++ linux/linux-3.4.x/kernel/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 */
};
 
/*

对比一下前后结果:

imageimage

可以看出效果很显著,整个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。

image

3.prepare_namespace

 

其他注意点

将所有的相关log都是用一个Kernel command line来控制起来,类似于initcall_debug

将其集成到每日集成中,每次开机获取一个报告。可以清晰看出每次regression。

给驱动人员提出建议,清理不需要模块,增加模块要谨慎。

Busybox下Bootchart的使用

Busybox下Bootchart的使用

posted on 2017-05-04 10:59  ArnoldLu  阅读(129)  评论(0编辑  收藏  举报

导航