文章目录

  • 一、Linux内核模块命令
  • 二、内核模块代码分析
  • 1.头文件
  • 2.模块初始化和退出函数
  • 3.模块参数的函数
  • (1)宏描述
  • (2)一般变量
  • (3)字符串
  • (4)数组
  • 4.学术规范
  • 三、实验
  • 1.任务
  • 2.过程
  • (1)编写内核模块.c文件
  • (2)编写Makefile文件
  • (3)编译
  • (4)安装模块:
  • (5)查看`printk`的输出在缓冲区的信息:
  • (6)卸载模块:
  • (7)清除`printk`输出在缓存区的信息:
  • 四、问题
  • 1.for循环初始化声明分开
  • 2.先卸载残留才能安装
  • 3.震惊!printk居然不能输出%c



一、Linux内核模块命令

  • 内核模块的安装(加载):
    sudo insmod module_name.ko 如果有参数的话就是加上param_name=value
  • int型:year=2020
  • 字符串:direction=embedded_system
    注意:都不行direction="embedded_system"direction=embedded systemdirection=embedded\ system
  • 数组:date_array=2020,3,9,用逗号分隔
  • 内核模块的卸载:
    sudo rmmod module_namemodprobe -r module_name
  • 查看系统已经加载的模块:
    lsmod
  • 查看系统已经加载的模块信息:
    modinfo
  • 查看printk输出在缓存区的信息:
    dmesg
  • 清除printk输出在缓存区的信息:
    sudo dmesg -c

二、内核模块代码分析

1.头文件

// 模块
#include <linux/module.h>
// 使用模块参数要的
#include <linux/moduleparam.h>
// 内核
#include <linux/kernel.h>

2.模块初始化和退出函数

// module_init()内的初始化函数。
static int __init hello_init(void)
{
    return 0;
}

// module_exit()内的退出函数。
static void __exit hello_exit(void)
{
}

// 内核模块入口,相当于main()函数,完成模块初始化
module_init(hello_init);
// 卸载时调用的函数入口,完成模块卸载
module_exit(hello_exit);
  • module_init()module_exit()内填的是具体实现初始化和退出时具体做什么的函数名字,比如hello_inithello_exit(随便起)。

3.模块参数的函数

(1)宏描述

使用宏MODULE_PARM_DESC(variable_name, "xxx")对定义的参数进行说明

参数:

  • 一般是static静态的

(2)一般变量

module_param(int_name, type, perm);module_param_named(ext_name, int_name, type, perm);

参数:

  • ext_name:(external)用户看到的参数名,对外的参数名
  • int_name:(internal)当参数中没有ext_name的时候,此参数即是用户看到的参数名,又是模块内接受参数的变量。
  • type:表示参数的数据类型的标识符byte, short, ushort, int, uint, long, ulong, charp(重点), bool, invboolcharp是指向字符串的指针,而不是char类型,也不能填char
  • perm:指定了在sysfs中相应文件的访问权限。(一般不考虑,0S_IRUSR即可)

(3)字符串

  • 一种是charp的字符串指针形式,内外名一致
  • 另一种是string的字符串数字形式,内外名可异名

module_param_string(ext_name, string, len, perm);

参数:

  • ext_name:展现给用户插入是的参数名,MODULE_PARM_DESC()使用的是这个而不是string
  • string:存储内容的字符串数组变量名
  • len:字符串的长度,不像charp是动态的,这个是预先分配的,一般就填字符串数组的长度。
  • perm:0

(4)数组

module_param_array(int_name, type, nump, perm);module_param_array_named(ext_name, int_name, type, nump, perm);

参数:

  • nump:系统动态判断出有多少个参数存放在数组中,然后将其写入一个整数变量的地址中,如&narr(类似scanf("%d",&narr))。
    动态写入,不加static

4.学术规范

// 内核模块描述
MODULE_DESCRIPTION("a simple driver module");
// GPL协议证书
MODULE_LICENSE("GPL");

三、实验

1.任务

编写一个内核模块;
编译该模块;
加载、卸载该模块;

2.过程

(1)编写内核模块.c文件

mkdir project && cd project
gedit catkinModule.c
// 模块
#include <linux/module.h>
// 使用模块参数要的
#include <linux/moduleparam.h>
// 内核
#include <linux/kernel.h>

/* 内外同名 */
// 一个int型的参数year
static int year = -1;
module_param(year, int, 0);
MODULE_PARM_DESC(year, "内外同名的int型的整数");

/* 内外异名 */
// 一个int型的参数month,对外是month_ext
static int month = -1;
module_param_named(month_ext, month, int, 0);
MODULE_PARM_DESC(month_ext, "内外异名的int型的整数");

