为什么linux kernel默认的页面大小是4K,而不是4M或8M?

本文探讨了操作系统中选择4K作为物理页面大小的原因。通过对比大页面和4K页面的优缺点,分析了4K页面在内存管理、TLB命中率、磁盘I/O效率等方面的优势,同时也指出了大页面可能带来的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

相信很多人在看内核内存管理部分的时候,都有这样一个疑问,为什么物理页面的大小选择4K,而不是大一些或者小一些呢?
这个问题没有固定的答案,仁者见仁智者见智,每个人的关注点不一样。所以这篇文章不是说给出一个固定的答案,更多的只是一篇讨论性的文章。
内核的页面大小首先跟CPU有关,不同的架构支持的页面大小也不相同,但有一个共同点,那就是肯定支持4K的页面大小。为什么处理器在设计的时候会选择4K,而不是其他,这个只有熟悉CPU历史的人才能给出答案,本人才疏学浅,回答不上来,有兴趣的自己找一找。当然处理器的设计者在考虑页面大小的时候,也不是凭空就选择了这个大小,除了历史因素,肯定综合了考虑了大页面、小页面的优缺点,所以这里主要列出这些优缺点,通过这些比较来发现内核为什么将默认的页面大小选择为4K。
现在更多争论的是大页面还是维持现在的页面4K大小,所以小页面的情况我们就不考虑了,通过比较前两者也可以得出小页面的情况。支持大页面的人通常认为大页面有以下好处:
1、减小page table占用的内存。
假设内存一定的话,页面大小越大,管理页面占用的内存也越小。现在内核中每个页面假设是4K的话,这4K不是全都可用,还有一部分用作struct page(大约是64bytes),如果是2.6.32的话,每个页还有一个page_cgroup(32bytes),也就是说内存大小的2.3%(96/4096)会被内核固定使用。如果页面大小是4M的话,这个比率大约是0.0006%。假设内存时64G的话,页面大小是4K,管理页占用的内存为1.472G,而页面大小是4M,管理页面占用的内存为0.393M。所以页面比较大时,节省的内存比较多。
2、提高TLB的命中率
每次访问内存的时候,都要将虚拟地址转换为物理地址,如果每次都访问页表的话,消耗比较大。因此,通常使用TLB来加速这个过程。但是TLB的可以直接转换的地址范围是有限的(具体就是项数乘以页面大小),一旦出现TLB miss,这时就必须去页表中查找。所以,如果是大页面的话,同样TLB项数的情况下,可以跟踪更大的内存。
3、提高磁盘I/O
我们知道在访问磁盘时,最耗时的操作就是查找写入盘区的起始位置,也就是在磁盘盘片上将读写头置于正确的位置上。所以如果是大页面的话,可以减少写入磁盘的次数。比如要写入4M的缓存,页面大小是4M的话,只需写入1次,而页面大小是4K的话,则需要写入4次。
4、提供缓存利用率
如果是大页面的话,可以减少访问伙伴系统的次数。调用伙伴系统的操作队系统的数据和指令高速缓存有相当的影响。内核越浪费这些资源,这些资源对用户空间进程就越不可用。
除了上面提高的好处,肯定还有的好处,就不一一列举了。那既然有这么好处,而且现在内存越来越大,为什么不更多地采用大页面呢?比如4M?
软件开发中,从来不会有非常完美的方案,都是在优缺点之间找到平衡点。同样,大页面有这样那样的好处,缺点也很明显。
1、最大的问题就是内存浪费,而且这个问题非常严重。比如这时要分配的内存是4M+1byte,这时需要两个页面才能满足分配的需要,这个时候浪费的内存为4M-1byte。如果页面是4K的话,浪费的内存数量为4k-1byte。页面太大,可能导致每个页面都只使用了部分空间,剩余的空间就被浪费了。当然对于数据库这样的系统来说,页面越大会越好,但是内核要考虑到通用的情况,而不是专注于特殊的应用场景。再比如,现在系统都是只分配虚拟地址空间,虚拟地址空间只有在真正被访问的时候,才映射物理页面,而且为了减少物理页面的浪费,对不访问的部分,则不作映射。如果页面太大,在映射很小的部分时,分配的内存会越大,浪费也就越大。系统在运行时,会频繁地请求内存页的操作,这样导致潜在的浪费会非常严重。这样的浪费会完全抵消减小page table的优势。
2、页面太大,会导致大量的内存碎片。因为底层的内存管理是以页面为单位。如果系统运行了很长时间,空闲的内存很多,但是连续的内存块都小于要分配的页面数。这时可以通过移动内存块或者利用swap来获取可用内存,但是会导致分配内存的操作很慢,这种慢会形成恶性循环,严重影响系统的性能。如果是小页面的话,内存的利用会比较紧凑,分配页面时需要的连续内存块的大小不像大页面那样需要的那么大。
3、如果CPU崩溃,TLB可以访问的内存越大,对系统的影响也越大。这时一把双刃剑,大页面可以提供TLB访问的内存数量,但是CPU崩溃时,会导致很多内存访问要去页表中请求物理地址。
4、兼容性问题。X86处理器支持的页面大小只有4K,所以如果页面过大的话,会导致兼容性问题。
5、如果页面太大,在将内存页换出到swap分区时,需要换出的内存也就越大,会影响性能。
总之,这个页面大小4K是在计算机发展过程中选择的,也是在实践中经过检验的,现在看来这个页面是合适的。
如果你还是想找到更多的证据说服自己的话,可以看看Linus Torvalds是怎么解释这个问题的:
<think>我们正在讨论的是Linux内核空间中申请4K对齐物理地址的内存接口。用户的问题是关于如何在内核空间分配物理地址4KB对齐的内存。根据Linux内核的内存管理机制,分配物理内存的常用接口有:1.`__get_free_pages()`:分配连续的物理页面,返回的是页面的虚拟地址(线性地址),但分配的物理内存是连续的,并且可以指定分配阶数(order),从而保证分配的物理内存大小和地址对齐。例如,分配一个页面4KB)可以使用`__get_free_pages(GFP_KERNEL,0)`,其中0表示分配2^0=1个页面。由于页面大小4KB,所以分配的物理地址自然就是4KB对齐的。2.`kmalloc()`:用于分配小块内存,可以指定对齐方式。但是,`kmalloc`分配的内存不一定能保证物理地址连续(除非使用SLAB分配器并且请求的大小小于一个页面),但通常用于分配较小的内存区域。对于4KB对齐的物理内存,我们可以使用`kmalloc`并指定对齐参数,例如`kmalloc(size,GFP_KERNEL|__GFP_ZERO|__GFP_HIGHMEM)`,但是`kmalloc`不能保证物理地址的连续性,而且对于4KB对齐,我们可以使用`kmalloc`并指定对齐要求为4KB,但实际上,`kmalloc`对于大块内存(比如大于一个页面)可能会使用页分配器,并且分配的内存虚拟地址是连续的,物理地址也是连续的(在低端内存区)。但是,更直接的方法是使用页分配器。3.`alloc_pages()`:返回的是`structpage`结构,然后我们可以使用`page_address()`将其转换为虚拟地址。这个函数同样可以指定分配阶数,因此可以分配多个连续的物理页面,并且物理地址是连续的,自然对齐到页面大小4KB)。因此,对于需要4KB对齐的物理内存,最直接的方法是使用页分配器函数,例如`__get_free_pages`者`alloc_pages`。注意:4KB对齐意味着物理地址必须是4096的倍数。由于页面大小4KB,所以分配一个多个完整的页面自然就是4KB对齐的。所以,我们可以这样分配:```c//分配一个页面4KB),物理地址4KB对齐unsignedlongaddr=__get_free_pages(GFP_KERNEL,0);//0表示分配2^0=1个页面if(!addr){//分配失败}//使用addr,它是一个虚拟地址,但对应的物理地址是连续的且4KB对齐//释放内存free_pages(addr,0);```者使用`alloc_pages`:```cstructpage*page=alloc_pages(GFP_KERNEL,0);//0阶,分配一个页面if(!page){//分配失败}void*addr=page_address(page);//使用addr//释放__free_pages(page,0);```另外,如果我们需要分配的内存大小超过一个页面,比如4页面(16KB),并且要求物理地址4KB对齐(实际上分配多个页面时,第一个页面的物理地址自然4KB对齐,整个内存块也是连续的),那么可以指定阶数为2(2^2=4页面)。关于用户提到的“物理地址对齐”,上述方法分配的物理内存都是连续的,并且起始地址是页面大小的整数倍(即4KB对齐)。此外,如果我们需要分配的内存要求物理地址连续,并且可能跨越高端内存,那么可以使用`dma_alloc_coherent`(用于DMA操作)者`alloc_pages`(使用`GFP_HIGHMEM`标志)。但是,对于4KB对齐,使用页分配器即可。总结:使用`__get_free_pages``alloc_pages`函数,分配阶数为0(即一个页面)即可获得4KB对齐的物理内存。注意:在64位系统上,`__get_free_pages`返回的虚拟地址可以直接使用。在32位系统中,如果分配的内存位于高端内存,那么不能直接使用`page_address`,可能需要映射。但通常,对于4KB的分配,一般不会使用高端内存(因为一个页面很小,通常从低端内存分配),所以使用`__get_free_pages`是安全的。参考引用[4]提到:Buddy分配器(即页分配器)在没有CMA(连续内存分配器)不允许阻塞的GFP标志时,会使用`__get_free_pages`从DMA/普通区域(低端内存)分配,并且这些内存已经是32位地址空间中由内核在启动时映射好的。因此,使用页分配器分配的内存,其虚拟地址到物理地址的映射已经存在,可以直接使用。因此,我们推荐使用页分配器函数来分配4KB对齐的物理内存。现在,生成相关问题。</think>### Linux内核中申请4K对齐物理地址的内存接口 在Linux内核中申请物理地址4KB对齐的内存,核心是使用**页分配器(Page Allocator)** 接口,因为内核大小默认4KB(`PAGE_SIZE`),分配完整页面时物理地址天然对齐。以下是具体方法: #### 1. **基础页分配函数(推荐)** ```c // 分配单个页面4KB),物理地址4KB对齐 unsigned long addr = __get_free_pages(GFP_KERNEL, 0); // 使用alloc_pages(返回struct page指针) struct page *page = alloc_pages(GFP_KERNEL, 0); void *addr = page_address(page); // 转换为虚拟地址 ``` **参数说明**: - `GFP_KERNEL`:标准分配标志(可休眠) - `0`:分配阶数(2⁰=1页=4KB) **释放内存**: ```c free_pages(addr, 0); // 配合__get_free_pages __free_pages(page, 0); // 配合alloc_pages ``` > ✅ **优势**:物理地址天然对齐,无需额外操作[^4]。 #### 2. **kmalloc对齐分配(特殊场景)** 若需小于4KB但强制对齐的内存: ```c // 分配4KB对齐的内存(如硬件寄存器访问) void *addr = kmalloc(size, GFP_KERNEL | __GFP_ALIGN | __GFP_ZERO); ``` **注意**: - 实际对齐依赖`kmalloc`的SLAB缓存对齐策略 - 大尺寸分配(≥ `PAGE_SIZE`)可能退回页分配器 #### 3. **DMA专用接口** 设备驱动需要DMA访问时: ```c // 分配DMA一致性内存(物理连续+4KB对齐) void *addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL); ``` **适用场景**:网卡缓冲区、磁盘I/O描述符等[^3]。 --- ### 关键差异总结 | 接口 | 物理连续 | 4KB对齐 | 适用场景 | |---------------------|----------|---------|-----------------------------| | `__get_free_pages()`| 是 | 自动 | 通用内核内存(≥4KB) | | `kmalloc()` | 不一定 | 可选 | 小内存(<4KB) | | `dma_alloc_coherent()`| 是 | 自动 | 设备DMA操作 | > ⚠️ **注意事项**: > - 32位系统需关注`ZONE_DMA`内存区域限制[^4] > - 高阶分配(如16KB)需调整阶数:`order = ilog2(size / PAGE_SIZE)` > - 物理地址可通过`virt_to_phys(addr)`获取 --- ### 代码示例(分配16KB物理对齐内存) ```c #include <linux/gfp.h> #include <linux/mm.h> // 分配16KB(4页),物理地址4KB对齐 int order = 2; // 2² = 4页 = 16KB unsigned long vaddr = __get_free_pages(GFP_KERNEL, order); if (!vaddr) { pr_err("Allocation failed\n"); return -ENOMEM; } // 获取物理地址 phys_addr_t paddr = virt_to_phys((void*)vaddr); // 使用后释放 free_pages(vaddr, order); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值