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

Linux中paging_init页表初始化函数的实现

进行页表初始化paging_init

void __init paging_init(void)
{
#ifdef CONFIG_X86_PAEset_nx();if (nx_enabled)printk("NX (Execute Disable) protection: active\n");
#endifpagetable_init();load_cr3(swapper_pg_dir);#ifdef CONFIG_X86_PAE/** We will bail out later - printk doesn't work right now so* the user would just see a hanging kernel.*/if (cpu_has_pae)set_in_cr4(X86_CR4_PAE);
#endif__flush_tlb_all();kmap_init();zone_sizes_init();
}

1. 代码详细解析

1.1. PAE和NX功能初始化

#ifdef CONFIG_X86_PAEset_nx();if (nx_enabled)printk("NX (Execute Disable) protection: active\n");
#endif
  • CONFIG_X86_PAE:内核配置选项,启用物理地址扩展
  • 只有在启用PAE时才执行这段代码
set_nx();
  • 检测和启用NX功能
  • 检查CPU是否支持NX(No Execute)特性
  • 设置全局变量 nx_enabled
if (nx_enabled)printk("NX (Execute Disable) protection: active\n");
  • 如果NX功能可用,打印激活信息
  • 提供启动时的调试信息

1.2. 页表初始化

        pagetable_init();

这是页表初始化的核心函数,主要完成:

// pagetable_init() 内部大致流程:
1. 初始化内核页表(swapper_pg_dir)
2. 建立内核空间的内存映射
3. 设置固定映射区域
4. 准备早期内存分配所需的页表结构

1.3. 加载页表基址

        load_cr3(swapper_pg_dir);

CR3寄存器:

  • x86架构的页表基址寄存器
  • 包含当前活动页表的物理地址

效果:激活新初始化的内核页表,CPU开始使用新的地址转换规则。

1.4. 启用PAE模式

#ifdef CONFIG_X86_PAE/** We will bail out later - printk doesn't work right now so* the user would just see a hanging kernel.*/if (cpu_has_pae)set_in_cr4(X86_CR4_PAE);
#endif

条件检查:

if (cpu_has_pae)
  • 检查CPU是否硬件支持PAE
  • cpu_has_pae 在启动早期通过CPUID检测设置

设置CR4寄存器:

set_in_cr4(X86_CR4_PAE);
  • X86_CR4_PAE:CR4寄存器的第5位(PAE启用位)
  • 启用物理地址扩展,允许访问超过4GB的物理内存

1.5. 刷新TLB

        __flush_tlb_all();

TLB刷新必要性:

  • 修改页表或CR3后必须刷新TLB
  • 否则CPU可能使用缓存的旧地址转换

1.6. 高端内存映射初始化

        kmap_init();
  • 初始化高端内存的临时映射机制
  • 建立kmap_atomic等函数所需的页表结构
  • 允许内核临时映射高端内存页面进行访问

1.7. 内存区域大小初始化

        zone_sizes_init();
  • 初始化内存管理区的页面计数
  • 设置ZONE_DMAZONE_NORMALZONE_HIGHMEM的大小
  • 为后续的伙伴系统分配器做准备

检测和启用CPU的NX功能set_nx

static void __init set_nx(void)
{unsigned int v[4], l, h;if (cpu_has_pae && (cpuid_eax(0x80000000) > 0x80000001)) {cpuid(0x80000001, &v[0], &v[1], &v[2], &v[3]);if ((v[3] & (1 << 20)) && !disable_nx) {rdmsr(MSR_EFER, l, h);l |= EFER_NX;wrmsr(MSR_EFER, l, h);nx_enabled = 1;__supported_pte_mask |= _PAGE_NX;}}
}

1.代码详细解析

1.1. 变量声明

unsigned int v[4], l, h;
  • v[4]:存储CPUID返回值的数组
    • v[0] = EAX, v[1] = EBX, v[2] = ECX, v[3] = EDX
  • l, h:MSR(模型特定寄存器)的低32位和高32位

