我们在内核中经常遇到初始化函数是这样定义的: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. 力扣(LeetCode)1016. 子串能表示从 1 到 N 数字的二进制串

    给定一个二进制字符串 S(一个仅由若干 '0' 和 '1' 构成的字符串)和一个正整数 N,如果对于从 1 到 N 的每个整数 X,其二进制表示都是 S 的子串,就返回 true,否则返回 false ...

  2. js插件---iCheck是用来做什么的

    js插件---iCheck是用来做什么的 一.总结 一句话总结:25 种参数 用来定制复选框(checkbox)和单选按钮(radio button) 定制复选框 定制单选按钮 1.iCheck常用的 ...

  3. mysqlsh : mysql shell tutorial

    MySQL Shell 是一个高级的命令行客户端以及代码编辑器for Mysql. 除了SQL,MySQL Shell也提供脚本能力 for JS and Python. When MySQL she ...

  4. SVN的安装

    Svn服务器的安装和配置 注意,一定要切换到最高管理权限:  su root  通过这个命令就可以完成! 1.安装svn服务器端软件从镜像服务器或者YUM源下载安装SVN服务器软件:yum insta ...

  5. 【洛谷p2142】高精度减法

    高精度减法第一遍没有过 高精度减法[传送门] 洛谷算法标签: 总之技术都在高精上了吧. 附代码: #include<iostream> #include<cstdio> #in ...

  6. 浅谈cookie、session

    揭秘Cookie: cookie说的直白点就是保存在用户浏览器端的一个键值对,举个例子,你现在登录了京东商城,你把浏览器关闭之后,你再打开京东,你还是可以对你的账户继续操作,已经购买的商品,订单都是可 ...

  7. <Matlab-3:追赶法(Doolittle分解)工具箱

    function x=chase (d,e,f,b) % --------------------------------------------------------------- %the me ...

  8. csu oj 1342: Double

    Description 有一个由M个整数组成的序列,每次从中随机取一个数(序列中每个数被选到的概率是相等的)累加,一共取N次,最后结果能被3整除的概率是多少? Input 输入包含多组数据.     ...

  9. sharding-jdbc读写分离原理解读

    原帖地址:https://blog.csdn.net/yanyan19880509/article/details/78170233 前言 很多时候,为了应付DB的高并发读写,我们会采用读写分离技术. ...

  10. Pick-up sticks

    Pick-up sticks Stan has n sticks of various length. He throws them one at a time on the floor in a r ...