Linux内核模块开发

内核模块

一种机制能让内核文件(zImage或者bzImage)本身并不包含某组件,而是在该组件需要被使用的时候,再动态的增加到内核。

内核模块特点

  • 内核本身并不被编译到内核文件
  • 可以根据需求,在内核运行期间动态的安装或卸载;

内核模块程序范例

#include<linux/init.h>
#include<linux/module.h>

static int hello_int(void)
{
    prinfk(KERN_WARNING"Hello, world!\n");
    return 0;
}

static void hello_exit(void)
{
    prinftk(KERN_INFO"Goodbye, world\n");
}

module_init(hello_init);
module_exit(hello_exit);
  1. 模块加载函数(必需)
    安装模块时被系统自动调用的函数,通过module_init宏来指定。
  2. 模块卸载函数(必需)
    卸载模块时被系统自动调用的函数,通过module_exit宏来指定。

内核模块编译

使用makefile.
  • 内核模块由一个源文件组成,该如何编写makefile?
ifneq ($(KERNELRELEASE),)

obj-m := hello.o

else

KDIR := /lib/modules/2.6.18-53.e15/build
all:
        make -C $(KDIR) M=$(PWD) modules # -C进入该目录下,执行该目录下的makefile文件 M表示当前代码所在路径 modules表示模块
clean:
        rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif
  • 内核模块由多个源文件组成,该如何编写makefile?
ifneq ($(KERNELRELEASE),)

obj-m := mymodule.o # 最终生成的执行文件名称
mymodule-objs := main.o add.o # 所有有关的源文件
else

KDIR := /lib/modules/2.6.18-53.e15/build
all:
        make -C $(KDIR) M=$(PWD) modules # -C进入该目录下,执行该目录下的makefile文件 M表示当前代码所在路径 modules表示模块
clean:
        rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif

内核模块安装与卸载

  • 加载 insmod
  • 卸载 rmmod
  • 查看 lsmod
  • 加载 modprobe: 如果insmod,也是加载一个模块到内核。但是它更加强大,会自动寻找相关内容,自动加载。

内核模块可选参数

#include<linux/init.h>
#include<linux/module.h>

MODULE_LICENSE("GPL") //遵循的开源协议
MODULE_AUTHOR("Your name")//作者申明
MODULE_DESCRIPTION("Hello World Module")//模块描述
MODULE_ALIAS("a simplest module")//模块别名

static int hello_int(void)
{
    prinfk(KERN_WARNING"Hello, world!\n");
    return 0;
}

static void hello_exit(void)
{
    prinftk(KERN_INFO"Goodbye, world\n");
}

module_init(hello_init);
module_exit(hello_exit);

module_param(name,type,perm):模块参数->指定模块参数,模块参数用于加载模块时传递参数给模块。

  • name 是模块参数的名称
  • type 是这个参数的类型 ->常见类型:bool 布尔型, int 整型, charp 字符串型
  • perm 是模块参数的访问类型 -> S_IRUGO:任何用户都对/sys/module中出现的该参数具有读权限;S_IWUSR:允许root用户修改/sys/module中出现的该参数;
    例如:
    int a = 3;
    module_param(a,int,S_IRUGO);
    或者
    char *st;
    module_param(st,charp,S_IRUGO);
    具体例子:
#include<linux/init.h>
#include<linux/module.h>

MODULE_LICENSE("GPL") //遵循的开源协议
MODULE_AUTHOR("Your name")//作者申明
MODULE_DESCRIPTION("Hello World Module")//模块描述
MODULE_ALIAS("a simplest module")//模块别名

static char *name = "dengbo sun";
static int age = 30;

module_param(age,int,S_IRUGO);
module_param(name,charp,S_IRUGO);

static int hello_int(void)
{
    prinfk(KERN_WARNING"Hello, world!\n");
    prinfk(KERN_EMERG"Name:%s\n",name);
    prinfk(KERN_EMERG"Age:%d\n",age);
    return 0;
}

static void hello_exit(void)
{
    prinftk(KERN_INFO"Goodbye, world\n");
}

module_init(hello_init);
module_exit(hello_exit);
$ insmod hello.ko age = 32

内核符号导出

/proc/kallsyms中记录了所有导出的符号名称和地址
为什么需要内核符号导出?方便其他模块使用该模块的函数
内核符号导出方式

EXPORT_SYMBO(func name);

常见问题

  • 版本不匹配:
    使用uname -r查看当前内核版本。
    解决办法:
    1. 使用 modprobe --force-modversion 强行插入内核.
    2. 找到相符合的内核版本.

总结

与应用程序区别在于,应用程序从main函数执行完成就没了。内核模块则一直都在,除非卸载。

内核打印

printfk    // 只能在内核内使用,并且有优先级
    /*优先级*/
    KERN_EMERG        ->"0"
    KERN_ALERT        ->"1"
    KERN_CRIT        ->"2"
    KERN_ERR        ->"3"
    KERN_WARNING        ->"4"
    KERN_NOTICE        ->"5"
    KERN_INFO        ->"6"
    KERN_DEBUG        ->"7"
printf    // 只能在应用程序内使用