1.2. 前置条件检查

if (cpu_has_pae && (cpuid_eax(0x80000000) > 0x80000001)) {

第一个条件:cpu_has_pae

  • 检查CPU是否支持物理地址扩展
  • NX功能需要PAE模式,因为只有在64位页表项中才有NX位

第二个条件:cpuid_eax(0x80000000) > 0x80000001

cpuid_eax(0x80000000)  // 获取最大扩展CPUID功能号
  • CPUID 0x80000000:查询CPU支持的扩展功能范围
  • 如果返回值 > 0x80000001,表示支持 0x80000001 功能
  • CPUID 0x80000001:包含AMD特性位,包括NX支持信息

1.3. 查询CPU特性

cpuid(0x80000001, &v[0], &v[1], &v[2], &v[3]);
CPUID 0x80000001 功能:

这个CPUID调用返回扩展特性位:

寄存器       内容
EDX (v[3])  扩展特性标志位位20: NX/XD (Execute Disable) 支持

1.4. NX特性位检查

if ((v[3] & (1 << 20)) && !disable_nx) {

第一个条件:v[3] & (1 << 20)

  • 检查EDX寄存器的第20位
  • 如果该位为1,表示CPU硬件支持NX功能

第二个条件:!disable_nx

  • disable_nx 是全局变量,通过内核参数设置

1.5. 启用NX功能

rdmsr(MSR_EFER, l, h);
l |= EFER_NX;
wrmsr(MSR_EFER, l, h);
  • MSR_EFER:模型特定寄存器
  • 控制CPU的扩展功能

操作步骤:

  1. 读取当前值rdmsr(MSR_EFER, l, h)
  2. 设置NX位l |= EFER_NX
  3. 写回寄存器wrmsr(MSR_EFER, l, h)

1.6. 设置内核状态

nx_enabled = 1;
__supported_pte_mask |= _PAGE_NX;
  • 设置全局变量,表示NX功能已启用
  • 其他代码可以通过检查这个变量来知道NX是否可用

页表项掩码更新:

  • __supported_pte_mask:定义内核支持的页表项位掩码
  • _PAGE_NX:NX位在页表项中的位置

页表初始化的核心函数pagetable_init

static void __init pagetable_init (void)
{unsigned long vaddr;pgd_t *pgd_base = swapper_pg_dir;#ifdef CONFIG_X86_PAEint i;/* Init entries of the first-level page table to the zero page */for (i = 0; i < PTRS_PER_PGD; i++)set_pgd(pgd_base + i, __pgd(__pa(empty_zero_page) | _PAGE_PRESENT));
#endif/* Enable PSE if available */if (cpu_has_pse) {set_in_cr4(X86_CR4_PSE);}/* Enable PGE if available */if (cpu_has_pge) {set_in_cr4(X86_CR4_PGE);__PAGE_KERNEL |= _PAGE_GLOBAL;__PAGE_KERNEL_EXEC |= _PAGE_GLOBAL;}kernel_physical_mapping_init(pgd_base);remap_numa_kva();/** Fixed mappings, only the page table structure has to be* created - mappings will be set by set_fixmap():*/vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;page_table_range_init(vaddr, 0, pgd_base);permanent_kmaps_init(pgd_base);#ifdef CONFIG_X86_PAE/** Add low memory identity-mappings - SMP needs it when* starting up on an AP from real-mode. In the non-PAE* case we already have these mappings through head.S.* All user-space mappings are explicitly cleared after* SMP startup.*/pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
#endif
}

1. 代码详细解析

1.1. 变量声明和初始化

unsigned long vaddr;
pgd_t *pgd_base = swapper_pg_dir;#ifdef CONFIG_X86_PAEint i;
  • vaddr:虚拟地址变量,用于后续计算
  • pgd_base:指向swapper_pg_dir,这是内核的主页全局目录
  • swapper_pg_dir:内核初始页表的根目录

1.2. PAE模式下的PGD初始化

#ifdef CONFIG_X86_PAE/* Init entries of the first-level page table to the zero page */for (i = 0; i < PTRS_PER_PGD; i++)set_pgd(pgd_base + i, __pgd(__pa(empty_zero_page) | _PAGE_PRESENT));
#endif

将PGD所有条目初始化为指向零页面

  • PTRS_PER_PGD:PGD中的条目数量(PAE模式下通常是4个)

  • empty_zero_page:全零的物理页面,用于处理缺页

  • __pa(empty_zero_page):获取零页面的物理地址

  • _PAGE_PRESENT:设置页面存在位

  • 将所有未使用的PGD条目指向安全的零页面

  • 如果访问未映射的地址,会访问零页面而不是随机内存

  • 零页面全为0,读取返回0,写入触发页错误

1.3. 启用PSE(Page Size Extension)

        /* Enable PSE if available */if (cpu_has_pse) {set_in_cr4(X86_CR4_PSE);}

PSE功能:

  • 允许使用4MB大页面(而不仅仅是4KB)
  • 减少TLB缺失,提高性能
  • 通过设置CR4寄存器的PSE位启用

1.4. 启用PGE(Page Global Enable)

        /* Enable PGE if available */if (cpu_has_pge) {set_in_cr4(X86_CR4_PGE);__PAGE_KERNEL |= _PAGE_GLOBAL;__PAGE_KERNEL_EXEC |= _PAGE_GLOBAL;}

PGE功能:

  • 全局页面:在任务切换时不被TLB刷新
  • 提高内核代码的性能(内核代码在所有进程间共享)
  • 更新内核页面的标志位,包含_PAGE_GLOBAL

1.5. 内核物理映射初始化

        kernel_physical_mapping_init(pgd_base);

核心功能:建立内核直接映射

  • 将物理内存直接映射到内核虚拟地址空间

1.6. NUMA内存重映射

        remap_numa_kva();

NUMA支持:

  • 非统一内存访问:多处理器系统中内存访问时间不一致
  • 为NUMA系统重新映射内核虚拟地址空间
  • 优化内存访问性能

1.7. 固定映射区域初始化

        /** Fixed mappings, only the page table structure has to be* created - mappings will be set by set_fixmap():*/vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;page_table_range_init(vaddr, 0, pgd_base);

固定映射:

  • 特殊用途:为需要固定虚拟地址的设备保留
  • 计算范围__fix_to_virt(__end_of_fixed_addresses - 1) 获取最后一个固定映射地址
  • 初始化页表page_table_range_init 为固定映射区域创建页表结构

1.8. 永久内核映射初始化

        permanent_kmaps_init(pgd_base);

永久内核映射:

  • kmap()函数建立永久内核映射区域
  • 允许内核临时映射高端内存页面

1.9. PAE模式下的低内存恒等映射

#ifdef CONFIG_X86_PAE/** Add low memory identity-mappings - SMP needs it when* starting up on an AP from real-mode. In the non-PAE* case we already have these mappings through head.S.* All user-space mappings are explicitly cleared after* SMP startup.*/pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
#endif

恒等映射的重要性:

1. BSP启动 → 建立完整页表(包括内核直接映射)
2. 设置临时恒等映射 → pgd_base[0] = pgd_base[768]
3. 唤醒APs → APs通过恒等映射执行启动代码
4. APs切换到保护模式 → 使用正常的内核页表
5. 清除恒等映射 → 恢复用户空间隔离
6. SMP系统就绪 → 所有处理器正常运行

建立内核直接内存映射kernel_physical_mapping_init

static void __init kernel_physical_mapping_init(pgd_t *pgd_base)
{unsigned long pfn;pgd_t *pgd;pmd_t *pmd;pte_t *pte;int pgd_idx, pmd_idx, pte_ofs;pgd_idx = pgd_index(PAGE_OFFSET);pgd = pgd_base + pgd_idx;pfn = 0;for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {pmd = one_md_table_init(pgd);if (pfn >= max_low_pfn)continue;for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) {unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET;/* Map with big pages if possible, otherwise create normal page tables. */if (cpu_has_pse) {unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;if (is_kernel_text(address) || is_kernel_text(address2))set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));elseset_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));pfn += PTRS_PER_PTE;} else {pte = one_page_table_init(pmd);for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) {if (is_kernel_text(address))set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));elseset_pte(pte, pfn_pte(pfn, PAGE_KERNEL));}}}}
}

