Linux模块机制浅析

 

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

模块的Hello World!

我们通过创建一个简单的模块进行测试。首先是源文件main.c和Makefile。

florian@florian-pc:~/module$ cat main.c

#include<linux/module.h>

#include<linux/init.h>

static int __init init(void)

{

printk("Hi module!\n");

return 0;

}

static void __exit exit(void)

{

printk("Bye module!\n");

}

module_init(init);

module_exit(exit);

其中init为模块入口函数,在模块加载时被调用执行,exit为模块出口函数,在模块卸载被调用执行。

florian@florian-pc:~/module$ cat Makefile

obj-m += main.o

#generate the path

CURRENT_PATH:=$(shell pwd)

#the current kernel version number

LINUX_KERNEL:=$(shell uname -r)

#the absolute path

LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)

#complie object

all:

make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules

#clean

clean:

make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

其中,obj-m指定了目标文件的名称,文件名需要和源文件名相同(扩展名除外),以便于make自动推导。

然后使用make命令编译模块,得到模块文件main.ko。

florian@florian-pc:~/module$ make

make -C /usr/src/linux-headers-2.6.35-22-generic M=/home/florian/module modules

make[1]: 正在进入目录 `/usr/src/linux-headers-2.6.35-22-generic'

Building modules, stage 2.

MODPOST 1 modules

make[1]:正在离开目录 `/usr/src/linux-headers-2.6.35-22-generic'

使用insmod和rmmod命令对模块进行加载和卸载操作,并使用dmesg打印内核日志。

florian@florian-pc:~/module$ sudo insmod main.ko;dmesg | tail -1

[31077.810049] Hi module!

florian@florian-pc:~/module$ sudo rmmod main.ko;dmesg | tail -1

[31078.960442] Bye module!

通过内核日志信息,可以看出模块的入口函数和出口函数都被正确调用执行。

模块文件

使用readelf命令查看一下模块文件main.ko的信息。

florian@florian-pc:~/module$ readelf -h main.ko

ELF Header:

Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

Class:                             ELF32

Data:                              2's complement, little endian

Version:                           1 (current)

OS/ABI:                            UNIX - System V

ABI Version:                       0

Type:                              REL (Relocatable file)

Machine:                           Intel 80386

Version:                           0x1

Entry point address:               0x0

Start of program headers:          0 (bytes into file)

Start of section headers:          1120 (bytes into file)

Flags:                             0x0

Size of this header:               52 (bytes)

Size of program headers:           0 (bytes)

Number of program headers:         0

Size of section headers:           40 (bytes)

Number of section headers:         19

Section header string table index: 16

我们发现main.ko的文件类型为可重定位目标文件,这和一般的目标文件格式没有任何区别。我们知道,目标文件是不能直接执行的,它需要经过链接器的地址空间分配、符号解析和重定位的过程,转化为可执行文件才能执行。

那么,内核将main.ko加载后,是否对其进行了链接呢?

模块数据结构

首先,我们了解一下模块的内核数据结构。

linux3.5.2/kernel/module.h:220

struct module

{

……

/* Startup function. */

int (*init)(void);

……

/* Destruction function. */

void (*exit)(void);

……

};

模块数据结构的init和exit函数指针记录了我们定义的模块入口函数和出口函数。

模块加载

模块加载由内核的系统调用init_module完成。

linux3.5.2/kernel/module.c:3009

/* This is where the real work happens */

SYSCALL_DEFINE3(init_module, void __user *, umod,

unsigned long, len, const char __user *, uargs)

{

struct module *mod;

int ret = 0;

……

/* Do all the hard work */

mod = load_module(umod, len, uargs);//模块加载

……

/* Start the module */

if (mod->init != NULL)

ret = do_one_initcall(mod->init);//模块init函数调用

……

return 0;

}

系统调用init_module由SYSCALL_DEFINE3(init_module...)实现,其中有两个关键的函数调用。load_module用于模块加载,do_one_initcall用于回调模块的init函数。

函数load_module的实现为。

linux3.5.2/kernel/module.c:2864

/* Allocate and load the module: note that size of section 0 is always

zero, and we rely on this for optional sections. */

static struct module *load_module(void __user *umod,

unsigned long len,

const char __user *uargs)

{

struct load_info info = { NULL, };

struct module *mod;

long err;

……

/* Copy in the blobs from userspace, check they are vaguely sane. */

err = copy_and_check(&info, umod, len, uargs);//拷贝到内核

if (err)

return ERR_PTR(err);

/* Figure out module layout, and allocate all the memory. */

mod = layout_and_allocate(&info);//地址空间分配

if (IS_ERR(mod)) {

err = PTR_ERR(mod);

goto free_copy;

}

……

/* Fix up syms, so that st_value is a pointer to location. */

err = simplify_symbols(mod, &info);//符号解析

if (err < 0)

goto free_modinfo;

err = apply_relocations(mod, &info);//重定位

if (err < 0)

goto free_modinfo;

……

}

函数load_module内有四个关键的函数调用。copy_and_check将模块从用户空间拷贝到内核空间,layout_and_allocate为模块进行地址空间分配,simplify_symbols为模块进行符号解析,apply_relocations为模块进行重定位。

由此可见,模块加载时,内核为模块文件main.ko进行了链接的过程!

至于函数do_one_initcall的实现就比较简单了。

linux3.5.2/kernel/init.c:673

int __init_or_module do_one_initcall(initcall_t fn)

{

int count = preempt_count();

int ret;

if (initcall_debug)

ret = do_one_initcall_debug(fn);

else

ret = fn();//调用init module

……

return ret;

}

