内核代码运行在 CPU 的最高特权级(Ring 0),没有用户态的运行时库和内存保护机制。这意味着大量用户态编程习惯在内核中不适用。
| 用户态函数 | 内核替代 | 头文件 |
|---|---|---|
printf() |
printk() |
<linux/kernel.h> |
malloc() |
kmalloc() / vmalloc() |
<linux/slab.h> / <linux/vmalloc.h> |
free() |
kfree() / vfree() |
同上 |
strcpy() |
strscpy() |
<linux/string.h> |
strlen() |
strlen() |
<linux/string.h> |
memcpy() |
memcpy() |
<linux/string.h> |
memset() |
memset() |
<linux/string.h> |
assert() |
BUG_ON() / WARN_ON() |
<linux/bug.h> |
#include <linux/slab.h>
// 分配物理连续内存(适用于 DMA)
void *kmalloc(size_t size, gfp_t flags);
void *kzalloc(size_t size, gfp_t flags); // 分配并清零
void kfree(const void *ptr);
// GFP flags
GFP_KERNEL // 可睡眠,优先级高(最常见的标志)
GFP_ATOMIC // 不可睡眠,用于中断上下文
GFP_NOWAIT // 不等待,分配失败立即返回 NULL
#include <linux/vmalloc.h>
// 分配虚拟连续、物理不连续的内存(大块内存)
void *vmalloc(unsigned long size);
void vfree(const void *addr);
| 分配函数 | 物理连续 | 可睡眠 | 适用场景 |
|---|---|---|---|
kmalloc |
是 | 是(GFP_KERNEL) | 小块内存、DMA 缓冲区 |
kzalloc |
是 | 是 | 同 kmalloc,分配后清零 |
vmalloc |
否 | 是 | 大块内存(如模块加载) |
#include <linux/uaccess.h>
// 用户空间 → 内核空间
unsigned long copy_from_user(void *dst, const void __user *src, unsigned long size);
// 内核空间 → 用户空间
unsigned long copy_to_user(void __user *dst, const void *src, unsigned long size);
// 返回值:0 成功,非0 表示未拷贝的字节数
// 内核内部会检查地址合法性,非法地址会触发 oops
内核编码规范鼓励用 goto 进行错误处理,优点:
避免重复释放代码
int my_function(void)
{
int ret;
struct resource *res;
res = kmalloc(sizeof(*res), GFP_KERNEL);
if (!res)
return -ENOMEM;
ret = some_init(res);
if (ret)
goto err_free;
ret = another_init(res);
if (ret)
goto err_first;
return 0;
err_first:
some_cleanup(res);
err_free:
kfree(res);
return ret;
}
// kernel_constraints.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/string.h>
#include <linux/uaccess.h>
static int __init constraints_init(void)
{
/* 1. kmalloc 示例:物理连续内存 */
char *kbuf;
kbuf = kmalloc(128, GFP_KERNEL);
if (!kbuf) {
printk(KERN_ERR "kmalloc failed\n");
return -ENOMEM;
}
strscpy(kbuf, "Hello from kmalloc", 128);
printk(KERN_INFO "kmalloc buffer: %s\n", kbuf);
kfree(kbuf);
/* 2. kzalloc 示例:分配并清零 */
int *arr;
arr = kzalloc(sizeof(int) * 10, GFP_KERNEL);
if (!arr)
return -ENOMEM;
printk(KERN_INFO "kzalloc arr[0]=%d (should be 0)\n", arr[0]);
kfree(arr);
/* 3. vmalloc 示例:虚拟连续大块内存 */
char *vbuf;
vbuf = vmalloc(1024 * 1024); /* 1MB */
if (!vbuf)
return -ENOMEM;
memset(vbuf, 'A', 1024);
printk(KERN_INFO "vmalloc buffer first byte: %c\n", vbuf[0]);
vfree(vbuf);
/* 4. 内核中没有 printf,用 printk */
printk(KERN_INFO "Kernel constraints demo loaded\n");
return 0;
}
static void __exit constraints_exit(void)
{
printk(KERN_INFO "Kernel constraints demo unloaded\n");
}
module_init(constraints_init);
module_exit(constraints_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example");
MODULE_DESCRIPTION("Demo kernel constraints");
obj-m := kernel_constraints.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 kernel_constraints.ko
dmesg | tail
# [xxxxx] kmalloc buffer: Hello from kmalloc
# [xxxxx] kzalloc arr[0]=0 (should be 0)
# [xxxxx] vmalloc buffer first byte: A
# [xxxxx] Kernel constraints demo loaded
sudo rmmod kernel_constraints
make clean
char buf[1024*1024]),会直接栈溢出导致内核崩溃kmalloc 最大分配通常为 4MB(取决于 SLAB 配置),大块内存应使用 vmallocREAD_ONCE、WRITE_ONCE、smp_rmb),大多数情况下不需要手动加 volatileQ1:为什么内核不能用 malloc 和 free?
A:内核运行在 Ring 0 特权级,不链接用户态 libc。malloc/free 依赖 brk/mmap 等系统调用,而系统调用本身是用户态进入内核态的接口,内核中不能调用自身。内核使用 kmalloc/kfree(物理连续)和 vmalloc/vfree(虚拟连续)替代。
Q2:kmalloc 和 vmalloc 的区别是什么?什么时候用哪个?
A:kmalloc 分配物理连续内存,速度快但可能因内存碎片失败,适合小块内存和 DMA 缓冲区。vmalloc 分配虚拟连续、物理不连续的内存,可以分配大块但有 TLB 抖动开销,适合大块内存(如模块加载)。
Q3:为什么内核中要用 copy_from_user / copy_to_user 而不是直接 memcpy?
A:用户空间指针可能指向非法地址(NULL、越界、内核地址),直接解引用会导致内核 oops。copy_from_user/copy_to_user 内部会检查地址合法性,非法地址时安全失败并返回错误码。
Q4:为什么内核中 goto 错误处理是推荐的做法?
A:内核代码中资源分配失败时需要逐层释放已分配的资源。goto 可以将错误处理集中到函数末尾,减少嵌套层次,避免重复释放代码。这是 Linux 内核编码规范明确推荐的模式。
Q5:内核栈空间有多大?超了会怎样?
A:x86_64 默认 8KB 或 16KB(取决于配置)。栈溢出会覆盖相邻内存页,导致不可预测的内核崩溃(oops 或 panic)。解决方案:使用 kmalloc 分配大缓冲区,避免深层递归,减少局部变量大小。