优化过程一般涉及到SPL、bootloader、内核、文件系统、应用程序加载等。
该文只记录内核中驱动部分的优化思路。
1. 打开内核启动时间戳
通过bootargs传参打开时间戳:
在bootargs中增加 initcall_debug printk.time=y
我们可以在Documentation/kernel-parameters.txt中看到,如下描述
也就是说,initcall_debug参数可以用于跟踪initcalls来分析内核的执行。而printk.time展示printk执行时的时间戳
也可以通过make menuconfig
Kernel hacking
[ * ] ---->Show timing infomation on printks
的方式显示时间戳,这样就可以不用printk.time=y
2.分析启动时间
内核中提供了show_delta脚本,帮助我们分析每个模块的耗时情况,文件位置在scripts/show_delta
该代码很简单,按行读入文件,并将行中时间戳减去上一个时间戳得到时间差后进行字符串拼接,用于将时间戳解析为时间差值保存到时间戳后面。
具体步骤
a. 将内核输出打印信息保存为kernel_start_log,放置内核源码目录
b. 在内核源码目录下执行scripts/show_delta kernel_start_log > kernel_duration_log
一般不会出问题,不过有可能报错,原因是脚本的python版本跟ubuntu的python版本不匹配导致,需要修改python版本或其中的print函数,给print加括号,linux4.xx的内核不存在该问题。
c. vi kernel_duration_log
转换过程如下:
转换前
[ 0.001158] initcall init_static_idmap+0x0/0xe8 returned 0 after 0 usecs [ 0.001174] calling spawn_ksoftirqd+0x0/0x44 @ 1 [ 0.001243] initcall spawn_ksoftirqd+0x0/0x44 returned 0 after 0 usecs [ 0.001259] calling init_workqueues+0x0/0x244 @ 1 [ 0.001497] initcall init_workqueues+0x0/0x244 returned 0 after 0 usecs [ 0.001514] calling rcu_scheduler_really_started+0x0/0x3c @ 1 [ 0.001532] initcall rcu_scheduler_really_started+0x0/0x3c returned 0 after 0 usecs [ 0.001544] calling relay_init+0x0/0x14 @ 1 [ 0.001555] initcall relay_init+0x0/0x14 returned 0 after 0 usecs [ 0.001566] calling jump_label_init_module+0x0/0x1c @ 1 [ 0.001579] initcall jump_label_init_module+0x0/0x1c returned 0 after 0 usecs [ 0.001595] calling mbus_init+0x0/0x4c @ 1 [ 0.001619] initcall mbus_init+0x0/0x4c returned 0 after 0 usecs [ 0.003182] calling sram_init+0x0/0xd0 @ 1 [ 0.003217] initcall sram_init+0x0/0xd0 returned 0 after 0 usecs [ 0.003236] calling ipc_ns_init+0x0/0x20 @ 1 [ 0.003251] initcall ipc_ns_init+0x0/0x20 returned 0 after 0 usecs [ 0.003266] calling init_mmap_min_addr+0x0/0x2c @ 1 [ 0.003279] initcall init_mmap_min_addr+0x0/0x2c returned 0 after 0 usecs [ 0.003296] calling net_ns_init+0x0/0x15c @ 1 [ 0.003321] initcall net_ns_init+0x0/0x15c returned 0 after 0 usecs [ 0.003522] calling vfp_init+0x0/0x174 @ 1 [ 0.003531] VFP support v0.3: implementor 41 architecture 2 part 30 variant 7 rev 5 [ 0.003550] initcall vfp_init+0x0/0x174 returned 0 after 0 usecs [ 0.003562] calling ptrace_break_init+0x0/0x34 @ 1 [ 0.003575] initcall ptrace_break_init+0x0/0x34 returned 0 after 0 usecs
转换后
[0.001155 < 0.000018 >] initcall init_static_idmap+0x0/0xe8 returned 0 after 0 usecs [0.001170 < 0.000015 >] calling spawn_ksoftirqd+0x0/0x44 @ 1 [0.001235 < 0.000065 >] initcall spawn_ksoftirqd+0x0/0x44 returned 0 after 0 usecs [0.001250 < 0.000015 >] calling init_workqueues+0x0/0x244 @ 1 [0.001489 < 0.000239 >] initcall init_workqueues+0x0/0x244 returned 0 after 0 usecs [0.001506 < 0.000017 >] calling rcu_scheduler_really_started+0x0/0x3c @ 1 [0.001523 < 0.000017 >] initcall rcu_scheduler_really_started+0x0/0x3c returned 0 after 0 usecs [0.001535 < 0.000012 >] calling relay_init+0x0/0x14 @ 1 [0.001546 < 0.000011 >] initcall relay_init+0x0/0x14 returned 0 after 0 usecs [0.001557 < 0.000011 >] calling jump_label_init_module+0x0/0x1c @ 1 [0.001571 < 0.000014 >] initcall jump_label_init_module+0x0/0x1c returned 0 after 0 usecs [0.001588 < 0.000017 >] calling mbus_init+0x0/0x4c @ 1 [0.001611 < 0.000023 >] initcall mbus_init+0x0/0x4c returned 0 after 0 usecs [0.003176 < 0.001565 >] calling sram_init+0x0/0xd0 @ 1 [0.003211 < 0.000035 >] initcall sram_init+0x0/0xd0 returned 0 after 0 usecs [0.003231 < 0.000020 >] calling ipc_ns_init+0x0/0x20 @ 1 [0.003246 < 0.000015 >] initcall ipc_ns_init+0x0/0x20 returned 0 after 0 usecs [0.003260 < 0.000014 >] calling init_mmap_min_addr+0x0/0x2c @ 1 [0.003273 < 0.000013 >] initcall init_mmap_min_addr+0x0/0x2c returned 0 after 0 usecs [0.003287 < 0.000014 >] calling net_ns_init+0x0/0x15c @ 1 [0.003312 < 0.000025 >] initcall net_ns_init+0x0/0x15c returned 0 after 0 usecs [0.003515 < 0.000203 >] calling vfp_init+0x0/0x174 @ 1 [0.003524 < 0.000009 >] VFP support v0.3: implementor 41 architecture 2 part 30 variant 7 rev 5 [0.003543 < 0.000019 >] initcall vfp_init+0x0/0x174 returned 0 after 0 usecs [0.003555 < 0.000012 >] calling ptrace_break_init+0x0/0x34 @ 1 [0.003569 < 0.000014 >] initcall ptrace_break_init+0x0/0x34 returned 0 after 0 usecs
根据得到的时间差值,针对性做优化。
3、驱动优化
为了加速启动时间,可以将部分驱动编译成模块放置启动脚本中,等应用程序运行后insmod到内核中
部分耗时驱动需要定位耗时的位置,便于找到解决措施,可通过阅读代码,猜测耗时位置并通过如下方式逐层查找
static int xxx_init() //耗时函数 { ktime_t calltime, delta, rettime; unsigned long long duration; int ret;
/**待测函数执行前时间戳**/ calltime = ktime_get();//获取当前时间戳
/**待测函数**/ xxx_function();//比如预计该函数比较耗时 /**计算时间差**/ rettime = ktime_get(); delta = ktime_sub(rettime, calltime); duration = (unsigned long long) ktime_to_ns(delta) >> 10; printk(KERN_DEBUG "%s %d %d duration:%lld usecs\n", __FILE__, __func__, __LINE__, duration); }
4、补充
我们先知道打印信息中的initcall来自哪里,可以通过以下方式找到,内核通过调用道init/main.c中的kernel_init -> do_basic_setup -> do_initcalls -> do_initcalls -> do_initcall_level -> do_one_initcall
682 int __init_or_module do_one_initcall(initcall_t fn) 683 { 684 int count = preempt_count(); 685 int ret; 686 687 if (initcall_debug) 688 ret = do_one_initcall_debug(fn); 689 else 690 ret = fn(); 691 692 msgbuf[0] = 0; 693 694 if (ret && ret != -ENODEV && initcall_debug) 695 sprintf(msgbuf, "error code %d ", ret); 696 697 if (preempt_count() != count) { 698 strlcat(msgbuf, "preemption imbalance ", sizeof(msgbuf)); 699 preempt_count() = count; 700 } 701 if (irqs_disabled()) { 702 strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf)); 703 local_irq_enable(); 704 } 705 if (msgbuf[0]) { 706 printk("initcall %pF returned with %s\n", fn, msgbuf); 707 } 708 709 return ret; 710 }
689行:initcall_debug为布尔值,bootargs设置了initcall_debug后,会执行do_one_initcall_debug
664 static int __init_or_module do_one_initcall_debug(initcall_t fn) 665 { 666 ktime_t calltime, delta, rettime; 667 unsigned long long duration; 668 int ret; 669 670 printk(KERN_DEBUG "calling %pF @ %i\n", fn, task_pid_nr(current)); 671 calltime = ktime_get(); 672 ret = fn(); 673 rettime = ktime_get(); 674 delta = ktime_sub(rettime, calltime); 675 duration = (unsigned long long) ktime_to_ns(delta) >> 10; 676 printk(KERN_DEBUG "initcall %pF returned %d after %lld usecs\n", fn, 677 ret, duration); 678 679 return ret; 680 }
现在很清晰的看到,calling和initcall的打印,具体fn函数指针是通过module_init相关的宏放进去的,这里暂不具体分析了。