Linux模块简介

(感谢周老师提供的资料,本文对此作了适当的修改及补充)

一.模块的概念

    Linux系统按照程序运行空间(或权限)分用户空间和内核空间,内核空间运行linux内核程序代码。Linux内核代码属于单内核(monolithic kernel),其优点是允许效率高,所有的内核代码都集成一体,代码的耦合度高。然而其缺点就是其优点导致,可扩展性和维护性差,比较麻烦。LKM模块机制解决了linux内核的缺陷,其提供了内核可以动态接入和卸载一些程序代码,这类代码称之为模块。

 

    模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。总之,模块是一个为内核(从某种意义上来说,内核也是一个模块)或其他内核模块提供使用功能的代码块。若一个模块程序用于管理某些硬件设备的,如网卡,键盘、摄像头等,这种模块称为设备驱动模块。

 二.模块的副作用

1、  装入的内核模块和其他内核部分一样,具有相同的访问权限,因此,差的内核模块会导致系统崩溃;

2、  为了使内核模块访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改这些符号表;

3、  有些模块要求利用其他模块的功能,因此,内核要维护模块之间的依赖性。

4、  内核必须能够在卸载模块时通知模块,并且要释放分配给模块的内存和中断等资源;   

5、  内核版本和模块版本的不兼容,也可能导致系统崩溃,因此,严格的版本检查是必需的。  

 

    尽管内核模块的引入同时也带来不少问题,但是模块机制确实是扩充内核功能一种行之有效的方法,也是在内核级进行编程的有效途径。

三.应用程序与模块的区别

 应用程序运行在OS环境中,而模块程序应用在kernel环境中。

功能项

OS

Kernel

关系

OS必然包含Kernel方能运行

Kernel可以成为OS一部分,没有OS时,Kernel能独立运行。

目标

服务于应用程序

服务于上层程序(基本是OS,也可直接服务应用程序,如路由器设备的系统代码)

功能

为应用程序提供良好的运行机制及环境,同时提供各种统一的资源接口。

以CPU资源为核心,进行各种任务调度和通讯机制,并且协同各种模块一道服务于上层(OS)。

 

 四.模块的编写规则

1. 因为模块不能像应用程序那样在系统平台下自由运行和结束,而是被系统的内核调用运行和释放。所以模块必须提供两个函数给内核调用init_module()和cleanup_module()。

2. 必须提供一种途径向内核告知自己是什么模块,MODULE_LICENSE()宏函数用来声明自己是遵循何种规则的模块。

3.模块初始化可以设定或模块之间调用,可使用module_param()和EXPORT_SYMBOL_GPL()。

4.为了便于使用者方便使用该模块,可以提供一些模块使用信息说明,如:MODULE_DESCRIPTION()、MODULE_AUTHOR()和MODULE_DEVICE_TABLE()、MODULE_VERSION()、MODULE_ALIAS()分别声明模块的描述、作者、设备表、版本、别名。

    从上述描述看,我们可以确定:init_module()、cleanup_module()MODULE_LICENSE()必须的,其它都是根据需要可选择的。

    另外,module_init()module_exit()两个函数可以替换inid_module()cleanup_module()函数,是因为系统内核便于用户多样化命名模块的挂载和卸载函数,当然用户的挂载函数名和卸载函数名必须以参数的形式分别填入module_init()module_exit()中,方能替代init_module()cleanup_module()这两函数。

五.最简单的模块程序

//引用头文件

#include "linux/init.h"

#include "linux/module.h"

//声明模块支持协议

MODULE_LICENSE("GPL");

 

//模块加载程序,被insmod命令调用

int __init init_module()

{

//此处你可写正在挂载代码

printk("Thismodule was mounted by the kernel!\n");//用dmesg命令可以查看该信息

}

 

//模块卸载程序,被rmmod命令调用

void __exit cleanup_module()

{

//此处你写正在卸载代码

printk("Thismodule was unmounted by the kernel!\n");//用dmesg命令可查看该信息

}

 

__init关键字表示后边的函数为初始化程序,该函数一旦执行完毕,系统就把该函数代码从内存中清理出来,因为初始化程序在整个模块生命周期中仅仅运行一次,所以一旦运行完毕就没有保留价值。

__exit关键字表示后边的函数为卸载函数,功能与__init一样。

 

上面是最简单的模块程序框架,注意模块没有向应用程序那样有主函数main(),而是由许多服务子程序堆积起来,被别的模块或应用程序调用,所以模块是一种服务程序,以内核一道服务与系统。

 

六.复杂的模块框架程序

//头文件引用

#include

#include

 

//模块协议支持声明

MODULE_LICENSE("GPL");

 

//作者声明

MODULE_AUTHOR("wozon.zhou mytel:119");

//模块描述,可用modinfo命令查看

MODULE_DESCRIPTION("The module is usedto test!");

//支持设备声明,可用modinfo命令查看

MODULE_SUPPORTED_DEVICE("NOdevice!");

//模块版本声明,可用modinfo命令查看

MODULE_VERSION("1.0V");

//模块参数声明

static int i_int = 0;

static char *name ="wozon";

module_param(i_int,int,S_IRUGO);

module_param(name,charp,S_IRUGO);

//模块参数表述,可用modinfo命令查看

MODULE_PARM_DESC(i_int,"input aint");

MODULE_PARM_DESC(name,"input astring");

 

//加法函数

