【Linux内核启动时间优化】

优化过程一般涉及到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相关的宏放进去的,这里暂不具体分析了。

猜你喜欢

转载自www.cnblogs.com/niu-dominic/p/11936569.html