内核编程五:从hello world开始

环境准备

  • 任意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)来进行源码级调试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值