优化开机过程中的内核空闲时间

本文探讨了Linux系统启动过程中的内核空闲时间优化方案,通过跟踪内核启动流程和关键函数,揭示了空闲时间的主要来源,并提出了具体的改进措施。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

rel="File-List" href="file:///C:%5CDOCUME%7E1%5Czjujoe%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">

优化开机过程中的内核空闲时间

作者: zjujoe 转载请注明出处

Emailzjujoe@yahoo.com

BLOGhttp://blog.youkuaiyun.com/zjujoe

 

Linux 下有一条命令:

~ # cat /proc/uptime

8.01 4.64

其中输出一表示系统启动时间,输出二表示系统空闲时间。

 

空闲时间表示此时 CPU 无事可做,将这段时间优化掉可以提高系统启动速度。

 

网络上已经有很多优化系统进入用户空间空闲时间的文档,我们也进行了很多相关实验。效果非常明显。

另外,我们发现,系统在进入用户空间之前,就已经存在一些空闲时间了!这里, 我们跟踪一下这一段空闲时间。

 

cat /proc/uptime 对应的内核实现

 107static int uptime_read_proc(char *page, char **start, off_t off,
  
 108                                 int count, int *eof, void *data)
  
 109{
  
 110        struct timespec uptime;
  
 111        struct timespec idle;
  
 112        int len;
  
 113        cputime_t idletime = cputime_add(init_task.utime, init_task.stime);
  
 114
  
 115        do_posix_clock_monotonic_gettime(&uptime);
  
 116        monotonic_to_bootbased(&uptime);
  
 117        cputime_to_timespec(idletime, &idle);
  
 118        len = sprintf(page,"%lu.%02lu %lu.%02lu/n",
  
 119                        (unsigned long) uptime.tv_sec,
  
 120                        (uptime.tv_nsec / (NSEC_PER_SEC / 100)),
  
 121                        (unsigned long) idle.tv_sec,
  
 122                        (idle.tv_nsec / (NSEC_PER_SEC / 100)));
  
 123
  
 124        return proc_calc_metrics(page, start, off, count, eof, len);
  
 125}
  

  
    
  
由此可见,所谓 idletime, 其实就是系统的 idle进程(0号进程init_task)的运行时间。 为了能在内核空间显示同样消息,我们模仿该函数,写一个打印时间信息的内核函数:
  
void print_uptime(const unsigned char * func, unsigned long line) {
  
        struct timespec uptime;
  
        struct timespec idle;
  
        cputime_t idletime;
  

  
    
  
        idletime = cputime_add(init_task.utime, init_task.stime);
  
        do_posix_clock_monotonic_gettime(&uptime);
  
        cputime_to_timespec(idletime, &idle);
  
        printk("%s: %lu@%s oscr0:%lx %lu.%02lu %lu.%02lu/n",/
  
                __func__, line, func, *(unsigned long *)0xf
  
   6a
  00010,/
  
                                       (unsigned long)uptime.tv_sec,/
  
                               (uptime.tv_nsec / (NSEC_PER_SEC / 100)),/
  
                                       (unsigned long)idle.tv_sec,/
  
                               (idle.tv_nsec / (NSEC_PER_SEC / 100)));/
  
}
  

  
    
  
其中, oscr 为一个free running  
  
   3.25M
   timer, 表示系统自 reset 以来的时间。对于我们这个实验,可以忽略它。
  

  
    
  
我们在需要打印时间信息的地方调用:
  
print_uptime(__func__, __LINE__);
  
就可以得到函数名,代码行位置,以及时间信息, 比如:
  

[    0.230000] print_uptime: 3233@synchronize_net oscr0:2715e1 0.22 0.17

最后一个 field 为系统的闲等时间。
  

  
    
  

关于启动过程中的进程

内核启动之初,系统还没有进程调度的概念, start_kernel(内核C语言入口函数) 会调用一堆的初始化函数,最后调用的是rest_init

 

 460static void noinline __init_refok rest_init(void)
  
 461        __releases(kernel_lock)
  
 462{
  
 463        int pid;
  
 464
  
 465        kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
  
 466        numa_default_policy();
  
 467        pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
  
 468        kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
  
 469        unlock_kernel();
  
 470
  
 471        /*
  
 472         * The boot idle thread must execute schedule()
  
 473         * at least once to get things moving:
  
 474         */
  
 475        init_idle_bootup_task(current);
  
 476        preempt_enable_no_resched();
  
 477        schedule();
  
 478        preempt_disable();
  
 479
  
 480        /* Call into cpu_idle with preempt disabled */
  
 481        cpu_idle();
  
 482}
  

 

我们在kernel_thread 函数前插入打印语句, 得到:

[    0.140000] print_uptime: 465@rest_init oscr0: 22a 0a 4 0.14 0.14

可见此时的 idle 时间并非真正的 idle, 而是实实在在的 CPU 运行时间。

 

