关于__init、__initdata和__exit、__exitdata的学习笔记

本文详细介绍了Linux内核中的__init、__initdata、__exit和__exitdata等宏定义,解释了它们如何帮助编译器将特定的函数和数据放入相应的section中,以及在模块加载和内核启动过程中的作用。强调了正确使用这些宏的重要性,避免Oops错误。

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

__init、__initdata和__exit、__exitdata介绍

定义位于<linux/init.h>:

这些宏定义的作用是告诉编译器将这些函数或者数据放入相应的section中,而在模块加载的阶段,.ko文件中的代码和数据的加载区域是根据section来加载的。

比如:如果函数的定义中带有__init,那么这个函数的所有代码会被放入.init.text的section中。

      如果函数的定义中带有__initdata,那么这个函数的所有代码会被放入.init.data的section中。

    之所以要使用这个宏定义,其中一个原因是标记为初始化的函数和数据,表明该函数和数据仅在初始化期间使用。在模块装载之后,模块装载就会将初始化函数扔掉。这样可以将该函数占用的内存释放出来。

这种释放根据是否编译进内核是有区别的:

(1)模块编译进内核:所有的初始化数据和函数都是在系统启动的最后阶段,在所有模块都初始化完成以后被内核统一释放的。所有你一般可以在内核启动信息的后面看到:

  1. PHY: 0:01 - Link is Up - 100/Full
  2. VFS: Mounted root (nfs filesystem) on device 0:14.
  3. devtmpfs: mounted
  4. Freeing init memory: 196K
  5. INIT: version 2.86 booting

(__init* 修饰的数据/函数,用于说明它们只在系统开机阶段使用,其占用的空间会在系统完成启动后释放。 启动后,它们不再有效)

(2)独立的模块:模块是通过module-init-tool中的insmod的程序利用系统调用来挂载的,而所有的初始化数据和函数都是被这个系统调用所使用的,在模块挂载完成并初始化过后,由系统调用来完成对初始化数据和函数所占空间的释放。

  • 所以对于将内核驱动代码中的函数和数据定义为“初始化”时需要注意:不要将驱动定义的文件方法(如 open、read、write、close)或者驱动在实际工作中需要使用的函数和数据定义为“初始化”属性,因为在驱动初始化后这些东东就已经被释放了,如果使用了就会Oops。

