Linux模块机制浅析

Linux模块机制浅析

 

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

模块的Hello
World!

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

[email protected]:~/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为模块出口函数,在模块卸载被调用执行。

[email protected]:~/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。

[email protected]:~/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打印内核日志。

[email protected]:~/module$ sudo insmod
main.ko;dmesg | tail -1

[31077.810049] Hi module!

[email protected]:~/module$ sudo rmmod
main.ko;dmesg | tail -1

[31078.960442] Bye module!

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

模块文件

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

[email protected]:~/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

时间: 2024-10-12 03:45:17

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

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

  1.   模块module 操作系统分微内核和宏内核,微内核优点,可以使操作系统仅作很少的事,其它事情如网络处理等都作为应用程序来实现,微内核精简的同时,必然带来性能的下降.而linux的宏内核设计,保证了设计性能,但linux作为一个通用操作系统,必然会兼容很多硬件,而linux本身的宏内核设计,导致了如果同时兼容所有的硬件,其编译代码将庞大无比,为了解决这个问题,linux效仿微内核,采用了模块这一天才思想.当内核配置make menuconfig时,可以选择M,将驱动作为模块来加载,其

webpack模块机制浅析【一】

webpack模块机制浅析[一] 今天看了看webpack打包后的代码,所以就去分析了下代码的运行机制. 下面这段代码是webpack打包后的最基本的形式,可以说是[骨架] (function(root,fn){ if(typeof exports ==='object'&&typeof module === 'object'){ module.exports = fn();//exports和module同时存在,说明时在node的CommonJs规范下,这个时候使用module.exp

windows和linux套接字中的select机制浅析

先来谈谈为什么会出现select函数,也就是select是解决什么问题的? 平常使用的recv函数时阻塞的,也就是如果没有数据可读,recv就会一直阻塞在那里,这是如果有另外一个连接过来,就得一直等待,这样实时性就不是太好. 这个问题的几个解决方法:1. 使用ioctlsocket函数,将recv函数设置成非阻塞的,这样不管套接字上有没有数据都会立刻返回,可以重复调用recv函数,这种方式叫做轮询(polling),但是这样效率很是问题,因为,大多数时间实际上是无数据可读的,花费时间不断反复执行

Linux学习笔记之内核启动流程与模块机制

本文旨在简单的介绍一下Linux的启动流程与模块机制: Linux启动的C入口位于/Linux.2.6.22.6/init/main.c::start_kernel() 下图简要的描述了一下内核初始化的流程: 本文我们分析一下do_initcalls ()函数,他负责大部分模块的初始化(比如U盘驱动就是在这里被初始化的). 1 static void __init do_initcalls(void) 2 { 3 initcall_t *call; 4 int count = preempt_c

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

有一种感动,叫内牛满面,有一种机制,叫模块机制.显然,这种模块机制给那些Linux的发烧友们带来了方便,因为模块机制意味着人们可以把庞大的Linux内核划分为许许多多个小的模块.对于编写设备驱动程序的开发者来说,从此以后他们可以编写设备驱动程序却不需要把她编译进内核,不用reboot机器,她只是一个模块,当你需要她的时候,你可以把她抱入怀中(insmod),当你不再需要她的时候,你可以把她一脚踢开(rmmod). 于是,忽如一夜春风来,内核处处是模块.让我们从一个伟大的例子去认识模块.这就是传说

linux内存管理浅析

[地址映射](图:左中)linux内核使用页式内存管理,应用程序给出的内存地址是虚拟地址,它需要经过若干级页表一级一级的变换,才变成真正的物理地址.想一下,地址映射还是一件很恐怖的事情.当访问一个由虚拟地址表示的内存空间时,需要先经过若干次的内存访问,得到每一级页表中用于转换的页表项(页表是存放在内存里面的),才能完成映射.也就是说,要实现一次内存访问,实际上内存被访问了N+1次(N=页表级数),并且还需要做N次加法运算.所以,地址映射必须要有硬件支持,mmu(内存管理单元)就是这个硬件.并且需

Linux进程IPC浅析[进程间通信SystemV共享内存]

Linux进程IPC浅析[进程间通信SystemV共享内存] 共享内存概念,概述 共享内存的相关函数 共享内存概念,概述: 共享内存区域是被多个进程共享的一部分物理内存 多个进程都可把该共享内存映射到自己的虚拟内存空间,所有用户空间的进程若要操作共享内存,都要将其映射到自己的虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信 共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容 本身不提供同

BIOS学习笔记之UEFI模块机制

本文旨在简单的介绍一下UEFI的模块机制: BIOS是固化在电脑主板上的一段程序,主要功能就是初始化主板,最后加载操作系统(OS):而UEFI通俗一点讲就是一种BIOS的规范,它提出了一种BIOS的实现架构并提供了一套开源的实现. 我们知道Linux的模块机制是通过宏来定义模块的入口点的(参考:Linux学习笔记之内核启动流程与模块机制),而UEFI的模块机制理解起来更容易,它的每个模块都有一个inf文件,里面描述了模块的详细信息,比如入口点(入口函数).模块包含哪些源文件等. 以U盘驱动为例:

linux模块编写

一.linux模块化机制简介   模块化的优势: linux内核是单内核结构,由于所有内容都集成在一起,效率很高,但可扩展性和可维护性相对较差,模块机制弥补这一缺陷. Linux模块可以通过静态或动态的方法加载到内核空间,静态加载是指在内核启动过程中加载:动态加载是指在内核运行的过程中随时加载.一个模块被加载到内核中时,就成为内核代码的一部分.模块加载入系统时,系统修改内核中的符号表,将新加载的模块提供的资源和符号添加到内核符号表中,以便模块间的通信. 从代码的特征上来看,模块就是可以完成一个独