1. 代码详细解析

1.1. 变量声明和初始化

        unsigned long pfn;pgd_t *pgd;pmd_t *pmd;pte_t *pte;int pgd_idx, pmd_idx, pte_ofs;pgd_idx = pgd_index(PAGE_OFFSET);pgd = pgd_base + pgd_idx;pfn = 0;

变量说明:

  • pfn:物理页框号,从0开始
  • pgd, pmd, pte:各级页表指针
  • pgd_idx, pmd_idx, pte_ofs:各级页表的索引
  • pgd_index(PAGE_OFFSET):计算内核空间起始地址在PGD中的索引

1.2. 外层循环:遍历PGD

        for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {pmd = one_md_table_init(pgd);if (pfn >= max_low_pfn)continue;

PGD循环:

  • pgd_idxPTRS_PER_PGD-1
  • 覆盖整个内核虚拟地址空间

PMD表初始化:

pmd = one_md_table_init(pgd);
  • 为当前PGD条目分配和初始化PMD表
  • 返回指向PMD表的指针

提前终止检查:

if (pfn >= max_low_pfn)continue;

如果所有物理内存都已映射,跳过剩余的PMD处理。

1.3. 内层循环:遍历PMD

                for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) {unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET;

PMD循环:

  • 遍历PMD中的所有条目
  • 继续直到处理完所有物理内存或PMD结束

地址计算:

address = pfn * PAGE_SIZE + PAGE_OFFSET;

计算当前物理页面对应的内核虚拟地址。

1.4. 大页映射处理(PSE支持)

                        /* Map with big pages if possible, otherwise create normal page tables. */if (cpu_has_pse) {unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;if (is_kernel_text(address) || is_kernel_text(address2))set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));elseset_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));pfn += PTRS_PER_PTE;}

