subsys_initcall
linux子系统的初始化_subsys_initcall():那些入口函数【转】
https://blog.csdn.net/u010388659/article/details/81265010
内核选项的解析完成之后,各个子系统的初始化即进入第二部分—入口函数的调用。通常USB、PCI这样的子系统都会有一个名为subsys_initcall的入口,如果你选择它们作为研究内核的切入点,那么就请首先找到它。
朱德庸在《关于上班这件事》里说,要花前半生找入口,花后半生找出口。可见寻找入口对于咱们这一生,对于看内核代码这件事儿都是无比重要的。
但是很多时候,入口并不仅仅只有subsys_initcall一个,比如PCI。
以下代码来自 linux内核源码中 include/linux/init.h 文件
117 #define pure_initcall(fn) __define_initcall("0",fn,1)
118
119 #define core_initcall(fn) __define_initcall("1",fn,1)
120 #define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
121 #define postcore_initcall(fn) __define_initcall("2",fn,2)
122 #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
123 #define arch_initcall(fn) __define_initcall("3",fn,3)
124 #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
125 #define subsys_initcall(fn) __define_initcall("4",fn,4)
126 #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
127 #define fs_initcall(fn) __define_initcall("5",fn,5)
128 #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
129 #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
130 #define device_initcall(fn) __define_initcall("6",fn,6)
131 #define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
132 #define late_initcall(fn) __define_initcall("7",fn,7)
133 #define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
134
135 #define __initcall(fn) device_initcall(fn)
这些入口有个共同的特征,它们都是使用__define_initcall宏定义的。它们的调用也不是随便的,而是按照一定顺序的,这个顺序就取决于__define_initcall宏。__define_initcall宏用来将指定的函数指针放到.initcall.init节里。
.initcall.init节
内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bass等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。 vmlinux.lds是存在于arch/<target>/目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。在vmlinux.lds文件里查找initcall.init就可以看到下面的内容
__inicall_start = .;
.initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end = .;
这就告诉我们.initcall.init节又分成了7个子节,而xxx_initcall入口函数指针具体放在哪一个子节里边儿是由xxx_initcall的定义中,__define_initcall宏的参数决定的,比如core_initcall将函数指针放在.initcall1.init子节,device_initcall将函数指针放在了.initcall6.init子节等等。各个子节的顺序是确定的,即先调用.initcall1.init中的函数指针再调用.initcall2.init中的函数指针,等等。不同的入口函数被放在不同的子节中,因此也就决定了它们的调用顺序。
注意:设备驱动程序中常见的module_init(x)函数,查看init.h文件发现,
#define module_init(x)__initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
这样推断 module_init 调用优先级为6低于subsys_initcall调用优先级4.
do_initcalls()函数
那些入口函数的调用由do_initcalls函数来完成。
do_initcall函数通过for循环,由__initcall_start开始,直到__initcall_end结束,依次调用识别到的初始化函数。而位于__initcall_start和__initcall_end之间的区域组成了.initcall.init节,其中保存了由xxx_initcall形式的宏标记的函数地址,do_initcall函数可以很轻松的取得函数地址并执行其指向的函数。
.initcall.init节所保存的函数地址有一定的优先级,越前面的函数优先级越高,也会比位于后面的函数先被调用。
由do_initcalls函数调用的函数不应该改变其优先级状态和禁止中断。因此,每个函数执行后,do_initcalls会检查该函数是否做了任何变化,如果有必要,它会校正优先级和中断状态。
另外,这些被执行的函数有可以完成一些需要异步执行的任务,flush_scheduled_work函数则用于确保do_initcalls函数在返回前等待这些异步任务结束。
666 static void __init do_initcalls(void)
667 {
668 initcall_t *call;
669 int count = preempt_count();
670
671 for (call = __initcall_start; call < __initcall_end; call++) {
672 ktime_t t0, t1, delta;
673 char *msg = NULL;
674 char msgbuf[40];
675 int result;
676
677 if (initcall_debug) {
678 printk("Calling initcall 0x%p", *call);
679 print_fn_descriptor_symbol(": %s()",
680 (unsigned long) *call);
681 printk("/n");
682 t0 = ktime_get();
683 }
684
685 result = (*call)();
686
687 if (initcall_debug) {
688 t1 = ktime_get();
689 delta = ktime_sub(t1, t0);
690
691 printk("initcall 0x%p", *call);
692 print_fn_descriptor_symbol(": %s()",
693 (unsigned long) *call);
694 printk(" returned %d./n", result);
695
696 printk("initcall 0x%p ran for %Ld msecs: ",
697 *call, (unsigned long long)delta.tv64 >> 20);
698 print_fn_descriptor_symbol("%s()/n",
699 (unsigned long) *call);
700 }
701
702 if (result && result != -ENODEV && initcall_debug) {
703 sprintf(msgbuf, "error code %d", result);
704 msg = msgbuf;
705 }
706 if (preempt_count() != count) {
707 msg = "preemption imbalance";
708 preempt_count() = count;
709 }
710 if (irqs_disabled()) {
711 msg = "disabled interrupts";
712 local_irq_enable();
713 }
714 if (msg) {
715 printk(KERN_WARNING "initcall at 0x%p", *call);
716 print_fn_descriptor_symbol(": %s()",
717 (unsigned long) *call);
718 printk(": returned with %s/n", msg);
719 }
720 }
721
722 /* Make sure there is no pending stuff from the initcall sequence */
723 flush_scheduled_work();
724 }
目前研究Linux驱动程序的启动流程,这篇文章对Linux子系统调用顺序进行了详细的讲解,同时也说明了设备驱动程序的调用顺序,很值得收藏。
————————————————
补充:
linux内核中的subsys_initcall是干什么的?
注意:使用的内核源码版本为5.1.3
1. subsys_initcall长什么样子?
它其实是个宏定义,定义如下:
#define subsys_initcall(fn) __define_initcall(fn, 4) (注意,这是使用在内置模块中的)
或
#define subsys_initcall(fn) module_init(fn) (注意,这是使用在可加载模块中的)
2. 进一步解剖__define_initcall
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
3. 再解剖___define_initcall
#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS #define ___define_initcall(fn, id, __sec) \ __ADDRESSABLE(fn) \ asm(".section \"" #__sec ".init\", \"a\" \n" \ "__initcall_" #fn #id ": \n" \ ".long " #fn " - . \n" \ ".previous \n"); #else #define ___define_initcall(fn, id, __sec) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(#__sec ".init"))) = fn; #endif
4. 结合以上内容可知
如果未定义宏CONFIG_HAVE_ARCH_PREL32_RELOCATIONS,那么subsys_initcall就被展开为:
static initcall_t __initcall_fn4 __used \
__attribute__((__section_(.initcall4.init))) = fn
也就是将fn链接到段.initcall4.init中
5. subsys_initcall与module_init有何联系?
5.1 先看看module_init是什么吧?
/* Each module must use one module_init(). */ (在编译为可加载模块时使用这个定义) #define module_init(initfn) \ static inline initcall_t __maybe_unused __inittest(void) \ { return initfn; } \ int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));
或
define module_init(x) __initcall(x); (在编译为内置模块时使用这个定义)
5.2 __initcall的定义是怎样的呢?
#define __initcall(fn) device_initcall(fn)
5.3 device_initcall是怎样的呢?
#define device_initcall(fn) __define_initcall(fn, 6)
5.4 结论
从以上分析可以看出:
在编译某驱动为内置代码时,subsys_initcall与module_init仅仅是__define_initcall的第二个参数不同而已,前者使用4,后者使用6,因此归纳出仅仅是谁先被执行的差异,subsys_initcall比module_init先执行