http://blog.csdn.net/zjujoe/article/details/5594961
内核启动时间优化一例
作者: 宋立新
Email:zjujoe@yahoo.com
前言
为了提高开机时间,我们需要优化的部分有:
1) bootloader 启动速度
2) Linux 内核启动速度
3) 文件系统启动速度
4) Nand (假设你的 Storage 使用 nand)的读速度
5) 应用框架启动速度
等等。
本文的内容为自己最近对某内核做的一个优化。供参考。
背景知识
Linux 内核的执行起点在 (arch/arm/kernel/head.S), 首先会初始化硬件(所做不多,因为很多操作 bootloader 已经完成), 获取 cpu id, machine id, 设置少量的页表,然后打开 MMU。清空 BSS 后, 会跳转到 C 语言函数 start_kernel
Start_kernel 调了很多早期初始化函数, 然后会调 用 rest_init.
Rest_init 函数中, 会创建 init 线程,并调度它, 该线程最 终调用用户空间的 init 程序, 进化为一切其他用户程序的祖宗进程。
原来的 rest_init 会继续执行,它先通过手工方式,把自己演变成0号进程, 然后调用 cpu_idle 函数, 此函数乃一 while(1){} 循环, 永不退 出!
这样, 系统启动显示运行0号进程,然后运行 1 号进程, 只有在系 统没事可干时, 才会调用 0 号进程, 所以, 0 号进程又叫 idle 进程。
对于我们来说,主要的任务(很多很多的 init 函数调用)则是由 1 号进程 init 来完成的。 它的主函数为:kernel_init, 其中会调用do_basic_setup,该函数会调用do_initcalls,内核启动的大多数时间就花在该函数里。
准备
首先,我们打开CONFIG_PRINTK_TIME, 这样,内核的串口打印信息就带有时间信息,精度为 jiffies (1 – 10 ms). 这基本满足我们的需要了。
其次,由于 initcall 中调用了很多初始化函数,我们加入打印语句,以便能够判断出某段时间花在那个初始函数上。
diff --git a/init/main.c b/init/main.c
index 7ef6a1e..7642983 100755
--- a/init/main.c
+++ b/init/main.c
@@ -800,8 +800,10 @@ static void __init do_initcalls(void)
{
initcall_t *call;
- for (call = __early_initcall_end; call < __initcall_end; call++)
+ for (call = __early_initcall_end; call < __initcall_end; call++) {
+ printk("=======================now will call :%p/n", call);
do_one_initcall(*call);
+ }
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
开始
准备工作做好后,我们就开始优化了。
编译好内核, 烧到手机中,开机, 取到串口打印信息。
然后就根据串口打印信息进行优化。
Action 1(节约0.18s)
第一个时间跨度发生在如下两行:
[ 0.030000] Calibrating delay loop... 103.62 BogoMIPS (lpj=518144)
[ 0.210000] Mount-cache hash table entries: 512
这段时间用于计算出 lpj (loops per jiffies), 我们可以通过在命令行预设该值,从而省掉这段时间:
--- a/arch/arm/configs/android2010_defconfig
+++ b/arch/arm/configs/android2010_defconfig
@@ -250,7 +250,7 @@ CONFIG_ALIGNMENT_TRAP=y
#
CONFIG_ZBOOT_ROM_TEXT=0x0
CONFIG_ZBOOT_ROM_BSS=0x0
-CONFIG_CMDLINE="console=ttyMTD2 console=ttyS0,115200n8 mem=104M ip=off mtdparts=evb1226-nand:4M(kernel),3M(r
+CONFIG_CMDLINE="console=ttyMTD2 console=ttyS0,115200n8 mem=104M lpj=518144 ip=off mtdparts=evb1226-nand:4M(k
# CONFIG_XIP_KERNEL is not set
# CONFIG_KEXEC is not set
编译,执行,发现这段时间没有了:
[ 0.030000] Calibrating delay loop (skipped) preset value.. 103.62 BogoMIPS (lpj=518144)
[ 0.030000] Mount-cache hash table entries: 512
这样,我们通过预设 lpj, 优化了 0.18 秒。
Action 2(节约0s)
[ 0.200000] =======================now will call :c00232ec
[ 0.420000] =======================now will call :c00232f0
查看c00232ec, 在 System.map 查询该函数,名为:populate_rootfs
其会调用:unpack_to_rootfs
其又会调用:gunzip
这个动作为解压缩 initramfs, 所以,
这 0.22 秒时间是必须的,无法优化。
Action 3(节约0.15s)
[ 0.440000] =======================now will call :c00233e8
[ 0.450000] IM9815 s6b33bl LCM
[ 0.670000] =======================now will call :c00233ec
c00233e8 对应文件 im9815fb_init 仔细查看 lcm 驱动, 发现其中有一些 mdelay, 试着减少这些 delay, 并保证 lcm 功能不收影响, 大大减少了 lcm 的时延。
[ 0.440000] =======================now will call :c00233e8
[ 0.440000] IM9815 s6b33bl LCM
[ 0.490000] =======================now will call :c00233ec
这样,我们通过优化 lcm 驱动, 优化了 0.15 秒。
Action 4(节约0.06s)
<4>[ 0.490000] =======================now will call :c00233f0
<4>[ 0.570000] =======================now will call :c00233f4
<4>[ 0.620000] =======================now will call :c00233f8
c00233f0 对应函数tty_init,
c00233f4 对应函数pty_init,
仔细分析, 这两个函数会注册一些 tty/pty 设备, 其中 tty 为 63 个, pty 为 16 个。
我们认为63个 tty 设备没有必要,于是修改了相关定义:
--- a/include/linux/vt.h
+++ b/include/linux/vt.h
@@ -18,8 +18,8 @@ extern int unregister_vt_notifier(struct notifier_block *nb);
* resizing).
*/
#define MIN_NR_CONSOLES 1 /* must be at least 1 */
-#define MAX_NR_CONSOLES 63 /* serial lines start at 64 */
-#define MAX_NR_USER_CONSOLES 63 /* must be root to allocate above this */
+#define MAX_NR_CONSOLES 15 /* serial lines start at 16 */
+#define MAX_NR_USER_CONSOLES 15 /* must be root to allocate above this */
修改完后,发现节约了 0.06s:
<4>[ 0.490000] =======================now will call :c00233f0
<4>[ 0.510000] =======================now will call :c00233f4
<4>[ 0.550000] =======================now will call :c00233f8
Action 5(节约1s)
[ 0.570000] =======================now will call :c0023408
[ 0.570000] Serial: 8250/16550 driver, 3 ports, IRQ sharing enabled
[ 0.570000] serial8250.0: ttyS0 at MMIO 0xf0010000 (irq = 5) is a 16550A
[ 0.570000] Port 0: Set UART ECR baud: 115200
[ 0.570000] console [ttyS0] enabled
[ 1.580000] serial8250.0: ttyS1 at MMIO 0xf0011000 (irq = 5) is a 16550A
[ 1.590000] serial8250.0: ttyS2 at MMIO 0xf0012000 (irq = 5) is a 16550A
[ 1.600000] =======================now will call :c002340c
c0023408 对应函数:serial8250_init, 仔细研究发现,这里的一秒钟时间主要是因为系统第一次注册 console 时,内核会把积累在log buffer 里的信息输出到这个 console, 优化的方法是在 cmdline 中加 quiet 参数:
CONFIG_CMDLINE="console=ttyMTD2 console=ttyS0,115200n8 mem=104M lpj=518144 quiet ip=off mtdparts=evb1226-nand:4M(kernel),3M(ramdisk),1M(panic),72M(system),4M(cache),-(userdata) init=/init"
修改以后, 1秒时延没有了:
<4>[ 0.640000] =======================now will call :c0023408
<6>[ 0.640000] Serial: 8250/16550 driver, 3 ports, IRQ sharing enabled
<6>[ 0.640000] serial8250.0: ttyS0 at MMIO 0xf0010000 (irq = 5) is a 16550A
<4>[ 0.640000] Port 0: Set UART ECR baud: 115200
<6>[ 0.640000] console [ttyS0] enabled
<6>[ 0.650000] serial8250.0: ttyS1 at MMIO 0xf0011000 (irq = 5) is a 16550A
<6>[ 0.650000] serial8250.0: ttyS2 at MMIO 0xf0012000 (irq = 5) is a 16550A
<4>[ 0.660000] =======================now will call :c002340c
总结
其它方法还有: 裁剪内核,删除不需要的代码、打印信息。 等等。
当然,任何时候,优化的代码是最重要的!