4. 内核空间与用户空间划分:3:1 / 2:2 虚拟地址空间原理
在 32 位 Linux 系统中,虚拟地址空间总大小为 4 GB(232 字节)。为了隔离操作系统内核与用户进程,Linux 将这一 4 GB 空间划分为内核空间和用户空间两部分。划分比例由内核编译时的配置决定,最常见的是 3:1 和 2:2 两种方案。
4.1 3:1 划分(默认配置)
在大多数 32 位 Linux 发行版中,默认采用 3:1 划分:
- 用户空间:0x00000000 ~ 0xBFFFFFFF(3 GB)
- 内核空间:0xC0000000 ~ 0xFFFFFFFF(1 GB)
用户进程的代码、数据、堆、栈以及动态库都映射在 0~3 GB 范围内。内核代码、全局数据结构、页表、模块等则位于 3~4 GB 的高地址区域。
4.2 2:2 划分(特殊配置)
当内核配置了 CONFIG_VMSPLIT_2G 选项时,采用 2:2 划分:
- 用户空间:0x00000000 ~ 0x7FFFFFFF(2 GB)
- 内核空间:0x80000000 ~ 0xFFFFFFFF(2 GB)
这种划分通常用于需要大量内核内存映射的场景(如大型网络服务器或特定嵌入式系统),但会减少每个用户进程可用的虚拟地址空间。
4.3 划分比例对比
| 特性 | 3:1 划分 | 2:2 划分 |
|---|---|---|
| 用户空间大小 | 3 GB | 2 GB |
| 内核空间大小 | 1 GB | 2 GB |
| 用户空间地址范围 | 0x00000000 ~ 0xBFFFFFFF | 0x00000000 ~ 0x7FFFFFFF |
| 内核空间起始地址 | 0xC0000000 | 0x80000000 |
| 适用场景 | 通用桌面/服务器 | 需要大量内核内存映射 |
| 用户进程最大虚拟内存 | ~3 GB | ~2 GB |
4.4 关键宏定义:PAGE_OFFSET 与 TASK_SIZE
Linux 内核通过两个关键宏来定义用户空间与内核空间的分界线:
4.4.1 PAGE_OFFSET
PAGE_OFFSET 定义了内核空间的起始虚拟地址,即用户空间与内核空间的分界线。在 3:1 划分中,PAGE_OFFSET 等于 0xC0000000;在 2:2 划分中,等于 0x80000000。
/* arch/x86/include/asm/page_32_types.h */
#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
/* 根据 CONFIG_VMSPLIT_2G 等选项定义 __PAGE_OFFSET */
#define __PAGE_OFFSET _AC(CONFIG_PAGE_OFFSET, UL)
/* 默认 3:1 划分时 CONFIG_PAGE_OFFSET = 0xC0000000 */
/* 2:2 划分时 CONFIG_PAGE_OFFSET = 0x80000000 */
4.4.2 TASK_SIZE
TASK_SIZE 定义了用户空间的最大地址,即用户进程可以访问的最高虚拟地址。通常 TASK_SIZE = PAGE_OFFSET,但在某些架构或配置下可能略有不同。
/* arch/x86/include/asm/processor.h */
#define TASK_SIZE_MAX ((1UL << __VIRTUAL_MASK_SHIFT) - PAGE_SIZE)
/* 32 位默认:TASK_SIZE = PAGE_OFFSET */
#define TASK_SIZE (test_thread_flag(TIF_ADDR32) ? \
IA32_PAGE_OFFSET : TASK_SIZE_MAX)
/* 简化理解:TASK_SIZE 通常等于 PAGE_OFFSET */
if (addr < TASK_SIZE) { /* 用户空间地址 */ } else { /* 内核空间地址 */ }。注意不要直接硬编码 0xC0000000,应始终使用 PAGE_OFFSET 或 TASK_SIZE 宏以保证可移植性。
4.5 虚拟地址空间划分 SVG 架构图
下图展示了 32 位 Linux 系统中 3:1 和 2:2 两种划分方案的虚拟地址空间布局:
4.6 64 位系统的地址空间划分
在 64 位架构(如 x86_64)中,虚拟地址空间远大于 4 GB,但 Linux 仍然保持用户空间与内核空间的划分原则。以 x86_64 为例:
- 用户空间:0x0000000000000000 ~ 0x00007FFFFFFFFFFF(128 TB)
- 内核空间:0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF(128 TB)
中间巨大的地址空洞(0x0000800000000000 ~ 0xFFFF7FFFFFFFFFFF)用于捕获非法访问。在 64 位系统中,PAGE_OFFSET 被定义为 0xFFFF800000000000,TASK_SIZE 为 0x00007FFFFFFFFFFF。
PAGE_OFFSET 是 32 位的 0xC0000000。始终使用内核提供的宏定义,并注意地址类型使用 unsigned long 而非 int,否则会导致地址截断错误。
4.7 用户态与内核态切换的硬件支持
用户空间与内核空间的隔离不仅依赖软件约定,更需要 CPU 硬件机制的支持:
| 硬件机制 | 作用 | 与地址空间划分的关系 |
|---|---|---|
| 特权级(Ring 0/3) | 控制指令执行权限 | 用户态只能访问用户空间,内核态可访问全部空间 |
| MMU / 页表 | 虚拟地址到物理地址的转换 | 每个进程有独立页表,内核空间映射在所有页表中 |
| 系统调用(syscall) | 用户态请求内核服务 | 通过 int 0x80 或 syscall 指令切换特权级 |
| TLB 隔离 | 缓存地址转换结果 | 进程切换时刷新 TLB,防止用户态访问内核映射 |
4.8 内核空间映射的物理内存区域
内核空间 1 GB(或 2 GB)并非全部用于映射物理内存,而是划分为多个逻辑区域:
4.9 本章小结
- 3:1 划分是 32 位 Linux 的默认方案,用户空间 3 GB,内核空间 1 GB。
- 2:2 划分通过
CONFIG_VMSPLIT_2G启用,用户空间和内核空间各 2 GB。 - PAGE_OFFSET 定义内核空间起始地址,是用户/内核空间的分界线。
- TASK_SIZE 定义用户进程可访问的最大地址,通常等于
PAGE_OFFSET。 - 64 位系统地址空间更大,但划分原则相同,
PAGE_OFFSET和TASK_SIZE的值相应变化。 - 内核空间内部进一步划分为直接映射区、vmalloc 区、持久映射区、固定映射区和高端内存映射区。