我们在内核中经常遇到初始化函数是这样定义的:static int __init init_func(); ,与普通函数相比,定义中多了__init。那么,__init是什么意思呢?还有与其匹配的__exit呢?

__init* macro

__init定义在:include/linux/init.h

#define __init __attribute__ ((__section__ (".init.text")))
#define __initdata __attribute__ ((__section__ (".init.data")))

It tells the compiler to put the variable or the function in a special section, which is declared in vmlinux.lds. init puts the function in the ".init.text" section and initdata puts the data in the ".init.data" section.
译文:__init宏告知编译器,将变量或函数放在一个特殊的区域,这个区域定义在vmlinux.lds中。__init将函数放在".init.text"这个代码区中,__initdata将数据放在".init.data"这个数据区中。

标记为初始化的函数,表明该函数供在初始化期间使用。在模块装载之后,模块装载就会将初始化函数扔掉。这样可以将该函数占用的内存释放出来。

__exit* macro
__exit定义在:include/linux/init.h

#ifdef MODULE
#define __exit __attribute__ ((__section__(".exit.text")))
#else
#define __exit __attribute_used__ __attribute__((__section__(".exit.text")))
#endif

The exit macro tells the compiler to put the function in the ".exit.text" section. The exit_data macro tells the compiler to put the data in the ".exit.data" section.
exit.* sections make sense only for the modules : exit functions will never be called if compiled statically. That's why there is a ifdef : exit.* sections will be discarded only if modules support is disabled.
译文:__exit宏告知编译器,将函数放在".exit.text"这个区域中。__exitdata宏则告知编译器将数据放在".exit.data"这个区域中。
exit.*区域仅仅对于模块是有用的:如果编译稳定的话,exit函数将永远不会被调用。只有当模块支持无效的时候,exit.*区域将被丢弃。这就是为什么定义中会出现ifdef。

Prototype of a module
A module must use the init and exit macros. Here is a prototype of a module :

#include <linux/module.h>
#include <linux/kernel.h>
#define MODULE_AUTHOR "tyler@agat.net"
#define MODULE_DESC "Description of the module"
int __init init_function(void)
{
  /* Do something */
  if (err)
    return -ERR;
  return ;
}
void __exit exit_function()
{
  /* Do something */
}
module_init(init_function);
module_exit(exit_function);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(MODULE_AUTHOR);
MODULE_DESCRIPTION(MODULE_DESC);

1)所有标识为__init的函数,在链接的时候,都放在.init.text这个区域中。在这个区域中,函数的摆放顺序是和链接顺序有关的,是不确定的。
2)所有的__init函数在区域.initcall.init中还保存了一份函数指针。在初始化时,内核会通过这些函数指针调用这些__init函数,并在整个初始化完成后,释放整个init区域 (包括.init.text, .initcall.init...)
注:这些函数在内核初始化过程中的调用顺序只和这里的函数指针顺序有关,和1)中所述的这些函数代码本身在.init.text区域中的顺序无关。

在2.4内核中,这些函数指针的顺序也是和链接顺序有关的,是不确定的。
在2.6内核中,.initcall.init区域又分成了7个子区域,分别是:

.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init

当需要把函数fn放到.initcall1.init区域时,只要声明core_initcall(fn); 即可。
其他的各个区域的定义方法分别是:

core_initcall(fn)-->.initcall1.init
postcore_initcall(fn)-->.initcall2.init
arch_initcall(fn)-->.initcall3.init
subsys_initcall(fn)-->.initcall4.init
fs_initcall(fn)-->.initcall5.init
device_initcall(fn)-->.initcall6.init
late_initcall(fn)-->.initcall7.init

而与2.4兼容的initcall(fn)则等价于device_initcall(fn).
各个子区域之间的顺序是确定的,即先调用.initcall1.init中的函数指针,再调用.initcall2.init中的函数指针,等等。而在每个子区域中的函数指针的顺序是和链接顺序相关的,是不确定的。
在内核中,不同的init函数被放在不同的子区域中,因此也就决定了他们的调用顺序。这样也就解决了一些init函数之间必须保证一定的调用顺序问题。
linux下 container_of()宏的简要解析
ARRAY_SIZE 宏还是比较有意思的,其实是个c 的编程技巧,这个技巧很有用哦!可以在include/linux/kernel.h 中找到它的定义:
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
该宏可以方便的求出一个数组中有多少数据成员,这在很多情况下是很有用的,比如对于 int a[]={1,5,65,23,12,20,3} 数组,可以使用该宏求出a[] 有7 个元素。
Linux中__init、__devinit等初始化宏
在内核里经常可以看到__init, __devinit这样的语句,这都是在init.h中定义的宏,gcc在编译时会将被修饰的内容放到这些宏所代表的section。
其典型的定义如下:

#define __init __section(.init.text) __cold notrace
#define __initdata __section(.init.data)
#define __initconst __section(.init.rodata)
#define __exitdata __section(.exit.data)
#define __exit_call __used __section(.exitcall.exit)

其典型用法如下:

static int __init xxx_drv_init(void)
{
  return pci_register_driver(&xxx_driver);
}

根据上面的定义与用法,xxx_drv_init()函数将会被link到.init.text段。
之所以加入这样的宏,原因有2:

1,一部分内核初始化机制依赖与它。
如kernel将初始化要执行的init函数,分为7个级别,core_initcall, postcore_initcall, arch_initcall, subsys_initcall, fs_iitcall, device_initcall, late_initcall。这7个级别优先级递减,即先执行core_initcall, 最后执行late_initcall。通过使用文中提到的宏,gcc会将初始化代码按下面的结构安排:

