x86路径下分页管理源码详解
x86的asm文件夹路径为/usr/src/linux-headers-6.8.0-45-generic/arch/x86/include/asm,是x86体系架构下的文件,本次分析了 pgtable_64.h , pgtable-2level.h 和pgtable-3level.h 三个文件。
pgtable_64.h 分析:
该文件包含修改和使用x86-64页表树所需的函数和定义,下面是对源码的详细分析:
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_X86_PGTABLE_64_H
#define _ASM_X86_PGTABLE_64_H
#include <linux/const.h>
#include <asm/pgtable_64_types.h>
#ifndef __ASSEMBLY__
/* 该文件包含修改和使用x86-64页表树所需的函数和定义 */
#include <asm/processor.h>
#include <linux/bitops.h>
#include <linux/threads.h>
#include <asm/fixmap.h>
/*
* 定义了一系列外部页表数组分别用于存储P4D、PUD、PMD、PTE的页表项
* 这些页表项是内核用于将虚拟地址转换为物理地址的关键数据结构
*/
extern p4d_t level4_kernel_pgt[512];
extern p4d_t level4_ident_pgt[512];
extern pud_t level3_kernel_pgt[512];
extern pud_t level3_ident_pgt[512];
extern pmd_t level2_kernel_pgt[512];
extern pmd_t level2_fixmap_pgt[512];
extern pmd_t level2_ident_pgt[512];
extern pte_t level1_fixmap_pgt[512 * FIXMAP_PMD_NUM];
extern pgd_t init_top_pgt[];
/* 将用于表示内核页表目录的指针指向内核初始化时创建的顶级页表目录 */
#define swapper_pg_dir init_top_pgt
/* 用于初始化系统的页表结构 */
extern void paging_init(void);
static inline void sync_initial_page_table(void) { }
/* 用于在调试时打印出错误的页表项信息,帮助定位问题 */
#define pte_ERROR(e) \
pr_err("%s:%d: bad pte %p(%016lx)\n", \
__FILE__, __LINE__, &(e), pte_val(e))
#define pmd_ERROR(e) \
pr_err("%s:%d: bad pmd %p(%016lx)\n", \
__FILE__, __LINE__, &(e), pmd_val(e))
#define pud_ERROR(e) \
pr_err("%s:%d: bad pud %p(%016lx)\n", \
__FILE__, __LINE__, &(e), pud_val(e))
/* 检查是否启用了五级页表 */
#if CONFIG_PGTABLE_LEVELS >= 5
#define p4d_ERROR(e) \
pr_err("%s:%d: bad p4d %p(%016lx)\n", \
__FILE__, __LINE__, &(e), p4d_val(e))
#endif
#define pgd_ERROR(e) \
pr_err("%s:%d: bad pgd %p(%016lx)\n", \
__FILE__, __LINE__, &(e), pgd_val(e))
/* 定义内存管理结构体 */
struct mm_struct;
#define mm_p4d_folded mm_p4d_folded
/* 通过检查是否启用第五级页表来判断p4d页是否被折叠 */
static inline bool mm_p4d_folded(struct mm_struct *mm)
{
return !pgtable_l5_enabled();
}
/* 用于在p4d和pud上根据虚拟地址设置pte */
void set_pte_vaddr_p4d(p4d_t *p4d_page, unsigned long vaddr, pte_t new_pte);
void set_pte_vaddr_pud(pud_t *pud_page, unsigned long vaddr, pte_t new_pte);
/* 用于设置PTE页表项,使用WRITE_ONCE宏来进行写操作 */
static inline void native_set_pte(pte_t *ptep, pte_t pte)
{
WRITE_ONCE(*ptep, pte);
}
/* 用于清除PTE页表项,通过调用native_set_pte函数并传递一个表示空页表项的值来实现 */
static inline void native_pte_clear(struct mm_struct *mm, unsigned long addr,
pte_t *ptep)
{
native_set_pte(ptep, native_make_pte(0));
}
/* 用于设置PTE页表项,使用WRITE_ONCE宏来进行写原子操作 */
static inline void native_set_pte_atomic(pte_t *ptep, pte_t pte)
{
native_set_pte(ptep, pte);
}
/* 用于设置PMD页表项,使用WRITE_ONCE宏来进行写操作 */
static inline void native_set_pmd(pmd_t *pmdp, pmd_t pmd)
{
WRITE_ONCE(*pmdp, pmd);
}
/* 用于清除PMD页表项,通过调用native_set_pmd函数并传递一个表示空页表项的值来实现 */
static inline void native_pmd_clear(pmd_t *pmd)
{
native_set_pmd(pmd, native_make_pmd(0));
}
/* 用于获取页表项的值并置0,在SMP(对称多处理)配置中,使用xchg指令来实现原子操作。 */
static inline pte_t native_ptep_get_and_clear(pte_t *xp)
{
#ifdef CONFIG_SMP
return native_make_pte(xchg(&xp->pte, 0));
#else
/* native_local_ptep_get_and_clear,但因循环依赖关系而重复 */
pte_t ret = *xp;
native_pte_clear(NULL, 0, xp);
return ret;
#endif
}
/* 用于获取页表项的值并置0,在SMP(对称多处理)配置中,使用xchg指令来实现原子操作。 */
static inline pmd_t native_pmdp_get_and_clear(pmd_t *xp)
{
#ifdef CONFIG_SMP
return native_make_pmd(xchg(&xp->pmd, 0));
#else
/* native_local_pmdp_get_and_clear,但因循环依赖关系而重复 */
pmd_t ret = *xp;
native_pmd_clear(xp);
return ret;
#endif
}
/* 用于设置PUD页表项,使用WRITE_ONCE宏来进行写操作 */
static inline void native_set_pud(pud_t *pudp, pud_t pud)
{
WRITE_ONCE(*pudp, pud);
}
/* 用于清除PUD页表项,通过调用native_set_pud函数并传递一个表示空页表项的值来实现 */
static inline void native_pud_clear(pud_t *pud)
{
native_set_pud(pud, native_make_pud(0));
}
/* 用于获取页表项的值并置0,在SMP(对称多处理)配置中,使用xchg指令来实现原子操作。 */
static inline pud_t native_pudp_get_and_clear(pud_t *xp)
{
#ifdef CONFIG_SMP
return native_make_pud(xchg(&xp->pud, 0));
#else
/* native_local_pudp_get_and_clear,
* but duplicated because of cyclic dependency
*/
pud_t ret = *xp;
native_pud_clear(xp);
return ret;
#endif
}
/* 用于设置P4D页表项,先检查是否启用五级页表或页表隔离,如果启用了页表隔离且没有五级页表,
则会对P4D进行特殊处理,将其值转换为PGD,并应用页表隔离逻辑。 */
static inline void native_set_p4d(p4d_t *p4dp, p4d_t p4d)
{
pgd_t pgd;
if (pgtable_l5_enabled() || !IS_ENABLED(CONFIG_PAGE_TABLE_ISOLATION)) {
WRITE_ONCE(*p4dp, p4d);
return;
}
pgd = native_make_pgd(native_p4d_val(p4d));
pgd = pti_set_user_pgtbl((pgd_t *)p4dp, pgd);
WRITE_ONCE(*p4dp, native_make_p4d(native_pgd_val(pgd)));
}
/* 用于清除P4D页表项,通过调用native_set_p4d函数并传递一个表示空页表项的值来实现 */
static inline void native_p4d_clear(p4d_t *p4d)
{
native_set_p4d(p4d, native_make_p4d(0));
}
/* 用于设置PGD页表项,使用WRITE_ONCE宏来进行写操作 */
static inline void native_set_pgd(pgd_t *pgdp, pgd_t pgd)
{
WRITE_ONCE(*pgdp, pti_set_user_pgtbl(pgdp, pgd));
}
/* 用于清除PGD页表项,通过调用native_set_pgd函数并传递一个表示空页表项的值来实现 */
static inline void native_pgd_clear(pgd_t *pgd)
{
native_set_pgd(pgd, native_make_pgd(0));
}
/* 转换功能:将页面和保护转换为页面条目,将页面条目和页面目录转换为它们所指向的页面 */
/* PGD - Level 4 access */
/* PUD - Level 3 access */
/* PMD - Level 2 access */
/* PTE - Level 1 access */
/*
* 为交换条目编码和解码
*
* | ... | 11| 10| 9|8|7|6|5| 4| 3|2| 1|0| <- bit number
* | ... |SW3|SW2|SW1|G|L|D|A|CD|WT|U| W|P| <- bit names
* | TYPE (59-63) | ~OFFSET (9-58) |0|0|X|X| X| E|F|SD|0| <- swp entry
*
* G (8) 被别名,用作 !present ptes 的 PROT_NONE 指示符。
* 我们需要从这里开始存储交换条目。
* 我们还需要避免使用 A 和 D,因为在非 present PTE 上,硬件可能会错误地设置它们。
*
* SD 1-4 位在非显示格式中不使用,可用于特殊用途:
*
* SD (1) 在Swp条目中用于存储soft dirty位,这有助于我们在页面迁移过程中记住soft dirty位。
*
* F (2) 在swp 条目中用于记录分页表何时受 userfaultfd WP 支持的写保护。
*
* E (3) 在swp 条目中用于记忆 PG_anon_exclusive。
*
* 第 7 位在swp 条目中应该为 0,因为 pmd_present 不仅检查 P,还检查 L 和 G。
*
* 偏移量通过二进制非运算进行反转,使高位物理比特置位。
*/
/* 交换分区的最大交换类型数量为2^5=32种,每种都有唯一的不同标识符 */
#define SWP_TYPE_BITS 5
/* 定义了交换偏移量再交换条目中开始的比特位置,通过给页面保护相关的标志+1来防止重叠 */
#define SWP_OFFSET_FIRST_BIT (_PAGE_BIT_PROTNONE + 1)
/* 我们在提取/编码偏移量时,总是先将其向上移动,然后再向下移动 */
/* 定义了在进行交换偏移量计算时,需要将偏移量左移的位数 */
#define SWP_OFFSET_SHIFT (SWP_OFFSET_FIRST_BIT+SWP_TYPE_BITS)
/* 这个宏定义了一个编译时检查,用于确保MAX_SWAPFILES_SHIFT不会超过SWP_TYPE_BITS */
#define MAX_SWAPFILES_CHECK() BUILD_BUG_ON(MAX_SWAPFILES_SHIFT > SWP_TYPE_BITS)
/* 提取类型的高比特位 */
#define __swp_type(x) ((x).val >> (64 - SWP_TYPE_BITS))
/* 上移(去掉类型),然后下移获取数值 */
#define __swp_offset(x) (~(x).val << SWP_TYPE_BITS >> SWP_OFFSET_SHIFT)
/* 偏移量上移 TYPE 位 “太远”,再下移。 偏移量通过二进制非运算进行反转,使物理高位置位 */
#define __swp_entry(type, offset) ((swp_entry_t) { \
(~(unsigned long)(offset) << SWP_OFFSET_SHIFT >> SWP_TYPE_BITS) \
| ((unsigned long)(type) << (64-SWP_TYPE_BITS)) })
/* 用于PTE和PMD与Swap Entry的相互转换 */
#define __pte_to_swp_entry(pte) ((swp_entry_t) { pte_val((pte)) })
#define __pmd_to_swp_entry(pmd) ((swp_entry_t) { pmd_val((pmd)) })
#define __swp_entry_to_pte(x) (__pte((x).val))
#define __swp_entry_to_pmd(x) (__pmd((x).val))
/* 清理或释放与高端内存映射相关的资源 */
extern void cleanup_highmap(void);
/*
* 表明该架构支持unmapped_area函数,用于在进程的地址空间中找到未映射区域
* 而HAVE_ARCH_UNMAPPED_AREA_TOPDOWN进一步指定支持从地址空间顶部向下搜索未映射区域的功能
* 这是为了支持某些特定类型的内存分配需求,如栈的扩展
*/
#define HAVE_ARCH_UNMAPPED_AREA
#define HAVE_ARCH_UNMAPPED_AREA_TOPDOWN
/* PAGE_KERNEL_NOCACHE表示该页面不应被缓存,此定义表明AGP内存区域访问不通过CPU缓存层次结构进行 */
#define PAGE_AGP PAGE_KERNEL_NOCACHE
/* 这个宏定义表明该架构支持AGP内存页面的处理,允许内核在需要时配置和使用AGP内存 */
#define HAVE_PAGE_AGP 1
/* fs/proc/kcore.c */
/* 用于在内核虚拟地址和物理偏移量之间进行转换 */
#define kc_vaddr_to_offset(v) ((v) & __VIRTUAL_MASK)
#define kc_offset_to_vaddr(o) ((o) | ~__VIRTUAL_MASK)
/* 支持快速检查两个页表项是否相同 */
#define __HAVE_ARCH_PTE_SAME
/* 用于内核通过虚拟地址访问物理内存页的信息 */
#define vmemmap ((struct page *)VMEMMAP_START)
/* 接受物理地址和大小作为参数,在内核页表中设置非缓存(Uncached)的内存映射
和写回(Write-Back)缓存策略的内存映射 */
extern void init_extra_mapping_uc(unsigned long phys, unsigned long size);
extern void init_extra_mapping_wb(unsigned long phys, unsigned long size);
/* 用于检查给定的虚拟地址范围是否允许快速的用户空间页表访问 */
#define gup_fast_permitted gup_fast_permitted
static inline bool gup_fast_permitted(unsigned long start, unsigned long end)
{
if (end >> __VIRTUAL_MASK_SHIFT)
return false;
return true;
}
/* 包含与页表反转相关函数 */
#include <asm/pgtable-invert.h>
#endif /* !__ASSEMBLY__ */
#endif /* _ASM_X86_PGTABLE_64_H */
pgtable-2level.h 分析
该文件是 Linux 内核中针对使用两级页表架构的特定页表操作的头文件,定义了一系列宏、函数和内联函数,用于处理页表项PTE、页中间目录项PMD和不一定使用的页上级目录项PUD的设置、清除与转换,下面是对源码的分析:
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_X86_PGTABLE_2LEVEL_H
#define _ASM_X86_PGTABLE_2LEVEL_H
/* 用于在页表项PTE和页全局目录项PGD出现问题时打印错误信息 */
#define pte_ERROR(e) \
pr_err("%s:%d: bad pte %08lx\n", __FILE__, __LINE__, (e).pte_low)
#define pgd_ERROR(e) \
pr_err("%s:%d: bad pgd %08lx\n", __FILE__, __LINE__, pgd_val(e))
/* 当直接修改页表中的 PTE 时,某些架构需要做一些特殊处理。因此,我们提供了以下钩子。 */
/* 用于设置页表项PTE、页中间目录项PMD和页上级目录项PUD的值,其中两级页表不使用PUD,故不进行操作 */
static inline void native_set_pte(pte_t *ptep , pte_t pte)
{
*ptep = pte;
}
static inline void native_set_pmd(pmd_t *pmdp, pmd_t pmd)
{
*pmdp = pmd;
}
static inline void native_set_pud(pud_t *pudp, pud_t pud)
{
}
/* 以原子方式设置 PTE,但在两级页表架构中直接调用而未进行原子操作 */
static inline void native_set_pte_atomic(pte_t *ptep, pte_t pte)
{
native_set_pte(ptep, pte);
}
/* 用于清除页表项PTE、页中间目录项PMD和页上级目录项PUD的值,其中两级页表不使用PUD,故不进行操作 */
static inline void native_pmd_clear(pmd_t *pmdp)
{
native_set_pmd(pmdp, __pmd(0));
}
static inline void native_pud_clear(pud_t *pudp)
{
}
static inline void native_pte_clear(struct mm_struct *mm,
unsigned long addr, pte_t *xp)
{
*xp = native_make_pte(0);
}
/*
* 提供了在多处理器环境中安全地获取并清除页表项PTE、页中间目录项PMD和页上级目录项PUD值的机制
* 这些函数使用 xchg 指令来确保操作的原子性
* 对于不支持 SMP 的系统,这些函数被定义为调用本地版本的函数
*/
#ifdef CONFIG_SMP
static inline pte_t native_ptep_get_and_clear(pte_t *xp)
{
return __pte(xchg(&xp->pte_low, 0));
}
#else
#define native_ptep_get_and_clear(xp) native_local_ptep_get_and_clear(xp)
#endif
#ifdef CONFIG_SMP
static inline pmd_t native_pmdp_get_and_clear(pmd_t *xp)
{
return __pmd(xchg((pmdval_t *)xp, 0));
}
#else
#define native_pmdp_get_and_clear(xp) native_local_pmdp_get_and_clear(xp)
#endif
#ifdef CONFIG_SMP
static inline pud_t native_pudp_get_and_clear(pud_t *xp)
{
return __pud(xchg((pudval_t *)xp, 0));
}
#else
#define native_pudp_get_and_clear(xp) native_local_pudp_get_and_clear(xp)
#endif
/* pte/pgoff 输入时的位操作辅助器 */
/* 用于在页表项或页面偏移量上进行位操作 */
static inline unsigned long pte_bitop(unsigned long value, unsigned int rightshift,
unsigned long mask, unsigned int leftshift)
{
return ((value >> rightshift) & mask) << leftshift;
}
/*
* 编码/解码交换条目和交换PTE。交换PTE是指!pte_none() && !pte_present()的所有PTE。
*
* 交换 PTE 的格式:
*
* 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
* 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
* <----------------- offset ------------------> 0 E <- type --> 0
*
* E 是独占标记,不存储在交换条目中。
*/
/* 交换分区的最大交换类型数量为2^5=32种,每种都有唯一的不同标识符 */
#define SWP_TYPE_BITS 5
/* 生成一个掩码,用于从交换空间条目的值中提取交换类型 */
#define _SWP_TYPE_MASK ((1U << SWP_TYPE_BITS) - 1)
/* 定义了交换类型在交换空间条目值中的起始位置 */
#define _SWP_TYPE_SHIFT (_PAGE_BIT_PRESENT + 1)
/* 定义了交换空间偏移量swap offset在交换空间条目值中的起始位置 */
#define SWP_OFFSET_SHIFT (_PAGE_BIT_PROTNONE + 1)
/* 使用BUILD_BUG_ON来在编译时检查MAX_SWAPFILES_SHIFT是否超过了SWP_TYPE_BITS */
#define MAX_SWAPFILES_CHECK() BUILD_BUG_ON(MAX_SWAPFILES_SHIFT > 5)
/* 从给定的交换空间条目x中提取交换类型 */
#define __swp_type(x) (((x).val >> _SWP_TYPE_SHIFT) \
& _SWP_TYPE_MASK)
/* 从给定的交换空间条目x中提取交换偏移量 */
#define __swp_offset(x) ((x).val >> SWP_OFFSET_SHIFT)
/* 根据给定的交换类型和偏移量创建一个交换空间条目 */
#define __swp_entry(type, offset) ((swp_entry_t) { \
(((type) & _SWP_TYPE_MASK) << _SWP_TYPE_SHIFT) \
| ((offset) << SWP_OFFSET_SHIFT) })
/* 将页表项PTE的值与交换空间条目相互转化 */
#define __pte_to_swp_entry(pte) ((swp_entry_t) { (pte).pte_low })
#define __swp_entry_to_pte(x) ((pte_t) { .pte = (x).val })
/* 我们借用第 7 位来存储交换 PTE 中的唯一性标记。*/
#define _PAGE_SWP_EXCLUSIVE _PAGE_PSE
/* 在两级页表上没有倒置的 PFN */
/* 因为两级页表架构通常不需要复杂的保护位操作或反转机制,因此不执行任何操作或返回默认值 */
static inline u64 protnone_mask(u64 val)
{
return 0;
}
static inline u64 flip_protnone_guard(u64 oldval, u64 val, u64 mask)
{
return val;
}
static inline bool __pte_needs_invert(u64 val)
{
return false;
}
#endif /* _ASM_X86_PGTABLE_2LEVEL_H */
pgtable-3level.h 分析
该文件是针对 x86 架构中支持 物理地址扩展PAE(Physical Address Extension)模式的处理器定义的三级页表结构相关的头文件。该文件包含了与三级页表管理相关的宏定义、函数声明等,主要用于处理页表项的读取、设置、清除等操作,源码分析如下:
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_X86_PGTABLE_3LEVEL_H
#define _ASM_X86_PGTABLE_3LEVEL_H
/*
* Intel 物理地址扩展 (PAE) 模式 - PPro+ CPU 上的三级页表。
* Copyright (C) 1999 Ingo Molnar <[email protected]>
*/
/* 用于在调试时打印出错误的页表项信息,帮助定位问题 */
#define pte_ERROR(e) \
pr_err("%s:%d: bad pte %p(%08lx%08lx)\n", \
__FILE__, __LINE__, &(e), (e).pte_high, (e).pte_low)
#define pmd_ERROR(e) \
pr_err("%s:%d: bad pmd %p(%016Lx)\n", \
__FILE__, __LINE__, &(e), pmd_val(e))
#define pgd_ERROR(e) \
pr_err("%s:%d: bad pgd %p(%016Lx)\n", \
__FILE__, __LINE__, &(e), pgd_val(e))
/* 一个通用的原子交换函数,用于以原子方式设置页表项 */
#define pxx_xchg64(_pxx, _ptr, _val) ({ \
_pxx##val_t *_p = (_pxx##val_t *)_ptr; \
_pxx##val_t _o = *_p; \
do { } while (!try_cmpxchg64(_p, &_o, (_val))); \
native_make_##_pxx(_o); \
})
/*
* 使用 set_pte 的规则:被分配的pte“必须”不存在或处于硬件不会尝试更新pte的状态
* 在无法做到这一点的情况下,可使用pte_get_and_clear获取旧的pte值,然后使用set_pte进行更新 -ben
*/
/* 以非原子方式设置页表项PTE */
static inline void native_set_pte(pte_t *ptep, pte_t pte)
{
WRITE_ONCE(ptep->pte_high, pte.pte_high);
smp_wmb();
WRITE_ONCE(ptep->pte_low, pte.pte_low);
}
/* 以原子方式设置页表项PTE */
static inline void native_set_pte_atomic(pte_t *ptep, pte_t pte)
{
pxx_xchg64(pte, ptep, native_pte_val(pte));
}
/* 以原子方式设置页中间目录项PMD */
static inline void native_set_pmd(pmd_t *pmdp, pmd_t pmd)
{
pxx_xchg64(pmd, pmdp, native_pmd_val(pmd));
}
/* 以原子方式设置页上级目录项PUD,如果启用了页表隔离CONFIG_PAGE_TABLE_ISOLATION
则会先对PUD的 P4D 部分进行处理 */
static inline void native_set_pud(pud_t *pudp, pud_t pud)
{
#ifdef CONFIG_PAGE_TABLE_ISOLATION
pud.p4d.pgd = pti_set_user_pgtbl(&pudp->p4d.pgd, pud.p4d.pgd);
#endif
pxx_xchg64(pud, pudp, native_pud_val(pud));
}
/* 对于PTE和PDE,在清除页表条目时必须先清除P位,因此要先清除下半部分,并通过编译器障碍执行排序 */
/* 分别用于清除页表项PTE和页中间目录项PMD,页上级目录项PUD */
static inline void native_pte_clear(struct mm_struct *mm, unsigned long addr,
pte_t *ptep)
{
WRITE_ONCE(ptep->pte_low, 0);
smp_wmb();
WRITE_ONCE(ptep->pte_high, 0);
}
static inline void native_pmd_clear(pmd_t *pmdp)
{
WRITE_ONCE(pmdp->pmd_low, 0);
smp_wmb();
WRITE_ONCE(pmdp->pmd_high, 0);
}
static inline void native_pud_clear(pud_t *pudp)
{
}
static inline void pud_clear(pud_t *pudp)
{
set_pud(pudp, __pud(0));
/*
* 根据英特尔应用程序说明 “TLB、分页结构缓存及其失效”,2007 年 4 月,
* 文件 317080-001,第 8.1 节:在 PAE 模式中,
* 如果顶层 pgd 发生变化,我们必须通过 cr3 明确刷新 TLB...
*
* 目前,所有调用 pud_clear() 的地方要么已经跟上 flush_tlb_mm(),
* 要么不需要刷新 TLB(x86_64 代码或 pud_clear_bad()),因此我们在这里不需要刷新 TLB。
*/
}
/* 用于以原子方式获取并清除页表项PTE和页中间目录项PMD,页上级目录项PUD的值 */
#ifdef CONFIG_SMP
static inline pte_t native_ptep_get_and_clear(pte_t *ptep)
{
return pxx_xchg64(pte, ptep, 0ULL);
}
static inline pmd_t native_pmdp_get_and_clear(pmd_t *pmdp)
{
return pxx_xchg64(pmd, pmdp, 0ULL);
}
static inline pud_t native_pudp_get_and_clear(pud_t *pudp)
{
return pxx_xchg64(pud, pudp, 0ULL);
}
#else
#define native_ptep_get_and_clear(xp) native_local_ptep_get_and_clear(xp)
#define native_pmdp_get_and_clear(xp) native_local_pmdp_get_and_clear(xp)
#define native_pudp_get_and_clear(xp) native_local_pudp_get_and_clear(xp)
#endif
/* 用于在页中间目录项PMD不存在时以更高效的方式设置它,而无需使用昂贵的原子操作
如果PMD已经存在,则仍然使用pxx_xchg64进行设置 */
#ifndef pmdp_establish
#define pmdp_establish pmdp_establish
static inline pmd_t pmdp_establish(struct vm_area_struct *vma,
unsigned long address, pmd_t *pmdp, pmd_t pmd)
{
pmd_t old;
/* 如果pmd已清除当前位,就可以不用昂贵的cmpxchg64:可以逐半更新pmdp,而无需与任何人竞争 */
if (!(pmd_val(pmd) & _PAGE_PRESENT)) {
/* 在设置高位之前,xchg 起到屏障的作用 */
old.pmd_low = xchg(&pmdp->pmd_low, pmd.pmd_low);
old.pmd_high = READ_ONCE(pmdp->pmd_high);
WRITE_ONCE(pmdp->pmd_high, pmd.pmd_high);
return old;
}
return pxx_xchg64(pmd, pmdp, pmd.pmd);
}
#endif
/*
* 编码/解码交换条目和交换PTE。交换PTE是指!pte_none() && !pte_present()的所有PTE。
*
* 交换 PTE 的格式:
*
* 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
* 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
* <----------------- offset ------------------> 0 E <- type --> 0
*
* E 是独占标记,不存储在交换条目中。
*/
/* 交换分区的最大交换类型数量为2^5=32种,每种都有唯一的不同标识符 */
#define SWP_TYPE_BITS 5
/* 生成一个掩码,用于从交换空间条目的值中提取交换类型 */
#define _SWP_TYPE_MASK ((1U << SWP_TYPE_BITS) - 1)
/* 定义了用于表示交换空间偏移量的第一个比特位的位置 */
#define SWP_OFFSET_FIRST_BIT (_PAGE_BIT_PROTNONE + 1)
/* 我们提取/编码偏移量时,总是先将偏移量向上移动,然后再向下移动 */
/* 定义了交换空间偏移量swap offset在交换空间条目值中的起始位置 */
#define SWP_OFFSET_SHIFT (SWP_OFFSET_FIRST_BIT + SWP_TYPE_BITS)
/* 使用BUILD_BUG_ON来在编译时检查MAX_SWAPFILES_SHIFT是否超过了SWP_TYPE_BITS */
#define MAX_SWAPFILES_CHECK() BUILD_BUG_ON(MAX_SWAPFILES_SHIFT > SWP_TYPE_BITS)
/* 从给定的交换空间条目x中提取交换类型 */
#define __swp_type(x) (((x).val) & _SWP_TYPE_MASK)
/* 从给定的交换空间条目x中提取交换偏移量 */
#define __swp_offset(x) ((x).val >> SWP_TYPE_BITS)
/* 根据给定的交换类型和偏移量创建一个交换空间条目 */
#define __swp_entry(type, offset) ((swp_entry_t){((type) & _SWP_TYPE_MASK) \
| (offset) << SWP_TYPE_BITS})
/*
* 通常,__swp_entry() 会将独立arch的 swp_entry_t 转换为独立arch的 swp_entry_t,
* 而__swp_entry_to_pte()只会将结果存储到pte中。但这里我们有32位 swp_entry_t 和 64 位 pte,
* 需要使用整整 64 位。因此,我们通过下面基于 64 位 __swp_entry() 的辅助宏,
* 将 “真正的 ”依赖arch的转换转移到 __swp_entry_too_pte() 中。
*/
/* 用于接受类型type和偏移量offset作为参数,并将它们编码成一个PTE值 */
#define __swp_pteval_entry(type, offset) ((pteval_t) { \
(~(pteval_t)(offset) << SWP_OFFSET_SHIFT >> SWP_TYPE_BITS) \
| ((pteval_t)(type) << (64 - SWP_TYPE_BITS)) })
/* 将swp_entry_t类型的交换空间条目转换为PTE */
#define __swp_entry_to_pte(x) ((pte_t){ .pte = \
__swp_pteval_entry(__swp_type(x), __swp_offset(x)) })
/*
* 与此类似,__pte_to_swp_entry() 不只是提取依赖arch的 swp_entry_t
* 还必须使用以下基于 64 位 __swp_type() 和 __swp_offset() 的宏
* 将其从 64 位转换为 32 位中间表示
*/
/* 用于从PTE中提取交换空间条目的类型和偏移量 */
#define __pteval_swp_type(x) ((unsigned long)((x).pte >> (64 - SWP_TYPE_BITS)))
#define __pteval_swp_offset(x) ((unsigned long)(~((x).pte) << SWP_TYPE_BITS >> SWP_OFFSET_SHIFT))
/* 将PTE转换回swp_entry_t类型的交换空间条目 */
#define __pte_to_swp_entry(pte) (__swp_entry(__pteval_swp_type(pte), \
__pteval_swp_offset(pte)))
/* 我们借用第 7 位来存储交换 PTE 中的唯一性标记。*/
#define _PAGE_SWP_EXCLUSIVE _PAGE_PSE
/* 包含了与页表操作相关的逆向或辅助函数 */
#include <asm/pgtable-invert.h>
#endif /* _ASM_X86_PGTABLE_3LEVEL_H */
标签:__,x86,pud,pte,pmd,源码,页表,Linux6.8,native
From: https://blog.csdn.net/weixin_51432496/article/details/142501312