优化开机过程中的内核空闲时间
来源:互联网 发布:win10命令改mac地址 编辑:程序博客网 时间:2024/04/28 12:55
~ # 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 *)0xf6a00010,\
(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:22a0a4 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:22a3d6 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等闲等函数。
- 优化开机过程中的内核空闲时间
- 优化开机过程中的内核空闲时间
- 开机过程中的内核打印
- linux开机过程中的内核打印
- 空闲时间
- 内核启动时间优化
- 系统空闲时间
- 最近空闲时间安排
- 空闲时间做什么。
- 怎样度过空闲时间
- 十月空闲时间工作计划
- 内核中的时间概念
- 内核中的时间
- 内核中的时间流
- linux内核介绍之开机启动过程
- 嵌入式linux开机时间优化小结
- RK2908开机时间分析及优化
- 利用注册表优化系统,减少开机时间
- 使用委托的优点,委托和事件的区别和联系
- .net富文本编辑器中大文本写入Oracle(string转clob)
- 再谈C#委托与事件
- Linux常用功能集锦
- 介绍Linux目录结构 & Linux分区大小、挂载点 和如何分区
- 优化开机过程中的内核空闲时间
- HttpWebRequest 超时
- Oracle锁定:悲观与乐观锁详解
- 修改UINavigationBar的背景
- usb驱动
- 思维导图培训心得
- 泰泽开发平台预览
- Android面试1
- Android中如何编译运行系统自带桌面Launcher2源码