当前位置: 首页 > news >正文

【linux内存管理】【基础知识 1】【pgd,p4d,pud,pmd,pte,pfn,pg,ofs,PTRS概念介绍】

一、背景

在开始内存管理之前,我们先对 linux 内存管理 相关的术语进行介绍。

二、关键术语与常用宏/函数介绍(基于 Linux 6.0.9 )

注:页表层次通常自上而下为 PGD -> P4D -> PUD -> PMD -> PTE(映射粒度由页大小决定)。有的平台会“fold”(折叠)某些层(例如 p4d 在很多配置是 folded),但抽象层名仍存在。

2.1 基本术语

1. PGD

  • PGD(Page Global Directory)
    顶层页目录条目类型,类型名 pgd_t。在多级页表体系中负责顶层索引。对用户态 mm,用 pgd_offset(mm, addr) 得到 pgd_t *

2. P4D

  • P4D(page 4th-level directory)
    在支持 5 层页表的平台存在(或在某些架构上被折叠)。类型 p4d_t。在某些内核配置下 p4d 是“folded”(即其实质和 pud 合并),但 API 仍然可用。

3. PUD

  • PUD(Page Upper Directory)
    次顶层目录,类型 pud_t

4. PMD

  • PMD(Page Middle Directory)
    中层目录,类型 pmd_t。PMD 可以直接映射大页(hugepage)或指向 PTE 表。

5. PTE

  • PTE(Page Table Entry)
    最底层的页表条目,类型 pte_t,包含物理页帧号(PFN)及权限位(可读/写/exec/COW/dirty/accessed 等)。

6. PFN

  • PFN(Page Frame Number)
    物理页帧号——物理页的编号。通常:phys_addr = pfn << PAGE_SHIFT

7. pg/ofs/PTRS

  • pg / ofs / PTRS

    • pg 在不同上下文含义不同:有时表示 struct page,有时表示页表 page(页表页)。

    • ofs 常用于表示页内偏移(offset),或页表索引偏移的局部变量名。

    • PTRS 常见如 PTRS_PER_PGD, PTRS_PER_PTE 等,表示该级页表条目的数量(power-of-two)。这些通常由页表位宽与每条目大小决定。
      例如索引计算常用:index = (addr >> SHIFT) & (PTRS_PER_xxx - 1)


2.2 常用宏或者函数介绍

xxx 可以是: pgd、p4d、pud、pmd、pte

1. xxx_index/ xxx_offset

  • xxx_index: 表示线性地址在对应的级别中的 偏移量
  • xxx_offset: 用来获取该级别目标偏移量对应项的指正

这些宏/函数通常成对出现:xxx_index(addr) 给出某层的索引,xxx_offset(...) / xxx_offset_k(...) 返回指向下一级结构的指针(或条目指针)。

1. pgd_index / pgd_offset
  • pgd_index(addr)
    计算地址在 PGD 层的索引(仅位运算)。等价思想:(addr >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1)。用于低级页表遍历或调试时快速得到索引。

  • pgd_offset(mm, addr) / pgd_offset_k(addr)
    返回 pgd_t *(顶层条目指针)。pgd_offset_k 专为内核全局 init_mm / kernel 映射使用。使用场景:从 mmaddr 开始逐层遍历页表。

2. p4d_index/p4d_offset
  • p4d_index(addr) / p4d_offset(pgd, addr)
    与 PGD 相同思路。通常若配置折叠,p4d_* 可能是内联/映射到 pud_* 的别名。使用场景:跨架构统一遍历代码。
3. pud_index/pud_offset
  • pud_index(addr) / pud_offset(p4d, addr)
    上同,返回 pud_t *
4. pmd_index/pmd_offset
  • pmd_index(addr) / pmd_offset(pud, addr)
    计算 pmd 索引或从 pud 得到 pmd 指针。使用场景:当要检查一个 addr 是否被 large page(PMD 大页)映射,或需要走到 PTE 层时先拿到 PMD。