大页优势:

  • 普通页:4KB,需要512个PTE条目映射2MB
  • 大页:2MB,一个PMD条目直接映射2MB

地址范围检查:

address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;

计算2MB大页的结束地址,用于检查是否包含内核代码。

内核代码特殊处理:

if (is_kernel_text(address) || is_kernel_text(address2))

如果大页包含内核代码区域,需要设置可执行权限

大页设置:

set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));  // 可执行大页
set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));       // 普通大页
pfn += PTRS_PER_PTE;  // 跳过512个页面(2MB)

1.5. 普通页映射处理(无PSE支持)

                        } else {pte = one_page_table_init(pmd);for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) {if (is_kernel_text(address))set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));elseset_pte(pte, pfn_pte(pfn, PAGE_KERNEL));}}

PTE表初始化:

pte = one_page_table_init(pmd);

为当前PMD条目分配和初始化PTE表

PTE级别循环:

for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++)

遍历PTE表中的所有条目(通常512个),为每个物理页面创建映射

内核代码检测:

if (is_kernel_text(address))set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));  // 可执行页面
elseset_pte(pte, pfn_pte(pfn, PAGE_KERNEL));       // 普通页面

在NUMA系统中重新映射内核虚拟地址空间remap_numa_kva

void __init remap_numa_kva(void)
{void *vaddr;unsigned long pfn;int node;for (node = 1; node < numnodes; ++node) {for (pfn=0; pfn < node_remap_size[node]; pfn += PTRS_PER_PTE) {vaddr = node_remap_start_vaddr[node]+(pfn<<PAGE_SHIFT);set_pmd_pfn((ulong) vaddr,node_remap_start_pfn[node] + pfn,PAGE_KERNEL_LARGE);}}
}