/* 两种字符串 */
// 一个指针形式的字符串direction(charp)
static char *direction = "default";
module_param(direction, charp, 0);
MODULE_PARM_DESC(direction, "指针形式的字符串");

// 一个数组形式的字符串subject(string),对外是subject_ext
#define LEN 10
static char subject[LEN];
module_param_string(subject_ext, subject, LEN, 0);
MODULE_PARM_DESC(subject_ext, "数组形式的字符串");

/* 内外同名的数组 */
// 一个int型的数组date_array
static int date_array[3];
// 用于保存数组int_array的实际元素个数
int narr;
module_param_array(date_array, int, &narr, 0);
MODULE_PARM_DESC(date_array, "int数组");

/* 内外异名的数组 */
// 一个int型的数组date_array2,对外是date_array2_ext
static int date_array2[3];
// 用于保存数组int_array的实际元素个数
int narr2;
module_param_array_named(date_array2_ext, date_array2, int, &narr2, 0);
MODULE_PARM_DESC(date_array2_ext, "int数组");

// module_init()内的初始化函数。输出参数:int、字符串、int数组
static int __init hello_init(void)
{
    printk(KERN_ALERT "Hello, my LKM.\n");
    // 输出int型的参数year
    printk(KERN_ALERT "[int]year:%d.\n", year);
    // 输出int型的参数month,对外是month_ext
    printk(KERN_ALERT "[int]month:%d.\n", month);
    // 输出指针形式的字符串direction(charp)
    printk(KERN_ALERT "[charp]direction:%s.\n", direction);
    // 输出数组形式的字符串subject(string)
    printk(KERN_ALERT "[string]subject:%s.\n", subject);
    // 输出int型的数组date_array
    printk(KERN_ALERT "[array]date_array:\n");
    int i;
    for (i = 0; i < narr; i++)
    {
        printk(KERN_ALERT "(%d) %d\n", i, date_array[i]);
    }
    // 输出int型的数组date_array2,对外是date_array2_ext
    printk(KERN_ALERT "[array]date_array2:\n");
    int j;
    for (j = 0; j < narr2; j++)
    {
        printk(KERN_ALERT "(%d) %d\n", j, date_array2[j]);
    }
    return 0;
}

// module_exit()内的退出函数。输出再见
static void __exit hello_exit(void)
{
    printk(KERN_ALERT "Bye, my LKM.\n");
}

module_init(hello_init);
module_exit(hello_exit);

// 内核模块描述
MODULE_DESCRIPTION("a simple driver module");
// GPL协议证书
MODULE_LICENSE("GPL");

(2)编写Makefile文件

gedit Makefile
obj-m := catkinModule.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order  Module.symvers

(3)编译

make

centos有内核签名加载模块失败 linux加载内核模块_centos有内核签名加载模块失败

PS:
如果重新编译的话,要把之前残留的垃圾删除(试过不管这些垃圾也能编译成功,但编辑结果还是原来的就很气)

# 清理垃圾
make clean
# 编译
make

(4)安装模块:

先清理一下缓存,不然一会就可能输出一大堆多余东西,影响到我们想要看到的输出东西

sudo dmesg -c

安装

sudo insmod catkinModule.ko year=2020 direction=embeddedSystem subject_ext=software date_array=2020,3,10 date_array2_ext=2020,3,11

centos有内核签名加载模块失败 linux加载内核模块_字符串_02


【可选部分】:

  • 查看系统已经加载的模块:
lsmod | grep catkinModule

centos有内核签名加载模块失败 linux加载内核模块_内核模块_03

  • 查看系统已经加载的模块信息:
modinfo catkinModule.ko

这是.ko文件,所以要在存在这个文件的目录(在这里就是当前编译的目录)下才能查看到。

centos有内核签名加载模块失败 linux加载内核模块_centos有内核签名加载模块失败_04

(5)查看printk的输出在缓冲区的信息:

sudo dmesg

centos有内核签名加载模块失败 linux加载内核模块_数组_05

(6)卸载模块:

sudo rmmod catkinModule

(7)清除printk输出在缓存区的信息:

sudo dmesg -c

centos有内核签名加载模块失败 linux加载内核模块_内核模块_06

四、问题

1.for循环初始化声明分开

for (int i = 0; i < narr; i++)

会报错:

centos有内核签名加载模块失败 linux加载内核模块_数组_07

解决:要么使用c99,要么就注意点分开写

int i;
for (i = 0; i < narr; i++)

2.先卸载残留才能安装

centos有内核签名加载模块失败 linux加载内核模块_字符串_08


先卸载:

sudo rmmod module_name

3.震惊!printk居然不能输出%c

出来是\x17之类的十六进制结果