(参考:https://blog.youkuaiyun.com/u010373922/article/details/23123785?utm_source=copy)

整体定义在include/linux/init.h

/* These are for everybody (although not all archs will actually
   discard it in modules) */
#define __init		__section(.init.text) __cold notrace
#define __initdata	__section(.init.data)
#define __initconst	__constsection(.init.rodata)
#define __exitdata	__section(.exit.data)
#define __exit_call	__used __section(.exitcall.exit)

#define __exit          __section(.exit.text) __exitused __cold notrace
如果一个函数加了个标记,则它会被放入.exit.text段,该段比较特殊,它被用于模块的退出函数,如果一个模块被编译到了内核映像中而不是以模块的形式编译的则该部分内存可以被链接器忽略,即不把它连接到内核中或者可以在系统启动后将它占用的内存给释放掉;同时如果内核不支持模块卸载,则也可以在链接时或者启动后释放该部分区域所占的内存.

 

/include/linux/compiler.h

/* Simple shorthand for a section definition 区段定义的简单速记*/
#ifndef __section
# define __section(S) __attribute__ ((__section__(#S)))
#endif

代码整理一下:

/* These are for everybody 这是给每一个的.(尽管不是所有的arch会在模块中抛弃它)
(although not all archs will actually discard it in modules) */
#define __init		__section(.init.text) __cold notrace
#define __initdata	__section(.init.data)
#define __initconst	__constsection(.init.rodata)
#define __exitdata	__section(.exit.data)
#define __exit_call	__used __section(.exitcall.exit)

/*
 * Some architecture have tool chains which do not handle rodata attributes
 * correctly. For those disable special sections for const, so that other
 * architectures can annotate correctly.
 */
#ifdef CONFIG_BROKEN_RODATA
#define __constsection(x)
#else
#define __constsection(x) __section(x)
#endif

/*
 * modpost check for section mismatches during the kernel build.
 * A section mismatch happens when there are references from a
 * code or data section to an init section (both code or data).
 * The init sections are (for most archs) discarded by the kernel
 * when early init has completed so all such references are potential bugs.
 * For exit sections the same issue exists.
 *
 * The following markers are used for the cases where the reference to
 * the *init / *exit section (code or data) is valid and will teach
 * modpost not to issue a warning.  Intended semantics is that a code or
 * data tagged __ref* can reference code or data from init section without
 * producing a warning (of course, no warning does not mean code is
 * correct, so optimally document why the __ref is needed and why it's OK).
 *
 * The markers follow same syntax rules as __init / __initdata.
 */
#define __ref            __section(.ref.text) noinline
#define __refdata        __section(.ref.data)
#define __refconst       __constsection(.ref.rodata)

/* compatibility defines */
#define __init_refok     __ref
#define __initdata_refok __refdata
#define __exit_refok     __ref


#ifdef MODULE
#define __exitused
#else
#define __exitused  __used
#endif

#define __exit          __section(.exit.text) __exitused __cold notrace

/* Used for MEMORY_HOTPLUG */
#define __meminit        __section(.meminit.text) __cold notrace
#define __meminitdata    __section(.meminit.data)
#define __meminitconst   __constsection(.meminit.rodata)
#define __memexit        __section(.memexit.text) __exitused __cold notrace
#define __memexitdata    __section(.memexit.data)
#define __memexitconst   __constsection(.memexit.rodata)

/* For assembly routines 汇编语言 */
#define __HEAD		.section	".head.text","ax"
#define __INIT		.section	".init.text","ax"
#define __FINIT		.previous

#define __INITDATA	.section	".init.data","aw",%progbits
#define __INITRODATA	.section	".init.rodata","a",%progbits
#define __FINITDATA	.previous

#define __MEMINIT        .section	".meminit.text", "ax"
#define __MEMINITDATA    .section	".meminit.data", "aw"
#define __MEMINITRODATA  .section	".meminit.rodata", "a"

/* silence warnings when references are OK */
#define __REF            .section       ".ref.text", "ax"
#define __REFDATA        .section       ".ref.data", "aw"
#define __REFCONST       .section       ".ref.rodata", "a"

大多是和section相关的定义,这个段定义什么作用呢,它是为了告诉链接器应该把这个函数或者数据放置在哪个位置。一般是指放置到内核镜像的哪个位置上。内核相当于一个非常大的可执行程序,它里面包含了好多内容,我们按照分段的原则来存放这些内容。具体段的存放规则是由vmlinux.lds文件定义,它是负责把段信息告诉链接器的,当然我们也要在代码中声明属于哪个段。 
 通常编译器将函数放在.text 节,变量放在.data 或 .bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的节中。那么例如:前面对__init 的定义便表示将它修饰的代码放在.init.text节。内核把段分的非常细致,是因为它会在运行过程中去定位相应的数据和代码,这样将更加方便处理。就像__init 修饰的所有代码都放在.init.text段,它只在启动阶段会被内核调用到,当初始化结束后就会释放这部分内存,以便充分利用内存,这个就是属于内存管理的部分了。

 

<think>好的,我现在需要回答用户关于Linux驱动程序中__initinit区别及适用场景的问题。首先,我得先理解这两个术语各自的含义作用。 根据用户提供的引用内容,尤其是引用[1]中的init_completion宏,可能涉及到初始化相关的函数或宏。不过更直接的线索可能在其他引用里。比如引用[4]提到了互斥锁自旋锁,这可能模块初始化有关,但暂时没看到直接的关联。可能需要结合Linux内核模块初始化的知识来理清。 在Linux内核开发中,模块的初始化函数通常使用__init宏来修饰。这个宏的作用是告诉编译器将函数放在特定的内存段中,这样在初始化完成后,这部分内存可以被释放,节省空间。比如,模块的加载函数可能会用module_init()宏来指定,而被指定的函数通常用__init修饰。 那用户提到的“init”可能是指另一个东西,比如函数名中的一部分,或者另一个宏。比如,可能有init_module()这样的函数名,或者是模块参数初始化的某个部分。但更常见的情况是,用户可能混淆了__initmodule_init()。因为module_init()是一个宏,用来声明模块的初始化函数,而这个初始化函数本身可能会用__init修饰。 例如,一个典型的模块初始化代码可能如下: ```c static int __init my_init(void) { // 初始化代码 } module_init(my_init); ``` 这里,__init修饰函数,而module_init()将函数注册为模块的入口点。当模块加载时,my_init函数会被调用,并且在初始化完成后,该函数占用的内存会被释放。 另一方面,如果用户提到的“init”是指module_init中的“init”,那么需要区分的是:__init是函数修饰符,而module_init()是模块声明宏。这两者的作用不同,但常一起使用。例如,module_init()宏可能展开为某种特定的声明,而__init则处理函数的内存分配。 再检查用户提供的引用内容,引用[1]中的init_completion可能初始化完成量有关,但可能不直接相关。引用[3]中的init_waitqueue_head属于初始化等待队列,这也是一种初始化操作,可能模块初始化阶段相关,但同样属于函数级别的初始化,可能用到了__init修饰的函数。 因此,可能的结论是:__init是一个宏,用于标记初始化函数,使其在初始化后被释放;而module_init()是一个宏,用于指定模块的初始化函数。用户可能将module_init()中的“init__init宏混淆了。例如,正确的使用场景是在定义模块初始化函数时,用__init修饰函数,然后用module_init()注册该函数。 适用场景方面,__init用于那些只在模块加载时运行一次的函数,之后不再使用,因此可以安全地释放其内存。而module_init()则用于指定模块的入口函数,即加载模块时要调用的函数。两者的区别在于功能层面:一个是内存管理修饰符,另一个是模块框架的声明。 另外,需要确认是否用户将其他init相关宏或函数混淆了,比如早期的init_module()函数,但现在通常使用module_init()宏来指定,而__init则是内核代码中常见的修饰符。 总结起来,__initmodule_init()的区别在于,前者是函数属性,用于优化内存使用,后者是模块初始化的入口声明。用户可能误将两者进行比较,而实际它们是互补使用的。</think>在Linux驱动开发中,`__init``init`的区别主要体现在**功能定位使用场景**上。以下是详细分析: --- ### 1. **__init宏的作用** `__init`是Linux内核源码中定义的**函数属性宏**,用于标记**仅在初始化阶段使用的函数**。 - **内存优化**:通过`__init`修饰的函数会被编译到内核的`.init.text`段,初始化完成后该段内存会被自动释放[^1]。 - **典型场景**:驱动模块的初始化函数、硬件资源的一次性配置函数。 - **示例代码**: ```c static int __init my_driver_init(void) { printk("Driver initialized\n"); return 0; } module_init(my_driver_init); ``` --- ### 2. **init的常见形式** `init`通常指**模块初始化入口的声明宏**,即`module_init()`中的参数: - **功能定位**:`module_init()`宏用于指定驱动模块加载时调用的初始化函数,例如: ```c module_init(my_driver_init); // 声明my_driver_init为模块入口 ``` - **`__init`的关系**:`module_init()`的参数函数通常需要配合`__init`宏使用,以优化内存。 --- ### 3. **核心区别** | 特性 | `__init`宏 | `module_init()`宏 | |-------------|-------------------------------------|-------------------------------------| | **作用** | 标记初始化阶段函数的内存段 | 声明模块的初始化入口函数 | | **生命周期**| 函数执行后内存被释放 | 定义模块加载时的初始化逻辑 | | **依赖关系**| 通常`module_init()`配合使用 | 依赖具体函数实现,可独立存在 | --- ### 4. **适用场景** - **使用`__init`的场景**: - 驱动模块的初始化函数(如资源分配、设备注册)。 - 仅在内核启动或模块加载时运行一次的代码。 - **禁用场景**:动态多次调用的函数不可使用`__init`(会导致内存错误)。 - **使用`module_init()`的场景**: - 定义驱动模块的加载入口(如`insmod`或`modprobe`时触发)。 - 需其他模块生命周期接口(如`module_exit()`)配合使用。 --- ### 5. **扩展问题** 1. **`__initdata``__init`有何区别?** `__initdata`用于标记初始化阶段使用的全局变量,`__init`类似,但其作用于数据而非函数[^1]。 2. **如何调试因错误使用`__init`导致的内核崩溃?** 可通过`objdump`工具分析`.init.text`段内容,或使用`dmesg`查看内存释放日志[^4]。 3. **模块卸载时如何释放`__init`占用的资源?** `__init`函数本身的内存由内核自动回收,但函数内分配的资源(如内存、设备)需在`module_exit()`对应的函数中手动释放。 --- ### 引用标识 : `init_completion`等初始化接口的内存管理机制 [^4]: 内核锁机制资源释放的关联场景
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值