1. 代码详细解析

1.1. 变量声明

        void *vaddr;unsigned long pfn;int node;

变量说明:

  • vaddr:虚拟地址指针,用于计算映射的目标地址
  • pfn:页框号(Page Frame Number),用于遍历物理页面
  • node:NUMA节点编号,用于遍历所有节点

1.2. 外层循环:遍历NUMA节点

        for (node = 1; node < numnodes; ++node) {

循环参数:

  • node = 1:从节点1开始(节点0通常是启动节点,已经映射好了)
  • node < numnodes:遍历到最后一个节点
  • numnodes:系统中NUMA节点的总数

NUMA系统:

  1. 多处理器架构:将系统划分为多个节点
  2. 非统一访问:本地内存访问快,远程内存访问慢

1.3. 内层循环:遍历节点中的页面

                for (pfn=0; pfn < node_remap_size[node]; pfn += PTRS_PER_PTE) {

循环参数:

  • pfn=0:从节点的第一个页面开始
  • pfn < node_remap_size[node]:遍历节点中需要重映射的所有页面
  • pfn += PTRS_PER_PTE:每次增加512个页面

1.4. 计算虚拟地址

                        vaddr = node_remap_start_vaddr[node]+(pfn<<PAGE_SHIFT);

地址计算:

node_remap_start_vaddr[node]  // 节点重映射的起始虚拟地址
pfn << PAGE_SHIFT             // 页框号转换为字节偏移(×4096)

1.5. 设置大页映射

                        set_pmd_pfn((ulong) vaddr,node_remap_start_pfn[node] + pfn,PAGE_KERNEL_LARGE);

函数参数:

set_pmd_pfn(虚拟地址, 物理页框号, 页面标志)

映射效果:

虚拟地址 vaddr → 物理地址 (node_remap_start_pfn[node]) + (pfn × 4096)
使用2MB大页映射

1.6. 为什么需要重映射?

问题

  • 内核默认使用统一的虚拟地址空间
  • 但物理内存分布在不同的NUMA节点上
  • 远程内存访问速度较慢

解决方案

  • 为每个NUMA节点创建优化的虚拟地址映射
  • 使得CPU可以高效访问本地内存
  • 减少远程内存访问的开销

2. 关键数据结构

NUMA重映射信息:

// 节点重映射起始虚拟地址
node_remap_start_vaddr[node]// 节点重映射起始物理页框号  
node_remap_start_pfn[node]// 节点需要重映射的页面数量
node_remap_size[node]

这些数据的来源:

  • 在系统启动时通过setup_memory函数检测
  • 由架构特定的NUMA初始化代码计算

为指定的虚拟地址范围初始化页表结构page_table_range_init

static void __init page_table_range_init (unsigned long start, unsigned long end, pgd_t *pgd_base)
{pgd_t *pgd;pmd_t *pmd;int pgd_idx, pmd_idx;unsigned long vaddr;vaddr = start;pgd_idx = pgd_index(vaddr);pmd_idx = pmd_index(vaddr);pgd = pgd_base + pgd_idx;for ( ; (pgd_idx < PTRS_PER_PGD) && (vaddr != end); pgd++, pgd_idx++) {if (pgd_none(*pgd))one_md_table_init(pgd);pmd = pmd_offset(pgd, vaddr);for (; (pmd_idx < PTRS_PER_PMD) && (vaddr != end); pmd++, pmd_idx++) {if (pmd_none(*pmd))one_page_table_init(pmd);vaddr += PMD_SIZE;}pmd_idx = 0;}
}

1. 代码详细解析

1.1. 变量声明和初始化

        pgd_t *pgd;pmd_t *pmd;int pgd_idx, pmd_idx;unsigned long vaddr;vaddr = start;pgd_idx = pgd_index(vaddr);pmd_idx = pmd_index(vaddr);pgd = pgd_base + pgd_idx;
  • pgd_t *pgd:当前PGD条目指针
  • pmd_t *pmd:当前PMD条目指针
  • pgd_idx, pmd_idx:当前在PGD和PMD中的索引
  • vaddr:当前处理的虚拟地址

初始化计算:

vaddr = start;                            // 从起始地址开始
pgd_idx = pgd_index(vaddr);               // 计算起始地址的PGD索引
pmd_idx = pmd_index(vaddr);               // 计算起始地址的PMD索引
pgd = pgd_base + pgd_idx;                 // 获取起始PGD指针

1.2. 外层循环:遍历PGD

        for ( ; (pgd_idx < PTRS_PER_PGD) && (vaddr != end); pgd++, pgd_idx++) {

循环条件:

  • pgd_idx < PTRS_PER_PGD:确保不超出PGD表范围
  • vaddr != end:确保没有处理完整个地址范围
  • pgd++, pgd_idx++:移动到下一个PGD条目

1.3. PGD级别初始化

                if (pgd_none(*pgd))one_md_table_init(pgd);

惰性初始化:

  • 检查PGD条目是否为空(未初始化)
  • 如果为空,调用one_md_table_init分配和初始化PMD表
  • 如果已初始化,直接使用现有的PMD表

1.4. 获取PMD指针

                pmd = pmd_offset(pgd, vaddr);

计算当前虚拟地址对应的PMD条目指针。

1.5. 内层循环:遍历PMD

                for (; (pmd_idx < PTRS_PER_PMD) && (vaddr != end); pmd++, pmd_idx++) {
  • pmd_idx < PTRS_PER_PMD:不超出PMD表范围(通常512个条目)
  • vaddr != end:没有处理完地址范围
  • pmd++, pmd_idx++:移动到下一个PMD条目

1.6. PMD级别初始化

                        if (pmd_none(*pmd))one_page_table_init(pmd);

PTE表分配:

  • 检查PMD条目是否为空
  • 如果为空,调用one_page_table_init分配和初始化PTE表
  • 如果已初始化,跳过PTE表分配

1.7. 地址递增和循环控制

                        vaddr += PMD_SIZE;}pmd_idx = 0;}

地址递增:

vaddr += PMD_SIZE;  // 通常2MB,移动到下一个PMD管理的区域

PMD索引重置:

pmd_idx = 0;  // 当切换到下一个PGD时,PMD索引从0开始

初始化永久内核映射permanent_kmaps_init

void __init permanent_kmaps_init(pgd_t *pgd_base)
{pgd_t *pgd;pmd_t *pmd;pte_t *pte;unsigned long vaddr;vaddr = PKMAP_BASE;page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);pgd = swapper_pg_dir + pgd_index(vaddr);pmd = pmd_offset(pgd, vaddr);pte = pte_offset_kernel(pmd, vaddr);pkmap_page_table = pte;
}

1. 代码详细解析

1.1. 变量声明

        pgd_t *pgd;pmd_t *pmd;pte_t *pte;unsigned long vaddr;

变量说明:

  • pgd_t *pgd:页全局目录指针
  • pmd_t *pmd:页中间目录指针
  • pte_t *pte:页表条目指针
  • unsigned long vaddr:虚拟地址变量

1.2. 设置永久内核映射基地址

        vaddr = PKMAP_BASE;

PKMAP_BASE 定义:

  • 永久内核映射区域的起始虚拟地址
#define PKMAP_BASE (0xff800000UL)

1.3. 初始化页表范围

        page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);

