前言
阅读 Linux
驱动源码时,我们发现驱动的 初始化函数
和 逆初始化函数
总会被一个宏
进行修饰,这个宏就决定了驱动在启动时的那个阶段执行。
示例1:
#define __init \
__section(".init.text") \
__cold __latent_entropy __noinitretpoline __nocfi
#define __exit __section(".exit.text") __exitused __cold notrace
#define module_init(x) __initcall(x);
#define module_exit(x) __exitcall(x);
示例2:
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
// Expands to
static int __attribute__((__section__(".init.text"))) __attribute__((__cold__))
__attribute__((__no_sanitize__("cfi")))
rockchip_canfd_driver_init(void)
{
return __platform_driver_register(&(rockchip_canfd_driver),
((struct module *)0));
}
static void *__attribute__((__section__(".discard.addressable")))
__attribute__((
__used__)) __UNIQUE_ID___addressable_rockchip_canfd_driver_init13 =
(void *)&rockchip_canfd_driver_init;
asm(".section \""
".initcall6"
".init"
"\", \"a\" \n"
"__initcall__kmod_Space__12_1222_rockchip_canfd_driver_init6"
": \n"
".long "
"rockchip_canfd_driver_init"
" - . \n"
".previous \n");
;
;
static void __attribute__((__section__(".exit.text"))) __attribute__((__used__))
__attribute__((__cold__)) __attribute__((patchable_function_entry(0, 0)))
rockchip_canfd_driver_exit(void)
{
platform_driver_unregister(&(rockchip_canfd_driver));
}
static exitcall_t __exitcall_rockchip_canfd_driver_exit
__attribute__((__used__))
__attribute__((__section__(".exitcall.exit"))) =
rockchip_canfd_driver_exit;
;
这段代码是 Linux 内核模块加载和卸载机制的一部分。它使用了一些宏和属性来确保模块在特定的阶段被正确加载和卸载。让我们逐步解析这段代码:
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
这个宏定义了 module_platform_driver
,它实际上调用了 module_driver
宏,传入了平台驱动的注册和注销函数。该宏扩展后的代码:
static int __attribute__((__section__(".init.text"))) __attribute__((__cold__))
__attribute__((__no_sanitize__("cfi")))
rockchip_canfd_driver_init(void)
{
return __platform_driver_register(&(rockchip_canfd_driver),
((struct module *)0));
}
__attribute__((__section__(".init.text")))
:将函数放置在.init.text
段中。这个段的内容在模块加载后会被释放,以节省内存。__attribute__((__cold__))
:指示编译器优化器这个函数很少被调用。__attribute__((__no_sanitize__("cfi")))
:禁用控制流完整性(CFI)检查。rockchip_canfd_driver_init
:模块初始化函数,调用platform_driver_register
注册平台驱动。
static void *__attribute__((__section__(".discard.addressable")))
__attribute__((
__used__)) __UNIQUE_ID___addressable_rockchip_canfd_driver_init13 =
(void *)&rockchip_canfd_driver_init;
__attribute__((__section__(".discard.addressable")))
:将变量放置在.discard.addressable
段中。这个段的内容在模块加载后会被丢弃。__attribute__((__used__))
:确保编译器不会优化掉这个变量。__UNIQUE_ID___addressable_rockchip_canfd_driver_init13
:一个唯一的变量名,用于防止符号冲突。
asm(".section \""
".initcall6"
".init"
"\", \"a\" \n"
"__initcall__kmod_Space__12_1222_rockchip_canfd_driver_init6"
": \n"
".long "
"rockchip_canfd_driver_init"
" - . \n"
".previous \n");
asm
:内联汇编代码,用于在.initcall6.init
段中插入一个初始化调用。.section ".initcall6.init", "a"
:创建或切换到.initcall6.init
段。__initcall__kmod_Space__12_1222_rockchip_canfd_driver_init6
:一个唯一的标签名。.long rockchip_canfd_driver_init - .
:插入初始化函数的地址。.previous
:返回到前一个段。
static void __attribute__((__section__(".exit.text"))) __attribute__((__used__))
__attribute__((__cold__)) __attribute__((patchable_function_entry(0, 0)))
rockchip_canfd_driver_exit(void)
{
platform_driver_unregister(&(rockchip_canfd_driver));
}
__attribute__((__section__(".exit.text")))
:将函数放置在.exit.text
段中。这个段的内容在模块卸载时会被调用。__attribute__((__used__))
:确保编译器不会优化掉这个函数。__attribute__((__cold__))
:指示编译器优化器这个函数很少被调用。__attribute__((patchable_function_entry(0, 0)))
:允许在运行时修改这个函数的入口点。rockchip_canfd_driver_exit
:模块卸载函数,调用platform_driver_unregister
注销平台驱动。
static exitcall_t __exitcall_rockchip_canfd_driver_exit
__attribute__((__used__))
__attribute__((__section__(".exitcall.exit"))) =
rockchip_canfd_driver_exit;
exitcall_t
:通常是一个函数指针类型。__attribute__((__used__))
:确保编译器不会优化掉这个变量。__attribute__((__section__(".exitcall.exit")))
:将变量放置在.exitcall.exit
段中,这个段的内容在模块卸载时会被调用。__exitcall_rockchip_canfd_driver_exit
:一个变量,其值为rockchip_canfd_driver_exit
函数的地址。
这段代码的主要功能是:
- 注册平台驱动:在模块加载时,
rockchip_canfd_driver_init
函数会被调用,注册rockchip_canfd_driver
。 - 注销平台驱动:在模块卸载时,
rockchip_canfd_driver_exit
函数会被调用,注销rockchip_canfd_driver
。 - 段管理:使用各种段属性和内联汇编,确保初始化和卸载代码在适当的时间被调用,并且不会占用不必要的内存。
通过这些机制,Linux 内核模块可以优雅地管理和加载/卸载平台驱动程序。
上述提及的宏都定义在include/linux/init.h
文件中,接下来我们一块学习下这个文件的内容。
一、include/linux/init.h
1. __initxx宏
#define __init __section(".init.text") __cold __latent_entropy __noinitretpoline __nocfi
#define __initdata __section(".init.data")
#define __initconst __section(".init.rodata")
宏 __init
、__initdata
和 __initconst
,用于标记内核初始化函数和数据。这些宏的作用是告诉编译器将标记的内容放在特定的初始化段中,以便在初始化完成后释放相关内存资源。具体来说:
-
__init
:- 用于标记初始化函数。
- 标记后的函数仅在系统启动时使用,初始化完成后可以释放其占用的内存。
- 使用方法:直接放在函数声明或定义前,如
static void __init initme(int x, int y)
或者放在函数原型后,如extern int initialize_foobar_device(int, int, int) __init;
-
__initdata
:- 用于标记初始化数据(已初始化的数据)。
- 标记后的数据仅在系统启动时使用,初始化完成后可以释放其占用的内存。
- 使用方法:放在变量名和等号之间,如
static int init_variable __initdata = 0;
-
__initconst
:- 用于标记常量初始化数据。
- 标记后的常量数据仅在系统启动时使用,初始化完成后可以释放其占用的内存。
- 使用方法:放在常量数组声明中,如
static const char linux_logo[] __initconst = { 0x32, 0x36, ... };
-
注意事项:
- 对于不在文件作用域内的数据(即在函数内部定义的数据),必须显式初始化,否则 GCC 会将其放入 BSS 段而不是初始化段。
通过这种方式,确保只有初始化阶段使用的函数和数据被正确标记并最终释放内存资源,从而优化系统的内存使用。
// 函数示例
static void __init init_function(int x, int y) {
extern int z;
z = x * y;
}
// 原型示例
extern int initialize_device(int, int, int) __init;
// 初始化数据示例
static int init_data __initdata = 0;
// 常量初始化数据示例
static const char logo[] __initconst = { 0x32, 0x36, ... };
这样,通过使用这些宏,可以有效地管理内核初始化过程中的内存资源,提高系统的性能和稳定性。
2. __exitxx宏
#define __exitdata __section(".exit.data")
#define __exit_call __used __section(".exitcall.exit")
#define __exit __section(".exit.text") __exitused __cold notrace
__exitdata
:将变量放置在.exit.data
段中。.exit.data
段用于存储在模块卸载时需要清理的数据。__exit_call
:将函数或数据放置在.exitcall.exit
段中,并标记为已使用。.exitcall.exit
段用于存储在模块卸载时需要调用的函数指针,确保这些函数在模块卸载时能够正确执行。__exit
:用于指定函数的特定属性和内存段。具体解释如下:__section(".exit.text")
:将函数放置在 .exit.text 段中。.exit.text 段用于存储模块卸载时需要执行的代码(如清理函数)。这确保了这些函数只在模块卸载时被调用。__exitused
:标记该函数为已使用 (__used),防止编译器优化掉这些函数。即使这些函数在其他地方没有直接调用,它们仍然会被保留在最终的二进制文件中。__cold:
提示编译器该函数很少被调用。编译器可以利用这个信息进行优化,例如将该函数放在不太常用的代码路径上,以提高缓存命中率。notrace
:禁止对该函数进行跟踪(tracing)。这对于内核中的某些关键函数非常重要,避免在调试或性能分析时引入不必要的开销。
3. __refxx宏
#define __ref __section(".ref.text") noinline
#define __refdata __section(".ref.data")
#define __refconst __section(".ref.rodata")
通过模块后处理(modpost)来检查段(section)不匹配的问题。当代码或数据段引用了init
或exit
段中的内容时,可能会导致潜在的bug。因为init
和exit
段在早期初始化完成后会被丢弃,继续引用这些段会导致未定义行为。
__ref
:用于标记函数,使其可以合法地引用init
或exit
段而不触发警告。它将函数放置在.ref.text
段,并添加noinline
属性以防止内联优化。__refdata
:用于标记数据,使其可以合法地引用init
或exit
段而不触发警告。它将数据放置在.ref.data
段。__refconst
:用于标记只读数据,使其可以合法地引用init
或exit
段而不触发警告。它将只读数据放置在.ref.rodata
段。 这些宏的存在使得开发者可以在特定情况下允许对init
或exit
段的引用,同时避免不必要的编译警告。需要注意的是,即使使用了这些宏,开发者仍然需要确保引用的合理性,避免实际运行时的问题。
4. __memxx 与 汇编相关宏
/* 用于内存热插拔 (MEMORY_HOTPLUG) */
// 下面的宏定义用于标记与内存热插拔相关的函数和数据,确保编译器将它们放置在特定的段中,并应用特定的属性。
#define __meminit __section(".meminit.text") __cold notrace \
__latent_entropy
// __meminit: 将函数放入 ".meminit.text" 段,标记为冷启动(__cold),不跟踪(notrace),并添加熵(__latent_entropy)。
#define __meminitdata __section(".meminit.data")
// __meminitdata: 将数据放入 ".meminit.data" 段,用于初始化时使用的数据。
#define __meminitconst __section(".meminit.rodata")
// __meminitconst: 将只读数据放入 ".meminit.rodata" 段,用于初始化时使用的只读数据。
#define __memexit __section(".memexit.text") __exitused __cold notrace
// __memexit: 将函数放入 ".memexit.text" 段,标记为退出时使用(__exitused),冷启动(__cold),不跟踪(notrace)。
#define __memexitdata __section(".memexit.data")
// __memexitdata: 将数据放入 ".memexit.data" 段,用于退出时使用的数据。
#define __memexitconst __section(".memexit.rodata")
// __memexitconst: 将只读数据放入 ".memexit.rodata" 段,用于退出时使用的只读数据。
/* 用于汇编例程 */
// 下面的宏定义用于汇编代码中的不同初始化和退出阶段,确保代码被正确地放置在指定的段中。
#define __HEAD .section ".head.text","ax"
// __HEAD: 将代码放入 ".head.text" 段,用于系统启动时的早期初始化代码。
#define __INIT .section ".init.text","ax"
// __INIT: 将代码放入 ".init.text" 段,用于系统启动时的初始化代码。
#define __FINIT .previous
// __FINIT: 返回到之前的段,通常用于结束一个初始化段。
#define __INITDATA .section ".init.data","aw",%progbits
// __INITDATA: 将数据放入 ".init.data" 段,用于系统启动时的初始化数据。
#define __INITRODATA .section ".init.rodata","a",%progbits
// __INITRODATA: 将只读数据放入 ".init.rodata" 段,用于系统启动时的初始化只读数据。
#define __FINITDATA .previous
// __FINITDATA: 返回到之前的段,通常用于结束一个初始化数据段。
#define __MEMINIT .section ".meminit.text", "ax"
// __MEMINIT: 将代码放入 ".meminit.text" 段,用于内存热插拔初始化代码。
#define __MEMINITDATA .section ".meminit.data", "aw"
// __MEMINITDATA: 将数据放入 ".meminit.data" 段,用于内存热插拔初始化数据。
#define __MEMINITRODATA .section ".meminit.rodata", "a"
// __MEMINITRODATA: 将只读数据放入 ".meminit.rodata" 段,用于内存热插拔初始化只读数据。
/* 当引用是允许的,用于静默警告 */
// 下面的宏定义用于标记可以安全引用的代码和数据段,避免编译器发出不必要的警告。
#define __REF .section ".ref.text", "ax"
// __REF: 将代码放入 ".ref.text" 段,用于可以安全引用的代码。
#define __REFDATA .section ".ref.data", "aw"
// __REFDATA: 将数据放入 ".ref.data" 段,用于可以安全引用的数据。
#define __REFCONST .section ".ref.rodata", "a"
// __REFCONST: 将只读数据放入 ".ref.rodata" 段,用于可以安全引用的只读数据。
5. 功能函数 与 变量的声明
/* Defined in init/main.c */
extern int do_one_initcall(initcall_t fn);
extern char __initdata boot_command_line[];
extern char *saved_command_line;
extern unsigned int reset_devices;
/* used by init/main.c */
void setup_arch(char **);
void prepare_namespace(void);
void __init init_rootfs(void);
extern struct file_system_type rootfs_fs_type;
do_one_initcall(initcall_t fn)
:执行一个初始化调用,初始化调用是一组函数,在内核启动过程中按特定顺序执行,以完成系统的初始化工作。。initcall_t
是一个函数指针类型,指向需要执行的初始化函数。boot_command_line[]
和saved_command_line
:boot_command_line[]
:保存启动时传递给内核的命令行参数。saved_command_line
:保存一份启动命令行的副本,供后续使用。
reset_devices
:一个无符号整数,控制是否重置设备。如果设置为非零值,则在初始化过程中会重置设备。setup_arch(char **)
:设置与架构相关的配置。这个函数根据不同的硬件架构(如x86、ARM等)进行必要的初始化工作。prepare_namespace()
:准备命名空间,包括挂载根文件系统和设置初始进程的工作目录等。 `- init_rootfs()`:初始化根文件系统(rootfs),这是内核启动后第一个可用的文件系统,通常是一个内存中的临时文件系统。
rootfs_fs_type
:定义根文件系统的类型,通常是一个结构体,描述了文件系统的特性。
6. 修饰驱动的初始化级别的宏
这些宏定义了不同阶段的初始化函数,每个宏对应一个特定的初始化阶段,确保内核在启动时能够按顺序调用这些函数。以下是每个宏的详细解释:
1. early_initcall(fn)
- 用途:非常早的初始化,优先级最高,通常用于早期硬件初始化。
- 示例:
static int __init my_early_init(void) { printk(KERN_INFO "My early initialization.\n"); return 0; } early_initcall(my_early_init);
下列其他宏的使用方法与此类,故不再举例!
2. pure_initcall(fn)
- 用途:纯粹的初始化,没有依赖于其他模块,仅用于初始化不能静态初始化的变量。
3. core_initcall(fn)
- 用途:核心初始化,用于初始化内核核心部分
4. core_initcall_sync(fn)
- 用途:同步核心初始化,确保某些核心初始化完成后才继续。
5. postcore_initcall(fn)
- 用途:核心初始化之后,但在子系统初始化之前。
6. postcore_initcall_sync(fn)
- 用途:同步核心初始化之后的初始化,确保某些初始化完成后才继续。
7. arch_initcall(fn)
- 用途:架构特定的初始化,用于初始化与特定架构相关的部分。
8. arch_initcall_sync(fn)
- 用途:同步架构特定的初始化,确保某些架构特定的初始化完成后才继续。
9. subsys_initcall(fn)
- 用途:子系统初始化,用于初始化内核子系统。
10. subsys_initcall_sync(fn)
- 用途:同步子系统初始化,确保某些子系统初始化完成后才继续。
11. fs_initcall(fn)
- 用途:文件系统初始化,用于初始化文件系统相关的部分。
12. fs_initcall_sync(fn)
- 用途:同步文件系统初始化,确保某些文件系统初始化完成后才继续。
13. rootfs_initcall(fn)
- 用途:根文件系统初始化,用于初始化根文件系统。
14. device_initcall(fn)
- 用途:设备初始化,用于初始化设备相关的部分。
15. device_initcall_sync(fn)
- 用途:同步设备初始化,确保某些设备初始化完成后才继续。
16. late_initcall(fn)
- 用途:较晚的初始化,通常用于最后的初始化步骤。
17. late_initcall_sync(fn)
- 用途:同步较晚的初始化,确保某些较晚的初始化完成后才继续。
- 示例:
static int __init my_late_sync_init(void) { printk(KERN_INFO "My late sync initialization.\n"); return 0; } late_initcall_sync(my_late_sync_init);
18. 上述1~17 宏实现解析
#define __initcall_section(__sec, __iid) \
#__sec ".init"
#define __initcall_stub(fn, __iid, id) fn
#define __define_initcall_stub(__stub, fn) \
__ADDRESSABLE(fn)
#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");
#else
#define ____define_initcall(fn, __unused, __name, __sec) \
static initcall_t __name __used \
__attribute__((__section__(__sec))) = fn;
#endif
#define __unique_initcall(fn, id, __sec, __iid) \
____define_initcall(fn, \
__initcall_stub(fn, __iid, id), \
__initcall_name(initcall, __iid, id), \
__initcall_section(__sec, __iid))
#define ___define_initcall(fn, id, __sec) \
__unique_initcall(fn, id, __sec, __initcall_id(fn))
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
__initcall_section(__sec, __iid)
:这个宏用于定义初始化函数所在的段(section)。它将段名和初始化函数的标识符结合起来,形成一个字符串,用于指定初始化函数的位置。
__initcall_stub(fn, __iid, id)
:这个宏生成一个初始化函数的“stub”(存根)。在某些架构中,初始化函数的地址可能需要特殊的处理方式,这个宏就是为了生成这种特殊的存根。
__define_initcall_stub(__stub, fn)
:这个宏定义了一个存根函数,并将其标记为可访问的。
____define_initcall(fn, __stub, __name, __sec)
:这个宏用于定义实际的初始化函数。它将函数 fn
放置在指定的段中,并生成一个符号 __name
。
__unique_initcall(fn, id, __sec, __iid)
:这个宏用于生成一个唯一的初始化函数定义。它结合了函数 fn
、ID id
、段 __sec
和初始化函数的标识符 __iid
。
___define_initcall(fn, id, __sec)
:这个宏是 __unique_initcall
的简化版本,它使用了一个默认的初始化函数标识符。
__define_initcall(fn, id)
:这个宏是 ___define_initcall
的进一步简化版本,它将初始化函数放置在默认的段 .initcall##id
中。较小的init段先执行,较大的init段后执行。
__initcall_name(initcall, __iid, id)
:这个宏生成了一个唯一的初始化函数名,用于区分不同的初始化函数。
__initcall_id(fn)
:这个宏生成了一个唯一的初始化函数标识符,用于在 __unique_initcall
中使用。
二、如何让驱动A的初始化一定在驱动B后执行?
驱动A的init函数使用late_initcall
宏修饰,驱动B的init函数使用device_initcall
或module_init
宏修饰,示例如下:
//驱动A
static int __init s5pv210_led0_init(void)
{
return platform_driver_register(&s5pv210_led0_driver);
}
static void __exit s5pv210_led0_exit(void)
{
platform_driver_unregister(&s5pv210_led0_driver);
}
device_initcall(s5pv210_led0_init);
module_exit(s5pv210_led0_exit);
//驱动B
static int __init s5pv210_led1_init(void)
{
return platform_driver_register(&s5pv210_led1_driver);
}
static void __exit s5pv210_led1_exit(void)
{
platform_driver_unregister(&s5pv210_led1_driver);
}
module_init(s5pv210_led1_init);
module_exit(s5pv210_led1_exit);
late_initcall
修饰的函数放在.initcall7.init
段,而module_init
修饰的函数则存放在.initcall6.init
段,故而前者后执行,后者先执行!