Linux 内核与用户空间 内存管理详解(堆与栈篇)

此文是 ChatGPT 生成的文章,帮助理解内核与用户空间的内存管理。

在Linux系统中,每个进程在运行时涉及两大"世界":

  • 用户空间(User Space)
  • 内核空间(Kernel Space)

它们在内存管理上各自独立,但底层都共享同一块物理内存。本文将系统梳理用户空间与内核空间的堆与栈管理,让你对 malloc/free、kmalloc/vmalloc/kfree 的使用、区别及原理有更清晰认知。

一、用户空间与内核空间的基本区别

特性 用户空间 内核空间
虚拟地址空间 每个进程独立 所有线程共享内核虚拟地址空间
权限 用户态(不可直接访问内核) 内核态(可访问物理内存、设备寄存器)
栈 用户栈,每线程一份 内核栈,每线程一份且空间较小
堆 用户堆(动态分配内存) 内核堆(动态内存,使用 kmalloc/vmalloc)

核心理解 :用户空间和内核空间不是两块独立物理内存,而是两套虚拟地址映射,底层最终都映射到同一块物理 RAM。

二、堆 管理对比

1. 用户空间堆

用户程序的动态内存管理由 C语言的标准库函数(如 glibc) 提供接口:

复制代码
int *p = malloc(100);
free(p);
  • malloc():向用户堆申请内存
  • free():释放用户堆内存
  • 背后机制:

① 用户态内存管理器先尝试使用已有空闲块

② 空间不足时通过 系统调用 向内核申请更多内存(如 brk/sbrk 或 mmap)

  • 返回的是用户空间虚拟地址,按需分页,物理页可能未立即分配

说明:

  • 用户堆由 进程的虚拟地址空间 管理
  • malloc() 的返回值是进程可访问的地址
  • free() 只是标记空闲,不一定马上释放物理内存

2. 内核空间堆

内核态代码(如驱动程序)不能使用 malloc/free,而使用:

  • kmalloc(size, flags) → kfree(ptr)
    • 分配虚拟连续、物理通常连续的内核小块内存
    • 适合 DMA 或小块频繁分配场景
  • vmalloc(size) → vfree(ptr)
    • 分配虚拟连续、物理不连续的大块内存
    • 适合大块缓冲区,但性能略低于 kmalloc

注意:kmalloc 对应 kfree,vmalloc 对应 vfree,不能混用。

三、栈 管理对比

1. 用户栈

  • 每个线程都有自己的用户栈,用于:
    • 保存函数参数
    • 局部变量
    • 返回地址
  • 自动分配和释放机制 :
    • 栈空间由内核在创建线程时分配
    • 当线程或进程结束时,内核自动回收
    • 也就是说,你不需要也不能手动释放用户栈

补充说明:

  • 用户栈通常是连续的虚拟地址空间
  • 栈大小一般几 MB(可通过 ulimit -s 查看)
  • 函数调用深度过大或局部变量过多可能导致栈溢出(stack overflow)

2. 内核栈

  • 每个线程进入内核态时使用内核栈
  • 空间固定且较小(通常 8KB 或 16KB)
  • 用于:
    • 内核函数调用
    • 保存现场
    • 临时局部变量
  • 注意事项 :
    • 内核栈不可动态扩展
    • 不要在内核栈里放大数组或大对象
    • 大缓冲区应使用 kmalloc 或 vmalloc 分配

四、一个完整执行流程示意

以用户程序调用 malloc() 为例:

复制代码
用户程序运行在用户态
    |
    |  malloc(100)
    v
用户态内存管理器先处理
    |
    | 空闲空间不足 → 发系统调用 brk/mmap
    v
CPU 切换到内核态
    |
    | 使用内核栈执行内核代码
    v
内核更新进程虚拟内存映射
    |
    | 返回用户态
    v
malloc 返回一个用户空间虚拟地址

说明:

  • CPU 切换到内核态时,用户栈和内核栈是独立的
  • 系统调用期间使用的是内核栈
  • 返回用户态后继续使用用户栈

五、用户堆与用户栈的回收

  • 用户堆 :由 free() 或内存管理器回收(可能延迟真正释放物理内存)
  • 用户栈 :
    • 线程或进程退出时,内核自动回收
    • 用户态程序无需手动释放
    • 栈空间是线程生命周期绑定的
  • 内核栈 :
    • 线程退出或中断/系统调用结束时自动恢复
    • 空间有限,局部变量要谨慎