参数分解:

  • vaddr:起始地址 = PKMAP_BASE
  • vaddr + PAGE_SIZE*LAST_PKMAP:结束地址
  • pgd_base:页表根目录

计算映射范围:

PAGE_SIZE * LAST_PKMAP  // 永久映射区域的总大小

1.4. 获取PGD指针

        pgd = swapper_pg_dir + pgd_index(vaddr);

1.5. 获取PMD指针

        pmd = pmd_offset(pgd, vaddr);

通过PGD条目找到对应的PMD表指针

1.6. 获取PTE指针

        pte = pte_offset_kernel(pmd, vaddr);

关键操作:

pte_offset_kernel(pmd, vaddr)  // 计算PKMAP_BASE对应的PTE条目

返回的PTE:

指向永久内核映射区域第一个页面的页表条目

1.7. 保存PTE表指针

        pkmap_page_table = pte;

这个指针是永久内核映射机制的核心入口点,后续的kmap函数依赖它

1.8. 为什么需要永久内核映射?

32位系统的限制

  • 虚拟地址空间只有4GB
  • 内核直接映射通常只有896MB
  • 物理内存可能超过4GB(使用PAE)
  • 需要机制来访问高端内存

PKMAP工作机制:

  • 永久内核映射使用固定的虚拟地址区域
  • 通过页表动态映射不同的物理页面
  • 类似"窗口",通过这个窗口访问高端内存

