0
点赞
收藏
分享

微信扫一扫

【内核模块实验】奔跑吧Linux内核


文章目录

  • ​​Debug​​
  • ​​可复用Makefile​​
  • ​​模块代码​​
  • ​​Makefile​​
  • ​​执行​​
  • ​​验证​​
  • ​​加载模块​​
  • ​​验证模块是否加载成功 —— lsmod​​
  • ​​验证模块是否加载成功 —— /sys/modules/*​​
  • ​​卸载模块​​
  • ​​给模块传递参数​​
  • ​​符号共享​​
  • ​​其他类型的符号​​
  • ​​链接其它本地文件​​
  • ​​查看系统文件内容​​

内核模块(Loadable Kernel Module,LKM),可在内核运行时加载一组目标代码来实现某个特定的功能,这样在实际使用 Linux 的过程中就不需要重新编译内核代码来实现动态扩展。

Debug

可复用Makefile

如果你想这么写Makefile,可能不太起作用,虽然好像能够具备良好的易用性。建议还是针对性地将文件名显示地写出来,就像下面这样,只需要改注释的那两行即可。(目前没有解决这个问题)

# Makefile to make module

BASEINCLUDE ?= /lib/modules/`uname -r`/build

# MODULE_NAME-objs := TARGET_FILE.o (TARGET_FILE.c)
parm_module-objs := module_parm.o
# obj-m := MODULE_NAME.o
obj-m := parm_module.o # module.o

all:
$(MAKE) -C $(BASEINCLUDE) M=`pwd` modules
clean:
$(MAKE) -C $(BASEINCLUDE) M=`pwd` clean
rm -f *.ko

模块代码

// my_test.c
#include <linux/init.h> // 包含了module_init()、module_exit()函数申明
#include <linux/module.h> // MODULE_xxx

// 模块入口,内核在初始化各个模块时有优先级顺序,内核把所有模块的初始化函数都存放在一个特别的段中来管理
static int __init my_test_init(void)
{
printk("my first kernel module init.\n");
return 0;
}

// 模块退出函数
static void __exit my_test_exit(void)
{
printk("goodbye.\n");
}

module_init(my_test_init);
module_exit(my_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jia ming");
MODULE_DESCRIPTION("my test kernel module.");
MODULE_ALIAS("mytest"); // 为用户空间提供合适的别名

Makefile

BASEINCLUDE ?= /lib/modules/`uname -r`/build # 模块安装路径
mytest-objs := my_test.o # 目标文件
obj-m := mytest.o # 定义模块名

all:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules;
clean:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) clean;
rm -f *.ko;

执行

jiaming@ubuntu:~/Documents/CProject$ tree .
.
├── Makefile
└── my_test.c

0 directories, 2 files


jiaming@ubuntu:~/Documents/CProject$ make
make -C /lib/modules/`uname -r`/build M=/home/jiaming/Documents/CProject modules;
make[1]: Entering directory '/usr/src/linux-headers-5.10.0-1044-oem'
CC [M] /home/jiaming/Documents/CProject/my_test.o
LD [M] /home/jiaming/Documents/CProject/mytest.o
MODPOST /home/jiaming/Documents/CProject/Module.symvers
CC [M] /home/jiaming/Documents/CProject/mytest.mod.o
LD [M] /home/jiaming/Documents/CProject/mytest.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.0-1044-oem'


jiaming@ubuntu:~/Documents/CProject$ tree . # 编译完成后会生成 mytest.ko 文件
.
├── Makefile
├── modules.order
├── Module.symvers
├── my_test.c
├── mytest.ko
├── mytest.mod
├── mytest.mod.c
├── mytest.mod.o
├── my_test.o
└── mytest.o

0 directories, 10 files

验证

jiaming@ubuntu:~/Documents/CProject$ file mytest.ko # x86_64 ELF 文件
mytest.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=80fe0e29ec10947eedd66ab3b21be2ab33f3b533, not stripped


jiaming@ubuntu:~/Documents/CProject$ modinfo mytest.ko
filename: /home/jiaming/Documents/CProject/mytest.ko
alias: mytest
description: my test kernel module.
author: Jia ming
license: GPL
srcversion: B30B37B2BFE0784D8BC61C4
depends:
retpoline: Y
name: mytest
vermagic: 5.10.0-1044-oem SMP mod_unload modversions

加载模块

​sudo insmod mytest.ko​

​dmesg​​​ 查看 ​​printk​​ 输出内容。

...
[182158.234851] [drm] ib test on ring 2 succeeded in 0 usecs
[182158.234864] [drm] ib test on ring 3 succeeded in 0 usecs
[182158.234880] [drm] ib test on ring 4 succeeded in 0 usecs
[184937.411931] my first kernel module init.

验证模块是否加载成功 —— lsmod

jiaming@ubuntu:~/Documents/CProject$ lsmod 
Module Size Used by
mytest 16384 0
ccm 20480 9
hid_generic 16384 0
...

验证模块是否加载成功 —— /sys/modules/*

加载完模块后,系统会在该目录下新建一个目录。

jiaming@ubuntu:/sys/module$ ll | grep mytest
drwxr-xr-x 5 root root 0 Aug 31 11:09 mytest/

卸载模块

sudo rmmod mytest
  • ​dmesg​​ 命令查看:
...
[185276.971010] goodbye.
  • ​lsmod​​ 中没有 mytest 模块。
  • ​/sys/modules​​ 中没有 mytest 目录。

给模块传递参数

可接受参数的模块

#include <linux/init.h> // 包含了module_init()、module_exit()函数申明
#include <linux/module.h> // MODULE_xxx

static int debug = 1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debugging information");
#define dprintk(args...) \
if(debug){ \
printk(KERN_DEBUG args); \
}

static int mytest = 100;
module_param(mytest, int, 0644);
MODULE_PARM_DESC(mytest, "test for module parameter");

// 模块入口,内核在初始化各个模块时有优先级顺序,内核把所有模块的初始化函数都存放在一个特别的段中来管理
static int __init my_test_init(void)
{
dprintk("my first kernel module init.\n");
dprintk("module parameter=%d\n", mytest);
return 0;
}

// 模块退出函数
static void __exit my_test_exit(void)
{
printk("goodbye.\n");
}

module_init(my_test_init);
module_exit(my_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jia ming");
MODULE_DESCRIPTION("my test kernel module.");
MODULE_ALIAS("mytest");

传入参数
​​​sudo insmod mytest.ko mytest=200​

查看结果
​​​dmesg​​ 查看

...
[185891.125462] my first kernel module init.
[185891.125473] module parameter=200

符号共享

模块之间相互调用。

EXPORT_SYMBOL() # 把函数或符号对全部内核代码公开,将函数以符号的方式导出给内核中的其它模块使用
EXPORT_SYMBOL_GPL() # 只能包含 GPL 许可的模块,MODULE_LICENSE("GPL")

导出的内核符号表可以通过 ​​/proc/kallsyms​​ 查看

...
0000000000000000 t cleanup_module [video]
0000000000000000 T acpi_video_get_edid [video]
0000000000000000 T acpi_video_unregister [video]
0000000000000000 T acpi_video_get_backlight_type [video]
0000000000000000 T acpi_video_set_dmi_backlight_type [video]
0000000000000000 t acpi_video_detect_exit [video]
0000000000000000 T acpi_video_register [video]
0000000000000000 t bpf_prog_6deef7357e7b4530 [bpf]
0000000000000000 t bpf_prog_6deef7357e7b4530 [bpf]
...

​符号在内核地址空间中的地址;符号属性;符号的字符串;哪些内核模块在使用这些符号​

module1_file.c

#include <linux/init.h> // 包含了module_init()、module_exit()函数申明
#include <linux/module.h> // MODULE_xxx

static int debug = 1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debugging information");
#define dprintk(args...) \
if(debug){ \
printk(KERN_DEBUG args); \
}

static int mytest = 100;
module_param(mytest, int, 0644);
MODULE_PARM_DESC(mytest, "test for module parameter");

// 模块入口,内核在初始化各个模块时有优先级顺序,内核把所有模块的初始化函数都存放在一个特别的段中来管理
static int __init my_test_init(void)
{
dprintk("my first kernel module init.\n");
dprintk("module parameter=%d\n", mytest);
return 0;
}

// 模块退出函数
static void __exit my_test_exit(void)
{
printk("goodbye.\n");
}

static int getvalue(void)
{
return mytest;
}

EXPORT_SYMBOL(getvalue);

module_init(my_test_init);
module_exit(my_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jiaming");
MODULE_DESCRIPTION("my test kernel module.");
MODULE_ALIAS("module1");

Makefile

# Makefile to make module

BASEINCLUDE ?= /lib/modules/`uname -r`/build

# MODULE_NAME-objs := TARGET_FILE.o (TARGET_FILE.c)
module1-objs := module1_file.o
# obj-m := MODULE_NAME.o
obj-m := module1.o

all:
$(MAKE) -C $(BASEINCLUDE) M=`pwd` modules
clean:
$(MAKE) -C $(BASEINCLUDE) M=`pwd` clean
rm -f *.ko

module2_file.c

#include <linux/init.h> // 包含了module_init()、module_exit()函数申明
#include <linux/module.h> // MODULE_xxx

extern int getvalue(void);

static int debug = 1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debugging information");
#define dprintk(args...) \
if(debug){ \
printk(KERN_DEBUG args); \
}

// static int mytest = 100;
// module_param(mytest, int, 0644);
// MODULE_PARM_DESC(mytest, "test for module parameter");

// 模块入口,内核在初始化各个模块时有优先级顺序,内核把所有模块的初始化函数都存放在一个特别的段中来管理
static int __init my_test_init(void)
{
dprintk("my second kernel module init.\n");
static int mytest;
mytest = getvalue();
dprintk("module parameter=%d\n", mytest);
return 0;
}

// 模块退出函数
static void __exit my_test_exit(void)
{
printk("goodbye.\n");
}

module_init(my_test_init);
module_exit(my_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jiaming");
MODULE_DESCRIPTION("my test kernel module.");
MODULE_ALIAS("module2");

Makefile

# Makefile to make module

BASEINCLUDE ?= /lib/modules/`uname -r`/build

# MODULE_NAME-objs := TARGET_FILE.o (TARGET_FILE.c)
module2-objs := module2_file.o
# obj-m := MODULE_NAME.o
obj-m := module1.o module2.o # obj-m := module2.o ../module1_file/module1.o

all:
$(MAKE) -C $(BASEINCLUDE) M=`pwd` modules
clean:
$(MAKE) -C $(BASEINCLUDE) M=`pwd` clean
rm -f *.ko
jiaming@ubuntu:~/Documents/Module$ sudo insmod module1.ko mytest=300
jiaming@ubuntu:~/Documents/Module$ sudo insmod module2.ko
jiaming@ubuntu:~/Documents/Module$ dmesg
...
[468913.095311] my first kernel module init.
[468913.095313] module parameter=300
[468919.610202] my second kernel module init.
[468919.610214] module parameter=300
jiaming@ubuntu:~/Documents/Module$ tree .
.
├── Makefile
├── module1_file.c
├── module1_file.o
├── module1.ko
├── module1.mod
├── module1.mod.c
├── module1.mod.o
├── module1.o
├── module2_file.c
├── module2_file.o
├── module2.ko
├── module2.mod
├── module2.mod.c
├── module2.mod.o
├── module2.o
├── modules.order
└── Module.symvers

0 directories, 17 files
jiaming@ubuntu:~/Documents/Module$ cat Module.symvers
0x7b6b3cd2 getvalue /home/jiaming/Documents/Module/module1 EXPORT_SYMBOL

其他类型的符号

module1_file.c

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

#define dprintk(args...) \
if(debug){ \
printk(KERN_DEBUG args); \
}

static int cnt = 10;
module_param(cnt, int, 0644);// bool charp int long short uint ulong ushort

static char * hi = "Hi, Linux";
module_param(hi, charp, 0644);

static int arr[] = {1,2,3,4,5};

static int nums = sizeof(arr)/sizeof(int);

module_param_array(arr, int, &nums, 0644);

static int debug = 1;
module_param(debug, int, 0644);
// MODULE_PARM_DESC(debug, "enable debugging information");

static int __init my_test_init(void)
{
int i;
dprintk("my first kernel module init.\n");
dprintk("module parameter cnt=%d\n", cnt);
printk(KERN_INFO "char*:%s", hi);
for(i=0; i<nums; i++)
{
printk("%d:%d", i, arr[i]);
}
printk("nums:%d", nums);
return 0;
}

static void __exit my_test_exit(void)
{
printk("goodbye 1.\n");
}

static int getvalue(void)
{
printk("parm cnt = %d from getvalue", cnt);
return cnt;
}

EXPORT_SYMBOL(cnt);
EXPORT_SYMBOL(hi);
EXPORT_SYMBOL(arr);
EXPORT_SYMBOL(getvalue);

module_init(my_test_init);
module_exit(my_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jiaming");
MODULE_DESCRIPTION("my test kernel module.");
MODULE_ALIAS("module1");

module2_file.c

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

extern int getvalue(void);
// extern int add(int a, int b);
extern int cnt;
extern char * hi;
extern int arr[];

static int debug = 1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debugging information");
#define dprintk(args...) \
if(debug){ \
printk(KERN_DEBUG args); \
}

static int __init my_test_init(void)
{
dprintk("my second kernel module init.\n");
static int mytest;
mytest = getvalue();
dprintk("get from module1 parameter mytest=%d\n", mytest);
dprintk("string=%s\n", hi);
dprintk("cnt=%d\n", cnt);
int i;
for(i = 0; i < 2; i++)
{
dprintk("%d:%d\n", i, arr[i]);
}
// dprintk("add(3, 4)=%d", add(3, 4));
return 0;
}

static void __exit my_test_exit(void)
{
printk("goodbye second kernel module.\n");
}


module_init(my_test_init);
module_exit(my_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jiaming");
MODULE_DESCRIPTION("my second test kernel module.");
MODULE_ALIAS("module2");
  • ​dmesg​
  • ​ls /sys/module/MODULE_DAME/​​​ 有​​parameters​​ 目录,可以显示参数名。
  • ​lsmod​
  • ​rmmod​
  • ​insmod​
  • ​Module.symvers​​ 查看到处符号
  • ​cat /proc/kallsyms​​ 显示内核所有导出的符号,所有大写的都是全局的符号

链接其它本地文件

查看系统文件内容


举报

相关推荐

0 条评论