Linux编程中C函数定义的第一行中出现的关键字“__init”是什么意思?

问题提出

在驱动程序模块的编写中,常常会在初始化函数的第一行中加上关键字“__init”,请问“__init”是什么意思?
比如下面的代码:

static int __init led_init(void) {
    // 初始化代码:注册设备、分配资源等
    return 0;
}

module_init(led_init);

详细解释

__init 的作用

在 Linux 内核代码中,__init 是一个宏,用于标记初始化函数的代码段。这是一种内核的优化方式,旨在减少内核的内存占用。


__init 的功能

  1. 标记初始化代码:

    • 内核会将带有 __init 标记的代码放入一个特殊的内存区(.init.text 段)。
    • 这段内存在初始化完成后(通常是模块加载完成或内核启动完成后)会被释放,节省内存。
  2. 仅在初始化时执行:

    • 这些代码通常只需要在模块加载或系统启动时运行一次。
    • 运行结束后,它们不会再被调用。

使用 __init 的场景

  1. 模块初始化函数:

    • 像例子中的 led_init,是模块加载时的入口函数,只需要运行一次,因此使用 __init 标记。
    • 例如:
      static int __init led_init(void) {
          // 初始化代码
          return 0;
      }
      module_init(led_init);
      
  2. 内核启动时的初始化代码:

    • 一些驱动程序或内核功能在启动时需要初始化,完成后可以释放相关的代码段。

内核如何处理 __init

  1. 编译时:

    • 编译器将标记为 __init 的代码放入 .init.text 段,而不是普通的代码段。
  2. 运行时:

    • 内核在启动或模块初始化时执行这些代码。
    • 一旦初始化完成,内核会释放 .init.text 段所占用的内存。

为什么要使用 __init

  1. 节省内存:

    • Linux 内核运行在多种硬件上,包括资源受限的嵌入式设备。
    • 释放不再使用的初始化代码可以显著节省内存。
  2. 优化内核空间:

    • Linux 内核运行在内核态,所有代码和数据都必须驻留在内存中。
    • __init 可以减少不必要的代码驻留。

不使用 __init 的后果

  • 如果初始化函数没有用 __init 标记,即使在完成初始化后,相关代码仍然会占用内存,直到模块被卸载或系统重启。
  • 在嵌入式系统中,内存资源宝贵,未释放的初始化代码可能会影响系统性能。

__init 相关的宏

  1. __initdata:

    • 用于标记初始化数据。
    • 初始化完成后,数据段会被释放。
    • 例如:
      static int __initdata my_init_data = 42;
      
  2. __exit:

    • 标记模块的退出函数。
    • 只有在支持动态加载模块的情况下,这些代码才会被编译和保留;否则会被忽略。
    • 例如:
      static void __exit led_exit(void) {
          // 退出代码
      }
      module_exit(led_exit);
      
  3. __exitdata:

    • 标记仅用于模块卸载时的数据。

例子分析

以下代码片段是模块的初始化函数,用 __init 优化内存:

static int __init led_init(void) {
    // 初始化代码:注册设备、分配资源等
    return 0;
}
module_init(led_init);
  • 工作原理:
    1. 当模块加载时,led_init 函数被调用。
    2. 由于函数被标记为 __init,它会被放入 .init.text 段。
    3. 加载完成后,.init.text 段会被释放,led_init 所占的内存被回收。

小结

__init 是 Linux 内核中的一个优化工具,用于标记初始化代码段。它的主要目的是节省内存和提升内核性能。在开发驱动程序或内核模块时,如果某些代码仅在初始化阶段需要运行,建议加上 __init 标记。

问:这样做符合C语言的语法规范吗?

答:它其实并不符合C语言的标准语法,但符合 C 语言的扩展语法规则,通过 GCC 编译器的扩展功能实现。

__init 的本质

__init 是一个宏,定义在 Linux 内核的头文件中(通常是 <linux/init.h>)。它的作用是通过 GCC 的编译器属性(attributes)将函数标记为特定的段(section)。这是一种扩展功能,超出了标准 C 的范围,但完全符合 GCC 支持的 C 语言扩展。

<linux/init.h> 中,__init 的定义类似于:

#define __init __section(.init.text)
  • __section 是另一个宏,用来指定代码的存储段。
  • .init.text 是内核专门为初始化代码保留的段。

当编译器看到函数前有 __init 时,会将该函数放入 .init.text 段,而不是普通的代码段。


为什么符合 C 语言规范?

在标准 C 中,函数定义的语法是:

[存储类修饰符] 返回类型 函数名(参数列表) { 函数体 }
  • __init 在展开后并不会改变函数定义的基本形式,只是通过编译器扩展为函数添加了属性。
  • 使用 GCC 时,这种属性是完全合法的,并且是内核代码中常见的写法。

例如:
展开后的代码实际是:

static int __attribute__((section(".init.text"))) led_init(void) {
    // 初始化代码
    return 0;
}

这是 GCC 支持的标准扩展语法,并没有违反 C 语言的规则。


在非 GCC 编译器中会发生什么?

如果在非 GCC 或不支持 __section 扩展的编译器上使用,可能会出现以下情况:

  1. 编译失败:
    编译器无法识别 __init,会报语法错误。

  2. 需要适配:
    在这种情况下,可以通过条件编译避免问题。例如:

    #ifdef __GNUC__
    #define __init __section(.init.text)
    #else
    #define __init
    #endif
    

__init 的实际用途

  1. 将函数放到 .init.text 段。
  2. 初始化完成后释放 .init.text 段的内存,节省资源。

小结

虽然 __init 不是标准 C 的一部分,但它通过 GCC 的扩展功能实现,符合 C 语言的扩展语法规则。因此,它在 Linux 内核开发中是合法且常用的。使用 __init 不会改变函数定义的规范性,只是为函数添加了段属性,便于内核在运行时进行内存管理优化。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昊虹AI笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值