初始化PMD页表one_md_table_init

static pmd_t * __init one_md_table_init(pgd_t *pgd)
{pmd_t *pmd_table;#ifdef CONFIG_X86_PAEpmd_table = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);set_pgd(pgd, __pgd(__pa(pmd_table) | _PAGE_PRESENT));if (pmd_table != pmd_offset(pgd, 0))BUG();
#elsepmd_table = pmd_offset(pgd, 0);
#endifreturn pmd_table;
}

1. 代码详细解析

1.1. 函数声明和变量

static pmd_t * __init one_md_table_init(pgd_t *pgd)
{pmd_t *pmd_table;

参数和变量:

  • pgd_t *pgd:输入的PGD条目指针
  • pmd_t *pmd_table:返回的PMD表指针
  • __init:表示这是初始化函数,完成后内存会被释放

1.2. PAE模式下的PMD表初始化

#ifdef CONFIG_X86_PAEpmd_table = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);

内存分配:

alloc_bootmem_low_pages(PAGE_SIZE)
  • 低端内存中分配一个页面(4KB)
  • 用于存储PMD表
  • 返回虚拟地址,转换为pmd_t *类型

为什么需要分配?

在PAE模式下:

  • 页表项是64位的(8字节)
  • 每个PMD表需要4KB空间存储512个条目
  • 需要动态分配内存

1.3. 设置PGD条目

        set_pgd(pgd, __pgd(__pa(pmd_table) | _PAGE_PRESENT));

操作分解:

__pa(pmd_table)                    // PMD表的物理地址
__pa(pmd_table) | _PAGE_PRESENT    // 组合物理地址和存在位
__pgd(...)                         // 创建PGD条目值
set_pgd(pgd, ...)                  // 设置PGD条目

设置后的PGD条目:

PGD条目指向: PMD表的物理地址 | PRESENT位

1.4. 一致性验证

        if (pmd_table != pmd_offset(pgd, 0))BUG();
验证逻辑:
pmd_offset(pgd, 0)  // 根据PGD计算PMD表应该的位置
  • 检查实际分配的PMD表位置与通过PGD计算的位置是否一致
  • 如果不一致,触发内核BUG(严重错误)

1.5. 非PAE模式下的PMD处理

#elsepmd_table = pmd_offset(pgd, 0);
#endifreturn pmd_table;
}

非PAE模式:

pmd_table = pmd_offset(pgd, 0);
  • 在非PAE模式下,PMD表是预分配
  • 直接通过pmd_offset计算PMD表位置
  • 不需要动态分配内存

初始化PTE页表one_page_table_init

static pte_t * __init one_page_table_init(pmd_t *pmd)
{if (pmd_none(*pmd)) {pte_t *page_table = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_TABLE));if (page_table != pte_offset_kernel(pmd, 0))BUG();return page_table;}return pte_offset_kernel(pmd, 0);
}

