【Linux API 揭秘】module_init与module_exit
【Linux API 揭秘】module_init与module_exit
Linux Version:6.6
Author:Donge
Github:linux-api-insides
1、函数作用
module_init
和module_exit
是驱动中最常用的两个接口,主要用来注册、注销设备驱动程序。
并且这两个接口的实现机制是一样的,我们先以module_init
为切入点分析。
2、module_init函数解析
2.1 module_init
#ifndef MODULE
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
......
#else /* MODULE */
......
/* Each module must use one module_init(). */
#define module_init(initfn) \
static inline initcall_t __maybe_unused __inittest(void) \
{ return initfn; } \
int init_module(void) __copy(initfn) \
__attribute__((alias(#initfn))); \
___ADDRESSABLE(init_module, __initdata);
......
#endif
函数名称:module_init
函数解析:
在
Linux
内核中,驱动程序可以以两种方式存在:内建(Builtin)
和模块(Module)
。内建驱动就是在编译时,直接编译进内核镜像中;而模块驱动则是在内核运行过程中动态加载卸载的。
module_init
函数的定义位置有两处,使用MODULE
宏作为判断依据。MODULE
是一个预处理器宏,仅当该驱动作为模块驱动时,编译的时候会加入MODULE
的定义。
这里难免会有疑问:为什么会有两套实现呢?
其实,当模块被编译进内核时,代码是存放在内存的.init
字段,该字段在内核代码初始化后,就会被释放掉了,所以当可动态加载模块需要加载时,就需要重新定义了。
2.1.1 模块方式
当驱动作为可加载模块时,MODULE
宏被定义,我们简单分析一下相关代码
#define module_init(initfn) \
static inline initcall_t __maybe_unused __inittest(void) \
{ return initfn; } \
int init_module(void) __copy(initfn) \
__attribute__((alias(#initfn))); \
___ADDRESSABLE(init_module, __initdata);
static inline initcall_t __maybe_unused __inittest(void) { return initfn; }
:一个内联函数,返回传入的initfn
函数。__maybe_unused
:编译器指令,用于告诉编译器,该函数可能不会使用,以避免编译器产生警告信息。
int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));
:init_module
函数的声明__copy(initfn)
:编译器指令,也就是将我们的initfn
函数代码复制到init_module
中,__attribute__((alias(#initfn)))
:编译器指令,将init_module
函数符号的别名设置为initfn
。
__
_ADDRESSABLE(init_module, __initdata);
:一个宏定义,主要用于将init_module
函数的地址放入__initdata
段,这样,当模块被加载时,init_module
函数的地址就可以被找到并调用。
总的来说,如果是可加载的ko
模块,module_init
宏主要定义了init_module
函数,并且将该函数与initfn
函数关联起来,使得当模块被加载时,初始化函数可以被正确地调用。
2.1.2 内建方式
当模块编译进内核时,MODULE
宏未被定义,所以走下面流程
#define module_init(x) __initcall(x);
2.2 __initcall
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define ___define_initcall(fn, id, __sec) \
__unique_initcall(fn, id, __sec, __initcall_id(fn))
#define __unique_initcall(fn, id, __sec, __iid) \
____define_initcall(fn, \
__initcall_stub(fn, __iid, id), \
__initcall_name(initcall, __iid, id), \
__initcall_section(__sec, __iid))
#define ____define_initcall(fn, __unused, __name, __sec) \
static initcall_t __name __used \
__attribute__((__section__(__sec))) = fn;
#define __initcall_stub(fn, __iid, id) fn
/* Format: <modname>__<counter>_<line>_<fn> */
#define __initcall_id(fn) \
__PASTE(__KBUILD_MODNAME, \
__PASTE(__, \
__PASTE(__COUNTER__, \
__PASTE(_, \
__PASTE(__LINE__, \
__PASTE(_, fn))))))
/* Format: __<prefix>__<iid><id> */
#define __initcall_name(prefix, __iid, id) \
__PASTE(__, \
__PASTE(prefix, \
__PASTE(__, \
__PASTE(__iid, id))))
#define __initcall_section(__sec, __iid) \
#__sec ".init"
/* Indirect macros required for expanded argument pasting, eg. __LINE__. */
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
函数名称:__initcall
文件位置:include/linux/init.h
函数解析:设备驱动初始化函数
2.2.1 代码调用流程
module_init(fn)
|--> __initcall(fn)
|--> device_initcall(fn)
|--> __define_initcall(fn, 6)
|--> ___define_initcall(fn, id, __sec)
|--> __initcall_id(fn)
|--> __unique_initcall(fn, id, __sec, __iid)
|--> ____define_initcall(fn, __unused, __name, __sec)
|--> __initcall_stub(fn, __iid, id)
|--> __initcall_name(prefix, __iid, id)
|--> __initcall_section(__sec, __iid)
|--> ____define_initcall(fn, __unused, __name, __sec)
进行函数分析前,我们先要明白
#
和##
的概念
2.2.2 #和##的作用
符号 | 作用 | 举例 |
---|---|---|
## | ## 符号 可以是连接的意思 |
例如 __initcall_##fn##id 为__initcall_fnid 那么,fn = test_init ,id = 6 时,__initcall##fn##id 为 __initcall_test_init6 |
# | # 符号 可以是字符串化的意思 |
例如 #id 为 "id" ,id=6 时,#id 为"6" |
更多干货可见:高级工程师聚集地,助力大家更上一层楼!
2.2.3 函数解析
下面分析理解比较有难度的函数
#define device_initcall(fn) __define_initcall(fn, 6)
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
.initcall##id
:通过##
来拼接两个字符串:.initcall6
#define ___define_initcall(fn, id, __sec) \
__unique_initcall(fn, id, __sec, __initcall_id(fn))
/* Format: <modname>__<counter>_<line>_<fn> */
#define __initcall_id(fn) \
__PASTE(__KBUILD_MODNAME, \
__PASTE(__, \
__PASTE(__COUNTER__, \
__PASTE(_, \
__PASTE(__LINE__, \
__PASTE(_, fn))))))
/* Indirect macros required for expanded argument pasting, eg. __LINE__. */
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
___PASTE
:拼接两个字符串__initcall_id
:它用于生成一个唯一的标识符,这个标识符用于标记初始化函数。__KBUILD_MODNAME
:当前正在编译的模块的名称__COUNTER__
:一个每次使用都会递增计数器,用于确保生成名称的唯一性__LINE__
:当前代码的行号
#define __unique_initcall(fn, id, __sec, __iid) \
____define_initcall(fn, \
__initcall_stub(fn, __iid, id), \
__initcall_name(initcall, __iid, id), \
__initcall_section(__sec, __iid))
#define ____define_initcall(fn, __unused, __name, __sec) \
static initcall_t __name __used \
__attribute__((__section__(__sec))) = fn;
#define __initcall_stub(fn, __iid, id) fn
/* Format: __<prefix>__<iid><id> */
#define __initcall_name(prefix, __iid, id) \
__PASTE(__, \
__PASTE(prefix, \
__PASTE(__, \
__PASTE(__iid, id))))
#define __initcall_section(__sec, __iid) \
#__sec ".init"
__unique_initcall
:调用____define_initcall
,关键实现部分
____define_initcall
:定义一个名为 __name
的 initcall_t
类型的静态变量,并将其初始化为 fn
,并放入特定的__sec
段中。
__initcall_stub
:表示唯一的函数名fn
__initcall_name
:表示一个唯一的变量名__initcall_section
: 生成一个唯一的段名。#__sec ".init"
:将两个字符串拼接起来,比如:__sec=.initcall6
,拼接后的段为:.initcall6.init
,该段为最终存储的段。
字段通过链接器链接起来,形成一个列表进行统一管理。
这些字段我们可以在
arch/arm/kernel/vmlinux.lds
中查看。
......
__initcall6_start = .; KEEP(*(.initcall6.init)) KEEP(*(.initcall6s.init))
......
3、module_exit函数解析
module_exit
和module_init
的实现机制几乎没有差别,下面就简单介绍一下。
3.1 module_exit
#ifndef MODULE
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
......
#else /* MODULE */
......
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __maybe_unused __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __copy(exitfn) \
__attribute__((alias(#exitfn))); \
___ADDRESSABLE(cleanup_module, __exitdata);
......
#endif
函数名称:module_exit
3.1.1 模块方式
作为模块方式,与module_init
的实现方式一样,定义cleanup_module
与exitfn
函数相关联,存放在__exitdata
段内。
3.1.2 内建方式
当模块编译进内核时,MODULE
宏未被定义,所以走下面流程
#define module_exit(x) __exitcall(x);
3.2 __exitcall
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn
#define __exit_call __used __section(".exitcall.exit")
函数名称:__initcall
文件位置:include/linux/init.h
函数解析:设备驱动卸载函数
__exitcall_##fn
:定义一个新的 exitcall_t
类型的静态变量,并赋值为fn
__exit_call
:__used __section(".exitcall.exit")
,定义该函数存储的段
4、扩展
还记得
__define_initcall
的定义吗?
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
#define __initcall(fn) device_initcall(fn)
不同的宏定义,被赋予了不同的调用等级,最后将不同的驱动初始化函数统一汇总到__initcallx_start
字段统一管理,形成一个有序的列表。
这样,我们在内核中,按照顺序遍历这个列表,最后执行对应的模块初始化函数fn
即可实现驱动的初始化。
【Linux API 揭秘】module_init与module_exit的更多相关文章
- Linux内核驱动将多个C文件编译成一个ko文件的方法——每一个C文件中都有module_init与module_exit
以两个C文件为例: 将本该被分别编译成adc_device.ko和adc_driver.ko的adc_device.c.adc_driver.c编译成一个ko文件! 採用方法: 第一步.改动C文件 1 ...
- 封装获取网络信息Linux—API类
封装获取网络信息Linux—API类 封装好的库: #ifndef NETINFORMATION_H #define NETINFORMATION_H #include <netdb.h> ...
- linux驱动 之 module_init解析 (上)【转】
转自:https://blog.csdn.net/Richard_LiuJH/article/details/45669207 版权声明:本文为博主原创文章,未经博主允许不得转载. https://b ...
- linux内核驱动module_init解析(2)
本文转载自博客http://blog.csdn.net/u013216061/article/details/72511653 如果了解过Linux操作系统启动流程,那么当bootloader加载完k ...
- linux内核驱动module_init解析(1)
本文转载自博客http://blog.csdn.net/richard_liujh/article/details/45669207 写过linux驱动的程序猿都知道module_init() 这个函 ...
- My Linux API
@图形界面与命令行界面切换 Linux预设提供了六个命令窗口终端机让我们来登录.默认我们登录的就是第一个窗口,也就是tty1,这个六个窗口分别为tty1,tty2 … tty6,你可以按下Ctrl + ...
- linux API函数大全
获取当前执行路径:getcwd1. API之网络函数 WNetAddConnection 创建同一个网络资源的永久性连接 WNetAddConnection2 创建同一个网络资源的连接 WNetAdd ...
- Server版Linux命令提示符揭秘
一直都在Ubuntu12.04和12.10 Desktop下玩.如今要在Centos6.3 Server版下做开发了,感觉还是非常不一样的. 克服一个有一个不顺利后,有那种站在山顶的 ...
- Linux API的fork()测试
现在到docker的实施阶段, 其底层的namespace,cgroup应该深入了解了. 其调用的API也慢慢熟悉起来吧. #include <unistd.h> #include < ...
- [DPI][TCP] linux API的接口如何控制urgent包的收发
做DPI,写协议栈的时候,处理到了urgent数据包.突然好奇应用层是如何控制发出urgent包的呢?而接收端又是如何知道,接受到了urgent包的呢? man 7 tcp,中有如下一段: TCP s ...
随机推荐
- 接手了个项目,被if..else搞懵逼了
背景 领导:"这个项目,今后就给你维护了啊,仔细点." 小猫:"好,没问题". 可当满怀信心的小猫打开项目工程包翻看一些代码之后,瞬间懵逼没了信心. 是这样的 ...
- 使用RFC跳过权限校验的方法
1.业务背景 由于业务流程的复杂性,用户往往只具备部分功能的权限,导致在操作自开发程序时出现权限问题.例如前台限制了用户对销售订单的修改,而自开发功能中又涉及单据修改,此时一味限制权限,则无法正常使用 ...
- 笔记本安装linux
下载 桌面版 Ubuntu 镜像 服务器版 Ubuntu 镜像 使用 Balena Etcher 制作系统安装盘 (1)官方网站下载: 点我下载 (2)下载完毕软件之后,打开软件,选择我们下载好的系统 ...
- HTB - CozyHosting - WriteUp
CozyHosting 前言:抓紧赛季末上一波分,错过开vip才能练了 信息收集 扫描看看端口的开放情况,开了22,80,5555.这里fscan显示会跳转到cozyhosting.htb. 那就需要 ...
- EvilBox : ONE - WriteUp
EvilBox : ONE 信息收集 扫描网段内存活主机,得到目标 nmap 进一步收集有效信息,只开放了22和80 访问80的页面没有什么有效信息 接着扫一下目录, 没有敏感的文件 在robots中 ...
- Java 面试题及答案整理(2021最新版)持续更新中~~~
2021年java实习校招秋招春招 后端 知识点及面试题(持续更新) Java面试总结汇总,整理了包括Java基础知识,集合容器,并发编程,JVM,常用开源框架Spring,MyBatis,数据库,中 ...
- SQL注入上传文件获取shell
SQL注入写文件的三个必要条件 Web目录有读写权限: 当目标网站的Web目录具有读写权限时,攻击者可以通过注入恶意SQL语句将恶意文件写入服务器上的Web目录. 知道文件的绝对路径: 攻击者需要知道 ...
- tty详解
linux下tty命令详解 [功能] 打印连接到标准输入的终端的文件名. [描述] 命令项: -s, --silent, --quiet: 什么也不打印,只是返回退出状态码. --help: 打印帮助 ...
- Unity3d_Rewired官方文档翻译:概念(一):InputManager、Players、Actions
仅翻译了官方文档中的Essentials(要点).Concepts(概念)两部分,这是文档中最重要的部分,理解了这两部分的内容应该足以让你将Rewired运用到你的项目中,之后再去阅读文档的其他部分也 ...
- 六一新玩法!AI涂鸦秒变精美艺术画
摘要:上华为云ModelArts体验AI涂鸦新玩法,赢漫威复仇者联盟乐高!祝大小朋友们六一儿童节快乐~ 本文分享自华为云社区<[云享热点]六一新玩法!AI 涂鸦秒变精美艺术画>,作者:华为 ...