即调用了模块的入口函数init。

模块卸载

模块卸载由内核的系统调用delete_module完成。

linux3.5.2/kernel/module.c:768

SYSCALL_DEFINE2(delete_module, const char __user *, name_user,

unsigned int, flags)

{

struct module *mod;

char name[MODULE_NAME_LEN];

int ret, forced = 0;

……

/* Final destruction now no one is using it. */

if (mod->exit != NULL)

mod->exit();//调用exit module

……

free_module(mod);//卸载模块

……

}

通过回调exit完成模块的出口函数功能,最后调用free_module将模块卸载。

结论

如此看来,内核模块其实并不神秘。传统的用户程序需要编译为可执行程序才能执行,而模块程序只需要编译为目标文件的形式便可以加载到内核,有内核实现模块的链接,将之转化为可执行代码。同时,在内核加载和卸载的过程中,会通过函数回调用户定义的模块入口函数和模块出口函数,实现相应的功能。

参考资料

http://hi.baidu.com/20065562/item/15dcc4ce92c3d510b67a24af

http://blog.chinaunix.net/uid-26009923-id-3840337.html

Linux模块机制浅析的更多相关文章

  1. Linux模块机制浅析_转

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

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

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

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

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

  4. 【linux驱动笔记】linux模块机制浅析

      1.   模块module 操作系统分微内核和宏内核,微内核优点,可以使操作系统仅作很少的事,其它事情如网络处理等都作为应用程序来实现,微内核精简的同时,必然带来性能的下降.而linux的宏内核设 ...

  5. webpack模块机制浅析【一】

    webpack模块机制浅析[一] 今天看了看webpack打包后的代码,所以就去分析了下代码的运行机制. 下面这段代码是webpack打包后的最基本的形式,可以说是[骨架] (function(roo ...

  6. Linux内核启动流程与模块机制

    本文旨在简单的介绍一下Linux的启动流程与模块机制: Linux启动的C入口位于/Linux.2.6.22.6/init/main.c::start_kernel() 下图简要的描述了一下内核初始化 ...

  7. Linux内核(6) - 模块机制与“Hello World!

    有一种感动,叫内牛满面,有一种机制,叫模块机制.显然,这种模块机制给那些Linux的发烧友们带来了方便,因为模块机制意味着人们可以把庞大的Linux内核划分为许许多多个小的模块.对于编写设备驱动程序的 ...

  8. typecho流程原理和插件机制浅析(第一弹)

    typecho流程原理和插件机制浅析(第一弹) 兜兜 393 2014年03月28日 发布 推荐 5 推荐 收藏 24 收藏,3.5k 浏览 虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终 ...

  9. Linux 设备模型浅析之 uevent 篇(2)

    Linux 设备模型浅析之 uevent 篇 本文属本人原创,欢迎转载,转载请注明出处.由于个人的见识和能力有限,不可能面 面俱到,也可能存在谬误,敬请网友指出,本人的邮箱是 yzq.seen@gma ...

随机推荐

  1. Oracle sql连接

    inner-join                    left-outer-join                 right-outer-join                 full- ...

  2. F#之旅9 - 正则表达式

    今天,cozy群有个群友发了条正则,问正则匹配相关的问题.虽然他的问题用html selector去处理可能更好,但是我也再一次发现:我忘了正则怎么写的了! 忘掉正则是有原因的,这篇文章会简单记录下F ...

  3. (UWP开发)更为合理的一种ListView下拉刷新(PullToRefresh)实现方法

    最近在做的一个项目需要用到下拉刷新,但是参考了现在网络上比较普遍的方法,觉得都不太好,因为要在外部套上一个SrollViewer,容易出现滚动错误.于是刚开始的时候就把思路定到了ListView内部的 ...

  4. Android中轻松显示Gif图片

    android中现在没有直接显示gif的view,只能通过mediaplay来显示,且还常常不能正常显示出来,为此写了这个gifview,其用法和imageview一样使用方法:1-把GifView. ...

  5. Python3使用urllib访问网页

    介绍 改教程翻译自python官网的一篇文档. urllib.request是一个用于访问URL(统一资源定位符)的Python模块.它以urlopen函数的形式提供了一个非常简单的接口,可以访问使用 ...

  6. 移动web资源整理

    [原]移动web资源整理 2013年初接触移动端,简单做下总结,首先了解下移动web带来的问题 设备更新换代快--低端机遗留下问题.高端机带来新挑战 浏览器厂商不统一--兼容问题多 网络更复杂--弱网 ...

  7. 简易版C语言程序语法

    <程序> -〉 <外部声明> | <函数定义><外部声明> -〉<头文件> | <变量> | <结构体> <头 ...

  8. Visual Studio 常用快捷键

    作为一个使用VisualStudio的程序员,使用快捷键会为你的开发提供助力. 下附个人开发过程中感觉比较实用的快捷键: 开始运行"devenv",启动相应版本的VisualStu ...

  9. 【EntityFramework Core】实体实例化注入

    由于逻辑需要,我希望能在EF Core实例化实体时,拿到实体并执行相关代码,所以我就研究了一番EF Core,得到以下方法. 1.创建实体初始化类,继承EntityMaterializerSource ...

  10. 阮一峰对js的见解(10大缺陷)

    一.为什么Javascript有设计缺陷?这里有三个客观原因,导致Javascript的设计不够完善.1. 设计阶段过于仓促Javascript的设计,其实只用了十天.而且,设计师是为了向公司交差,本 ...