关于内核的xxx_initcall

本文深入探讨了Linux内核中的subsys_initcall()宏及其工作原理,详细介绍了它是如何组织和调用初始化函数的,以及这些函数在系统启动过程中的执行时机。

在内核代码里到处都能看到这个subsys_initcall(),而它到底是干什么的呢?让我们来揭开它的神秘面纱。

 

先来看一段代码:

---------------------------------------------------------------------

include/linux/init.h

174 /*

175  * Early initcalls run before initializing SMP.

176  *

177  * Only for built-in code, not modules.

178  */

179 #define early_initcall(fn)    __define_initcall("early",fn,early)

180

181 /*

182  * A "pure" initcall has no dependencies on anything else, and purely

183  * initializes variables that couldn't be statically initialized.

184  *

185  * This only exists for built-in code, not for modules.

186  */

187 #define pure_initcall(fn)              __define_initcall("",fn,0)

188

189 #define core_initcall(fn)             __define_initcall("1",fn,1)

190 #define core_initcall_sync(fn)      __define_initcall("1s",fn,1s)

191 #define postcore_initcall(fn)         __define_initcall("2",fn,2)

192 #define postcore_initcall_sync(fn)  __define_initcall("2s",fn,2s)

193 #define arch_initcall(fn)             __define_initcall("3",fn,3)

194 #define arch_initcall_sync(fn)      __define_initcall("3s",fn,3s)

195 #define subsys_initcall(fn)           __define_initcall("4",fn,4)

196 #define subsys_initcall_sync(fn)    __define_initcall("4s",fn,4s)

197 #define fs_initcall(fn)               __define_initcall("5",fn,5)

198 #define fs_initcall_sync(fn)        __define_initcall("5s",fn,5s)

199 #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)

200 #define device_initcall(fn)           __define_initcall("6",fn,6)

201 #define device_initcall_sync(fn)    __define_initcall("6s",fn,6s)

202 #define late_initcall(fn)             __define_initcall("7",fn,7)

203 #define late_initcall_sync(fn)      __define_initcall("7s",fn,7s)

---------------------------------------------------------------------

类似于subsys_initcall()还有很多,但它们都依赖于__define_initcall(),再来看__define_initcall()的定义:

---------------------------------------------------------------------

include/linux/init.h

 

131 typedef int (*initcall_t)(void);

 

165  *

