一、模块的概念

模块是一种向linux内核添加设备驱动程序、文件系统及其他组件的有效办法。无需重新编译新内核和重启系统就可以向内核加入功能。增加了linux的可扩展性。

优点:
        将功能模块后,内核image就不会发生膨胀。
        方便测试新的内核特性。

二、HelloWorld模块

文件hello.c:

#include <linux/module.h> /*定义了与module相关的一些宏和函数,如MODULE_LICENSE等*/
#include <linux/init.h>   /*定义了module_init和module_exit宏*/
#include <linux/kernel.h> /*定义了printk函数*/

/*
 * 初始化函数,当模块装载时被调用,如果成功装载则返回0,
 * 否则返回非0
 */
static int hello_init(void)
{
        printk(KERN_ALERT "Hello world\n");
        return 0;
}

/*
 * 退出函数,当模块被卸载时被调用,释放在初始化函数中申请的资源
 */
static void hello_exit(void)
{
        printk(KERN_ALERT "Goodbye world\n");
}

module_init(hello_init);/*将hello_init函数注册为模块init函数*/
module_exit(hello_exit);/*将hello_eixt函数注册为模块exit函数*/

MODULE_LICENSE("GPL");/*模块所使用的许可协议*/
MODULE_AUTHOR("ice");/*模块的作者*/



三、编译模块


生成最终模块文件需要编写Makefile,如下:


文件Makefile:



ifneq ($(KERNELRELEASE),)
        obj-m := hello.o
else
        KERNELDIR ?= /lib/modules/`uname -r`/build
        PWD := `pwd`

default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif





这个makefile被调用两次,在命令行执行make时,KERNELRELEASE未被设置,所以会执行else中语句。

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules会进入到内核代码树的目录并读取内核顶层的Makefile。

该Makefile会设置KERNELRELEASE,再次读取模块目录下的Makefile时,设置obj-m从而开始进行模块的编译工作。


-C : 在执行make之前先进入$(KERNELDIR)目录,即内核树的顶层目录,然后再执行make,此时make读取的makefile就是内核树顶层目录中的Makefile文件。

M=$(PWD) : 将M的参数值传递给内核树的顶层Makefile,即M的值为当前的模块源文件所在的目录。

modules : make的执行目标


obj-m := hello.o  表示最终生成模块hello.ko文件


如果源文件包含多个.c文件(file1.c, file2.c),obj-m应编写如下:

obj-m := hello.o

hello-objs := file1.o file2.o


四、装载和卸载模块


使用insmod和modprobe命令来装载模块。

命令如:insmod ./hello.ko


insmod装载模块流程:

1.insmod会使用系统调用函数sys_init_module

2.sys_init_module给模块分配分存

3.sys_init_module将模块复制到内存区域

4.调用模块的初始化函数


modprobe与insmod的区别在于modprobe会处理模块的依赖关系,将依赖的模块装载到内核中。


使用lsmod命令可以查看当前系统已经安装的模块信息列表,该命令读取/proc/modules文件。


使用rmmod和modprobe -r命令来卸载已安装模块。

命令如:rmmod hello


五、模块参数


通过模块参数,可以在系统启动或模块装载时指定或修改参数的值,类似于命令参数,不必将参数的值写死在代码中,增强了模块的灵活性,同时用户程序可以通过模块参数读取模块的数据。


外部参数:装载模块时,在命令中使用名称,如insmod ./hello.ko who=jack

内部变量: 模块代码里面使用的变量名称


module_param(name, type, perm);

name: 外部参数和内部变量同为name。

type: 参数或变量的数据类型,值为:

        byte

        short

        ushort

        int

        uint

        long

        ulong

        charp: 字符指针,在传递数据时,由内核为其分配内存,并将字符指针指向内存的首地址

        bool

        invbool

perm: 模块参数在sysfs文件系统下对应的文件访问权限,一般在/sys/module/生成,如果为0,不会生成对应文件。

        其值可以为S_IRUGO | S_IWUSR或0644等。


module_param_named(name, variable, type, perm);

将外部参数name与内部变量variable关联起来,这样外部参数名称就不必要与内部变量同名,可以隐藏内部变量的名称。


module_param_string(name, string, len, perm);

字符串形式的模块参数,等价于module_param(name, charp, perm)。


module_param_array(name, type, nump, perm);

数组形式的模块参数,命令行中参数值以逗号分隔。

type: 数组元素的数据类型。

nump: 指针类型,用于存放从外部参数传递过来的数组元素个数,当传递元素的个数超过了数组的大小时会报错。


module_param_array_named(name, array, type, nump, perm);


以上module_开头都是宏,这些模块参数宏定义在<linux/moduleparam.h>文件中。


MODULE_PARM_DESC(name, description)

描述参数,可以通过modinfo命令查看参数描述信息


六、模块符号表

模块可以导出自己的函数或变量供其它模块使用。这样提高了代码的复用性。

模块导出的函数信息存放在模块导出符号表中,该符号表可看作为内核的API,导出的函数必须非static。


EXPORT_SYMBOL(name)

EXPORT_SYMBOL_GPL(name)

EXPORT_SYMBOL_GPL导出的name只能被使用GPL许可协议的模块使用。


七、内核模块与应用程序

1.应用程序为完成某个任务,而模块一般服务将来某个请求。

2.模块退出时必须清理初始化时申请的资源,而应用程序不必要。

3.模块是以类似事件驱动的方式工作,而不是所有的应用程序都是事件驱动。

4.应用程序通过链接库使用外部函数,而模块只能通过导出符号表。

5.应用程序的错误是无害的,因为模块是运行在内核空间,所以模块的错误可能会影响整个系统。


八、综合示例

1.目录结构

|--Makefile

|--hello/

     |--hello.c

     |--hello.h

     |--Makefile

