野火嵌入式Linux——驱动开发(内核模块)
内核模块的作用
解决linux内核可扩展性和可维护性相对较差的缺陷
现代内核派系
宏内核:关键功能和服务功能均在内核空间提供
-
运行效率高
-
扩展性较差
微内核:内核空间只提供关键功能,服务功能在用户空间提供
-
运行效率较低
-
安全性、扩展性较高

左侧:宏内核(Monolithic Kernel)架构
这是 Linux、Unix 等传统操作系统的典型设计:
- 内核是一个整体:所有核心功能(VFS、IPC、进程调度、设备驱动等)都运行在内核态,作为一个单一的大程序。
- 应用程序通过系统调用(system call)与内核交互:APP 无法直接操作硬件或核心功能,必须通过统一的系统调用接口进入内核态,由内核完成操作。
- 优点:
- 性能高:模块间直接函数调用,无额外开销。
- 功能集成度高:各子系统深度整合,协作高效。
- 缺点:
- 内核庞大复杂,维护难度高。
- 一处模块崩溃(如驱动)可能导致整个内核宕机。
右侧:微内核(Microkernel)架构
这是 QNX、Minix 等系统采用的设计思路:
- 内核极度精简:只保留最基础的功能(如内存管理、进程调度、基础 IPC),运行在核心态。
- 大部分功能移到用户态:文件系统、设备驱动、应用层 IPC 等都作为独立的用户态服务进程运行。
- 应用程序通过消息传递与服务交互:APP 直接与用户态服务通信,服务之间也通过内核提供的 IPC 机制通信。
- 优点:
- 稳定性高:单个服务崩溃不会影响整个系统。
- 模块化强,易于扩展和移植。
- 缺点:
- 性能开销大:频繁的进程间消息传递和上下文切换。
| 维度 | 宏内核(左侧) | 微内核(右侧) |
|---|---|---|
| 核心思想 | 功能集中,效率优先 | 功能分离,稳定优先 |
| 交互方式 | 系统调用(进入内核态) | 消息传递(用户态服务间) |
| 故障影响 | 内核模块崩溃 → 系统宕机 | 服务崩溃 → 仅该功能不可用 |
| 代表系统 | Linux, FreeBSD, Windows | QNX, Minix, seL4 |
内核模块加载/卸载
-
使用insmod命令加载
-
使用rmmod命令卸载
内核模块入口/出口
-
module_init():加载模块式该函数自动执行,进行初始化操作
-
module_exit():卸载模块时函数自动执行,进行清理操作
内核模块信息声明
-
MODULE_LICENSE():表示模块代码接受的软件许可协议,Linux内核遵循GPL V2开源协议,内核模块与linux内核保持一致即可。
-
MODULE_AUTHOR():描述模块的作者信息
-
MODULE_DESCRIPTION():对模块的简单介绍
-
MODULE_ALIAS():给模块设置一个别名
内核模块实验一
hellomoudle.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_EMERG "[ KERN_EMERG ] Hello Module Init\n");
printk( "[ default ] Hello Module Init\n");
return 0;
}
static void __exit hello_exit(void)
{
printk("[ default ] Hello Module Exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
//MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("hello world module");
MODULE_ALIAS("test_module");
内核模块头文件
- #include <linux/module.h>:包含内核模块信息声明的相关函数
- #include <linux/init.h>:包含了 module_init()和 module_exit()函数的声明
- #include <linux/kernel.h>:包含内核提供的各种函数,如printk
内核模块打印函数
- printf:glibc实现的打印函数,工作于用户空间
- printk:内核模块无法使用glibc库函数,内核自身实现的一个类printf函数,但是需要指定打印等级。
- \#define KERN_EMERG "<0>" 通常是系统崩溃前的信息
- #define KERN_ALERT "<1>" 需要立即处理的消息
- #define KERN_CRIT "<2>" 严重情况
- #define KERN_ERR "<3>" 错误情况
- #define KERN_WARNING "<4>" 有问题的情况
- #define KERN_NOTICE "<5>" 注意信息
- #define KERN_INFO "<6>" 普通消息
- #define KERN_DEBUG "<7>" 调试信息
查看当前系统printk打印等级:`cat /proc/sys/kernel/printk`
- 当前控制台日志级别
- 默认消息日志级别
- 最小的控制台级别
- 默认控制台日志级别
打印内核所有打印信息:dmesg
- 内核log缓冲区大小有限制,缓冲区数据可能被冲掉
简单来说:printk 是内核输出日志的 “入口”,打印等级决定了哪些日志会被内核记录;dmesg 是读取这些已记录日志的 “出口”,我会用通俗的方式拆解它们的关联逻辑。
(日志体系理解)先理解 cat /proc/sys/kernel/printk 的输出含义
执行该命令会得到 4 个数字(示例:4 4 1 7),这 4 个数字是 printk 的核心规则,直接决定哪些日志会被记录:
表格
| 位置 | 含义 | 作用 |
|---|---|---|
| 第 1 个(console_loglevel) | 控制台输出等级 | 只有「消息等级 < 该值」的日志会输出到终端(如串口 / 控制台) |
| 第 2 个(default_message_loglevel) | 默认消息等级 | 若 printk 未指定等级,消息会使用这个默认等级 |
| 第 3 个(minimum_console_loglevel) | 最小控制台等级 | 控制台等级不能被设置得比这个更小 |
| 第 4 个(default_console_loglevel) | 默认控制台等级 | 系统启动时的初始控制台等级 |
关键规则:内核仅会记录「消息等级 < console_loglevel」的日志到缓冲区(比如示例中 console_loglevel=4,那么等级 0-3 的日志会被记录,等级 4 及以上的不会)。
(日志体系理解)printk 等级如何影响 dmesg 能看到的内容
举个具体例子,帮你理解:假设你在内核模块中写了 3 行 printk 代码:
printk(KERN_ERR "这是错误消息(等级3)\n"); // 等级3
printk(KERN_WARNING "这是警告消息(等级4)\n");// 等级4
printk(KERN_INFO "这是普通消息(等级6)\n"); // 等级6
此时系统 printk 默认等级是 4 4 1 7(console_loglevel=4):
- KERN_ERR(3):3 < 4 → 写入内核日志缓冲区 →
dmesg能看到; - KERN_WARNING(4):4 不小于 4 → 不写入缓冲区 →
dmesg看不到; - KERN_INFO(6):6 不小于 4 → 不写入缓冲区 →
dmesg看不到。
makefile
KERNEL_DIR=../../ebf_linux_kernel/build_image/build
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := hellomodule.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean copy
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
copy:
sudo cp *.ko /home/embedfire/workdir
Makefile分析
-
KERNEL_DIR:指向linux内核具体路径
-
export:导出变量给子Makefile使用
-
obj-m := <模块名>.o:定义要生成的模块
-
$(MAKE):Makefile的默认变量,值为make
-
选项”-C”:让make工具跳转到linux内核目录下读取顶层Makefile
-
M=:表示内核模块源码目录
-
$(CURDIR):Makefile默认变量,值为当前目录所在路径
-
make modules:执行Linux顶层Makefile的伪目标,它实现内核模块的源码读取并编译为.ko文件
成功截图

内核模块实验二
模块参数
根据不同应用场合给内核模块传递不同参数,提高内核模块灵活性
-
定义一个常见变量
-
使用module_param宏把传参值赋给变量
module_param(name, type, perm)
-
name:参数名
-
type:参数类型,如int、byte、bool、charp...
-
perm:读写权限
-
不允许设置可执行权限
-
在“/sys/module/模块名/parameters”目录下,会生成该参数对应的文件名
-
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int itype=0;
module_param(itype,int,0);
static bool btype=0;
module_param(btype,bool,0700);
static char ctype=0;
module_param(ctype,byte,0);
static char *stype=0;
module_param(stype,charp,0644);
static int __init param_init(void)
{
printk(KERN_ALERT "param init!\n");
printk(KERN_ALERT "itype=%d\n",itype);
printk(KERN_ALERT "btype=%d\n",btype);
printk(KERN_ALERT "ctype=%d\n",ctype);
printk(KERN_ALERT "stype=%s\n",stype);
return 0;
}
static void __exit param_exit(void)
{
printk(KERN_ALERT "module exit!\n");
}
EXPORT_SYMBOL(itype);
int my_add(int a, int b)
{
return a+b;
}
EXPORT_SYMBOL(my_add);
int my_sub(int a, int b)
{
return a-b;
}
EXPORT_SYMBOL(my_sub);
module_init(param_init);
module_exit(param_exit);
MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("module_param");
MODULE_ALIAS("module_param");

符号共享
内核模块可以共享导出的符号表
-
变量共享
-
函数共享
EXPORT_SYMBOL(sym)
-
sym:变量名或者函数名
查看符号表
cat /proc/kallsyms | grep xxx
Makefile修改
-
有依赖关系的模块,放在一起进行编译
-
clean伪目标增加清除共享文件夹指令
模块手动加载
-
加载时,必须先加载相关依赖模块
-
卸载时,顺序相反
在上面程序中,导出了一个变量和两个函数,导出的这些变量函数我们可以放到其他模块中进行声明和使用
calculation.h
#ifndef __CALCULATION_H__
#define __CALCULATION_H__
extern int itype;
int my_add(int a, int b);
int my_sub(int a, int b);
#endif
calculation.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include "calculation.h"
static int __init calculation_init(void)
{
printk(KERN_ALERT "calculation init!\n");
printk(KERN_ALERT "itype+1 = %d, itype-1 = %d\n", my_add(itype,1), my_sub(itype,1));
return 0;
}
static void __exit calculation_exit(void)
{
printk(KERN_ALERT "calculation exit!\n");
}
module_init(calculation_init);
module_exit(calculation_exit);
MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("calculation module");
MODULE_ALIAS("calculation_module");

由于要使用parametermodule.c导出的参数和变量,所以这里的calculation依赖parametermodule
更多推荐
所有评论(0)