linux内核入门之module

本文介绍Linux内核模块的基础概念及开发流程,包括模块的优势、HelloWorld模块实例、模块的编译与加载方式、模块参数设置及模块间如何共享功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、模块的概念

模块是一种向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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值