致谢:

微信公众号:嵌入式企鹅圈 每天都新增爱好者关注,感谢大家的支持和大牛们的建议。

本人将竭力出品很多其它优质的原创文章回馈大家的厚爱。

引子:模块化机制长处

模块化机制(module)是Linux系统的一大创新。是Linux驱动开发和执行的基础(当然,module并不不过支撑驱动)。

其长处在于:

1.在系统执行动态载入模块。扩充内核的功能。

不须要时能够卸载。

2. 改动内核功能,不必又一次所有编译整改内核,仅仅须要编译对应模块就可以。

3.模块目标代码一旦被载入重定位到内核,其作用域和静态链接的代码全然等价。

本文重点阐述Linux module载入的来龙去脉,当中的奥秘就在于对宏module_init的解读。

一、模块样例

hello_module.c代码例如以下:

#include <linux/module.h> /* Needed by all modules */

#include <linux/kernel.h> /* Needed for KERN_ALERT */

#include <linux/init.h> /*Needed for __init */

static int __init test_init(void){

printk(KERN_ALERT"Hello world!\n");

return 0;

}

static void __exit test_exit(void){

printk(KERN_ALERT"Goodbye world!\n");

}

module_init(test_init);

module_exit(test_exit);

二、模块编程要点

1.头文件 linux/module.h、linux/kernel.h、linux/init.h

2. 定义模块的初始化函数test_init(名字随意)和卸载函数test_exit(名字随意)。

3. 用宏module_init声明初始化函数,用宏module_exit声明卸载函数。

三、模块执行

模块代码有两种执行的方式:

1. 编译成可动态载入的module。并通过insmod来动态载入,接着进行初始化。

2. 静态编译链接进内核,在系统启动过程中进行初始化。

有些模块是必需要编译到内核。和内核一起执行的。从不卸载,如vfs、platform_bus等等。

四、静态链接和初始化

Make menuconfig时选择将模块编译到内核即为静态链接,或者直接在makefile文件里指定为obj-y
+=hello_module.o

1module宏展开

头文件路径:include/linux/init.h

//静态编译链接时未定义宏MODULE

#ifndef MODULE

typedef int (*initcall_t)(void);

#define __define_initcall(level,fn,id)
\

static initcall_t __initcall_##fn##id
__used \

__attribute__((__section__(".initcall" level ".init"))) = fn

#define device_initcall(fn) __define_initcall("6",fn,6)

#define __initcall(fn) device_initcall(fn)

#define module_init(x) __initcall(x);

所以:

module_init(test_init)展开为:

__initcall(test _init)->

device_initcall(test _init)->

__define_initcall("6", test _init,6)->

static initcall_t __initcall_test_init_6
__attribute__((__section__(".initcall6.init"))) = test_init;

即是定义了一个类型为initcall_t的函数指针变量__initcall_test_init_6。并赋值为test_init。该变量在链接时会链接到section(.initcall6.init).

2linux链接脚本

路径 arch/arm/kernel/vmlinux.ld.S

#include <asm-generic/vmlinux.lds.h>

SECTIONS{

INIT_CALLS

}

路径:include/ asm-generic/vmlinux.lds.h

#define INIT_CALLS \

VMLINUX_SYMBOL(__initcall_start) = .; \

INITCALLS \

VMLINUX_SYMBOL(__initcall_end) = .;

#define INITCALLS \

….

*(.initcall6.init) \

可见__initcall_test_init_6将会链接到section(.initcall6.init).

3初始化

在linux启动的第三个阶段kernel_init的函数里会调用:

路径init/main.c

Kernel_init

do_basic_setup

do_initcalls

static void __init do_initcalls(void){

initcall_t *fn;

for (fn = __early_initcall_end; fn < __initcall_end; fn++)

do_one_initcall(*fn);

}

即取出函数指针__initcall_test_init_6的值并进行调用,即运行test_init。

五、动态链接载入和初始化

Make menuconfig时选择将模块编译成模块即为动态链接。或者直接在makefile文件里指定为obj-m
+=hello_module.o