在内核初始化时,从__initcall_start到__initcall_end之间的initcall被一次执行。
2,提高系统效率
初始化代码的特点是,在系统启动时运行,且一旦运行后马上推出内存,不再占用内存。
================================================================================
常用的宏:
__init,标记内核启动时所用的初始化代码,内核启动完成后就不再使用。其所修饰的内容被放到.init.text section中。
__exit,标记模块退出代码,对非模块无效。
__initdata,标记内核启动时所用的初始化数据结构,内核启动完成后不再使用。其所修饰的内容被放到.init.data section中。
__devinit,标记设备初始化所用的代码。
__devinitdata,标记设备初始化所用的数据结构。
__devexit,标记设备移除时所用的代码。
xxx_initcall,7个级别的初始化函数
==================================================================================
driver中的使用:
module_init, module_exit函数所调用的函数,需要分别用__init和__exit来标记。
pci_driver数据结构不需要标记。
probe和remove函数用__devinit和__devexit来标记。
如果remove使用__devexit标记,则在pci_drvier结构中要用__devexit_p(remove)来引用remove函数。
如果不确定需不需要添加宏,则不要添加。

转自:http://blog.chinaunix.net/uid-24807808-id-3127876.html

linux初始化宏__init, __exit的更多相关文章

  1. linux内核中经常用到的设备初始化宏

    内核使用了大量不同的宏来标记具有不同作用的函数和数据结构.如宏__init.__devinit等.这些宏在include/linux/init.h头文件中定义.编译器通过这些宏可以把代码优化放到合适的 ...

  2. 浅析 Linux 初始化 init 系统

    近年来,Linux 系统的 init 进程经历了两次重大的演进,传统的 sysvinit 已经逐渐淡出历史舞台,新的 UpStart 和 systemd 各有特点,越来越多的 Linux 发行版采纳了 ...

  3. 【转】Linux 初始化 init 系统 [sysvinit systemd upstart]

    http://www.ibm.com/developerworks/cn/views/linux/libraryview.jsp?sort_by=&show_abstract=true& ...

  4. 浅析 Linux 初始化 init 系统,第 1 部分: sysvinit 第 2 部分: UpStart 第 3 部分: Systemd

    浅析 Linux 初始化 init 系统,第 1 部分: sysvinit  第 2 部分: UpStart 第 3 部分: Systemd http://www.ibm.com/developerw ...

  5. powerpc e500系列,linux初始化的tlb汇编,添加人肉代码注释

    powerpc e500的内核启动,关于tlb的初始化可以说是重头戏.看懂这段代码后,powerpc的虚实映射基本不在话下. 这段初始化tlb要考虑的,主要是将boot可能初始化过的tlb全清零,然后 ...

  6. 温故之--Linux 初始化 init 系统

    参选URL: http://www.ibm.com/developerworks/cn/linux/1407_liuming_init1/index.html 本系列一共三篇,看完记住,那水平就不一样 ...

  7. Linux 初始化系统(init)- systemd

    wikipedia 浅析 Linux 初始化 init 系统 systemd 中文手册 fedoraproject - systemd 1. Systemd 简介 Systemd 是 Linux 系统 ...

  8. 内核初始化优化宏(__init, __devinit)

    在内核里经常可以看到__init, __devinit这样的语句,这都是在init.h中定义的宏,gcc在编译时会将被修饰的内容放到这些宏所代表的section. 原文地址:http://blog.c ...

  9. Linux内核宏DEVICE_ATTR使用

    1.前言 在Linux驱动程序编写中,使用DEVICE_ATTR宏,可以定义一个struct device_attribute设备属性,并使用sysfs的API函数,便可以在设备目录下创建出属性文件, ...

随机推荐

  1. Android 错误集合

    1. Error:java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 erro ...

  2. 《剑指offer》第五十九题(队列的最大值)

    // 面试题59(二):队列的最大值 // 题目:给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值.例如, // 如果输入数组{2, 3, 4, 2, 6, 2, 5, 1}及滑动窗口的大小 ...

  3. RestTemplate学习

    在学习spring cloud的时候,用到了RestTemplate,找到一篇博客,写的很好,学习转载! 文章转载自:https://blog.csdn.net/itguangit/article/d ...

  4. lambda表达式与方法重载问题

    笔者之前在学习Java8新特性的时候,最吸引我的就是lambda表达式,它无疑为Java函数编程提供了强有力的支持.lambda表达式的使用方法很简单,下面给出最简单的用法. // Interface ...

  5. array_map的使用

    其结果为:

  6. HeadFirst Ruby 第十五章总结 Saving and loading data

    前言 在上一章讲述了如何进行基础的操作,比如 处理 GET 请求的 get route, 再比如下载 gem 等等方面的知识.在这一章节,作者告诉我们如何储存.处理数据.整个过程分三步走: 首先,当 ...

  7. 单细胞数据高级分析之消除细胞周期因素 | Removal of cell cycle effect

    The normalization method described above aims to reduce the effect of technical factors in scRNA-seq ...

  8. LeetCode--155--最小栈(java版)

    设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈. push(x) -- 将元素 x 推入栈中. pop() -- 删除栈顶的元素. top() -- 获取栈顶元素. ...

  9. ActiveSupport::Concern 和 gem 'name_of_person'(300✨) 的内部运行机制分析

    理解ActiveRecord::Concern: 参考:include和extend的区别: https://www.cnblogs.com/chentianwei/p/9408963.html 传统 ...

  10. Spring Batch JSON 支持

    Spring Batch 4.1 开始能够支持 JSON 格式了.这个发布介绍了一个新的数据读(item reader)能够读取一个 JSON 资源,这个资源按照下面的格式: [   {     &q ...