int Amodule_add(int a,int b)

{

return a + b;

}

EXPORT_SYMBOL_GPL(Amodule_add);//函数引出声明

 

//模块挂载函数

static int __init Amodule_init(void)

{

printk("Hellothe p module was installed!\n");

printk("Thei_int is %d, and the name is %s\n",i_int,name);

return0;

}

 

//卸载函数

static void __exit Amodule_exit(void)

{

printk("Hellop base module was removed!\n");

}

//绑定装载和卸载函数

module_init(Amodule_init);//被insmod命令调用

module_exit(Amodule_exit);//被rmmod命令调用


模块参数module_param(参数名称,参数类型,参数权限),为模块定义一个参数。在装载模块时,用户可以向模块传递参数,形式为"insmode(或modprobe) 模块名  参数名=参数值",如果不传递,参数将使用模块内定义的缺省值。

参数类型:

名称

说明

名称

说明

byte

8bits

long

有符号64bits

short

有符号16bits

ulong

无符号64bits

ushort

无符号16bits

charp

字符指针

int

有符号32bits

bool

布尔值

uint

无符号32bits

invbool

反布尔值


参数选项:

标志

含义

S_IRUSR值为4

用户可读写

S_IWUSR值为2

用户可写

S_IXUSR值为1

用户可执行

S_IRWXU值为7

用户可读写执行

S_IRGRP值为4

组可读

S_IWGRP值为2

组可写

S_IXGRP值为1

组可执行

S_IRWXG值为7

组可读写执行

S_IROTH值为4

其他人可读

S_IWOTH值为2

其他人可写

S_IXOTH值为1

其他人可执行

S_IRWXO值为7

其他人可读写执行

S_IRUGO

用户组其他人可读

 

 

 七.编译模块

7.1典型的内核模块编译Makefile

#To display info during Ccompiling

$(warning KERNELRELEASE=$(KERNELRELEASE))

#To check the KERNELRELEASE enviroment value

ifeq ($(KERNELRELEASE),)

#It's NULL, so to set the enviroment value

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

PWD :=$(shell pwd)

modules:

        $(MAKE) -C $(KERNELDIR)M=$(PWD) modules

modules_install:

        $(MAKE) -C $(KERNELDIR)M=$(PWD) modules_install

clean:

        rm -rf *.o *~ core .depend.*.cmd *.ko *.mod.c .tmp_versions Module* modules*

.PHONY: modules modules_install clean

#To create obj file

else

          obj-m := modulename.o

endif


内核模块编译需要内核源码支持,对内核源码有如下要求:

1、  必须编译过,方能提供必要的.O(或.KO)动态库文件和头文件的支持;

2、  注意版本与自己编写模块代码要一致。

 

用户的Makefile在一次编译中,一共被调用三次:

1、  第一次,是make命令调用,设置内核源码路径,同时下达modules工作命令。

2、  第二次是有内核的modules调用该Makefile,完成将源码编译成.o文件。

3、  第三次还是内核的modules调用该MakeFile文件,完成将.o文件编译成.ko文件。

 Linux模块简介_第1张图片

目标输出文件obj-m := modulename.o,表示动态模块,如果为静态的,则为obj-y := modulename.o。

                   .PHONY: modulesmodules_install clean为伪目标定义,就是定义modules、modules_install和clean是命令,而不是文件。

7.2编译多模块文件

obj-m := modulename.o和obj-y := modulename.o仅仅编译单个文件的当个模块。

多个单文件和单模块编译则:obj-m:=xxx.o yyy.o zzz.o就可以编译三个模块了。

多个文件编译成一个模块:

obj-m :=xxx.o

xxx-objs :=a.ob.o c.o

或多文件及多模块:

方案一:

obj-m :=A.o

A-objs:=ab.o  ac.o ad.o

obj-m +=B.o

B-objs:=bb.o  bc.o bd.o

方案二:

obj-m :=A.o B.o

A-objs:=ab.o  ac.o ad.o

B-objs:=bb.o  bc.o bd.o


八.运行模块

编译后的.ko模块文件,如何运行?

首先,模块是系统内核的一部分,所以只有root权限方能运行,使用sudo命令可以进入。

1、  用insmod命令加载:insmod xxx.ko,内核会调用xxx.ko模块中的挂载程序,如果挂载程序有printk命令,可以使用dmesg命令查看。xxx.ko模块就被加载到内核中,可以用lsmod查看xxx模块信息。

2、  用rmmod命令可以卸载模块:rmmod xxx.ko或者rmmod xxx卸载,内核会调用xxx.ko模块中卸载程序,这时候,xxx模块被内核卸载。

3、  可以使用modinfo查看模块信息:modinfo xxx.ko,其会列出xxx模块的相关信息。

 

注:

lsmod命令列出的内容,其实就是列出/proc/modules文件的内容。

Dmesg命令列出的内容,其实就是列出/var/log/messages文件内容。

另外一个命令modprobe命令:

modprobe [-acdlrtvV][--help][模块文件][符号名称 = 符号值]

modprobe可载入指定的个别模块,或是载入一组相依赖的模块。modprobe会根据depmod所产生的依赖关系,决定要载入哪些模块。若在载入过程中发生错误,在modprobe会卸载整组的模块。

依赖关系是通过读取/lib/modules/2.6.xx/modules.dep得到的。而该文件是通过depmod 所建立。


你可能感兴趣的:(Linux)