1. 代码详细解析

1.1. 函数声明

static pte_t * __init one_page_table_init(pmd_t *pmd)
{

参数和返回值:

  • pmd_t *pmd:输入的PMD条目指针
  • 返回值pte_t * - PTE表的指针
  • __init:初始化函数,完成后内存会被释放

1.2. PMD空值检查

        if (pmd_none(*pmd)) {
  • 避免重复初始化:如果PMD已指向有效的PTE表,直接返回
  • 惰性初始化:只在需要时分配PTE表,节省内存

1.3. PTE表内存分配

                pte_t *page_table = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);

内存分配:

alloc_bootmem_low_pages(PAGE_SIZE)
  • 低端内存中分配一个页面(4KB)
  • 用于存储PTE表(页表条目数组)
  • 返回虚拟地址,转换为pte_t *类型

1.4. 设置PMD条目

                set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_TABLE));

操作分解:

__pa(page_table)                    // PTE表的物理地址
__pa(page_table) | _PAGE_TABLE      // 组合物理地址和页表标志
__pmd(...)                          // 创建PMD条目值
set_pmd(pmd, ...)                   // 设置PMD条目

_PAGE_TABLE标志:

// 包含多个页表相关标志位,如:
_PAGE_PRESENT   // 页面存在
_PAGE_RW        // 可读写  
_PAGE_USER      // 用户可访问
_PAGE_ACCESSED  // 已访问

设置后的PMD条目:

PMD条目指向: PTE表的物理地址 | 页表标志位

1.5. 一致性验证

                if (page_table != pte_offset_kernel(pmd, 0))BUG();

验证逻辑:

pte_offset_kernel(pmd, 0)  // 通过PMD计算PTE表应该的位置
  • 检查实际分配的PTE表位置与通过PMD计算的位置是否一致
  • 如果不一致,触发内核BUG

1.6. 返回PTE表指针

                return page_table;}

返回新分配的PTE表指针,供调用者继续初始化具体的页表条目。

1.7. 已初始化情况的处理

        return pte_offset_kernel(pmd, 0);
}

如果PMD已初始化:

pte_offset_kernel(pmd, 0)
  • 直接通过PMD条目计算PTE表的虚拟地址
  • 返回现有的PTE表指针
http://www.dtcms.com/a/483245.html

相关文章:

  • 端侧大模型推理笔记
  • 可以建立网站的平台seo专业课程
  • 网站在那里备案企业信息管理系统的设计与实现
  • 设备管理系统原型设计实战:PC/APP/PDA多端页面解析
  • 西安建设教育网站wordpress homepage
  • Transformer-输入部分
  • Python接口与抽象基类详解:从规范定义到高级应用
  • 免费网站建设价格费用.net做网站用什么的多
  • 专业高端网站建设服务公司百度指数趋势
  • AI商品换模特及场景智能化
  • 网站开发定制推广杭州视频在线生成链接
  • 异步任务使用场景与实践
  • 300多个Html5小游戏列表和下载地址
  • 企业门户网站方案建网站有报价单吗
  • 企业网站开发价钱低免费开个人网店
  • 建网站软件下载那个软件可以做三个视频网站
  • Excel使用教程笔记
  • 论文阅读《LIMA:Less Is More for Alignment》
  • wordpress 网站暂停app建设网站
  • 考研408--组成原理--day1
  • 网络公司构建网站杭州旅游团购网站建设
  • 【数值分析】非线性方程与方程组的数值解法的经典算法(附MATLAB代码)
  • 文件外链网站智慧团建官网登录入口电脑版
  • 如何在Windows上为Java配置多个版本的环境变量
  • 如何将自己做的网站放到网上去如何做电商创业
  • 杭州市建设信用网郑州优化网站关键词
  • 农业与供应链类 RWA 落地研究报告
  • p2p理财网站开发cms和wordpress
  • 合肥seo整站优化网站做跳转付款
  • 物联网的调试