Linux 系统中的页面大小(Page Size)默认是 4KB。这个数字看起来平淡无奇,但它背后是硬件约束、历史兼容、性能权衡等多重因素的综合结果。为什么不是 1KB?为什么不是 64KB?4KB 这个”恰好”的数字,藏着怎样的设计考量?
一、页面大小的基础概念
1.1 什么是页
虚拟内存被分割成固定大小的块,称为页面(Page)。物理内存同样被分割成等大小的页框(Page Frame)。操作系统通过页表将虚拟页面映射到物理页框:
虚拟地址空间: 0x00000000 - 0xFFFFFFFF (4GB)页面大小: 4KB = 4096 字节页面数量: 4GB / 4KB = 1,048,576 页
虚拟地址结构(以 32 位系统为例):┌──────────────┬───────────────┐│ 页号 (20位) │ 页内偏移 (12位) │└──────────────┴───────────────┘ 2^20 = 1M 页 2^12 = 4KB 偏移4KB 的页大小意味着虚拟地址的低 12 位是页内偏移,高位是页号。这个划分直接影响页表的大小和 TLB 的效率。
1.2 页表的工作方式
页表是虚拟内存的核心数据结构。每个页表项(PTE)通常占 4 字节(32 位系统)或 8 字节(64 位系统),存储物理页号和访问权限等信息。
1.3 TLB:页表的缓存
TLB(Translation Lookaside Buffer)是 CPU 内部的页表缓存,缓存最近使用的虚拟地址到物理地址的映射。每次内存访问,CPU 先查 TLB,命中则直接得到物理地址;未命中则需要遍历页表(Page Table Walk),代价高昂。
TLB 的容量有限。以 Intel Core i7 为例:
| TLB 层级 | 容量 | 覆盖范围(4KB 页) |
|---|---|---|
| L1 ITLB | 64 项 | 256 KB |
| L1 DTLB | 64 项 | 256 KB |
| L2 STLB | 1536 项 | 6 MB |
页大小直接决定 TLB 能覆盖的内存范围。4KB 页下,1536 项 TLB 只能覆盖 6MB。如果程序的工作集大于 6MB,就会出现 TLB miss,性能下降。
二、4KB 的历史渊源
2.1 早期计算机的约束
| 年份 | 典型系统 | 内存大小 | 页大小选择 | 原因 |
|---|---|---|---|---|
| 1964 | IBM 360 | 1-2 MB | 4KB | 磁芯存储器的管理粒度 |
| 1977 | VAX-11/780 | 4-16 MB | 512B | 小内存下的精细管理 |
| 1985 | Intel 386 | 4-16 MB | 4KB | 兼容 IBM 体系 |
| 1993 | Pentium | 16-64 MB | 4KB | 沿用 386 页大小 |
IBM System/360 是最早采用 4KB 页大小的主流系统之一。这个选择并非偶然:1960 年代的磁芯存储器以 4096 字节为一个管理单位,4KB 正好对应这一硬件粒度。
Intel 386 处理器(1985 年)引入分页机制时选择了 4KB,这个决定直接影响了后续所有 x86 处理器,进而影响了 Linux 的设计。
2.2 DEC VAX 的反面教材
VAX-11/780 使用 512 字节的页大小。在 4MB 内存的机器上,512 字节页需要的页表项数是 4KB 页的 8 倍。页表本身占用的内存比例过高,成为系统的沉重负担。
VAX 后来在 VAX-11/785 中将页大小提升,但 512 字节的设计已经被锁定在指令集架构中,无法更改。这是一个反面教训:页大小一旦确定,就极难改变。
2.3 为什么不是 2 的其他幂次?
| 页大小 | 优点 | 缺点 | 为什么没选 |
|---|---|---|---|
| 512B | 内部碎片极少 | 页表巨大,TLB 覆盖极小 | VAX 的失败经验 |
| 1KB | 碎片少 | 页表偏大 | 不如 4KB 平衡 |
| 2KB | 碎片较少 | 页表中等 | 硬件不支持 |
| 4KB | 综合最优 | 大内存场景 TLB 不足 | 被广泛采用 |
| 8KB | 页表减半 | 碎片增加 | 部分架构使用 |
| 16KB | 页表更小 | 碎片明显 | arm64 可选 |
| 64KB | TLB 覆盖大 | 碎片严重 | arm64 可选/大型机 |
4KB 恰好处于”页表不太大、碎片不太严重”的甜蜜点。
三、页面大小的权衡分析
3.1 内部碎片
内部碎片(Internal Fragmentation)是指页面中未被使用的空间。假设进程需要 1 字节,操作系统也必须分配一个完整页面。
假设进程分配了 N 个页面,每个页面平均浪费一半:- 4KB 页:平均浪费 2KB/页- 64KB 页:平均浪费 32KB/页
对于大量小对象(如文件元数据、网络连接缓冲区),浪费更严重。定量分析:假设一个系统有 1000 个进程,每个进程平均有 20 个部分填充的页面:
| 页大小 | 每页浪费 | 总浪费 |
|---|---|---|
| 4KB | ~2KB | ~40MB |
| 64KB | ~32KB | ~640MB |
在内存紧张的嵌入式系统上,640MB 的浪费是不可接受的。
3.2 页表大小
页表大小取决于虚拟地址空间和页大小的比值:
| 页大小 | 页表项数(4GB 空间) | 页表大小(每项 8B) | 占 4GB 比例 |
|---|---|---|---|
| 4KB | 1,048,576 | 8 MB | 0.2% |
| 16KB | 262,144 | 2 MB | 0.05% |
| 64KB | 65,536 | 512 KB | 0.01% |
| 2MB | 2,048 | 16 KB | 0.0004% |
4KB 页的页表占 8MB,对现代系统可以接受。但在 64 位系统上,虚拟地址空间远大于 4GB(通常是 48 位或 57 位),页表问题变得严峻:
# 64 位 Linux 的虚拟地址空间# 典型配置:48 位虚拟地址 = 256 TB# 4KB 页:256TB / 4KB = 64G 页表项# 如果全部映射,页表本身需要 ~512GB!
# 这就是为什么 x86_64 使用四级页表# 每级只映射实际使用的部分,避免分配完整页表3.3 TLB 覆盖范围
TLB 是页面大小选择中最关键的硬件因素。TLB 容量有限,页大小直接决定 TLB 能覆盖多少内存:
| 页大小 | L2 STLB 1536 项覆盖 | 对数据库的影响 |
|---|---|---|
| 4KB | 6 MB | 频繁 TLB miss |
| 2MB | 3 GB | 大幅减少 miss |
| 1GB | 1.5 TB | 几乎不 miss |
这就是为什么数据库(如 Oracle、PostgreSQL)强烈推荐使用 HugePages。
3.4 I/O 效率
页面大小也影响磁盘 I/O 的效率:
# 磁盘读取一页的开销 = 寻道时间 + 旋转延迟 + 传输时间
# 假设 HDD 参数:# 寻道时间:5ms# 旋转延迟:4ms (7200 RPM)# 传输速率:100MB/s
# 读取 4KB:5 + 4 + 0.04 = 9.04ms(传输只占 0.4%)# 读取 64KB:5 + 4 + 0.64 = 9.64ms(传输只占 6.6%)# 读取 1MB:5 + 4 + 10 = 19ms(传输占 52%)
# 页面越大,每次 I/O 传输的数据越多,利用率越高# 但也意味着读入更多不需要的数据(预读浪费)4KB 的页大小在机械硬盘时代是一个合理的 I/O 粒度,但在 SSD 时代显得偏小。SSD 的 4K 随机读性能远好于 HDD,但连续读仍然更高效。
四、4KB 与磁盘块大小的关系
4.1 磁盘扇区的历史
磁盘的最小读写单位是扇区(Sector),历史上是 512 字节。现代磁盘(包括 SSD)已经开始使用 4K 物理扇区(Advanced Format):
| 时期 | 扇区大小 | 原因 |
|---|---|---|
| 1980-2010 | 512 字节 | 兼容 DOS 时代 |
| 2010-至今 | 4K 物理扇区 | 提高纠错效率,减少开销 |
当页面大小(4KB)等于磁盘物理扇区大小(4KB)时,一个页面的读写恰好对应一个磁盘扇区,避免了读写放大(Read-Modify-Write)问题。这并非巧合,而是硬件和软件协同演进的结果。
4.2 文件系统块大小
# 查看 ext4 文件系统的块大小sudo tune2fs -l /dev/sda1 | grep "Block size"# 输出:Block size: 4096
# ext4 默认块大小也是 4KB# 这与页大小对齐,使得一个文件系统块恰好对应一个内存页# 大大简化了页面缓存的实现文件系统块大小与页大小对齐是一个重要的设计原则。如果两者不匹配,一个页面可能跨越多个文件系统块,或者一个文件系统块被拆分到多个页面,增加了实现复杂度。
五、x86_64 的多级页表与 4KB
5.1 四级页表结构
x86_64 使用四级页表(48 位虚拟地址):
虚拟地址(48 位有效):┌─────┬─────┬─────┬─────┬──────────┐│ PML4│ PDPT│ PD │ PT │ Offset ││9位 │9位 │9位 │9位 │12位 │└─────┴─────┴─────┴─────┴──────────┘ 每级 512 项 页内偏移
4KB 页 + 四级页表:- 每个页表占 4KB(512 项 × 8 字节/项)- 每个页表恰好一页,管理方便注意:每个页表恰好占 4KB,包含 512 个 8 字节的页表项。这不是巧合——页大小决定了页表的大小,页表又恰好占一页。这种自相似的设计简化了内存管理。
5.2 5 级页表(LA57)
2017 年,Intel 引入了 5 级页表(Linear Address 57,LA57),将虚拟地址从 48 位扩展到 57 位:
# 检查是否支持 5 级页表# Linux 4.14+ 支持cat /proc/cpuinfo | grep la57
# 5 级页表虚拟地址空间:# 48 位:256 TB# 57 位:128 PB# 对大内存服务器(TB 级)非常有意义5 级页表增加了一级,但每一级页表仍然占 4KB。4KB 的基本粒度没有改变。
5.3 用代码查看当前页大小
// C 程序获取页大小#include <unistd.h>#include <stdio.h>
int main() { long page_size = sysconf(_SC_PAGESIZE); printf("Page size: %ld bytes\n", page_size); // 输出:Page size: 4096 bytes return 0;}# Shell 中查看页大小getconf PAGESIZE# 输出:4096
# 或者cat /proc/sys/vm/page_size # 某些系统# 或者pagesize六、现代系统的大页支持
6.1 为什么需要大页
4KB 页在 64 位系统上的主要问题是 TLB 覆盖不足。以一个使用 10GB 内存区域的数据库为例:
4KB 页:- 需要的 TLB 项:10GB / 4KB = 2,621,440 项- L2 STLB 容量:~1536 项- TLB 命中率:极低
2MB 大页:- 需要的 TLB 项:10GB / 2MB = 5120 项- L2 STLB 容量:~1536 项- TLB 命中率:仍然不足,但好很多
1GB 大页:- 需要的 TLB 项:10GB / 1GB = 10 项- L2 STLB 容量:~1536 项- TLB 命中率:接近 100%6.2 静态 HugePages
# 查看 HugePages 配置cat /proc/meminfo | grep -i huge# HugePages_Total: 0# HugePages_Free: 0# Hugepagesize: 2048 kB
# 预分配 HugePages(需要 root)echo 1024 > /proc/sys/vm/nr_hugepages# 分配 1024 个 2MB 大页 = 2GB 大页内存
# 在程序中使用 HugePages# 方法 1:shmget with SHM_HUGETLB# 方法 2:mmap with MAP_HUGETLB# 方法 3:mount hugetlbfs 文件系统静态 HugePages 的缺点是需要预先分配,不能被换出,缺乏灵活性。
6.3 透明大页(Transparent HugePages,THP)
# 查看透明大页状态cat /sys/kernel/mm/transparent_hugepage/enabled# [always] madvise never
# always:内核自动尝试使用大页# madvise:只在应用通过 madvise(MADV_HUGEPAGE) 请求时使用# never:禁用透明大页
# 查看 THP 使用情况cat /sys/kernel/mm/transparent_hugepage/defragTHP 的优势是无需修改应用程序,内核在后台自动将 4KB 页合并为 2MB 大页。但它也有问题:
- 内存碎片化:长时间运行后,物理内存碎片化,无法找到连续的 2MB 区域
- 延迟尖峰:khugepaged 线程在后台合并页面时可能引起延迟
- 调试困难:大页的分配和换出行为不如 4KB 页可预测
6.4 数据库与大页
# PostgreSQL 推荐配置 HugePages# postgresql.conf:# huge_pages = try # 尝试使用 HugePages
# 计算需要的 HugePages 数量# shared_buffers = 4GB# 4GB / 2MB = 2048 个 HugePages
# 需要预留一些额外页给其他共享内存echo 2300 > /proc/sys/vm/nr_hugepages
# Redis 使用 HugePages 的注意事项# Redis 官方建议:如果不使用 THP,可以直接使用静态 HugePages# 但 THP 可能导致 fork 延迟(Copy-on-Write 时大页的复制代价高)七、不同架构的页大小选择
7.1 主流架构对比
| 架构 | 默认页大小 | 支持的页大小 | 大页支持 |
|---|---|---|---|
| x86 | 4KB | 4KB | 4MB |
| x86_64 | 4KB | 4KB | 2MB, 1GB |
| arm64 | 4KB | 4KB, 16KB, 64KB | 对应大页 |
| PowerPC | 64KB | 4KB, 64KB, 16MB | 16MB, 16GB |
| IA-64 | 16KB | 4KB-256KB | 多种大页 |
| RISC-V | 4KB | 4KB | 2MB, 1GB |
7.2 arm64 的 16KB 页选项
Apple Silicon(M1/M2/M3)使用 16KB 页大小,而非 4KB:
# macOS (Apple Silicon) 上的页大小pagesize# 输出:16384(16KB)
# Linux arm64 可以在编译时选择页大小# CONFIG_ARM64_PAGE_SHIFT=12 → 4KB# CONFIG_ARM64_PAGE_SHIFT=14 → 16KB# CONFIG_ARM64_PAGE_SHIFT=16 → 64KB
# 16KB 页的优势:# - TLB 覆盖范围扩大 4 倍# - 页表项数减少 4 倍# - 更好的压缩率(更多对象可放入同一页)
# 16KB 页的劣势:# - 内部碎片增加# - 与 x86 生态的默认配置不兼容Apple 选择 16KB 页是为了在保持内存效率的同时提升 TLB 覆盖范围,这在内存带宽敏感的 SoC 上尤为重要。
7.3 为什么 Linux 不切换默认页大小
Linux 保持 4KB 默认页大小,原因并非技术惯性那么简单:
- ABI 兼容性:很多用户态程序假设页大小是 4KB(如
sysconf(_SC_PAGESIZE)的返回值),改变会导致兼容性问题 - 二进制兼容:在 64KB 页的系统上,4KB 对齐的代码可能无法正确运行
- 跨架构一致性:Linux 支持数十种架构,统一默认页大小简化了开发和测试
- 渐进式改进:通过 THP 和 HugePages,可以在不改变默认页大小的情况下获得大页的好处
八、页面大小对实际应用的影响
8.1 内存密集型应用
| 应用类型 | 推荐页大小 | 原因 |
|---|---|---|
| 数据库 | 2MB/1GB | 大工作集,TLB miss 代价高 |
| 虚拟机 | 2MB | Guest 物理地址映射开销大 |
| 科学计算 | 2MB | 大数组遍历,TLB 压力大 |
| Web 服务器 | 4KB | 小对象多,碎片化严重 |
| 嵌入式系统 | 4KB | 内存紧张,碎片浪费不可接受 |
8.2 Copy-on-Write 的页大小影响
Linux 的 fork() 使用 Copy-on-Write(COW)机制,子进程共享父进程的页面,只在写入时复制。
4KB 页 COW:- 修改 1 字节只需要复制 4KB 页面- Redis fork 子进程做 RDB 快照时,内存开销小
2MB 大页 COW:- 修改 1 字节需要复制 2MB 页面- 如果大量页面被修改,内存暴增- 这就是 Redis 在使用 THP 时延迟飙升的原因# Redis 官方建议禁用 THPecho never > /sys/kernel/mm/transparent_hugepage/enabled
# 原因:Redis 使用 fork + COW 做持久化# THP 导致 COW 时复制 2MB 而非 4KB# 在写入密集场景下,内存使用量可能翻倍8.3 mmap 和页对齐
// mmap 的偏移必须是页大小的整数倍#include <sys/mman.h>#include <fcntl.h>
int fd = open("large_file.dat", O_RDONLY);
// 正确:偏移 4096 = 1 × 4KBchar *p1 = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 4096);
// 错误:偏移 1000 不是页对齐的// char *p2 = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 1000);// 报错:EINVAL页大小影响所有涉及内存映射的接口。在 64KB 页的系统上,mmap 的偏移必须是 64KB 的倍数,这可能影响文件格式的兼容性。
九、总结
9.1 为什么 Linux 默认 4KB?
| 原因 | 说明 |
|---|---|
| 历史兼容 | 从 IBM 360 到 x86,4KB 被广泛验证 |
| 平衡设计 | 内部碎片和页表大小的最佳折中点 |
| 硬件支持 | 所有主流 CPU 架构都支持 4KB 页 |
| 磁盘对齐 | 4K 物理扇区与 4KB 页大小对齐 |
| 自相似性 | 页表本身恰好占一页,简化内存管理 |
| 渐进优化 | 大页作为 4KB 的补充,而非替代 |
9.2 页面大小选择的权衡
| 因素 | 大页面 | 小页面 |
|---|---|---|
| 内部碎片 | 多(浪费空间) | 少(节省空间) |
| 页表大小 | 小(节省内存) | 大(占用内存) |
| TLB 命中率 | 高(覆盖范围大) | 低(覆盖范围小) |
| 内存利用率 | 低(小对象浪费多) | 高(精细分配) |
| COW 开销 | 高(复制代价大) | 低(复制代价小) |
| I/O 效率 | 高(单次传输多) | 低(需多次传输) |
核心观点:4KB 是经过历史验证的平衡选择,满足大多数场景的需求。它既不是最优解(不存在最优解),也不是偶然选择,而是硬件演进、软件兼容和工程权衡共同塑造的结果。对于特殊场景,Linux 提供了 HugePages 和 THP 作为补充。
参考资料
- Linux Memory Management Documentation — Linux 内核虚拟内存文档
- Intel 64 and IA-32 Architectures Software Developer’s Manual — Intel 分页机制
- Transparent Hugepage Support — Linux THP 文档
- ARM Architecture Reference Manual — ARMv8 页表配置
- RFC: arm64 16K pages — arm64 16KB 页大小讨论
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






