几种 XXX_initcall 宏研究.doc

来源:互联网 发布:东方梦华录mac 编辑:程序博客网 时间:2024/05/22 01:55

引言

今天阅读启动相关代码(bootinfo.c), 看到了一个函数:
232 core_initcall(bootinfo_init);
 
不由想起前两天看到的: 607 subsys_initcall(gpiolib_debugfs_init)

 

结合平时我们使用的:4549 module_init(pxa3xx_nand_init);
built-in时对应:249 #define module_init(x)  __initcall(x);

 

虽然大体知道他们的意思以及运行顺序,但还是打算稍微深入了解一下。于是开始跟踪代码。

do_initcalls 开始

还记得启动过程会调用一个函数:do_initcalls 以处理 initcalls, 于是以此为出发点,先看一下该函数本身:
664 static void __init do_initcalls(void)
665 {
666         initcall_t *call;
667         int count = preempt_count();
668 
669         for (call = __initcall_start; call < __initcall_end; call++) {
673                 unsigned long j, j0 = 0;        
674                 char *msg = NULL;
675                 char msgbuf[40];
676                 int result;
677 
678                 if (initcall_debug) {
686                         j0 = jiffies;
687                 }
688 
689                 result = (*call)();
690 
691                 if (initcall_debug) {
692                         j = jiffies - j0;
705                         if (j > 1) {
706                                 printk("initcall 0x%p ran for %d msecs: ",
707                                         *call, jiffies_to_msecs(j));
708                                 print_fn_descriptor_symbol("%s()/n",
709                                         (unsigned long) *call);
710                         }
711                 }
712 
713                 if (result && result != -ENODEV && initcall_debug) {
714                         sprintf(msgbuf, "error code %d", result);
715                         msg = msgbuf;
716                 }
717                 if (preempt_count() != count) {
718                         msg = "preemption imbalance";
719                         preempt_count() = count;
720                 }
721                 if (irqs_disabled()) {
722                         msg = "disabled interrupts";
723                         local_irq_enable();
724                 }
725                 if (msg) {
726                         printk(KERN_WARNING "initcall at 0x%p", *call);
727                         print_fn_descriptor_symbol(": %s()",
728                                         (unsigned long) *call);
729                         printk(": returned with %s/n", msg);
730                 }
731         }
732 
733         /* Make sure there is no pending stuff from the initcall sequence */
734         flush_scheduled_work();
735 }
 

可以看到, 该函数其实就是按照一个初始函数列表去调用一个一个的函数。

一个有价值的信息时我们可以通过启动参数 initcall_debug=1 来让内核打印出这些函数的相关(时间、顺序等), 这对启动优化特别有帮助。

 

再看看do_initcalls 是何时被调用的:

744 static void __init do_basic_setup(void)
745 {
746         /* drivers will send hotplug events */
747         init_workqueues();
748         usermodehelper_init();
749         driver_init();
750         init_irq_proc();
751         do_initcalls();
752 }
821 static int __init kernel_init(void * unused)
822 {
823         lock_kernel();
824         /*
825          * init can run on any cpu.
826          */
827         set_cpus_allowed(current, CPU_MASK_ALL);
828         /*
829          * Tell the world that we're going to be the grim
830          * reaper of innocent orphaned children.
831          *
832          * We don't want people to have to make incorrect
833          * assumptions about where in the task array this
834          * can be found.
835          */
836         init_pid_ns.child_reaper = current;
837 
838         cad_pid = task_pid(current);
839 
840         smp_prepare_cpus(setup_max_cpus);
841 
842         do_pre_smp_initcalls();
843 
844         smp_init();
845         sched_init_smp();
846 
847         cpuset_init_smp();
848 
849         do_basic_setup();
850 
851         /*
852          * check if there is an early userspace init.  If yes, let it do all
853          * the work
854          */
855 
856         if (!ramdisk_execute_command)
857                 ramdisk_execute_command = "/init";
858 
859         if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
860                 ramdisk_execute_command = NULL;
861                 prepare_namespace();
862         }
863 
864         /*
865          * Ok, we have completed the initial bootup, and
866          * we're essentially up and running. Get rid of the
867          * initmem segments and start the user-mode stuff..
868          */
869         init_post();
870         return 0;
871 }