166  * The `id' arg to __define_initcall() is needed so that multiple

167  * initcalls can point at the same handler without causing duplicate-symbol build errors.

168  */

169

170 #define __define_initcall(level,fn,id) \

171       static initcall_t __initcall_##fn##id __used \

172       __attribute__((__section__(".initcall" level ".init"))) = fn

173

---------------------------------------------------------------------

__define_initcall()宏只是定义一个initcall_t类型的静态变量,并且声明要把这个静态变量放在特定的段里而已。上面我们看到initcall_t即是指向一个无参数有int返回值的函数的指针。

 

许多的子系统都有自己的初始化函数,而这些初始化的函数又根据功能不同被分开在不同的子段里,子段的排列顺序则由链接决定。为了向后兼容,initcall()把调用,也就是一个个指向初始化函数的函数指针放进设备初始化子段里。

 

在各个平台的链接脚本文件arch/xxx/kernel/vmlinux.lds.S中,我们总能看到下面的语句:

INIT_CALLS

这个宏有如下的定义:

---------------------------------------------------------------------

include/asm-generic/vmlinux.lds.h

606 #define INIT_CALLS                                           \

607                 VMLINUX_SYMBOL(__initcall_start) = .;        \

608                 INITCALLS                                    \

609                 VMLINUX_SYMBOL(__initcall_end) = .;

---------------------------------------------------------------------

INIT_CALLS即是定义一个新的段,而定义段的字段的任务则由宏INITCALLS完成:

---------------------------------------------------------------------

include/asm-generic/vmlinux.lds.h

585 #define INITCALLS                                            \

586         *(.initcallearly.init)                               \

587         VMLINUX_SYMBOL(__early_initcall_end) = .;            \

588         *(.initcall0.init)                                   \

589         *(.initcall0s.init)                                  \

590         *(.initcall1.init)                                   \

591         *(.initcall1s.init)                                  \

592         *(.initcall2.init)                                   \

593         *(.initcall2s.init)                                  \

594         *(.initcall3.init)                                   \

595         *(.initcall3s.init)                                  \

596         *(.initcall4.init)                                   \

597         *(.initcall4s.init)                                  \

598         *(.initcall5.init)                                   \

599         *(.initcall5s.init)                                  \

600         *(.initcallrootfs.init)                              \

601         *(.initcall6.init)                                   \

602         *(.initcall6s.init)                                  \

603         *(.initcall7.init)                                   \

604         *(.initcall7s.init)

---------------------------------------------------------------------

 

而这些初始化函数又是在何时调用的呢?我们看到start_kernel()-> rest_init()-> kernel_init()-> do_basic_setup(void)-> do_initcalls(),而正是do_initcalls()处理了这些初始化函数,其定义为:

---------------------------------------------------------------------

init/main.c

765 extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

766

767 static void __init do_initcalls(void)

768 {

769         initcall_t *fn;

770

771         for (fn = __early_initcall_end; fn < __initcall_end; fn++)

772                 do_one_initcall(*fn);

773

774         /* Make sure there is no pending stuff from the initcall sequence */

775         flush_scheduled_work();

776 }

---------------------------------------------------------------------

do_initcalls()又调用do_one_initcall()函数类处理这些调用。

---------------------------------------------------------------------

init/main.c

715 static char msgbuf[64];

716 static struct boot_trace_call call;

717 static struct boot_trace_ret ret;

718

719 int do_one_initcall(initcall_t fn)

720 {

721      int count = preempt_count();

722      ktime_t calltime, delta, rettime;

723

724      if (initcall_debug) {

725         call.caller = task_pid_nr(current);

726         printk("calling  %pF @ %i\n", fn, call.caller);

727         calltime = ktime_get();

728         trace_boot_call(&call, fn);

729         enable_boot_trace();

730     }

731

732     ret.result = fn();

733

734     if (initcall_debug) {

735        disable_boot_trace();

736        rettime = ktime_get();

737        delta = ktime_sub(rettime, calltime);

738        ret.duration = (unsigned long long) ktime_to_ns(delta) >> 10;

739        trace_boot_ret(&ret, fn);

740        printk("initcall %pF returned %d after %Ld usecs\n", fn,

741        ret.result, ret.duration);

742     }

743

744     msgbuf[0] = 0;

745

746     if (ret.result && ret.result != -ENODEV && initcall_debug)

747        sprintf(msgbuf, "error code %d ", ret.result);

748

749     if (preempt_count() != count) {

750        strlcat(msgbuf, "preemption imbalance ", sizeof(msgbuf));

751        preempt_count() = count;

752     }

753     if (irqs_disabled()) {

754        strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));

755        local_irq_enable();

756     }

757     if (msgbuf[0]) {

758        printk("initcall %pF returned with %s\n", fn, msgbuf);

759     }

760

761     return ret.result;

762 }

---------------------------------------------------------------------

<think>我们需要确保驱动在内核启动早期加载,以便其他依赖该驱动的模块可以正常使用。在Linux内核中,驱动的加载顺序主要由以下因素决定: 1. 内核初始化级别:通过指定驱动的初始化函数到不同的初始化级别(如`arch_initcall`、`subsys_initcall`、`device_initcall`等) 2. 模块依赖关系:使用MODULE_SOFTDEP在模块中声明软依赖 然而,您的要求是“在内核运行其他驱动之前”先运行这个驱动,这通常意味着我们希望这个驱动在系统启动的早期阶段就被初始化。 解决方案: 1. 将驱动编译进内核(而不是作为模块),并指定较高的初始化优先级(如`subsys_initcall`或`arch_initcall`)。 2. 如果必须作为模块加载,则需要在系统启动脚本中确保该模块在其它模块之前被加载(例如,在/etc/modules中靠前,或者在initramfs中提前加载)。 具体修改步骤: 方法一:编译进内核(推荐确保早期加载) - 在Kconfig中添加配置选项,并在Makefile中编译该驱动。 - 在驱动源文件中,将初始化函数标记为`subsys_initcall`(内核初始化阶段较早)或`arch_initcall`(更早)。 方法二:作为模块加载,但强制早期加载 - 使用`module_init`声明初始化函数,但通过修改启动脚本确保首先加载此模块。 这里我们主要讨论方法一,因为编译进内核可以确保在系统启动的早期阶段初始化。 修改驱动代码: 将原来的`module_platform_driver`改为手动注册,并使用`subsys_initcall`。 步骤: 1. 移除`module_platform_driver`宏 2. 定义驱动注册和注销函数 3. 使用`subsys_initcall`指定初始化函数,使用`module_exit`指定退出函数(即使编译进内核,退出函数不会被调用,但保留以便作为模块时使用) 修改后的驱动代码(关键部分): ```c // ... 前面的代码保持不变 ... // 移除: module_platform_driver(dhsocdev_driver); static int __init dhsocdev_driver_init(void) { return platform_driver_register(&dhsocdev_driver); } static void __exit dhsocdev_driver_exit(void) { platform_driver_unregister(&dhsocdev_driver); } // 使用subsys_initcall来确保早期初始化 subsys_initcall(dhsocdev_driver_init); module_exit(dhsocdev_driver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Driver for dhtest,soc-dev device"); MODULE_VERSION("1.0"); ``` 这样,当驱动被编译进内核时,它会在子系统初始化阶段被调用,这个阶段在大多数设备驱动初始化之前(除了那些使用更早初始化级别的,如arch_initcall)。 注意:如果您的驱动依赖于其他更底层的驱动(例如时钟控制器、中断控制器等),则不能过早初始化。在这种情况下,您可能需要依赖内核的初始化顺序,或者使用设备树机制来确保正确的初始化顺序。 另一种方法:使用设备树中的initcall优先级(新版本内核) - 在设备树节点中添加`linux,initcall-debug`属性,但这主要用于调试,并不直接控制顺序。 总结: - 对于需要在内核启动早期运行的驱动,将其编译进内核并设置为`subsys_initcall`或`arch_initcall`。 - 如果依赖其他驱动,则不能早于被依赖的驱动初始化,此时需要通过内核initcall顺序自然处理。 最后,如果您的驱动是为特定硬件平台(如ARM SoC)编写的,可以考虑将驱动放到`arch/arm/mach-xxx`下的特定板级文件中,并在该平台的早期初始化函数中注册平台设备。 但是,由于您使用的是平台设备驱动框架,上面修改为`subsys_initcall`的方法是最直接的。 另外,如果您的设备是一个关键设备(例如系统时钟),可能需要更早的初始化,那么可能需要将其标记为`arch_initcall`。不过请注意,`arch_initcall`阶段的资源可能还不完整(如内存分配器可能还没准备好),因此要谨慎。 建议: - 如果不依赖于其他驱动,使用`subsys_initcall`足够早。 - 如果需要在设备模型初始化之前就初始化,可能需要使用`early_platform`机制(但这种情况较少见)。 因此,按照上述修改即可。 注意:如果驱动作为模块加载,则`subsys_initcall`无效,因为模块加载时使用`module_init`(等同于`device_initcall`)。所以,确保驱动编译进内核是关键。 如果必须作为模块,那么只能通过系统启动脚本控制加载顺序。 例如,在构建initramfs时,将模块放入initramfs,并在init脚本中首先加载该模块。或者在系统中使用`/etc/modules`文件,将模块名放在文件开头。 但模块加载顺序控制不如编译进内核可靠。 因此,如果要求在内核运行其他驱动之前运行,强烈建议编译进内核并使用`subsys_initcall`。 修改后的完整驱动代码示例(只展示修改部分): ```c // ... 前面的代码不变 ... // 移除 module_platform_driver(dhsocdev_driver); static int __init dhsocdev_driver_init(void) { return platform_driver_register(&dhsocdev_driver); } static void __exit dhsocdev_driver_exit(void) { platform_driver_unregister(&dhsocdev_driver); } // 使用subsys_initcall确保在子系统初始化阶段加载 subsys_initcall(dhsocdev_driver_init); module_exit(dhsocdev_driver_exit); ``` 这样,当内核启动时,该驱动将在较早的阶段被初始化。 §§相关问题§§: 1. Linux内核中不同的初始化级别(如arch_initcall, subsys_initcall)有哪些?它们的顺序是怎样的? 2. 如何在内核驱动中处理模块之间的依赖关系? 3. 如果两个驱动都需要早期初始化且存在依赖关系,如何解决? 4. 如何在ARM平台设备树中指定设备的初始化顺序? </think>是的,可以通过修改内核启动顺序,确保您的驱动在内核初始化阶段优先于其他驱动运行。这里有几种实现方法: ### 方法1:使用`subsys_initcall`宏(推荐) 这是最常见且标准的方法,通过指定初始化级别确保驱动优先加载: ```c #include <linux/init.h> // 替换原来的 module_init subsys_initcall(dhsocdev_driver_init); // 保持原来的 module_exit module_exit(dhsocdev_driver_exit); ``` #### Linux内核初始化级别顺序: 1. `pure_initcall` - 最早的初始化 2. `core_initcall` 3. `postcore_initcall` 4. `arch_initcall` 5. `subsys_initcall` ← 推荐级别 6. `fs_initcall` 7. `device_initcall` - 常规驱动级别 8. `late_initcall` ### 方法2:使用`early_initcall`(更早启动) 如果需要更早的初始化(在内存管理系统之前): ```c early_initcall(dhsocdev_driver_init); ``` ### 方法3:在内核启动参数中指定顺序 在启动参数中明确指定驱动加载顺序: ```c // 在驱动中添加: module_init(dhsocdev_driver_init); module_exit(dhsocdev_driver_exit); // 在启动参数中添加: dhsocdev.custom_init=1 ``` 然后在驱动中处理: ```c static int __init dhsocdev_custom_init(void) { if (!custom_init_param) return -ENODEV; return platform_driver_register(&dhsocdev_driver); } early_param("dhsocdev.custom_init", dhsocdev_custom_init); ``` ### 完整修改后的驱动示例 ```c #include <linux/init.h> // ... [其他头文件保持不变] // ... [device definitions保持不变] static int __init dhsocdev_driver_init(void) { pr_info("DHSOCDEV: EARLY INIT STARTED\n"); // 1. 优先初始化硬件 void __iomem *peri_base = ioremap(0xFF200000, DHSOC_PERI_REGION); if (!peri_base) return -ENOMEM; // 2. 配置关键时钟 iowrite32(VCXO_1_CLK_IN, peri_base + VCXO_1_OFFSET); // 3. 注册平台驱动 int ret = platform_driver_register(&dhsocdev_driver); pr_info("DHSOCDEV: EARLY INIT COMPLETE\n"); return ret; } subsys_initcall(dhsocdev_driver_init); // 早期初始化 module_exit(dhsocdev_driver_exit); ``` ### 确保优先初始化的关键点 1. **硬件依赖处理**: ```c // 在probe之前初始化关键硬件 static int __init dhsocdev_driver_init(void) { // 优先配置时钟和复位 soc_configure_pll(); soc_reset_peripherals(); // 再注册驱动 return platform_driver_register(&dhsocdev_driver); } ``` 2. **解决驱动依赖**: 在`Makefile`中确保依赖关系: ```make obj-$(CONFIG_DHSOCDEV) += dhsocdev.o dhsocdev-objs := dhsoc_core.o dhsoc_io.o dhsoc_dma.o ``` 3. **内核配置**: 在Kconfig中设置为默认启用: ```kconfig config DHSOCDEV bool "DHSOC Early Init Device" default y depends on ARCH_XYZ help Enable this for early initialization of SOC peripherals ``` ### 验证加载顺序 ```bash # 查看内核初始化顺序 dmesg | grep "initcall" # 预期输出 [ 0.000000] initcall dhsocdev_driver_init+0x0/0x100 [dhsocdev] early [ 5.000000] initcall other_driver_init+0x0/0x80 [other_driver] normal ``` ### 注意事项 1. **资源可用性问题**: - 在`subsys_initcall`阶段,内存管理系统可能尚未完全初始化 - 避免使用`kmalloc()`,改用`memblock_alloc()` ```c #include <linux/memblock.h> void *early_mem = memblock_alloc(size, PAGE_SIZE); ``` 2. **中断处理限制**: - 早期中断处理可能不可用,需要延迟处理 ```c void soc_delayed_irq_setup(struct work_struct *work) { request_irq(priv->irq, dhsocdev_interrupt, IRQF_SHARED, ...); } INIT_DELAYED_WORK(&soc_work, soc_delayed_irq_setup); schedule_delayed_work(&soc_work, msecs_to_jiffies(500)); ``` 3. **并发访问问题**: ```c static DEFINE_SPINLOCK(early_lock); spin_lock(&early_lock); /* 关键操作 */ spin_unlock(&early_lock); ``` ### 替代方案:Initramfs集成 如果需要在用户空间驱动之前初始化: ```bash # 在initramfs中添加 #!/init echo "Mounting early devices" modprobe dhsocdev mount /dev/dhsoc /mnt/socdev ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值