环境准备
- 任意Linux系统一个
- 安装内核开发所依赖的软件包
- kernel-devel
源码
helloworld_lkm.c
[root@hcss-ecs-0a97 helloworld_lkm]# cat helloworld_lkm.c
/*
*第一个内核代码
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
MODULE_AUTHOR("<insert your name here>");
MODULE_DESCRIPTION("LKP book:ch4/helloworld_lkm: hello, world, our first LKM");
MODULE_LICENSE("Dual MIT/GPL");
MODULE_VERSION("0.1");
static int __init helloworld_lkm_init(void)
{
printk(KERN_INFO "Hello, world\n");
return 0; /* success */
}
static void __exit helloworld_lkm_exit(void)
{
printk(KERN_INFO "Goodbye cruel world\n");
}
module_init(helloworld_lkm_init);
module_exit(helloworld_lkm_exit);
Makefile
[root@hcss-ecs-0a97 helloworld_lkm]# cat Makefile
# Simplest kernel module Makefile
PWD := $(shell pwd)
obj-m += helloworld_lkm.o
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
install:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules_install
clean:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
运行测试
[root@hcss-ecs-0a97 helloworld_lkm]# make
[root@hcss-ecs-0a97 helloworld_lkm]# insmod helloworld_lkm.ko
[root@hcss-ecs-0a97 helloworld_lkm]# dmesg | tail -1
[3264263.940405] Hello, world
[root@hcss-ecs-0a97 helloworld_lkm]# rmmod helloworld_lkm.ko
[root@hcss-ecs-0a97 helloworld_lkm]# dmesg | tail -1
[3264303.042697] Goodbye cruel world
C文件代码解读
1. 头文件引用
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
- #include <linux/init.h>:包含内核初始化相关的宏。
- #include <linux/kernel.h>:包含内核级打印和日志功能的宏,例如 printk。
- #include <linux/module.h>:包含内核模块开发所需的头文件,定义了模块的入口和出口函数以及一些模块的元数据。
2. 模块元数据
MODULE_AUTHOR("<insert your name here>");
MODULE_DESCRIPTION("LKP book:ch4/helloworld_lkm: hello, world, our first LKM");
MODULE_LICENSE("Dual MIT/GPL");
MODULE_VERSION("0.1");
- MODULE_AUTHOR("<insert your name here>"):指定模块的作者,通常写上你的名字。
- MODULE_DESCRIPTION(...):描述模块的功能或目的,这里写的是模块是“LKP 书籍第4章的 hello, world 示例”。
- MODULE_LICENSE("Dual MIT/GPL"):指定模块的许可证,这里使用 MIT 和 GPL 双重许可证。
- MODULE_VERSION("0.1"):指定模块的版本号。
3. 初始化函数
static int __init helloworld_lkm_init(void)
{
printk(KERN_INFO "Hello, world\n");
return 0; /* success */
}
- __init:这是一个宏,指示该函数仅在模块加载时调用,并在模块卸载时从内存中释放。
- printk(KERN_INFO "Hello, world\n"):在内核日志中打印 Hello, world。KERN_INFO 是一个日志级别,表示普通的信息。
- return 0;:返回 0 表示初始化成功。
4. 卸载函数
static void __exit helloworld_lkm_exit(void)
{
printk(KERN_INFO "Goodbye, world\n");
}
- __exit:这是一个宏,指示该函数仅在模块卸载时调用。
- printk(KERN_INFO "Goodbye, world\n"):在内核日志中打印 Goodbye, world。
5. 注册模块入口和出口函数
module_init(helloworld_lkm_init);
module_exit(helloworld_lkm_exit);
- module_init(helloworld_lkm_init):指定模块加载时调用的初始化函数。
- module_exit(helloworld_lkm_exit):指定模块卸载时调用的退出函数。
总结
这是一个简单的 Linux 内核模块(LKM)示例,模块加载时打印 “Hello, world”,卸载时打印 “Goodbye cruel world”。通过 module_init 和 module_exit 宏,分别指定了模块加载和卸载时的函数。
Makefile文件解读
1. 变量定义
PWD := $(shell pwd)
- PWD 变量获取当前工作目录的路径,$(shell pwd) 命令会返回执行 pwd 命令的输出(即当前目录的路径)。这个路径用于后续的 make 命令中,指示源代码所在的目录。
2. 编译目标
obj-m += helloworld_lkm.o
- obj-m: 这是 Makefile 中的一个变量,用于指定要编译的内核模块的目标文件(object files)。在 Linux 内核的模块编译过程中,内核会根据 obj-m 的值来确定哪些模块需要编译,并最终将它们编译为 .ko(内核模块)文件。
- +=: 这是一个追加操作符。在 Makefile 中,+= 表示将右边的内容添加到左边变量的现有值中。因此,obj-m += helloworld_lkm.o 会把 helloworld_lkm.o 追加到 obj-m 中。
- helloworld_lkm.o: 这是一个目标文件(object file),它通常是通过编译源代码文件 helloworld_lkm.c 得到的。它是一个编译后的二进制文件,用于生成内核模块。
3. all 目标(编译模块)
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
- all 是默认目标。运行 make 命令时,默认会执行此目标。
- make -C /lib/modules/$(shell uname -r)/build/:-C 选项告诉 make 在指定的目录下执行构建,这里指定的是当前运行内核版本的源代码目录,/lib/modules/$(shell uname -r)/build/ 这个路径是当前内核源代码所在的目录。
- M=$(PWD):这部分告诉 make 使用当前工作目录(即 PWD)作为模块的源代码目录。
- modules:表示要构建内核模块。
4. install 目标(安装模块)
install:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules_install
- install 目标用于安装编译好的模块。
- make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules_install:这条命令会将编译后的模块安装到系统中,通常是 /lib/modules/$(uname -r)/ 目录下的适当位置。
- modules_install 会将模块安装到相应的内核模块目录,确保它能够在系统中正确加载。
在我们的这个实验中,这部分的内容可忽略
5. clean 目标(清理构建文件)
clean:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
- clean 目标用于清理构建过程中生成的临时文件。
- make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean:这条命令会删除生成的对象文件(.o 文件)和其他构建过程中产生的临时文件。它会确保每次构建时是从一个干净的状态开始。
总结
- Makefile 中的每个目标(all、install、clean)都依赖于内核源代码目录 /lib/modules/$(shell uname -r)/build/,该路径是动态获取的,指向当前系统的内核源代码位置。
- all 目标用于编译内核模块,install 目标用于安装模块,clean 目标用于清理构建生成的文件。
- 使用 make 命令时,根据目标的不同,分别执行模块的编译、安装或清理操作。
这样,你可以通过执行 make 来编译模块,make install 来安装模块,make clean 来清理构建文件。
注意
- /lib/modules/$(uname -r)/build/ 是指向内核构建目录的符号链接,包含了编译内核模块所需的文件,但不包括完整的内核源代码。
- 如果你打算进行 内核源码级的调试,你需要下载和配置 完整的内核源代码。可以通过系统的包管理器(如 yum 或 apt-get)来下载内核开发包(linux-source),或者直接从 kernel.org 下载完整的内核源代码。
配置内核源代码和使用调试工具(如 gdb 或 kgdb)来进行源码级调试。