kernel_init 乃是 1号进程 init 的执行函数,可见do_initcalls是在系统运行到 init 进程后在即将进入用户空间前调用的。

 

再看看 初始函数列表 __initcall_start 是如何生成的, 搜索一下, 发现: 链接文件:arch/arm/kernel/vmlinux.lds.S 中有:

51                 __initcall_start = .;
 52                         INITCALLS
 53                 __initcall_end = .;
 
其中:INITCALLS 定义如下:
328 #define INITCALLS                                                       /
329         *(.initcall0.init)                                              /
330         *(.initcall0s.init)                                             /
331         *(.initcall1.init)                                              /
332         *(.initcall1s.init)                                             /
333         *(.initcall2.init)                                              /
334         *(.initcall2s.init)                                             /
335         *(.initcall3.init)                                              /
336         *(.initcall3s.init)                                             /
337         *(.initcall4.init)                                              /
338         *(.initcall4s.init)                                             /
339         *(.initcall5.init)                                              /
340         *(.initcall5s.init)                                             /
341         *(.initcallrootfs.init)                                         /
342         *(.initcall6.init)                                              /
343         *(.initcall6s.init)                                             /
344         *(.initcall7.init)                                              /
345         *(.initcall7s.init)
 
原来在这些段里面定义的函数会被 do_initcalls 调用。由于这些段名似乎是合成的, 我们直接 freetext 查找可能不行,我们再从前面说到的 XXX_initcall 宏的定义那边去跟踪,希望能够衔接到一起。
 

看看宏的定义

我们看一下各种 XXX_initcall 宏的定义:

176 #define pure_initcall(fn)               __define_initcall("",fn,0)
177 
178 #define core_initcall(fn)               __define_initcall("1",fn,1)
179 #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
180 #define postcore_initcall(fn)           __define_initcall("2",fn,2)
181 #define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)
182 #define arch_initcall(fn)               __define_initcall("3",fn,3)
183 #define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
184 #define subsys_initcall(fn)             __define_initcall("4",fn,4)
185 #define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)
186 #define fs_initcall(fn)                 __define_initcall("5",fn,5)
187 #define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
188 #define rootfs_initcall(fn)             __define_initcall("rootfs",fn,rootfs)
189 #define device_initcall(fn)             __define_initcall("6",fn,6)
190 #define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)
191 #define late_initcall(fn)               __define_initcall("7",fn,7)
192 #define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)
193 
194 #define __initcall(fn) device_initcall(fn)

 

再看一下 __define_initcall 的定义:

166 #define __define_initcall(level,fn,id) /
167         static initcall_t __initcall_##fn##id __used /
168         __attribute__((__section__(".initcall" level ".init"))) = fn

这样, 两边就衔接起来了: 原来所有的 XXX_initcall 宏所定义的函数都会放到同一个初始化函数列表中,只是不同的宏定义的函数会被放到不同的位置,数字小的会被先调用。

 

对于普通驱动,我们大多会使用  __initcall 或者 device_initcall宏, 那么使用同样宏的函数的调用顺序如何呢? 这取决与他们的链接顺序,我们可以到 System.map 文件去查看链接后的结果,比如, 可以看到:

__initcall_pxafb_init6 排在 __initcall_tty_init6 前面。

 

由于编译顺序在改变了文件名、目录名甚至配置文件后都可能变,所以我们应该尽量不要在同类的XXX_initcall 宏之间产生依赖关系。 实在需要,可以考虑使用 _sync 版本。

 

原创粉丝点击