博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
内核启动时间优化
阅读量:2427 次
发布时间:2019-05-10

本文共 5411 字,大约阅读时间需要 18 分钟。

前言

       为了提高开机时间,我们需要优化的部分有:

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 来完成的。 它的主函数为:, 其中会调用,该函数会调用,内核启动的大多数时间就花在该函数里。
 

准备

首先,我们打开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 查询该函数,名为:
其会调用:
其又会调用:

 

这个动作为解压缩 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

 

总结

其它方法还有: 裁剪内核,删除不需要的代码、打印信息。 等等。

当然,任何时候,优化的代码是最重要的!

转载地址:http://mrbmb.baihongyu.com/

你可能感兴趣的文章
【代码规范】google开源c\c++项目代码规范
查看>>
【C语言】c语言常用的几个函数源代码【strlen,strcpy,strcat,strstr】
查看>>
【C语言】杨辉三角问题
查看>>
【C语言】size与strlen的区别解析
查看>>
【C语言】指针深入理解-指针与数组的关系
查看>>
【C语言】C语言中常用函数源代码【strncpy ,strncat ,strncmp】
查看>>
【linux】入门学习Linux常用必会命令实例详解
查看>>
【java】java高级开发之泛型
查看>>
【java】sting和stringbuilder与stringbuffer的区别辨析
查看>>
【Java】【算法练习】题目描述:输入一个整数数组,判断该数组是不是某二叉搜索树的后续遍历的结果。如果是输出yes,不是输出no,数组任意两个数字不相同。
查看>>
【Java】给定一个二叉树和其中的一个节点,请找出中序遍历的下一个节点且返回, 注意:树中的节点不仅包含左右子节点,同时包含父节点的指针。
查看>>
【Java】内存泄漏与内存溢出 学习总结
查看>>
【Java】命名规范
查看>>
【Java】—— java基础篇
查看>>
【Java】—— JVM篇
查看>>
【Java】【Linux】【操作系统】知识重点——操作系统篇
查看>>
【Java】【多线程】—— 多线程篇
查看>>
【计算机网络】—— TCP/IP篇
查看>>
【Java】【算法】——算法篇
查看>>
【Java】【数据库】知识重点——数据库篇
查看>>