kernel_thread 函数创建了系统的 1 号进程, rest_init 函数会最终调用 cpu_idle, 变成真正的 idle 进程。

 

底下的主要任务则由1 号进程 init 来完成, 最终它会运行用户空间的 init 程序, 变成一个用户进程。

 

跟踪空闲时间发生位置

首先研究 init 函数:

849 static int __init init(void *unused)
  
850 {
  
851         lock_kernel();
  
852         /*
  
853          * init can run on any cpu.
  
854          */
  
855         set_cpus_allowed(current, CPU_MASK_ALL);
  
856         /*
  
857          * Tell the world that we're going to be the grim
  
858          * reaper of innocent orphaned children.
  
859          *
  
860          * We don't want people to have to make incorrect
  
861          * assumptions about where in the task array this
  
862          * can be found.
  
863          */
  
864         init_pid_ns.child_reaper = current;
  
865 
  
866         cad_pid = task_pid(current);
  
867 
  
868         smp_prepare_cpus(max_cpus);
  
869 
  
870         do_pre_smp_initcalls();
  
871 
  
872         smp_init();
  
873         sched_init_smp();
  
874 
  
875         cpuset_init_smp();
  
876 
  
877         do_basic_setup();
  
878 
  
879         /*
  
880          * check if there is an early userspace init.  If yes, let it do all
  
881          * the work
  
882          */
  
883 
  
884         if (!ramdisk_execute_command)
  
885                 ramdisk_execute_command = "/init";
  
886 
  
887         if (sys_access((const char __user *)ramdisk_execute_command, 0) != 0) {
  
888                 ramdisk_execute_command = NULL;
  
889                 prepare_namespace();
  
890         }
  
891 
  
892         /*
  
893          * Ok, we have completed the initial bootup, and
  
894          * we're essentially up and running. Get rid of the
  
895          * initmem segments and start the user-mode stuff..
  
896          */
  
897         init_post();
  
898         return 0;
  
899 }
  

加入打印语句, 发现 do_basic_setup 花了不少闲等时间。

[    0.140000] print_uptime: 877@init oscr0: 22a 3d6 0.15 0.14

[    0.960000] print_uptime: 879@init oscr0:4b92ee 1.27 0.72

 

再来看 do_basic_setup 函数:

726 static void __init do_basic_setup(void)
  
727 {
  
728         /* drivers will send hotplug events */
  
729         init_workqueues();
  
730         usermodehelper_init();
  
731         driver_init();
  
732         init_irq_proc();
  
733         do_initcalls();
  
734 }
  

 

同样, 定位出 do_initcalls 函数花了不少闲等时间。

676 static void __init do_initcalls(void)
  
677 {
  
678         initcall_t *call;
  
679         int count = preempt_count();
  
680 
  
681         for (call = __initcall_start; call < __initcall_end; call++) {
  
682                 char *msg = NULL;
  
683                 char msgbuf[40];
  
684                 int result;
  
685 
  
686                 if (initcall_debug) {
  
687                         printk("Calling initcall 0x%p", *call);
  
688                         print_fn_descriptor_symbol(": %s()",
  
689                                                    (unsigned long)*call);
  
690                         printk("/n");
  
691                 }
  
692 
  
693                 result = (*call) ();
  
694 
  
695                 if (result && result != -ENODEV && initcall_debug) {
  
696                         sprintf(msgbuf, "error code %d", result);
  
697                         msg = msgbuf;
  
698                 }
  
699                 if (preempt_count() != count) {
  
700                         msg = "preemption imbalance";
  
701                         preempt_count() = count;
  
702                 }
  
703                 if (irqs_disabled()) {
  
704                         msg = "disabled interrupts";
  
705                         local_irq_enable();
  
706                 }
  
707                 if (msg) {
  
708                         printk(KERN_WARNING "initcall at 0x%p", *call);
  
709                         print_fn_descriptor_symbol(": %s()",
  
710                                                    (unsigned long)*call);
  
711                         printk(": returned with %s/n", msg);
  
712                 }
  
713         }
  
714 
  
715         /* Make sure there is no pending stuff from the initcall sequence */
  
716         flush_scheduled_work();
  
717 }
  

 

该函数正如其名, 调用各种 init 函数。在 693 行前后插入语句, 再结合各驱动初始化函数的打印信息,发现,空闲时间主要发生在 tcp/ip, 驱动 misc_control, usb host, mmc 的初始化函数上。

 

继续, 我们定位出 mmc 驱动中调用了 msleep(1) 将其改为 mdelay(1) 后, 发现闲等时间没有了, 忙等时间也缩短了!

 

总结

内核启动过程中, 使用 msleep 函数会导致进程切换, 而此时只有 0 idle 进程与 1 init 进程。切换到 idle 进程没有意义, 反而浪费了进程切换时间。

所以,系统启动过程中应该使用mdelay 等忙等函数, 而不能使用 msleep 等闲等函数。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值