Linux内核入口函数——initcall

本文探讨了Linux内核中模块初始化宏module_init和非模块化驱动的初始化方法,如device_initcall等,介绍了initcall的不同等级及其在内核启动过程中的执行顺序。

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

写过Linux驱动的人都知道module_init宏,因为它声明了一个驱动的入口函数。

除了module_init宏,你会发现在Linux内核中有许多的驱动并没有使用module_init宏来声明入口函数,而是看到了许多诸如以下的声明:

static int __init qcom_iommu_init(void)
{
 int ret;

 ret = platform_driver_register(&qcom_iommu_ctx_driver);
 if (ret)
  return ret;

 ret = platform_driver_register(&qcom_iommu_driver);
 if (ret)
  platform_driver_unregister(&qcom_iommu_ctx_driver);

 return ret;
}
device_initcall(qcom_iommu_init);
static int __init ebsa110_init(void)
{
 arm_pm_idle = ebsa110_idle;
 return platform_add_devices(ebsa110_devices, ARRAY_SIZE(ebsa110_devices));
}

arch_initcall(ebsa110_init);

上述举例的两个驱动入口分别使用了device_initcall()arch_initcall()来声明驱动入口,这些本质上都是对initcall的调用,module_init也如此。

initcall等级

Linux内核对initcall进行了等级划分,每一种类型的initcall都有对应等级,等级0-7。

路径:include/init/init.h

/* initcalls are now grouped by functionality into separate 
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 */

#define __define_initcall(fn, id) \
 static initcall_t __initcall_##fn##id __used \
 __attribute__((__section__(".initcall" #id ".init"))) = fn; \
 LTO_REFERENCE_INITCALL(__initcall_##fn##id)

id越小等级越高,Linux会按照等级由高到低顺序执行:

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)  __define_initcall(fn, early)

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 * Keep main.c:initcall_level_names[] in sync.
 */
#define pure_initcall(fn)  __define_initcall(fn, 0)

#define core_initcall(fn)  __define_initcall(fn, 1)
#define core_initcall_sync(fn)  __define_initcall(fn, 1s)
#define postcore_initcall(fn)  __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn)  __define_initcall(fn, 3)
#define arch_initcall_sync(fn)  __define_initcall(fn, 3s)
#define subsys_initcall(fn)  __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn)   __define_initcall(fn, 5)
#define fs_initcall_sync(fn)  __define_initcall(fn, 5s)
#define rootfs_initcall(fn)  __define_initcall(fn, rootfs)
#define device_initcall(fn)  __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn)  __define_initcall(fn, 7)
#define late_initcall_sync(fn)  __define_initcall(fn, 7s)

#define __initcall(fn) device_initcall(fn)

这么做的目的主要是根据优先级依次对设备进行初始化,例如会先初始化与架构相关的,然后再初始化内核子系统。

Linux对initcall的调用

在Linux启动时,会依次遍历所有等级的initcall,以完成一系列的初始化。

initcall的调用流程:

start_kernel->
   kernel_init->
      kernel_init_freeable->
         do_basic_setup->
            do_initcalls->
               do_initcall_level()

do_initcalls()函数中,会遍历所有等级的initcall,完成初始化。

static void __init do_initcalls(void)
{
 int level;
 size_t len = strlen(saved_command_line) + 1;
 char *command_line;

 command_line = kzalloc(len, GFP_KERNEL);
 if (!command_line)
  panic("%s: Failed to allocate %zu bytes\n", __func__, len);

    //遍历所有等级的initcall,level变量对应等级
 for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
  /* Parser modifies command_line, restore it each time */
  strcpy(command_line, saved_command_line);
  do_initcall_level(level, command_line);//执行该等级下的所有函数
 }

 kfree(command_line);
}

do_initcall_level()会执行对应等级下的所有函数:

static void __init do_initcall_level(int level, char *command_line)
{
 initcall_entry_t *fn;

 parse_args(initcall_level_names[level],
     command_line, __start___param,
     __stop___param - __start___param,
     level, level,
     NULL, ignore_unknown_bootoption);

 trace_initcall_level(initcall_level_names[level]);
 for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
  do_one_initcall(initcall_from_entry(fn));
}

module_init等级

module_init宏使用的是device_initcall,等级为6

#define device_initcall(fn)  __define_initcall(fn, 6)
......
#define __initcall(fn) device_initcall(fn)
......
#define module_init(x) __initcall(x);

在一些内核驱动中,直接使用了device_initcall()来声明驱动入口,其效果与使用module_init是一样的。

### 定义 `device_init_func` 和属性解析 在Linux内核编程中,定义和特定的编译器属性用于控制函数的行为及其放置位置。对于设备初始化而言,定义通常会利用这些特性来确保初始化代码能够被正确处理。 #### 使用 `__attribute__((used))` 该属性表明即使优化过程可能认为某个变量或函数未被使用,也应保留它。这通过以下方式实现: ```c #define __used __attribute__((__used__)) ``` 这意味着任何标记为 `__used` 的实体都将强制编译器将其保留在最终二进制文件中[^2]。 #### Section 属性的应用 Section 属性允许程序员指定某段代码或者数据应该放在可执行文件中的哪个部分。这对于初始化特别有用,因为可以将所有的初始化调用集中到一起,在启动时按顺序执行。例如: ```c #define ___define_initcall(fn, id, __sec) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(#__sec ".init"))) = fn; ``` 这段代码创建了一个静态全局变量,并将其放入名为 `.init` 的特殊节区中。当系统启动时,所有位于此类节区内的函数都会被执行[^1]。 #### 设备初始化的具体应用 考虑一个具体的场景——驱动程序或其他模块化组件的加载。为了使这类功能能够在适当的时间点自动注册自己,常常采用类似于下面的方式: ```c static int my_device_init(void){ printk(KERN_INFO "My Device Initialized\n"); return 0; } module_init(my_device_init); ``` 这里 `module_init()` 是一种常见的机制,用来声明一个入口点给动态加载模块。而更复杂的环境中,则可能会见到更多种类的初始化级别,比如早于其他服务之前完成某些设置的工作[^4]。 #### 解决编译问题的方法 如果遇到与上述有关联的编译错误,建议采取以下几个方面进行排查: - **确认头文件包含**:确保包含了必要的头文件以便访问所需的定义。 - **检查拼写错误**:仔细审查源码中有无错别字或者其他形式上的失误。 - **验证链接配置**:有时问题并非出自语法本身而是由于链接阶段缺少相应的库支持造成的。 - **查阅文档资料**:参考官方手册以及社区资源获取更多信息和支持。 ```cpp // 示例:正确的定义使用方法 #include <linux/init.h> #include <linux/module.h> static int example_driver_init(void){ pr_info("Example Driver Loaded.\n"); return 0; } module_init(example_driver_init); MODULE_LICENSE("GPL"); // 声明许可证类型 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值