5. pte_index/pte_offset/pte_offset_map/pte_offset_kernel
  • pte_index(pmd, addr) / pte_offset(pmd, addr)
    计算并返回 pte_t *(即指向对应 PTE 表的入口)。常见变体:

    • pte_offset_map(pmd, addr):在需要映射页表页到内核地址空间时(有些架构 PTE 表本身也在高端内存),返回可直接读取/写的 pte_t *(并可能需要 pte_unmap())。

    • pte_offset_kernel(pmd, addr):内核模式下直接返回 pte_t *(当 PTE 表可直接以内核地址访问时)。使用场景:在内核需要直接读取/修改一个地址的 PTE(例如 mm 管理工具、debug、swap 或特殊内存管理场景),通常在持有恰当锁的前提下使用。

  • pgd_index, p4d_index, pud_index, pmd_index, pte_index(xxx_index)
    一般用于纯算法/调试中计算某级的数组索引。


2. __xxx / xxx_val

  • __pte(x) / __pmd(x) / __pgd(x)
    将一个原始数值(通常 unsigned long)包装为相应类型 (pte_t, pmd_t…) 的构造器。用于从数值构造条目(非正式更新条目时)或测试。不要在没有正确锁与同步情况下随意把数值写回页表。

  • pte_val(pte) / pmd_val(pmd) / pgd_val(pgd)
    将条目抽成底层数值(unsigned long),便于位操作或打印。使用场景:调试、打印或对位域进行分析。

  • xxx_pfn(例如 pte_pfn(pte)
    从一个 PTE 中取出 PFN(即页帧号)。常见用于:将 PTE 转为 struct page *(配合 pfn_to_page())或计算物理地址。


3. set_xxx / xxx_clear

替换/写入用法(写操作必须小心)

  • set_pmd(pmdp, new_pmd) / pmd_clear(pud, addr, pmdp)

    • set_pmd:把 pmd_t 写入 pmd 条目(常见在创建/修改 pmd 映射时使用),通常需在持有 pmd_spinlock / 块级锁 或在 mm 的页表写锁保护下执行(不同架构同步机制不同)。

    • pmd_clear / pte_clear:从页表中移除条目(解除映射),通常在释放映射、unmap、swap out 等场景使用。清除 PTE 必须保证 CPU TLB 被刷新/无竞争。

  • set_pte_at(mm, addr, ptep, pte)
    常用于设置用户地址空间的 PTE(写入带有 mm 信息的替代),通常要在 page_table_lock 或相应写锁保护下做,并在之后 flush_tlb/flush_icache 等。

4. lookup_address

  • lookup_address(unsigned long addr, unsigned int *level)
    在内核中用于查找某个虚拟地址在页表中对应的最底层条目(返回 pte_t *pmd_t 等,level 表示返回条目是哪一级)。非常适合只读查询 PTE,而不需要手动多级遍历的场景。 这个函数只在 x86 下支持, arm64 中是没有的

5. pfn_to_page/page_to_pfn

  • pfn_to_page(pfn) / page_to_pfn(page)
    PFN 与 struct page * 之间的转换。

6. virt_to_page/virt_to_phys

  • virt_to_page() / virt_to_phys()(仅对直接映射的内核虚拟地址安全)
    可从内核虚拟地址直接得到 struct page * / 物理地址。对 vmalloc 地址不适用(vmalloc 映射非连续)。

7. pte_unmap/pte_offset_map

  • pte_unmap() / pte_offset_map()
    如果 pte_offset_map 为架构映射页表页提供了映射,需要在读完后 pte_unmap()。在某些架构 pte_offset_kernel 不需映射,pte_offset_map 则会做必要的映射。

2.3 x86 与 arm64 的主要差别(对上述概念/宏的影响)

  1. 页表层数与折叠(folding)

    • x86 (legacy) 可支持 4-level(PGD→PUD→PMD→PTE)或在启用 PAE/5-level 时更多层次。内核中常见 p4d 层通常在 x86 被“folded”或作为占位,API 仍存在但实现为 pass-through。

    • arm64 通常使用 4 级(PGD/PUD/PMD/PTE)但实现细节与索引位偏移不同;也支持长线性映射与不同页大小(4K、16K、64K)在特定实现中。

  2. 页大小与大页(Huge pages)支持

    • x86 常见 4KB(可选 2MB(PMD large)或 1GB(PUD large))。

    • arm64 在硬件上也支持 4KB/16KB/64KB page sizes(实现依平台)。因此 PMD 是否能表示 large page 与页大小有关。

  3. 条目位布局(flags, PFN bit positions)

    • pte_val() / pmd_val() 抽出的原始数值位域在不同架构上具体含义不同(低位通常是标志位,PFN 位从某个位起)。因此用 pte_pfn() / pmd_pfn() 抽象函数获取 PFN,而不是直接位移 pte_val
  4. 访问 PTE 表的方式(是否需要 pte_offset_map 映射)

    • 在某些架构上(尤其物理内存直接映射的 x86)内核可以直接访问页表页;而在某些实现中需要通过 pte_offset_map 明确把页表页映射到可读内核虚拟地址。API 层面 pte_offset_kernelpte_offset_map 提供兼容方案。
  5. 名称与宏的差异

    • 有些宏在某平台会被定义为别名或内联空操作(例如 p4d fold)。因此编写跨架构代码时常用 pgd_offset() / p4d_offset() / pud_offset() 等链式调用,而不要直接假设 p4d 一定存在。

三、测试驱动

下面给出一个 kernel module(leo_study/pgtable/pgtable_sig_test.c)。目的

  • 安全地分配一页内存(vmalloc),演示如何读取该地址在内核页表中的 PGD/P4D/PUD/PMD/PTE 信息(使用 pgd_offset_kp4d_offsetpud_offsetpmd_offsetpte_offset_kernellookup_addresspgd_index 等);

  • 展示 __ptepte_valpte_pfnpfn_to_pagepage_to_pfn 的用法;

  • 展示 PTRS_PER_* / *_index 的含义;

  • 给出注释掉set_pte_at / pte_clear / set_pmd 用法示例(非常危险,需在受控环境并加锁执行),并解释如何正确加锁/刷新 TLB(示例注释中说明)。

请在一台测试机或 QEMU 上运行该模块(不要在生产机器上测试写操作部分)。

3.1 code

// File: pgtable_demo.c
// Compile against Linux 6.0.9 headers. Example: make with a simple Kbuild
// This module demonstrates reading page table entries and related macros.
// WARNING: no destructive modifications are performed. Destructive examples are commented.#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/highmem.h>
#include <linux/sched.h>
#include <linux/pagemap.h>
#include <linux/pgtable.h>
#include <linux/huge_mm.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("leo");
MODULE_DESCRIPTION("Page table demo: pgd/p4d/pud/pmd/pte and related macros (Linux 6.0.9 style)");// size for vmalloc region (one page)
#define DEMO_SIZE PAGE_SIZEstatic void *vmaddr = NULL;pte_t *my_lookup_address(unsigned long addr, unsigned int *level)
{pgd_t *pgd;p4d_t *p4d;pud_t *pud;pmd_t *pmd;pte_t *pte = NULL;// 内核地址空间 mm == NULL; 故这里使用 current->active_mmpgd = pgd_offset(current->active_mm, addr); // 第一级页表if (pgd_none(*pgd) || pgd_bad(*pgd))return NULL;printk("%s: pgd=%px *pgd=%#llx\n", __func__, pgd,(unsigned long)pgd_val(*pgd));p4d = p4d_offset(pgd, addr); // 第四级页表if (p4d_none(*p4d) || p4d_bad(*p4d))return NULL;printk("%s: p4d=%px *p4d=%#llx\n", __func__, p4d,(unsigned long)p4d_val(*p4d));pud = pud_offset(p4d, addr); // 第二级页表if (pud_none(*pud) || pud_bad(*pud))return NULL;printk("%s: pud=%px *pud=%#llx\n", __func__, pud,(unsigned long)pud_val(*pud));pmd = pmd_offset(pud, addr); // 第三级页表if (pmd_none(*pmd) || pmd_bad(*pmd))return NULL;printk("%s: pmd=%px *pmd=%#llx\n", __func__, pmd,(unsigned long)pmd_val(*pmd));if (pmd_leaf(*pmd)) { // 大页映射*level = 2;return (pte_t *)pmd;}pte = pte_offset_kernel(pmd, addr); // 最后一级页表if (!pte)return NULL;printk("%s: pte=%px *pte=%#llx\n", __func__, pte,(unsigned long)pte_val(*pte));*level = 3;return pte;
}static void dump_indices(unsigned long addr)
{unsigned long pgd_i = pgd_index(addr);
#ifdef p4d_indexunsigned long p4d_i = p4d_index(addr);
#elseunsigned long p4d_i = 0;
#endifunsigned long pud_i = pud_index(addr);unsigned long pmd_i = pmd_index(addr);unsigned long pte_i = pte_index(addr); /* pte_index uses pmd pointer in some impl; result only depends on addr bits */printk("pgd_index(%lu) = (((%#llx) >> PGDIR_SHIFT_%d) & (PTRS_PER_PGD_%d - 1)) = %lu\n",addr, addr, PGDIR_SHIFT, PTRS_PER_PGD, pgd_i);// printk("p4d_index(%lu) = (((%lu) >> P4D_SHIFT_%d) & (PTRS_PER_P4D_%d - 1)) = %lu\n", addr, addr, P4D_SHIFT, PTRS_PER_P4D, p4d_i);printk("pud_index(%#llx) = (((%#llx) >> PUD_SHIFT_%d) & (PTRS_PER_PUD_%d - 1)) = %lu\n",addr, addr, PUD_SHIFT, PTRS_PER_PUD, pud_i);printk("pmd_index(%#llx) = (((%#llx) >> PMD_SHIFT_%d) & (PTRS_PER_PMD_%d - 1)) = %lu\n",addr, addr, PMD_SHIFT, PTRS_PER_PMD, pmd_i);printk("pte_index(%#llx) = (((%#llx) >> PAGE_SHIFT_%d) & (PTRS_PER_PTE_%d - 1)) = %lu\n",addr, addr, PAGE_SHIFT, PTRS_PER_PTE, pte_i);pr_info("ADDR 0x%lx indices: pgd=%lu p4d=%lu pud=%lu pmd=%lu pte=%lu\n",addr, pgd_i, p4d_i, pud_i, pmd_i, pte_i);
}/* Helper to do safe lookup of PTE using lookup_address */
static void inspect_address(unsigned long addr)
{unsigned int level = 0;pte_t *ptep;pmd_t *pmdp;pud_t *pudp;p4d_t *p4dp;pgd_t *pgdp;/* lookup_address returns pointer to lowest-level entry that maps addr (pte or upper-level for huge page)and fills `level` with the level found (e.g., PG_LEVEL_NONE/PG_LEVEL_*). */
#if defined(CONFIG_X86) || defined(CONFIG_X86_64)ptep = lookup_address(addr, &level);printk(KERN_INFO "Architecture: x86/x86_64\n");#elif defined(CONFIG_ARM64)printk(KERN_INFO "Architecture: ARM64 (with PAC support)\n");// ARM64 上使用安全的ptep = my_lookup_address(addr, &level);#elif defined(CONFIG_ARM)printk(KERN_INFO "Architecture: ARM\n");#elseprintk(KERN_INFO "Architecture: Unknown\n");
#endifpr_info("lookup_address: addr=0x%lx returned ptep=%px level=%u\n", addr,ptep, level);/* Walk down manually from kernel top-level for demonstration (kernel mapping) */pgdp = pgd_offset_k(addr);pr_info(" pgd at %px, val=0x%lx\n", pgdp, pgd_val(*pgdp));p4dp = p4d_offset(pgdp, addr);pr_info(" p4d at %px, val=0x%lx\n", p4dp, p4d_val(*p4dp));pudp = pud_offset(p4dp, addr);pr_info(" pud at %px, val=0x%lx\n", pudp, pud_val(*pudp));pmdp = pmd_offset(pudp, addr);pr_info(" pmd at %px, val=0x%lx\n", pmdp, pmd_val(*pmdp));/* If pmd is huge mapping, pte_offset_kernel may behave differently; still we can try pte_offset_kernel */{pte_t *pte_k = NULL;/* pte_offset_kernel returns pte pointer for kernel (if supported) */pte_k = pte_offset_kernel(pmdp, addr);pr_info(" pte_offset_kernel -> %px\n", pte_k);if (pte_k) {unsigned long pval = pte_val(*pte_k);unsigned long pfn = pte_pfn(*pte_k);struct page *pg = pfn_to_page(pfn);pr_info("   pte_val=0x%lx pte_pfn=%lu pfn_to_page=%px\n",pval, pfn, pg);}}/* show use of __pte / pte_val */if (ptep) {unsigned long raw = pte_val(*ptep);pte_t copy = __pte(raw);pr_info(" lookup pte raw 0x%lx ; reconstructed via __pte -> pte_val=0x%lx\n",raw, pte_val(copy));}
}/* Print PTRS_PER_* values */
static void dump_ptrs(void)
{pr_info("PTRS_PER_PGD=%lu PTRS_PER_P4D=%lu PTRS_PER_PUD=%lu PTRS_PER_PMD=%lu PTRS_PER_PTE=%lu\n",(unsigned long)PTRS_PER_PGD,
#ifdef PTRS_PER_P4D(unsigned long)PTRS_PER_P4D,
#else0,
#endif(unsigned long)PTRS_PER_PUD, (unsigned long)PTRS_PER_PMD,(unsigned long)PTRS_PER_PTE);
}static int __init pgtable_demo_init(void)
{pr_info("pgtable_demo: init\n");dump_ptrs();/* allocate a vmalloc page (non-contiguous physical) to show vmalloc address handling */// vmaddr = kmalloc(DEMO_SIZE, GFP_KERNEL);vmaddr = vmalloc(DEMO_SIZE);if (!vmaddr) {pr_err("vmalloc failed\n");return -ENOMEM;}/* touch the page to ensure mapping exists */memset(vmaddr, 0x5a, DEMO_SIZE);pr_info("vmalloc returned %px %lu %#llx\n", vmaddr,(unsigned long)vmaddr, (unsigned long)vmaddr);/* dump indices for the allocated kernel virtual address */dump_indices((unsigned long)vmaddr);/* Inspect page-table entries via safe helper */inspect_address((unsigned long)vmaddr);/* Show virt_to_page where applicable (only for direct mapped memory; vmalloc not direct) */pr_info("virt_to_page(vmaddr) may be invalid for vmalloc; virt_to_page(vmaddr) -> %px\n",virt_to_page(vmaddr));/* Show page and pfn conversions for an alloc_page */{struct page *p = alloc_page(GFP_KERNEL);if (p) {unsigned long pfn = page_to_pfn(p);pr_info("alloc_page -> page=%px pfn=%lu pfn_to_page=%px\n",p, pfn, pfn_to_page(pfn));__free_page(p);}}pr_info("pgtable_demo: init done\n");return 0;
}static void __exit pgtable_demo_exit(void)
{pr_info("pgtable_demo: exit\n");if (vmaddr) {vfree(vmaddr);vmaddr = NULL;}pr_info("pgtable_demo: cleaned\n");
}module_init(pgtable_demo_init);
module_exit(pgtable_demo_exit);

文章转载自:

http://bE4yZ073.mjzcn.cn
http://y51UYT6C.mjzcn.cn
http://W4GxtTBu.mjzcn.cn
http://BTZ9ut7H.mjzcn.cn
http://5EBCa0Oe.mjzcn.cn
http://oA9NVs6m.mjzcn.cn
http://iPU9zsAL.mjzcn.cn
http://2nVY7JiM.mjzcn.cn
http://vT55W9U6.mjzcn.cn
http://CN82p6iZ.mjzcn.cn
http://5N1kiBF3.mjzcn.cn
http://rmY7ZGMz.mjzcn.cn
http://ybfZcI4B.mjzcn.cn
http://iDrAKCSy.mjzcn.cn
http://G9VjlJVj.mjzcn.cn
http://HoqENTPw.mjzcn.cn
http://ULKhEz8Y.mjzcn.cn
http://te5JKqKw.mjzcn.cn
http://Ji9WvuQR.mjzcn.cn
http://ZTqy7EGn.mjzcn.cn
http://2cnn84T9.mjzcn.cn
http://8QfbPc2E.mjzcn.cn
http://nlMiy6Lf.mjzcn.cn
http://mehIiMgZ.mjzcn.cn
http://u1Qs2MdB.mjzcn.cn
http://CavdNuDa.mjzcn.cn
http://pIjt7lCa.mjzcn.cn
http://F6jHJiOA.mjzcn.cn
http://CW2t8tRa.mjzcn.cn
http://P7LzA05H.mjzcn.cn
http://www.dtcms.com/a/378610.html

相关文章:

  • 详解mcp以及agent java应用架构设计与实现
  • 硬件开发2-ARM裸机开发2-IMX6ULL
  • 电商网站被DDoS攻击了怎么办?
  • Java NIO的底层原理
  • QT 常用控件(概述、QWidget核心属性、按钮类控件、显示类控件、输入类控件、多元素控件、容器类控件、布局管理器)
  • MATLAB2-结构化编程和自定义函数-台大郭彦甫视频
  • 鸿蒙的编程软件的介绍
  • 鸿蒙审核问题——Scroll中嵌套了List/Grid时滑动问题
  • REDPlayer 鸿蒙原生视频播放库组件介绍与使用指南
  • HarmonyOS 应用开发深度解析:ArkUI 声明式 UI 与现代化状态管理最佳实践
  • redis 入门-1
  • Json-rpc通信项目(基于C++ Jsoncpp muduo库)
  • TODO的面试(dw三面、sqb二面、ks二面)
  • Vibe Coding实战项目:用Qwen3-Coder做了个AI跳舞视频生成器
  • Vue 封装Input组件 双向通信
  • 【混合开发】进阶到【大前端++】
  • ZooKeeper Java客户端与分布式应用实战
  • 【复习】计网每日一题---传输层无连接不可靠服务
  • 2025年秋招答疑:AI面试如何破解在线作弊难题?
  • KafKa01:在Windows系统上安装Kafka
  • 【Big Data】Amazon S3 专为从任何位置检索任意数量的数据而构建的对象存储
  • C++:模版进阶
  • 【Canvas与旗帜】圆角红面白边蓝底梅花五星旗
  • 不同局域网远程桌面连接:设置让外网电脑直接windows自带远程桌面访问内网计算机,简单3步实现通用详细教程
  • set 认识及使用
  • 如何打造“高效、安全、精准、可持续”的智能化实验室?
  • 究竟什么时候用shared_ptr,什么时候用unique_ptr?
  • 前端抽象化,打破框架枷锁:react现代化项目中的思想体现
  • 基于开源AI智能名片、链动2+1模式与S2B2C商城小程序的流量运营与个人IP构建研究
  • gstreamer:创建组件、管道和总线,实现简单的播放器(Makefile,代码测试通过)