文章目录
- 概述
- module_init
- module_platform_driver
- 设置参数回调early_param
- 函数篇
- pure_initcall
- core_initcall
- postcore_initcall
- arch_initcall
- builtin_platform_driver
- device_initcall
- module_platform_driver
- __define_initcall添加到了哪里?
- 如何被调用的?
- __init/__exit/__initdata
概述
各类初始化方法定义见:include\linux\init.h
#define __setup_param(str, unique_id, fn, early) \static const char __setup_str_##unique_id[] __initconst \__aligned(1) = str; \static struct obs_kernel_param __setup_##unique_id \__used __section(".init.setup") \__aligned(__alignof__(struct obs_kernel_param)) \= { __setup_str_##unique_id, fn, early }#define early_param(str, fn) \__setup_param(str, fn, fn, 1)#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)/** Early initcalls run before initializing SMP.** Only for built-in code, not modules.*/
//用于定义早期初始化函数,优先级为early
#define early_initcall(fn) __define_initcall(fn, early)//用于定义纯初始化函数,优先级为0
#define pure_initcall(fn) __define_initcall(fn, 0)//用于定义核心功能初始化函数,优先级为1
#define core_initcall(fn) __define_initcall(fn, 1)
//与core_initcall相同,但启用同步调用(即等待初始化函数完成后再继续进行),下同
#define core_initcall_sync(fn) __define_initcall(fn, 1s)//用于定义核心功能后期初始化函数,优先级为2
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)//用于定义架构相关的初始化函数(如PCI初始化),优先级为3
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)//用于定义子系统初始化函数,优先级为4
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)//用于定义文件系统初始化函数,优先级为5
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)//用于定义根文件系统初始化函数,优先级为rootfs
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)//用于定义设备初始化函数,优先级为6
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)//用于定义后期初始化函数,优先级为7
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
module_init
//定义于include\linux\module.h
#define module_init(x) __initcall(x);//定义于include\linux\init.h
#define __initcall(fn) device_initcall(fn)
module_platform_driver
这个宏用于简化平台设备驱动程序的注册和注销。
//include\linux\device\driver.h
/** 这个宏用于定义一个驱动程序的初始化和退出函数,并将它们注册为模块的初始化和退出函数* 该宏最终注册了驱动的初始化函数和退出函数,并使用module_init和module_exit完成了驱动的* 初始化和退出函数的声明。* module_init函数中实现的模块初始化函数实际为:* platform_driver_register(&(__driver), ##__VA_ARGS__);* module_exit函数中实现的模块退出函数实际为:* platform_driver_unregister(&(__driver) , ##__VA_ARGS__);* 因module_platform_driver注册时只有一个参数,因此##__VA_ARGS__实际为空*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);//include\linux\platform_device.h
#define module_platform_driver(__platform_driver) \module_driver(__platform_driver, platform_driver_register, \platform_driver_unregister)
platform驱动注册示例:
//drivers\devfreq\exynos-bus.c
static const struct of_device_id exynos_bus_of_match[] = {{ .compatible = "samsung,exynos-bus", },{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, exynos_bus_of_match);static struct platform_driver exynos_bus_platdrv = {.probe = exynos_bus_probe,.shutdown = exynos_bus_shutdown,.driver = {.name = "exynos-bus",.pm = pm_sleep_ptr(&exynos_bus_pm),.of_match_table = exynos_bus_of_match,},
};
module_platform_driver(exynos_bus_platdrv);
module_driver宏其它驱动实现示例对于其他类型的驱动程序,例如 I2C 驱动程序,可能需要额外的参数:
#define module_i2c_driver(__i2c_driver, __init_arg) \module_driver(__i2c_driver, i2c_driver_register, \i2c_driver_unregister, __init_arg)
在这个示例中,假设 i2c_driver_register 和 i2c_driver_unregister 需要额外的参数 __init_arg,可以这样定义宏:
static struct i2c_driver example_i2c_driver = {.driver = {.name = "example_i2c",.owner = THIS_MODULE,},// 其他字段可以根据需要填充
};
#define __init_arg 0x1234 // 假设这是初始化参数
module_i2c_driver(example_i2c_driver, __init_arg);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author Name");
MODULE_DESCRIPTION("A simple I2C driver example");
通过使用可变参数,module_driver 宏可以适应不同类型的驱动程序的需求,提供一个通用的解决方案来处理驱动程序的注册和注销。对于平台驱动程序来说,通常不需要额外的参数,因此宏调用时不传递可变参数。对于其他类型的驱动程序,如果注册和注销函数需要额外的参数,可以在宏调用时传递这些参数。
设置参数回调early_param
early_param宏,其作用是在内核启动的早期阶段,为指定参数注册一个回调函数。
示例代码:
//drivers\pci\pci.c
static int __init pci_setup(char *str)
{while (str) {char *k = strchr(str, ',');if (k)*k++ = 0;if (*str && (str = pcibios_setup(str)) && *str) {if (!strcmp(str, "nomsi")) {pci_no_msi();} else if (!strncmp(str, "noats", 5)) {pr_info("PCIe: ATS is disabled\n");pcie_ats_disabled = true;} else if (!strcmp(str, "noaer")) {pci_no_aer();} else if (!strcmp(str, "earlydump")) {pci_early_dump = true;} else if (!strncmp(str, "realloc=", 8)) {pci_realloc_get_opt(str + 8);} else if (!strncmp(str, "realloc", 7)) {pci_realloc_get_opt("on");} else if (!strcmp(str, "nodomains")) {pci_no_domains();} else if (!strncmp(str, "noari", 5)) {pcie_ari_disabled = true;} else if (!strncmp(str, "cbiosize=", 9)) {pci_cardbus_io_size = memparse(str + 9, &str);} else if (!strncmp(str, "cbmemsize=", 10)) {pci_cardbus_mem_size = memparse(str + 10, &str);} else if (!strncmp(str, "resource_alignment=", 19)) {resource_alignment_param = str + 19;} else if (!strncmp(str, "ecrc=", 5)) {pcie_ecrc_get_policy(str + 5);} else if (!strncmp(str, "hpiosize=", 9)) {pci_hotplug_io_size = memparse(str + 9, &str);} else if (!strncmp(str, "hpmmiosize=", 11)) {pci_hotplug_mmio_size = memparse(str + 11, &str);} else if (!strncmp(str, "hpmmioprefsize=", 15)) {pci_hotplug_mmio_pref_size = memparse(str + 15, &str);} else if (!strncmp(str, "hpmemsize=", 10)) {pci_hotplug_mmio_size = memparse(str + 10, &str);pci_hotplug_mmio_pref_size = pci_hotplug_mmio_size;} else if (!strncmp(str, "hpbussize=", 10)) {pci_hotplug_bus_size =simple_strtoul(str + 10, &str, 0);if (pci_hotplug_bus_size > 0xff)pci_hotplug_bus_size = DEFAULT_HOTPLUG_BUS_SIZE;} else if (!strncmp(str, "pcie_bus_tune_off", 17)) {pcie_bus_config = PCIE_BUS_TUNE_OFF;} else if (!strncmp(str, "pcie_bus_safe", 13)) {pcie_bus_config = PCIE_BUS_SAFE;} else if (!strncmp(str, "pcie_bus_perf", 13)) {pcie_bus_config = PCIE_BUS_PERFORMANCE;} else if (!strncmp(str, "pcie_bus_peer2peer", 18)) {pcie_bus_config = PCIE_BUS_PEER2PEER;} else if (!strncmp(str, "pcie_scan_all", 13)) {pci_add_flags(PCI_SCAN_ALL_PCIE_DEVS);} else if (!strncmp(str, "disable_acs_redir=", 18)) {disable_acs_redir_param = str + 18;} else {pr_err("PCI: Unknown option `%s'\n", str);}}str = k;}return 0;
}
early_param("pci", pci_setup);
"pci"是参数名称,pci_setup是回调函数。在内核启动的早期阶段,当命令行参数中包含以"pci."开头的参数时,会触发pci_setup函数的执行。这样可以在内核启动期间对PCI相关的参数进行设置和处理。
函数篇
pure_initcall
pure_initcall是指Linux内核中的一种初始化函数类型,它用于在内核初始化过程的早期执行。这类初始化函数不包含任何内核模块的使用或依赖,并且是在内核初始化过程的早期阶段执行的。 pure_initcall函数通常在内核的早期阶段执行,并且在其他类型的初始化函数执行之前。它用于执行一些与硬件或系统底层操作相关的初始化,以确保系统正确启动和配置。 这类初始化函数的特点是它们不依赖于其他内核模块,不会引入模块的加载和卸载过程,并且不依赖于任何运行时环境。它们通常执行一些基本的系统设置,例如初始化调度器、初始化定时器、启用硬件设备等。
示例代码:
//drivers\pci\pci.c
static int __init pci_realloc_setup_params(void)
{resource_alignment_param = kstrdup(resource_alignment_param,GFP_KERNEL);disable_acs_redir_param = kstrdup(disable_acs_redir_param, GFP_KERNEL);return 0;
}
pure_initcall(pci_realloc_setup_params);
core_initcall
arch\x86\power\cpu.c
static int __init bsp_pm_check_init(void)
{/** Set this bsp_pm_callback as lower priority than* cpu_hotplug_pm_callback. So cpu_hotplug_pm_callback will be called* earlier to disable cpu hotplug before bsp online check.*/pm_notifier(bsp_pm_callback, -INT_MAX);return 0;
}core_initcall(bsp_pm_check_init);
postcore_initcall
postcore_initcall函数是在核心初始化期间执行的一个初始化函数类型。核心初始化包括一系列早期的系统设置和底层组件的初始化。在这个阶段,内核还没有完全准备好运行,并且某些子系统和功能可能还没有完全初始化。
postcore_initcall函数用于在核心初始化的后期执行,当内核已经完成了必要的核心初始化操作时。这些函数通常用于完成一些与核心配置和系统初始化相关的任务,例如初始化内存管理、初始化设备子系统等。它们会在核心初始化的较后阶段执行,但在其他类型的初始化函数(如设备初始化函数和驱动程序注册)之前。
示例代码:
//drivers\pci\pci-driver.c
static int __init pci_driver_init(void)
{int ret;ret = bus_register(&pci_bus_type);if (ret)return ret;#ifdef CONFIG_PCIEPORTBUSret = bus_register(&pcie_port_bus_type);if (ret)return ret;
#endifdma_debug_add_bus(&pci_bus_type);return 0;
}
postcore_initcall(pci_driver_init);
arch_initcall
static __init int pci_arch_init(void)
{int type, pcbios = 1;type = pci_direct_probe();if (!(pci_probe & PCI_PROBE_NOEARLY))pci_mmcfg_early_init();if (x86_init.pci.arch_init)pcbios = x86_init.pci.arch_init();/** Must happen after x86_init.pci.arch_init(). Xen sets up the* x86_init.irqs.create_pci_msi_domain there.*/x86_create_pci_msi_domain();if (!pcbios)return 0;pci_pcbios_init();/** don't check for raw_pci_ops here because we want pcbios as last* fallback, yet it's needed to run first to set pcibios_last_bus* in case legacy PCI probing is used. otherwise detecting peer busses* fails.*/pci_direct_init(type);if (!raw_pci_ops && !raw_pci_ext_ops)printk(KERN_ERR"PCI: Fatal: No config space access function found\n");dmi_check_pciprobe();dmi_check_skip_isa_align();return 0;
}
arch_initcall(pci_arch_init);
builtin_platform_driver
builtin_platform_driver 是用于将平台驱动编译为内核的一部分的宏。通过 builtin_platform_driver 注册的驱动将在内核编译期间静态链接到内核中,成为内核的一部分。这意味着驱动将在内核启动时自动加载和运行,并不需要通过模块加载机制进行加载。这种方式避免了运行时加载模块的开销,并将驱动直接编译到内核中,使其成为内核的固有功能。
示例代码:
//drivers\pci\controller\dwc\pcie-hisi.c
static struct platform_driver hisi_pcie_almost_ecam_driver = {.probe = pci_host_common_probe,.driver = {.name = "hisi-pcie-almost-ecam",.of_match_table = hisi_pcie_almost_ecam_of_match,.suppress_bind_attrs = true,},
};
builtin_platform_driver(hisi_pcie_almost_ecam_driver);
device_initcall
device_initcall 是一个初始化函数标记,用于在 Linux 内核启动过程中按顺序执行的回调函数。通过 device_initcall 标记的驱动初始化函数将在内核初始化过程的特定阶段调用。这些函数以静态方式注册在内核中,适用于不需要动态加载和卸载的驱动。需要注意的是,通过 device_initcall 注册的驱动函数应尽量保持执行时间较短,以免阻塞其他初始化过程。
示例代码:
//drivers\pci\controller\dwc\pci-imx6.c
static int __init imx6_pcie_init(void)
{
#ifdef CONFIG_ARMstruct device_node *np;np = of_find_matching_node(NULL, imx6_pcie_of_match);if (!np)return -ENODEV;of_node_put(np);/** Since probe() can be deferred we need to make sure that* hook_fault_code is not called after __init memory is freed* by kernel and since imx6q_pcie_abort_handler() is a no-op,* we can install the handler here without risking it* accessing some uninitialized driver state.*/hook_fault_code(8, imx6q_pcie_abort_handler, SIGBUS, 0,"external abort on non-linefetch");
#endifreturn platform_driver_register(&imx6_pcie_driver);
}
device_initcall(imx6_pcie_init);
module_platform_driver
module_platform_driver:通过这个宏注册的平台驱动会被编译为内核模块,可以在运行时通过模块加载机制动态加载和卸载。相应地,该驱动需要通过模块加载工具加载到内核并通过 insmod 或其他方式加载和管理。这允许在不重新编译内核的情况下添加或移除驱动模块。
示例代码:
//drivers\pci\controller\dwc\pci-exynos.c
static struct platform_driver exynos_pcie_driver = {.probe = exynos_pcie_probe,.remove_new = exynos_pcie_remove,.driver = {.name = "exynos-pcie",.of_match_table = exynos_pcie_of_match,.pm = &exynos_pcie_pm_ops,},
};
module_platform_driver(exynos_pcie_driver);
__define_initcall添加到了哪里?
在第一章中,如#define pure_initcall(fn)
的定义,实际上是 __define_initcall(fn, 0)
。下面我们看下__define_initcall
宏相关定义如下(建议从最后一行读起):
//这两宏在include\linux\compiler_types.h
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)//从本行开始,在include\linux\init.h
typedef int (*initcall_t)(void);/* Format: <modname>__<counter>_<line>_<fn> */
#define __initcall_id(fn) \__PASTE(__KBUILD_MODNAME, \__PASTE(__, \__PASTE(__COUNTER__, \__PASTE(_, \__PASTE(__LINE__, \__PASTE(_, fn))))))/* Format: __<prefix>__<iid><id> */
#define __initcall_name(prefix, __iid, id) \__PASTE(__, \__PASTE(prefix, \__PASTE(__, \__PASTE(__iid, id))))#ifdef CONFIG_LTO_CLANG
/** With LTO, the compiler doesn't necessarily obey link order for* initcalls. In order to preserve the correct order, we add each* variable into its own section and generate a linker script (in* scripts/link-vmlinux.sh) to specify the order of the sections.*/
#define __initcall_section(__sec, __iid) \#__sec ".init.." #__iid/** With LTO, the compiler can rename static functions to avoid* global naming collisions. We use a global stub function for* initcalls to create a stable symbol name whose address can be* taken in inline assembly when PREL32 relocations are used.*/
#define __initcall_stub(fn, __iid, id) \__initcall_name(initstub, __iid, id)#define __define_initcall_stub(__stub, fn) \int __init __stub(void); \int __init __stub(void) \{ \return fn(); \} \__ADDRESSABLE(__stub)
#else
#define __initcall_section(__sec, __iid) \#__sec ".init"#define __initcall_stub(fn, __iid, id) fn#define __define_initcall_stub(__stub, fn) \__ADDRESSABLE(fn)
#endif/** CONFIG_HAVE_ARCH_PREL32_RELOCATIONS 是 Linux 内核配置选项之一,用于指示是否启用对具有* PREL32 类型重定位的体系结构的支持。在 Linux 内核的构建配置过程中,可以通过启用或禁用不同* 的配置选项来控制内核中的某些功能和特性。CONFIG_HAVE_ARCH_PREL32_RELOCATIONS 是其中的一* 个选项,它用于特定体系结构的支持。* PREL32(Partial Relative 32-bit)是一种重定位类型,用于将代码和数据的地址转换为相对于当* 前位置的偏移量。这种类型的重定位在某些体系结构上可用,并且被用于节省内存,因为它不需要使用* 完整的 32 位地址。通过启用 CONFIG_HAVE_ARCH_PREL32_RELOCATIONS 选项,内核会在特定体系* 结构上启用对 PREL32 类型重定位的支持。这样,在构建内核时,适当的编译器和链接器选项将被使用,* 以便生成和处理 PREL32 类型的重定位。*/
#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#define ____define_initcall(fn, __stub, __name, __sec) \__define_initcall_stub(__stub, fn) \asm(".section \"" __sec "\", \"a\" \n" \__stringify(__name) ": \n" \".long " __stringify(__stub) " - . \n" \".previous \n"); \static_assert(__same_type(initcall_t, &fn));
#else
/** __attribute__是GNU C 编译器(GCC)特有的一个属性,用于将函数或变量放置在特定的* 内存段(section)中* 将static initcall_t类型变量__name放入到__section__.initcallX.init段中,x为优先级* 并将调用函数fn赋值给该变量* * 在链接过程中,不同的段(如 .initcall1.init、.initcall2.init 等)会被链接器收集到特定* 的段中。链接器脚本会将这些段合并到一个连续的区域中,这样在运行时就可以通过* initcall_levels 数组进行访问。* 链接器脚本会确保这些段按照级别顺序排列,并生成一些符号指示每个级别的起始位置,如* initcall_entry_t __initcall0_start[] = {};*/
#define ____define_initcall(fn, __unused, __name, __sec) \static initcall_t __name __used \__attribute__((__section__(__sec))) = fn;
#endif/** 这里__unique_initcall又变成了宏____define_initcall,这个宏在Line70/81定义,共有四个参数* __initcall_stub未使用,按Line52,此处为函数名* __initcall_name为调用函数按宏实现方式生成名称* __initcall_section宏根据配置,会根据宏CONFIG_LTO_CLANG开关是否要开连接成两种形式,这里* 我们关注Line49~50方式,最终被连接到一起形成了 .initcallX.init形式*/
#define __unique_initcall(fn, id, __sec, __iid) \____define_initcall(fn, \__initcall_stub(fn, __iid, id), \__initcall_name(initcall, __iid, id), \__initcall_section(__sec, __iid))/** __initcall_id宏在Line7定义,将调用函数名称和其它如* __COUNTER__(编译器内置宏,生成唯一递增整数值)、* __LINE__(行号)等进行连接,组成唯一的ID* 生成格式:__KBUILD_MODNAME__自增整数__行号_调用函数名称* 这里可以看到___define_initcall第三个参.initcall0 变成了__sec,也就是段的定义*/
#define ___define_initcall(fn, id, __sec) \__unique_initcall(fn, id, __sec, __initcall_id(fn))/** 以 #define pure_initcall(fn) __define_initcall(fn, 0)为例,* 此处优先级在___define_initcall替换中变成了两个参数* ___define_initcall(fn, 0, .initcall0)*/
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
如何被调用的?
对于early级别的处理,在do_pre_smp_initcalls
函数中调用:
typedef int (*initcall_t)(void);
typedef initcall_t initcall_entry_t;
extern initcall_entry_t __initcall_start[];static void __init do_pre_smp_initcalls(void)
{initcall_entry_t *fn;trace_initcall_level("early");for (fn = __initcall_start; fn < __initcall0_start; fn++)do_one_initcall(initcall_from_entry(fn));
}
内核初始化时,会调用do_initcalls
,通过该函数依次调用,实现了对所有注册的initcall完成调用工作。
//include\linux\init.h
#define __initdata __section(".init.data")typedef int (*initcall_t)(void);
typedef initcall_t initcall_entry_t;extern initcall_entry_t __initcall_start[];
extern initcall_entry_t __initcall0_start[];
extern initcall_entry_t __initcall1_start[];
extern initcall_entry_t __initcall2_start[];
extern initcall_entry_t __initcall3_start[];
extern initcall_entry_t __initcall4_start[];
extern initcall_entry_t __initcall5_start[];
extern initcall_entry_t __initcall6_start[];
extern initcall_entry_t __initcall7_start[];
extern initcall_entry_t __initcall_end[];//从本行开始,init\main.c
static initcall_entry_t *initcall_levels[] __initdata = {__initcall0_start,__initcall1_start,__initcall2_start,__initcall3_start,__initcall4_start,__initcall5_start,__initcall6_start,__initcall7_start,__initcall_end,
};/* Keep these in sync with initcalls in include/linux/init.h */
static const char *initcall_level_names[] __initdata = {"pure","core","postcore","arch","subsys","fs","device","late",
};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));
}static void __init do_initcalls(void)
{int level;size_t len = saved_command_line_len + 1;char *command_line;command_line = kzalloc(len, GFP_KERNEL);if (!command_line)panic("%s: Failed to allocate %zu bytes\n", __func__, len);//优先级0的先执行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);
}
__init/__exit/__initdata
定义于 include/linux/init.h
引导之后,内核释放一个特殊的部分;用 __init 标记的函数和用 __initdata 标记的数据结构在引导完成后被丢弃:同样地,模块在初始化后丢弃此内存。 __exit 用于声明只在退出时需要的函数:如果此文件未编译为模块,则该函数将 被删除。请参阅头文件以使用。请注意,使用 EXPORT_SYMBOL() 或 EXPORT_SYMBOL_GPL() 将标记为 __init 的函数导出到模块是没有意义 的——这将出问题。