内核模块不能直接用标准c++编写,因linux内核不支持c++运行时(无new/delete/rtti/异常/stl),必须禁用c++特性、仅调用内核导出的c接口,源文件应使用.c后缀并以gcc编译。

内核模块不能直接用标准 C++ 编写
Linux 内核本身不支持 C++ 运行时(没有 new、delete、RTTI、异常、STL),所有内核模块必须用纯 C 编写,且只能调用内核导出的 C 接口。哪怕你用 g++ 编译,也必须禁用 C++ 特性,并显式链接内核头文件和符号表。强行混用 C++ 代码(比如定义类、调用 std::vector)会导致编译失败或运行时 oops。
实操建议:
- 源文件后缀用
.c,不是.cpp - Makefile 中强制指定
CC = $(CROSS_COMPILE)gcc,禁用-x c++类参数 - 避免任何
#include <string></string>、#include <memory></memory>等用户态头文件 - 字符串操作用
strcpy、strnlen(注意是strnlen不是strlen)、memcmp,而非std::string - 内存分配只用
kmalloc()/kfree(),且必须检查返回值是否为NULL
网络包过滤必须用 netfilter + nf_hook_ops
Linux 内核不提供“注册一个回调函数就能拦截所有包”的简单接口。真实路径是:在协议栈关键点(如 PRE_ROUTING、LOCAL_IN)注册 nf_hook_ops 结构体,挂载自定义钩子函数。这个函数接收 struct sk_buff *,可读取 IP/TCP 头,但不能随意修改 skb 数据区(需用 skb_pull/skb_push 安全调整)。
常见错误现象:
- 忘记在
module_init中调用nf_register_net_hook(&init_net, &my_hook)→ 模块加载成功但完全不生效 - 钩子函数返回
NF_ACCEPT以外的值(如NF_DROP)后,未返回对应常量而是返回 0 或 -1 → 导致内核 panic - 在钩子中调用可能 sleep 的函数(如
printk频繁输出、msleep)→ 触发 “scheduling while atomic” 错误 - 未对
skb->data做协议校验就强转struct iphdr *→ 访问越界(例如包被分片、有 VLAN tag、非 IP 协议)
最小可行钩子示例(IP 层丢弃目标端口 6666 的 TCP 包):
static unsigned int drop_port_hook(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state) {
struct iphdr *iph;
struct tcphdr *tcph;
if (!skb || !skb->data)
return NF_ACCEPT;
iph = ip_hdr(skb);
if (iph->protocol != IPPROTO_TCP)
return NF_ACCEPT;
// 确保 TCP 头完整且可访问
if (skb->len ihl*4 + sizeof(*tcph))
return NF_ACCEPT;
tcph = (struct tcphdr *)((u8 *)iph + iph->ihl*4);
if (ntohs(tcph->dest) == 6666)
return NF_DROP;
return NF_ACCEPT;
}
static struct nf_hook_ops my_hook = {
.hook = drop_port_hook,
.pf = PF_INET,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_FIRST,
};
模块编译依赖内核头文件与 Kbuild 系统
不能用系统默认的 /usr/include,必须指向当前运行内核的 build 目录(通常是 /lib/modules/$(uname -r)/build)。否则会因结构体定义不一致(如 sk_buff 成员偏移变化)导致加载后立即 oops。
关键配置项:
- Makefile 必须包含
KDIR ?= /lib/modules/$(shell uname -r)/build - 必须使用内核提供的
make -C $(KDIR) M=$(PWD) modules方式构建,不能直接调用gcc - 模块源码开头需加
#include <linux></linux>、#include <linux></linux>、#include <linux></linux>、#include <linux></linux>、#include <linux></linux> - 必须导出许可证:在模块末尾加
MODULE_LICENSE("GPL");,否则insmod拒绝加载("module license taints kernel")
调试内核模块比用户程序困难得多
没有 printf,只有 printk();没有 gdb 单步,只有 kgdb 或 crash 工具;一次错误可能导致系统冻结。最实用的调试组合是:dmesg -w 实时看日志 + cat /proc/kallsyms | grep your_func 查符号地址 + rmmod 后再 insmod 快速迭代。
容易被忽略的细节:
-
printk()默认日志级别是KERN_WARNING,若想确保显示,加前缀如printk(KERN_INFO "my msg\n") - 模块卸载时必须调用
nf_unregister_net_hook(&init_net, &my_hook),否则再次加载会报-EBUSY - 钩子函数里不要打印整个
skb,而应只打关键字段(如iph->daddr,tcph->dest),否则日志刷屏且影响性能 - 测试拦截逻辑时,优先用
nc -v 127.0.0.1 6666本地环回,避免触发防火墙或路由规则干扰
真正的难点不在写几行 hook,而在于理解 skb 生命周期、netfilter 钩子触发顺序、以及如何在无锁、无睡眠、无内存保护的环境下做安全判断——这些稍有不慎,模块就不是“不工作”,而是让整个系统失去响应。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











