文章目录
- 一、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 system
和direction=embedded\ system
- 数组:
date_array=2020,3,9
,用逗号分隔
- 内核模块的卸载:
sudo rmmod module_name
或modprobe -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_init
和hello_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
,invbool
charp
是指向字符串的指针,而不是char
类型,也不能填char
。 - perm:指定了在sysfs中相应文件的访问权限。(一般不考虑,
0
或S_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
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
【可选部分】:
- 查看系统已经加载的模块:
lsmod | grep catkinModule
- 查看系统已经加载的模块信息:
modinfo catkinModule.ko
这是.ko
文件,所以要在存在这个文件的目录(在这里就是当前编译的目录)下才能查看到。
(5)查看printk
的输出在缓冲区的信息:
sudo dmesg
(6)卸载模块:
sudo rmmod catkinModule
(7)清除printk
输出在缓存区的信息:
sudo dmesg -c
四、问题
1.for循环初始化声明分开
for (int i = 0; i < narr; i++)
会报错:
解决:要么使用c99,要么就注意点分开写
int i;
for (i = 0; i < narr; i++)
2.先卸载残留才能安装
先卸载:
sudo rmmod module_name
3.震惊!printk居然不能输出%c
出来是\x17之类的十六进制结果