问题提出
在驱动程序模块的编写中,常常会在初始化函数的第一行中加上关键字“__init”,请问“__init”是什么意思?
比如下面的代码:
static int __init led_init(void) {
// 初始化代码:注册设备、分配资源等
return 0;
}
module_init(led_init);
- 1
- 2
- 3
- 4
- 5
- 6
详细解释
__init
的作用
在 Linux 内核代码中,__init
是一个宏,用于标记初始化函数的代码段。这是一种内核的优化方式,旨在减少内核的内存占用。
__init
的功能
-
标记初始化代码:
- 内核会将带有
__init
标记的代码放入一个特殊的内存区(.init.text
段)。 - 这段内存在初始化完成后(通常是模块加载完成或内核启动完成后)会被释放,节省内存。
- 内核会将带有
-
仅在初始化时执行:
- 这些代码通常只需要在模块加载或系统启动时运行一次。
- 运行结束后,它们不会再被调用。
使用 __init
的场景
-
模块初始化函数:
- 像例子中的
led_init
,是模块加载时的入口函数,只需要运行一次,因此使用__init
标记。 - 例如:
static int __init led_init(void) { // 初始化代码 return 0; } module_init(led_init);
- 1
- 2
- 3
- 4
- 5
- 像例子中的
-
内核启动时的初始化代码:
- 一些驱动程序或内核功能在启动时需要初始化,完成后可以释放相关的代码段。
内核如何处理 __init
-
编译时:
- 编译器将标记为
__init
的代码放入.init.text
段,而不是普通的代码段。
- 编译器将标记为
-
运行时:
- 内核在启动或模块初始化时执行这些代码。
- 一旦初始化完成,内核会释放
.init.text
段所占用的内存。
为什么要使用 __init
?
-
节省内存:
- Linux 内核运行在多种硬件上,包括资源受限的嵌入式设备。
- 释放不再使用的初始化代码可以显著节省内存。
-
优化内核空间:
- Linux 内核运行在内核态,所有代码和数据都必须驻留在内存中。
__init
可以减少不必要的代码驻留。
不使用 __init
的后果
- 如果初始化函数没有用
__init
标记,即使在完成初始化后,相关代码仍然会占用内存,直到模块被卸载或系统重启。 - 在嵌入式系统中,内存资源宝贵,未释放的初始化代码可能会影响系统性能。
与 __init
相关的宏
-
__initdata
:- 用于标记初始化数据。
- 初始化完成后,数据段会被释放。
- 例如:
static int __initdata my_init_data = 42;
- 1
-
__exit
:- 标记模块的退出函数。
- 只有在支持动态加载模块的情况下,这些代码才会被编译和保留;否则会被忽略。
- 例如:
static void __exit led_exit(void) { // 退出代码 } module_exit(led_exit);
- 1
- 2
- 3
- 4
-
__exitdata
:- 标记仅用于模块卸载时的数据。
例子分析
以下代码片段是模块的初始化函数,用 __init
优化内存:
static int __init led_init(void) {
// 初始化代码:注册设备、分配资源等
return 0;
}
module_init(led_init);
- 1
- 2
- 3
- 4
- 5
- 工作原理:
- 当模块加载时,
led_init
函数被调用。 - 由于函数被标记为
__init
,它会被放入.init.text
段。 - 加载完成后,
.init.text
段会被释放,led_init
所占的内存被回收。
- 当模块加载时,
小结
__init
是 Linux 内核中的一个优化工具,用于标记初始化代码段。它的主要目的是节省内存和提升内核性能。在开发驱动程序或内核模块时,如果某些代码仅在初始化阶段需要运行,建议加上 __init
标记。
问:这样做符合C语言的语法规范吗?
答:它其实并不符合C语言的标准语法,但符合 C 语言的扩展语法规则,通过 GCC 编译器的扩展功能实现。
__init
的本质
__init
是一个宏,定义在 Linux 内核的头文件中(通常是
)。它的作用是通过 GCC 的编译器属性(attributes)将函数标记为特定的段(section)。这是一种扩展功能,超出了标准 C 的范围,但完全符合 GCC 支持的 C 语言扩展。
在
中,__init
的定义类似于:
#define __init __section(.init.text)
- 1
__section
是另一个宏,用来指定代码的存储段。.init.text
是内核专门为初始化代码保留的段。
当编译器看到函数前有 __init
时,会将该函数放入 .init.text
段,而不是普通的代码段。
为什么符合 C 语言规范?
在标准 C 中,函数定义的语法是:
[存储类修饰符] 返回类型 函数名(参数列表) { 函数体 }
- 1
__init
在展开后并不会改变函数定义的基本形式,只是通过编译器扩展为函数添加了属性。- 使用 GCC 时,这种属性是完全合法的,并且是内核代码中常见的写法。
例如:
展开后的代码实际是:
static int __attribute__((section(".init.text"))) led_init(void) {
// 初始化代码
return 0;
}
- 1
- 2
- 3
- 4
这是 GCC 支持的标准扩展语法,并没有违反 C 语言的规则。
在非 GCC 编译器中会发生什么?
如果在非 GCC 或不支持 __section
扩展的编译器上使用,可能会出现以下情况:
-
编译失败:
编译器无法识别__init
,会报语法错误。 -
需要适配:
在这种情况下,可以通过条件编译避免问题。例如:#ifdef __GNUC__ #define __init __section(.init.text) #else #define __init #endif
- 1
- 2
- 3
- 4
- 5
__init
的实际用途
- 将函数放到
.init.text
段。 - 初始化完成后释放
.init.text
段的内存,节省资源。
小结
虽然 __init
不是标准 C 的一部分,但它通过 GCC 的扩展功能实现,符合 C 语言的扩展语法规则。因此,它在 Linux 内核开发中是合法且常用的。使用 __init
不会改变函数定义的规范性,只是为函数添加了段属性,便于内核在运行时进行内存管理优化。



评论记录:
回复评论: