3. 内核内存分配与释放
任务目标
在Linux内核驱动模块中,实现kmalloc、kzalloc、vmalloc三种内存分配方式,演示各自的使用场景与释放方法,并处理内存分配失败的情况。通过内核自带的泄漏检测机制验证模块卸载后无内存泄漏。
环境与依赖
- Linux内核源码树(需已配置好编译环境,支持模块编译)
- 内核版本:建议4.x及以上(支持kmemleak)
- 本案例代码基于内核模块实现,不依赖具体硬件平台
核心实现
以下代码实现了一个完整的内核模块,包含三种分配方式、失败处理逻辑以及通过/proc接口触发的分配与释放操作。模块加载时注册proc入口,卸载时自动释放所有未释放内存并触发泄漏检测。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#define BUF_SIZE 4096
#define KMALLOC_SIZE 1024
#define VALLOC_SIZE (64 * 1024)
static char *kmem_buf;
static char *kzalloc_buf;
static char *vmalloc_buf;
static struct proc_dir_entry *proc_entry;
static char proc_buf[BUF_SIZE];
/* 分配所有内存 */
static int alloc_all(void)
{
/* kmalloc: 物理连续,适合小内存 */
kmem_buf = kmalloc(KMALLOC_SIZE, GFP_KERNEL);
if (!kmem_buf) {
pr_err("kmalloc failed\n");
return -ENOMEM;
}
/* kzalloc: 分配并清零 */
kzalloc_buf = kzalloc(KMALLOC_SIZE, GFP_KERNEL);
if (!kzalloc_buf) {
pr_err("kzalloc failed\n");
kfree(kmem_buf);
kmem_buf = NULL;
return -ENOMEM;
}
/* vmalloc: 虚拟地址连续,适合大内存 */
vmalloc_buf = vmalloc(VALLOC_SIZE);
if (!vmalloc_buf) {
pr_err("vmalloc failed\n");
kfree(kmem_buf);
kfree(kzalloc_buf);
kmem_buf = NULL;
kzalloc_buf = NULL;
return -ENOMEM;
}
/* 写入测试数据 */
memset(kmem_buf, 0xAA, KMALLOC_SIZE);
memset(kzalloc_buf, 0, KMALLOC_SIZE);
memset(vmalloc_buf, 0xBB, VALLOC_SIZE);
return 0;
}
/* 释放所有内存 */
static void free_all(void)
{
if (kmem_buf) {
kfree(kmem_buf);
kmem_buf = NULL;
}
if (kzalloc_buf) {
kfree(kzalloc_buf);
kzalloc_buf = NULL;
}
if (vmalloc_buf) {
vfree(vmalloc_buf);
vmalloc_buf = NULL;
}
}
/* proc读操作:显示当前分配状态 */
static ssize_t proc_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int len;
len = snprintf(proc_buf, BUF_SIZE,
"kmem_buf: %s\nkzalloc_buf: %s\nvmalloc_buf: %s\n",
kmem_buf ? "allocated" : "NULL",
kzalloc_buf ? "allocated" : "NULL",
vmalloc_buf ? "allocated" : "NULL");
return simple_read_from_buffer(buf, count, ppos, proc_buf, len);
}
/* proc写操作:接收命令 */
static ssize_t proc_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
char cmd[16];
if (count >= sizeof(cmd))
return -EINVAL;
if (copy_from_user(cmd, buf, count))
return -EFAULT;
cmd[count] = '\0';
if (strncmp(cmd, "alloc", 5) == 0) {
if (alloc_all() == 0)
pr_info("All memory allocated\n");
else
pr_err("Allocation failed\n");
} else if (strncmp(cmd, "free", 4) == 0) {
free_all();
pr_info("All memory freed\n");
} else {
pr_warn("Unknown command: %s\n", cmd);
}
return count;
}
static const struct proc_ops proc_fops = {
.proc_read = proc_read,
.proc_write = proc_write,
};
static int __init mem_demo_init(void)
{
proc_entry = proc_create("mem_demo", 0666, NULL, &proc_fops);
if (!proc_entry) {
pr_err("Failed to create proc entry\n");
return -ENOMEM;
}
pr_info("mem_demo module loaded\n");
return 0;
}
static void __exit mem_demo_exit(void)
{
/* 确保所有内存已释放 */
free_all();
proc_remove(proc_entry);
pr_info("mem_demo module unloaded\n");
}
module_init(mem_demo_init);
module_exit(mem_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Memory allocation and release demo");
编译与加载
创建Makefile并编译模块:
obj-m := mem_demo.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
执行make编译,然后加载模块:
sudo insmod mem_demo.ko
验证方法
- 功能验证:加载模块后,通过proc接口触发分配与释放:
# 触发分配
echo "alloc" | sudo tee /proc/mem_demo
# 查看分配状态
cat /proc/mem_demo
# 预期输出:三个缓冲区均显示"allocated"
# 触发释放
echo "free" | sudo tee /proc/mem_demo
cat /proc/mem_demo
# 预期输出:三个缓冲区均显示"NULL"
- 内存泄漏检测:卸载模块前开启内核泄漏检测:
# 开启kmemleak
echo scan > /sys/kernel/debug/kmemleak
sudo rmmod mem_demo
# 等待扫描
sleep 5
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
通过条件:kmemleak输出中无与mem_demo相关的未引用内存条目,且dmesg中无分配失败或释放异常信息。
关键说明
- kmalloc分配物理连续内存,适合小规模、频繁访问的数据结构;kzalloc在kmalloc基础上自动清零;vmalloc分配虚拟地址连续内存,适合大块内存但需要页表映射,访问效率略低。
- 分配失败处理采用逐级回滚策略:后续分配失败时释放前面已成功分配的内存,避免部分分配导致泄漏。
- 模块卸载时调用
free_all()确保所有内存释放,这是防止泄漏的最后防线。 - proc接口仅用于教学演示,实际驱动中应通过文件操作或ioctl触发分配逻辑。
常见问题
- 编译报错:确认内核头文件已安装,且Makefile中KDIR路径正确。
- proc文件无法写入:确认模块加载成功且权限正确(0666)。
- kmemleak无输出:需内核配置
CONFIG_DEBUG_KMEMLEAK=y,且debugfs已挂载。