六、关键理解点

  1. 用户空间和内核空间各自管理堆和栈,但最终都映射到同一块物理 RAM。
  2. 不能混用内核和用户空间接口:
    • 用户态:malloc/free
    • 内核态:kmalloc/kfree 或 vmalloc/vfree
  3. malloc/free 并非直接分配物理内存,通常先在用户堆管理器中分配,必要时向内核申请。
  4. 栈空间自动管理:
    • 用户栈随线程/进程生命周期自动分配和释放
    • 内核栈随线程进入/退出内核态自动管理
  5. 内核栈空间有限,局部变量尽量小,避免大数组占用。
  6. kmalloc/vmalloc 区别:
    • kmalloc:小块,物理连续
    • vmalloc:大块,虚拟连续,物理不连续

七、最简对照表

区域 申请接口 释放接口 特点
用户堆 malloc free 用户态库函数,虚拟地址管理
内核小块堆 kmalloc kfree 虚拟连续,物理通常连续
内核大块堆 vmalloc vfree 虚拟连续,物理不连续
用户栈 自动分配/释放 自动回收 每线程一份,函数调用控制,程序结束回收
内核栈 自动分配/释放 自动回收 每线程一份,空间有限,函数调用控制

总结一句话

用户程序用 malloc/free 管用户堆;内核代码用 kmalloc/kfree 或 vmalloc/vfree 管内核堆;用户栈和内核栈各自独立,由内核自动管理,但都最终映射到物理内存。

用一个最常见的例子串起来:程序启动 → malloc() → 进入内核 → 返回用户态。

1. 程序刚启动时

比如 这个程序:

复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *p = malloc(100);
    printf("%p\n", p);
    free(p);
    return 0;
}

程序运行起来后,操作系统会给它分配一块用户空间虚拟地址空间。这块空间里大致有:

  • 代码段
  • 数据段
  • 堆
  • 栈

此时:

  • main() 运行在用户态
  • p 这个指针变量本身,通常在用户栈里
  • malloc(100) 申请到的内存,在用户堆里

2. malloc(100) 不是直接找物理内存

很多人会误以为:

malloc() 直接向内核申请一段真实内存

其实不是这么简单。

malloc() 的工作方式大致是:

  1. 先看用户态的内存管理器手里有没有"现成可用的小块"
  2. 如果有,直接分给你
  3. 如果没有,再通过系统调用向内核要更多空间
    • 常见方式是 brk/sbrk
    • 或 mmap

所以:

  • malloc() 是用户态库函数
  • 它自己先管理一部分内存
  • 真正需要更多内存时,才借助内核

你可以把它理解成:

malloc() 像一个"中间房东",先在自己手里分配;不够了,再找"总房东"内核要。

3. 为什么调用系统调用时会进内核?

假设 malloc() 发现内存不够了,它可能会发起系统调用。

这时 CPU 会:

  • 从用户态 切到内核态
  • 保存当前用户程序的执行现场
  • 切换到当前线程对应的内核栈
  • 开始执行内核代码

注意这里非常关键:

用户栈和内核栈不是同一个

  • 用户态执行时,用的是用户栈
  • 进入内核后,用的是内核栈

也就是说,同一个线程在不同状态下,可能会用到两套栈。

4. 内核收到请求后怎么处理?

内核收到 brk 或 mmap 之类请求后,不一定马上就给你"真实的物理内存"。

它通常会做的是:

  • 修改进程的虚拟内存区域管理
  • 建立或更新页表映射
  • 标记哪些虚拟地址以后可用

很多时候是先给你虚拟地址空间 ,真正的物理页可能在你第一次访问那块内存时才分配,这叫按需分页。

所以你会看到:

复制代码
int *p = malloc(100);

这里的 p 是一个用户空间虚拟地址,不是"物理地址"。

5. free() 时发生什么?

你调用:

复制代码
free(p);

其实是把这块内存还给用户态的内存管理器。

它可能会:

  • 把这块小内存放回空闲链表
  • 合并相邻空闲块
  • 过一会儿才真正把一部分空间还给内核

所以 free() 也不一定立刻让物理内存消失,只是"标记为可复用"。

相关推荐
律宏阔1 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔1 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
Zwawa1 小时前
把按键和 LED 状态显示出来:第一次看见程序到底在干什么
单片机·嵌入式
穷人小水滴1 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
haliu1 小时前
【FHE】(十一):密文链协议——两条不变式,和 112 与 2100 的分工
嵌入式·fhe·推理引擎·边缘推理·同态加密推理
haliu1 小时前
【FHE】(十):密文里的 attention——没有 max,softmax 怎么办
人工智能·嵌入式·c·fhe·推理引擎·c11·边缘推理·同态加密推理
乱码三千1 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei371 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei371 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei371 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化