编译成模块的命令是:

make –C $KERNEL_PATH M=$MODULE_PATH modules

即使用linux根文件夹下的makefile,运行该makefile下的modules伪目标。对当前模块进行编译。编译的结果是可重定位文件,insmod载入时才完毕终于的链接动作。

1Module编译选项

Linux根文件夹下的makefile定义了modules伪目标会用到的编译选项。

//即定义宏MODULE,-D是GCC定义宏的语法。

MODFLAGS = -DMODULE

//GCC编译模块代码时会用到该选项,即定义宏MODULE。这与在头文件里用#define
MODULE是一样的。

CFLAGS_MODULE = $(MODFLAGS)

2Module_init宏展开

头文件路径:include/linux/init.h

#ifndef MODULE /*编译成module时定义了宏MODULE*/

#else /* MODULE obj-m*/

typedef int (*initcall_t)(void);

#define module_init(initfn) \

static inline initcall_t __inittest(void) \

{ return initfn; } \

int init_module(void) __attribute__((alias(#initfn)));

__inittest不过为了检測定义的函数是否符合initcall_t类型,假设不是__inittest类型在编译时将会报错。所以真正的宏定义是:

#define module_init(initfn)

int init_module(void) __attribute__((alias(#initfn)));

alias属性是GCC的特有属性,将定义init_module为函数initfn的别名。所以module_init(test_init)的作用就是定义一个变量名init_module,其地址和test_init是一样的。

3Hello_module.mod.c

编译成module的模块都会自己主动产生一个*.mod.c的文件,Hello_module.mod.c的内容例如以下:

struct module __this_module

__attribute__((section(".gnu.linkonce.this_module"))) = {

.name = KBUILD_MODNAME,

.init = init_module,

#ifdef CONFIG_MODULE_UNLOAD

.exit = cleanup_module,

#endif

.arch = MODULE_ARCH_INIT,

};

即定义了一个类型为module的全局变量__this_module,其成员init即为init_module。也即是test_init.而且该变量会链接到section(".gnu.linkonce.this_module").

4动态载入

insmod是busybox提供的用户层命令:

路径busybox/modutils/ insmod.c

insmod_main

bb_init_module

init_module

路径busybox/modutils/modutils.c:

# define init_module(mod, len, opts) .\

syscall(__NR_init_module, mod, len, opts)

该系统调用相应内核层的sys_init_module函数。

路径:kernel/module.c

SYSCALL_DEFINE3(init_module,…)

//载入模块的ko文件,并解释各个section,重定位

mod = load_module(umod, len, uargs);

//查找section(".gnu.linkonce.this_module")

modindex = find_sec(hdr, sechdrs, secstrings,

".gnu.linkonce.this_module");

//找到Hello_module.mod.c定义的module数据结构

mod = (void *)sechdrs[modindex].sh_addr;

if (mod->init != NULL)

ret = do_one_initcall(mod->init); //调用test_init.

模块的传參、符号导出、模块依赖等机制以后再另文描写叙述

Linux模块化机制和module_init的更多相关文章

  1. Linux模块机制浅析

    Linux模块机制浅析   Linux允许用户通过插入模块,实现干预内核的目的.一直以来,对linux的模块机制都不够清晰,因此本文对内核模块的加载机制进行简单地分析. 模块的Hello World! ...

  2. Linux模块机制浅析_转

    Linux模块机制浅析 转自:http://www.cnblogs.com/fanzhidongyzby/p/3730131.htmlLinux允许用户通过插入模块,实现干预内核的目的.一直以来,对l ...

  3. 【ARM-Linux开发】Linux模块机制浅析

    Linux模块机制浅析   Linux允许用户通过插入模块,实现干预内核的目的.一直以来,对linux的模块机制都不够清晰,因此本文对内核模块的加载机制进行简单地分析. 模块的Hello World! ...

  4. android & Linux uevent机制

    Linux uevent机制 Uevent是内核通知android有状态变化的一种方法,比如USB线插入.拔出,电池电量变化等等.其本质是内核发送(可以通过socket)一个字符串,应用层(andro ...

  5. 利用linux信号机制调试段错误(Segment fault)

    在实际开发过程中,大家可能会遇到段错误的问题,虽然是个老问题,但是其带来的隐患是极大的,只要出现一次,程序立即崩溃中止.如果程序运行在PC中,segment fault的调试相对比较方便,因为可以通过 ...

  6. Linux 内存机制详解宝典

    Linux 内存机制详解宝典 在linux的内存分配机制中,优先使用物理内存,当物理内存还有空闲时(还够用),不会释放其占用内存,就算占用内存的程序已经被关闭了,该程序所占用的内存用来做缓存使用,对于 ...

  7. Linux Namespaces机制——实现

    转自:http://www.cnblogs.com/lisperl/archive/2012/05/03/2480573.html 由于Linux内核提供了PID,IPC,NS等多个Namespace ...

  8. Linux Namespaces机制

    转自:http://www.cnblogs.com/lisperl/archive/2012/05/03/2480316.html Linux Namespaces机制提供一种资源隔离方案.PID,I ...

  9. Linux分页机制之概述--Linux内存管理(六)

    1 分页机制 在虚拟内存中,页表是个映射表的概念, 即从进程能理解的线性地址(linear address)映射到存储器上的物理地址(phisical address). 很显然,这个页表是需要常驻内 ...

随机推荐

  1. 02-vue过滤器和键盘修饰符

    过滤器 概念:Vue.js 允许你自定义过滤器,可被用作一些常见的文本格式化.过滤器可以用在两个地方:mustache 插值和 v-bind 表达式.过滤器应该被添加在 JavaScript 表达式的 ...

  2. Django总结四

    0.ORM操作 1.必会的13条 返回对象列表的 all filter exclude order_by reverse distinct 特殊的对象列表 values values_list 返回对 ...

  3. Patch 21352635 - Database Patch Set Update 11.2.0.4.8

    一.CPU和PSU 近日,将数据库从9.2.0.6升级到11.2.0.4后,发现11.2.0.4通过DBLINK访问其他的9i库时发生ORA-02072错误,通过Google找到解决方案,即升级到PS ...

  4. UNIX环境高级编程--6

    系统数据文件和信息    数据文件都是ASCII文本文件,并且使用标准I/O库读这些文件,例如口令文件/etc/passwd和组文件/etc/group就是经常被多个程序频繁使用的两个文件.    口 ...

  5. oracle数据库忘记用户名和密码莫着急

    刚安装完Oracle 11g后,登录的时候没有记住用户名和密码,解决方法:新建一个用户 第一步:以系统身份登录 cmd--->sqlplus 提示输入用户名,然后输入sqlplus/as sys ...

  6. JVM 优化之逃逸分析

    整理自 周志明<深入JVM> 1, 是JVM优化技术,它不是直接优化手段,而是为其它优化手段提供依据. 2,逃逸分析主要就是分析对象的动态作用域. 3,逃逸有两种:方法逃逸和线程逃逸.   ...

  7. Active Learning主动学习

    Active Learning主动学习 我们使用一些传统的监督学习方法做分类的时候,往往是训练样本规模越大,分类的效果就越好.但是在现实生活的很多场景中,标记样本的获取是比较困难的,这需要领域内的专家 ...

  8. 学习随笔-Java WebService

    webService 可以将应用程序转换成网络应用程序.是简单的可共同操作的消息收发框架. 基本的webService平台是 XML 和 HTTP. HTTP 是最常用的互联网协议: XML 是 we ...

  9. html base 又一重大发现

    base 一个曾经不记得的标签,虽然接触Javaweb这么久了,但是还有很多基础性的东西都被我忽略掉了,还有很多基础但实用的技巧应该没有被我发现,虽然不使用这些技巧对功能实现没有多大影响.但是,发现这 ...

  10. Java编译器、JVM、解释器

    Java虚拟机(JVM)是可运行Java代码的假想计算机.只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行.本文首先简要介绍从Java文件的编译 ...