无法在这个位置找到: head2.htm
当前位置: 建站首页 > 新闻动态 > 行业新闻 >

Linux核心控制模块剖析(module

时间:2021-04-02 18:26来源:未知 作者:jianzhan 点击:
module_init在学习培训linux驱动器开发设计时,最先必须掌握Linux的控制模块化体制(module),可是module其实不只是用以支撑点驱动器的载入和卸载掉。一个非常简单的控制模块事例以下:
module_init

在学习培训linux驱动器开发设计时,最先必须掌握Linux的控制模块化体制(module),可是module其实不只是用以支撑点驱动器的载入和卸载掉。一个非常简单的控制模块事例以下:

// filename: HelloWorld.c
#include linux/module.h 
#include linux/init.h 
static int hello_init(void)
 printk(KERN_alert "hello world\n");
 return 0;
static void hello_exit(void)
 printk(KERN_ALERT "Bye Bye World\n");
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");

控制模块编码有二种运作方法,一是静态数据编译程序联接进核心,在系统软件起动全过程中开展原始化;一是编译程序成可动态性载入的module,根据insmod动态性载入重精准定位到核心。这二种方法能够在makefile中通快递过obj-y或obj-m选择项开展挑选。

而一旦可动态性载入的控制模块总体目标编码(.ko)被载入重精准定位到核心,其功效域和静态数据连接的编码是彻底等额的的。因此这类运作方法的优势不言而喻:

可依据系统软件必须运作动态性载入控制模块,以扩大核心作用,不用时将其卸载掉,以释放出来运行内存室内空间; 当必须改动核心作用时,只需编译程序相对控制模块,而无须再次编译程序全部核心。

由于那样的优势,在开展机器设备驱动器开发设计时,大部分全是将其编译程序成可动态性载入的控制模块。可是必须留意,一些控制模块务必要编译程序到核心,随核心一起运作,从来不卸载掉,如 vfs、platform_bus等。

那麼一样一份C编码怎样完成这二种方法的呢?

回答就取决于module_init宏!下边大家一起來剖析module_init宏。(这儿常用的Linux核心版本号为3.10.10)

精准定位到Linux核心源代码中的 include/linux/init.h,能看到有以下编码:

#ifndef MODULE
// 省去
#define module_init(x) __initcall(x);
// 省去
#else
#define module_init(initfn) \
 int init_module(void) __attribute__((alias(#initfn)));
// 省去
#endif

显而易见,MODULE 是由Makefile操纵的。上边一部分用以将控制模块静态数据编译程序联接进核心,下边一部分用以编译程序可动态性载入的控制模块。接下去大家对这二种状况开展剖析。

方法一:#ifndef MODULE

编码整理:

#define module_init(x) __initcall(x);
-- #define __initcall(fn) device_initcall(fn)
 -- #define device_initcall(fn) __define_initcall(fn, 6)
 -- #define __define_initcall(fn, id) \
 static initcall_t __initcall_##fn##id __used \
 __attribute__((__section__(".initcall" #id ".init"))) = fn

即 module_init(hello_init) 进行为:

static initcall_t __initcall_hello_init6 __used \
 __attribute__((__section__(".initcall6.init"))) = hello_init

这儿的 initcall_t 是涵数指针种类,以下:

typedef int (*initcall_t)(void);

GNU编译程序专用工具链适用客户自定section,因此大家阅读文章Linux源代码时,会发觉很多应用以下一类使用方法:

__attribute__((__section__("section-name"))) 

__attribute__用于特定自变量或构造位域的独特特性,之后的双括弧中的內容是特性表明,它的英语的语法文件格式为:__attribute__ ((attribute-list))。它有一位置的管束,一般放于申明的尾部且“ ;” 以前。

这儿的attribute-list为__section__(“.initcall6.init”)。一般,编译程序器将转化成的编码储放在.text段中。但是时将会必须别的的段,或是必须将一些涵数、自变量储放在独特的段中,section特性便是用于特定将一个涵数、自变量储放在特殊的段中。

因此这儿的含意便是:界定一个名叫 __initcall_hello_init6 的涵数指针自变量,并原始化作 hello_init(偏向hello_init);而且该涵数指针自变量储放于 .initcall6.init 编码段中。

接下去,大家根据查询连接脚本制作( arch/$(ARCH)/kernel/vmlinux.lds.S)来啦解 .initcall6.init 段。

能看到,.init段中包括 INIT_CALLS,它界定在include/asm-generic/vmlinux.lds.h。INIT_CALLS 进行后可得:

#define INIT_CALLS \
 VMLINUX_symbol(__initcall_start) = .; \
 *(.initcallearly.init) \
 INIT_CALLS_LEVEL(0) \
 INIT_CALLS_LEVEL(1) \
 INIT_CALLS_LEVEL(2) \
 INIT_CALLS_LEVEL(3) \
 INIT_CALLS_LEVEL(4) \
 INIT_CALLS_LEVEL(5) \
 INIT_CALLS_LEVEL(rootfs) \
 INIT_CALLS_LEVEL(6) \
 INIT_CALLS_LEVEL(7) \
 VMLINUX_SYMBOL(__initcall_end) = .;

进一步进行为:

 __initcall_start = .; \
 *(.initcallearly.init) \
 __initcall0_start = .; \
 *(.initcall0.init) \
 *(.initcall0s.init) \
 // 省去1、2、3、4、5
 __initcallrootfs_start = .; \
 *(.initcallrootfs.init) \
 *(.initcallrootfss.init) \
 __initcall6_start = .; \
 *(.initcall6.init) \
 *(.initcall6s.init) \
 __initcall7_start = .; \
 *(.initcall7.init) \
 *(.initcall7s.init) \
 __initcall_end = .;

上边这种编码段最后在kernel.img中按依次次序机构,也就决策了坐落于在其中的一些涵数的实行依次次序(__initcall_hello_init6 坐落于 .initcall6.init 段中)。.init 或是 .initcalls 段的特性便是,当核心起动结束后,这一段中的运行内存会被释放出来掉。这一点从核心起动信息内容能看到:

freeing unused kernel memory: 124k高清 ( - )

那麼储放于 .initcall6.init 段中的 __initcall_hello_init6 是如何样被启用的呢?大家看文档 init/main.c,编码整理以下:

start_kernel
-- rest_init
 -- kernel_thread
 -- kernel_init
 -- kernel_init_freeable
 -- do_basic_setup
 -- do_initcalls
 -- do_initcall_level(level)
 -- do_one_initcall(initcall_t fn)

kernel_init 这一涵数是做为一个核心进程被启用的(该进程最终会起动第一个客户过程init)。

大家主要关心 do_initcalls 涵数,以下:

static void __init do_initcalls(void)
 int level;
 for (level = 0; level ARRAY_SIZE(initcall_levels) - 1; level++)
 do_initcall_level(level);
}

涵数 do_initcall_level 以下:

static void __init do_initcall_level(int level)
 // 省去
 for (fn = initcall_levels[level]; fn initcall_levels[level+1]; fn++)
 do_one_initcall(*fn);
}

涵数 do_one_initcall 以下:

int __init_or_module do_one_initcall(initcall_t fn)
 int ret;
 // 省去
 ret = fn();
 return ret;
}

initcall_levels 的界定以下:

static initcall_t *initcall_levels[] __initdata = {
 __initcall0_start,
 __initcall1_start,
 __initcall2_start,
 __initcall3_start,
 __initcall4_start,
 __initcall5_start,
 __initcall6_start,
 __initcall7_start,
 __initcall_end,
};

initcall_levels[] 中的组员来源于于 INIT_CALLS 的进行,如“__initcall0_start = .;”,这儿的 __initcall0_start是一个自变量,它跟编码里边界定的自变量的功效是一样的,因此编码里边可以应用__initcall0_start。因而在 init/main.c 中能够根据 extern 的方式将这种自变量引进,以下:

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];

到这儿大部分就搞清楚了,在 do_initcalls 涵数时会解析xml initcalls 段中的每个涵数指针,随后实行这一涵数指针。由于编译程序器依据连接脚本制作的规定将每个涵数指针连接来到特定的部位,因此能够安心地用 do_one_initcall(*fn) 来实行有关原始化涵数。

大家事例中的 module_init(hello_init) 是 level6 的 initcalls 段,较为靠后启用,许多外设驱动器都启用 module_init 宏,假如是静态数据编译程序联接进核心,则这种涵数指针会依照编译程序依次次序插进到 initcall6.init 段中,随后等候 do_initcalls 涵数启用。

方法二:#else

有关编码:

#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)));

因而,用动态性载入方法时,能够不应用 module_init 和 module_exit 宏,而立即界定 init_module 和 cleanup_module 涵数,实际效果是一样的。

alias 特性是 gcc 的独有特性,将界定 init_module 为涵数 initfn 的别称。因此 module_init(hello_init) 的功效便是界定一个自变量名 init_module,其详细地址和 hello_init 是一样的。

所述事例编译程序可动态性载入控制模块全过程中,会全自动造成 HelloWorld.mod.c 文档,內容以下:

#include linux/module.h 
#include linux/vermagic.h 
#piler.h 
MODULE_INFO(vermagic, VERMAGIC_STRING);
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,
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";

得知,其界定了一个种类为 module 的全局性自变量 __this_module,组员 init 为 init_module(即 hello_init),且该自变量连接到 .gnu.linkonce.this_module 段中。

编译程序后个人所得的 HelloWorld.ko 必须根据 insmod 将其载入进核心,因为 insmod 是 busybox 出示的客户层指令,因此大家必须阅读文章 busybox 源代码。编码整理以下:(文档 busybox/modutils/ insmod.c)

insmod_main
-- bb_init_module
 -- init_module

而 init_module 界定以下:(文档 busybox/modutils/modutils.c)

#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)

因而,该系统软件启用相匹配核心层的 sys_init_module 涵数。

返回Linux核心源码(kernel/module.c),编码整理:

SYSCALL_DEFINE3(init_module, ...)
-- load_module
 -- do_init_module(mod)
 -- do_one_initcall(mod- init);

文档(include/linux/syscalls.h)中,有:

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

进而产生 sys_init_module 涵数。

到此,剖析结束。疏漏的地方,还望诸位阅读者强调!

有关阅读文章

linux tee指令

tee指令用以将数据信息跳转到文档,另外一层面还能够出示一份跳转数据信息的团本做为事后指令的stdin。简易的说便是把数据信息跳转到给定文

Linux之nfs文档系统软件详细说明

NFS 定义 互联网文档系统软件 (NFS) 是 Unix 系统软件和互联网额外储存文档管理方法器常见的互联网文档系统软件 , 容许好几个顾客端根据互联网共享资源文档访

linux查询端口号占有状况

转自wangtao1993/ 1、lsof -i:端口号号 list open files 用以查询某一端口号的占有状况,例如

linux安裝微软公司雅黑等字体样式

linux安裝微软公司雅黑等字体样式 1、查验字体样式是不是早已安裝:fc-list                   ----------查验全部现有的字体样式

Linux中apt与apt-get指令的差别与表述

全文出處:apt-vs-apt-get/Ubuntu 16.04 公布时,一个让人瞩目的新特点就是 apt 指令的引进。实际上早就在 2014

(责任编辑:admin)
织梦二维码生成器
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
无法在这个位置找到: ajaxfeedback.htm
栏目列表
推荐内容


扫描二维码分享到微信