|--other/

     |--other.c

     |--Makefile

 

2.源代码  

2.1.顶层Makefile:


obj-y = hello/ other/
KERNELDIR ?= /lib/modules/`uname -r`/build
PWD := `pwd`
modules:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules



 


2.2.文件hello/hello.h

void hello_prt(void);



    

2.3.文件hello/hello.c:


#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include "hello.h"

/*charp*/
static char *chp;
module_param(chp, charp, 0600);
MODULE_PARM_DESC(chp, "[char pointer]");

/*string*/
static char str[100];
module_param_string(str, str, 100, S_IRUSR | S_IWUSR);
MODULE_PARM_DESC(str, "[string]");

/*array*/
static int len;
static int intarr[5];
module_param_array(intarr, int, &len, 0600);
MODULE_PARM_DESC(intarr, "[array of int]");

void hello_prt(void)
{
        printk(KERN_ALERT "hello\n");
}

EXPORT_SYMBOL(hello_prt);

static int __init hello_init(void)
{
        int i;
        printk(KERN_ALERT "Hello world\n");

        printk(KERN_ALERT "chp:%s\n", chp);
        printk(KERN_ALERT "str:%s\n", str);

        for (i = 0; i < len; i++) {
                printk(KERN_ALERT "intarr[%d]=%d\n", i, intarr[i]);
        }

        return 0;
}

static void hello_exit(void)
{
        printk(KERN_ALERT "Goodbye world\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ice");





2.4.文件hello/Makefile:


ifneq ($(KERNELRELEASE),)
    obj-m := hello.o
else
    KERNELDIR=/lib/modules/`uname -r`/build
        PWD := `pwd`
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

clean:
    rm -rf *.o *.mod.c *.ko
    rm -rf Module.* .*cmd .tmp_versions




2.5.文件other/other.c:


#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include "../hello/hello.h"

static int other_init(void)
{
        printk(KERN_ALERT "init other\n");
        hello_prt();/*使用hello模块的导出函数*/
        return 0;
}

static void other_exit(void)
{
        printk(KERN_ALERT "exit other\n");
}

module_init(other_init);
module_exit(other_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ice");



2.6.文件other/Makefile:


ifneq ($(KERNELRELEASE),)
    obj-m := other.o
else
    KERNELDIR=/lib/modules/`uname -r`/build
        PWD := `pwd`
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

clean:
    rm -rf *.o *.mod.c *.ko
    rm -rf Module.* .*cmd .tmp_versions




3.测试


进入到顶层Makefile所在的目录,执行make


[root@ice-centos module]# make
make -C /lib/modules/2.6.18-371.1.2.el5.centos.plus/build M=/root/test/module modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-371.1.2.el5.centos.plus-i686'
  CC [M]  /root/test/module/hello/hello.o
  CC [M]  /root/test/module/other/other.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/module/hello/hello.mod.o
  LD [M]  /root/test/module/hello/hello.ko
  CC      /root/test/module/other/other.mod.o
  LD [M]  /root/test/module/other/other.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-371.1.2.el5.centos.plus-i686'
[root@ice-centos module]# ls
hello/  Makefile  Module.markers  Module.symvers  other/
[root@ice-centos module]# modinfo hello/hello.ko other/other.ko ==> 查看模块文件信息
filename:       hello/hello.ko
author:         ice
license:        GPL
srcversion:     5EBD27C704FD9027F9DB618
depends:        
vermagic:       2.6.18-371.1.2.el5.centos.plus SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1
parm:           chp:[char pointer] (charp)
parm:           str:[string] (string)
parm:           intarr:[array of int] (array of int)
filename:       other/other.ko
author:         ice
license:        GPL
srcversion:     6417B31CEB48894C5F7EE55
depends:        hello ==> 因为使用了hello模块的函数,所以依赖hello模块
vermagic:       2.6.18-371.1.2.el5.centos.plus SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1
[root@ice-centos module]# insmod hello/hello.ko chp=aa str=abcdefg intarr=11,22,33,44
[root@ice-centos module]# insmod other/other.ko  
[root@ice-centos module]# tail -9 /var/log/messages
Oct 30 16:02:01 ice-centos kernel: Hello world
Oct 30 16:02:01 ice-centos kernel: chp:aa
Oct 30 16:02:01 ice-centos kernel: str:abcdefg
Oct 30 16:02:01 ice-centos kernel: intarr[0]=11
Oct 30 16:02:01 ice-centos kernel: intarr[1]=22
Oct 30 16:02:01 ice-centos kernel: intarr[2]=33
Oct 30 16:02:01 ice-centos kernel: intarr[3]=44
Oct 30 16:02:08 ice-centos kernel: init other
Oct 30 16:02:08 ice-centos kernel: hello ==> 调用hello_prt函数打印的信息
[root@ice-centos module]# ls /sys/module/hello/ ==> hello模块装载成功后,会在/sys/module目录下生成hello模块目录
parameters/ refcnt      sections/   srcversion  
[root@ice-centos module]# ll /sys/module/hello/parameters/ ==> hello模块的模块参数
total 0
-rw------- 1 root root 4096 Oct 30 16:05 chp
-rw------- 1 root root 4096 Oct 30 16:05 intarr
-rw------- 1 root root 4096 Oct 30 16:05 str
[root@ice-centos module]# rmmod hello ==> 由于hello模块被other模块引用,所以等other模块卸载后才能卸载hello模块
ERROR: Module hello is in use by other
[root@ice-centos module]# rmmod other
[root@ice-centos module]# rmmod hello
[root@ice-centos module]# tail -2 /var/log/messages
Oct 30 16:03:29 ice-centos kernel: exit other
Oct 30 16:03:30 ice-centos kernel: Goodbye world