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

Linux中页表缓存初始化pgtable_cache_init函数的实现

为PGD和PMD创建slab分配器pgtable_cache_init

void __init pgtable_cache_init(void)
{if (PTRS_PER_PMD > 1) {pmd_cache = kmem_cache_create("pmd",PTRS_PER_PMD*sizeof(pmd_t),PTRS_PER_PMD*sizeof(pmd_t),0,pmd_ctor,NULL);if (!pmd_cache)panic("pgtable_cache_init(): cannot create pmd cache");}pgd_cache = kmem_cache_create("pgd",PTRS_PER_PGD*sizeof(pgd_t),PTRS_PER_PGD*sizeof(pgd_t),0,pgd_ctor,PTRS_PER_PMD == 1 ? pgd_dtor : NULL);if (!pgd_cache)panic("pgtable_cache_init(): Cannot create pgd cache");
}

函数功能概述

这个函数初始化内核的页表缓存系统,为页全局目录(PGD)和页中间目录(PMD)创建专用的slab分配器缓存。这是虚拟内存管理系统的核心基础设施

代码逐行详细解释

第一部分:函数声明和PMD缓存条件检查

void __init pgtable_cache_init(void)
{if (PTRS_PER_PMD > 1) {
  • void __init pgtable_cache_init(void)

    • __init:这是一个宏,表示该函数只在系统初始化阶段使用。初始化完成后,这个函数占用的内存会被释放,不会一直留在内核中
    • 函数名 pgtable_cache_init 明确表示这是页表缓存的初始化函数
    • 无参数,无返回值
  • if (PTRS_PER_PMD > 1)

    • PTRS_PER_PMD 是一个架构相关的常量,表示每个PMD(Page Middle Directory)中包含的指针数量
    • 这个条件检查系统是否使用三级页表架构:
      • 如果 PTRS_PER_PMD > 1:表示使用三级页表(PGD → PMD → PTE)
      • 如果 PTRS_PER_PMD == 1:表示使用两级页表(PGD → PTE),PMD层被省略

第二部分:PMD slab缓存创建

                pmd_cache = kmem_cache_create("pmd",PTRS_PER_PMD*sizeof(pmd_t),PTRS_PER_PMD*sizeof(pmd_t),0,pmd_ctor,NULL);
  • pmd_cache = kmem_cache_create(...)

    • pmd_cache 是一个全局变量,用于存储PMD slab缓存的指针。
    • kmem_cache_create 是内核slab分配器的API,用于创建专用的对象缓存。
  • 参数详细解析

    • "pmd":缓存的名称字符串,用于调试和/proc/slabinfo中的显示。

    • PTRS_PER_PMD*sizeof(pmd_t):第一个大小参数是对象大小

      • pmd_t 是PMD项的数据类型
      • PTRS_PER_PMD 是每个PMD中的项数
      • 计算结果是分配一个完整PMD表所需的总内存大小
    • PTRS_PER_PMD*sizeof(pmd_t):第二个大小参数是对齐大小

      • 这里与对象大小相同,确保每个PMD对象正确对齐
      • 对齐对于CPU访问效率和缓存性能很重要
    • 0:标志位,控制缓存的行为。

      • 0表示使用默认标志,不设置特殊行为
    • pmd_ctor构造函数,在从缓存分配对象时自动调用。

      • 用于初始化PMD内存,将其所有项设置为空或无效状态
      • 确保新分配的PMD处于已知的清洁状态
    • NULL析构函数,在对象返回到缓存时调用。

      • 这里设为NULL,表示PMD对象不需要特殊的清理操作
                if (!pmd_cache)panic("pgtable_cache_init(): cannot create pmd cache");}
  • if (!pmd_cache)

    • 检查 kmem_cache_create 的返回值,如果返回NULL表示缓存创建失败。
  • panic("pgtable_cache_init(): cannot create pmd cache")

    • panic 是内核的紧急处理函数,会使系统停止运行。
    • 如果PMD缓存创建失败,系统无法正常进行内存管理,必须立即停止。
    • 错误信息明确指出了失败的原因和位置。

第三部分:PGD slab缓存创建

        pgd_cache = kmem_cache_create("pgd",PTRS_PER_PGD*sizeof(pgd_t),PTRS_PER_PGD*sizeof(pgd_t),0,pgd_ctor,PTRS_PER_PMD == 1 ? pgd_dtor : NULL);
  • PGD缓存创建,与PMD类似但有一些重要区别:

  • 参数解析

    • "pgd":缓存名称,标识这是PGD缓存

    • PTRS_PER_PGD*sizeof(pgd_t):对象大小,计算一个完整PGD表所需内存

      • pgd_t 是PGD项的数据类型
      • PTRS_PER_PGD 是每个PGD中的项数
    • PTRS_PER_PGD*sizeof(pgd_t):对齐大小

    • 0:标志位。

    • pgd_ctor:PGD的构造函数,用于初始化新分配的PGD。

    • PTRS_PER_PMD == 1 ? pgd_dtor : NULL条件析构函数

      • 这是一个三元条件表达式:
        • 如果 PTRS_PER_PMD == 1(两级页表),使用 pgd_dtor 析构函数
        • 否则(三级页表),使用 NULL(无析构函数)
      • 这是因为在两级页表架构中,PGD需要特殊的清理操作
        if (!pgd_cache)panic("pgtable_cache_init(): Cannot create pgd cache");
}
  • if (!pgd_cache)

    • 检查PGD缓存是否创建成功。
  • panic("pgtable_cache_init(): Cannot create pgd cache")

    • 如果PGD缓存创建失败,触发内核恐慌。
    • PGD是页表的根目录,没有它系统完全无法进行虚拟内存管理。

架构差异的深层理解

三级页表架构(如x86 with PAE)

虚拟地址: [PGD索引][PMD索引][PTE索引][页内偏移]
页表结构: PGD → PMD → PTE → 物理页
  • PTRS_PER_PMD > 1:需要独立的PMD缓存
  • PGD不需要特殊析构

两级页表架构(如早期x86)

虚拟地址: [PGD索引][PTE索引][页内偏移]  
页表结构: PGD → PTE → 物理页
  • PTRS_PER_PMD == 1:PMD被优化掉,不需要PMD缓存
  • PGD可能需要特殊清理,因此需要析构函数

PGD缓存析构过程pgd_dtor

static inline void pgd_list_del(pgd_t *pgd)
{struct page *next, **pprev, *page = virt_to_page(pgd);next = (struct page *)page->index;pprev = (struct page **)page->private;*pprev = next;if (next)next->private = (unsigned long)pprev;
}
/* never called when PTRS_PER_PMD > 1 */
void pgd_dtor(void *pgd, kmem_cache_t *cache, unsigned long unused)
{unsigned long flags; /* can be called from interrupt context */spin_lock_irqsave(&pgd_lock, flags);pgd_list_del(pgd);spin_unlock_irqrestore(&pgd_lock, flags);
}

函数功能概述

这两个函数协同工作,用于从全局PGD链表中安全地删除一个PGD页表。这是两级页表架构中PGD缓存析构过程的核心部分

代码逐行详细解释

第一部分:pgd_list_del - 链表删除核心逻辑

static inline void pgd_list_del(pgd_t *pgd)
{struct page *next, **pprev, *page = virt_to_page(pgd);
  • static inline void pgd_list_del(pgd_t *pgd)

    • static:函数只在当前文件内可见
    • inline:建议编译器内联展开,避免函数调用开销
    • pgd_t *pgd:参数是要删除的PGD页表的虚拟地址
  • 变量声明

    • struct page *next:存储链表中的下一个节点
    • struct page **pprev:指向前驱节点的next指针的指针
    • struct page *page = virt_to_page(pgd)关键转换
      • virt_to_page(pgd) 将PGD的虚拟地址转换为对应的struct page结构指针
      • 在内核中,每个物理页对应一个struct page结构
      • 这里利用struct page来存储链表的指针信息
        next = (struct page *)page->index;pprev = (struct page **)page->private;
  • next = (struct page *)page->index

    • page->index 存储着链表中下一个节点的地址
    • 这里进行了类型转换,将unsigned long转换为struct page *
    • 这是Linux内核中常见的"借用"技巧,利用struct page的字段存储自定义数据
  • pprev = (struct page **)page->private

    • page->private 存储着指向前驱节点next指针的地址
    • 转换为struct page **类型,即指向指针的指针
    • 这种设计实现了高效的链表删除操作

第二部分:双向链表删除算法

        *pprev = next;if (next)next->private = (unsigned long)pprev;
  • *pprev = next

    • 这是双向链表删除的核心操作
    • 更新前驱节点的next指针,使其指向当前节点的下一个节点
    • 这样就从链表中移除了当前节点
  • if (next) next->private = (unsigned long)pprev

    • 如果存在下一个节点,更新它的private字段
    • 让下一个节点的pprev指向前驱节点的next指针位置
    • 这样就完成了双向链表的完整性维护

第三部分:pgd_dtor - 析构函数实现

/* never called when PTRS_PER_PMD > 1 */
void pgd_dtor(void *pgd, kmem_cache_t *cache, unsigned long unused)
{unsigned long flags; /* can be called from interrupt context */
  • /* never called when PTRS_PER_PMD > 1 */

    • 明确说明这个函数只在两级页表架构中被调用
    • 在三级页表架构中不会使用
  • 函数签名

    • void *pgd:要销毁的PGD对象
    • kmem_cache_t *cache:所属的slab缓存(未使用)
    • unsigned long unused:未使用的参数
  • unsigned long flags

    • 用于保存中断状态的变量
    • 这个函数从中断上下文调用

第四部分:中断安全的锁保护

        spin_lock_irqsave(&pgd_lock, flags);pgd_list_del(pgd);spin_unlock_irqrestore(&pgd_lock, flags);
}
  • spin_lock_irqsave(&pgd_lock, flags)

    • 获取自旋锁并保存当前中断状态
    • pgd_lock 是保护PGD链表的全局自旋锁
    • irqsave 变体会在获取锁时禁用中断,防止死锁
  • pgd_list_del(pgd)

    • 在锁保护下调用实际的链表删除函数
  • spin_unlock_irqrestore(&pgd_lock, flags)

    • 释放自旋锁并恢复之前的中断状态
    • 这是关键的安全操作,确保系统稳定性

数据结构关系详解

PGD链表结构

全局PGD链表:
pgd_list_head → page1 → page2 → page3 → ... → NULL↑           ↑       ↑       ↑|           |       |       |
通过pprev/next指针连接每个struct page的字段使用:
- page->index:    存储next指针 (struct page *)
- page->private:  存储pprev指针 (struct page **)

PGD缓存初始化过程pgd_ctor

static inline void pgd_list_add(pgd_t *pgd)
{struct page *page = virt_to_page(pgd);page->index = (unsigned long)pgd_list;if (pgd_list)pgd_list->private = (unsigned long)&page->index;pgd_list = page;page->private = (unsigned long)&pgd_list;
}
void pgd_ctor(void *pgd, kmem_cache_t *cache, unsigned long unused)
{unsigned long flags;if (PTRS_PER_PMD == 1)spin_lock_irqsave(&pgd_lock, flags);memcpy((pgd_t *)pgd + USER_PTRS_PER_PGD,swapper_pg_dir + USER_PTRS_PER_PGD,(PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t));if (PTRS_PER_PMD > 1)return;pgd_list_add(pgd);spin_unlock_irqrestore(&pgd_lock, flags);memset(pgd, 0, USER_PTRS_PER_PGD*sizeof(pgd_t));
}

函数功能概述

这两个函数协同工作,用于初始化新的PGD页表并将其添加到全局PGD链表中。主要功能包括复制内核空间映射、管理PGD链表以及在两级页表架构中的特殊处理

代码逐行详细解释

第一部分:pgd_list_add - 链表添加核心逻辑

static inline void pgd_list_add(pgd_t *pgd)
{struct page *page = virt_to_page(pgd);
  • static inline void pgd_list_add(pgd_t *pgd)

    • static inline:内联函数,减少函数调用开销
    • pgd_t *pgd:要添加到链表的PGD虚拟地址
  • struct page *page = virt_to_page(pgd)

    • 将PGD虚拟地址转换为对应的struct page结构
    • 这是内核中物理页管理的核心数据结构
        page->index = (unsigned long)pgd_list;if (pgd_list)pgd_list->private = (unsigned long)&page->index;
  • page->index = (unsigned long)pgd_list

    • 设置新页面的index字段为当前链表头
    • 新节点指向原来的链表头,实现头部插入
  • if (pgd_list) pgd_list->private = (unsigned long)&page->index

    • 如果链表不为空,更新原链表头节点的private字段
    • 让原链表头指向新节点的index字段位置
    • 维护双向链表的完整性
        pgd_list = page;page->private = (unsigned long)&pgd_list;
}
  • pgd_list = page

    • 更新全局链表头指针,指向新添加的页面
    • 完成头部插入操作
  • page->private = (unsigned long)&pgd_list

    • 设置新页面的private字段指向链表头指针的地址
    • 这样新节点可以直接找到链表头

第二部分:pgd_ctor - 构造函数实现

void pgd_ctor(void *pgd, kmem_cache_t *cache, unsigned long unused)
{unsigned long flags;if (PTRS_PER_PMD == 1)spin_lock_irqsave(&pgd_lock, flags);
  • 函数参数

    • void *pgd:要初始化的PGD内存
    • kmem_cache_t *cache:所属的slab缓存(未使用)
    • unsigned long unused:未使用的参数
  • if (PTRS_PER_PMD == 1) spin_lock_irqsave(&pgd_lock, flags)

    • 条件加锁:只在两级页表架构中需要锁保护
    • 因为只有在两级页表中才需要操作共享的PGD链表
    • 三级页表架构不需要链表管理
        memcpy((pgd_t *)pgd + USER_PTRS_PER_PGD,swapper_pg_dir + USER_PTRS_PER_PGD,(PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t));
  • 关键的内存复制操作
    • 源地址swapper_pg_dir + USER_PTRS_PER_PGD

      • swapper_pg_dir 是内核初始页全局目录
      • USER_PTRS_PER_PGD 是用户空间PGD项的数量
      • 所以这是内核空间映射的起始位置
    • 目标地址(pgd_t *)pgd + USER_PTRS_PER_PGD

      • 新PGD的内核空间部分
    • 复制大小(PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t)

      • 只复制内核空间部分,不复制用户空间部分
        if (PTRS_PER_PMD > 1)return;pgd_list_add(pgd);spin_unlock_irqrestore(&pgd_lock, flags);
  • if (PTRS_PER_PMD > 1) return

    • 如果是三级页表架构,直接返回
    • 不需要后续的链表操作和用户空间初始化
  • pgd_list_add(pgd)

    • 将新PGD添加到全局链表
    • 只在两级页表架构中执行
  • spin_unlock_irqrestore(&pgd_lock, flags)

    • 释放锁并恢复中断状态
        memset(pgd, 0, USER_PTRS_PER_PGD*sizeof(pgd_t));
}
  • memset(pgd, 0, USER_PTRS_PER_PGD*sizeof(pgd_t))
    • 将用户空间部分的PGD项清零
    • 只清零用户空间部分,内核空间部分已经复制完成
    • 确保新进程的用户空间映射初始为空白

数据结构关系详解

内存布局理解

PGD内存布局:
+------------------+ 高地址
|   内核空间映射   | ← 从swapper_pg_dir复制而来
+------------------+ USER_PTRS_PER_PGD边界
|   用户空间映射   | ← 被memset清零
+------------------+ 低地址总大小:PTRS_PER_PGD * sizeof(pgd_t)
用户空间:USER_PTRS_PER_PGD项
内核空间:PTRS_PER_PGD - USER_PTRS_PER_PGD项

链表结构构建过程

初始状态:
pgd_list = NULL添加第一个PGD后:
pgd_list → page1
page1->index = NULL
page1->private = &pgd_list添加第二个PGD后:
pgd_list → page2 → page1
page2->index = page1
page2->private = &pgd_list  
page1->private = &page2->index

架构差异处理详解

两级页表架构(PTRS_PER_PMD == 1)

// 完整执行路径:
1. 获取锁
2. 复制内核映射
3. 添加到PGD链表
4. 释放锁
5. 清零用户映射

三级页表架构(PTRS_PER_PMD > 1)

// 简化执行路径:
1. 复制内核映射
2. 直接返回
// 不需要锁和链表操作
http://www.dtcms.com/a/503630.html

相关文章:

  • 量子计算机会普及个人使用吗?
  • 嵌入式入门:APP+BSP+HAL 三层分级架构浅析
  • 使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 19--测试框架Pytest基础 3--前后置操作应用
  • 面试面试面试
  • 北京响应式的网站下载了模板如何做网站
  • 中山企业营销型网站制作wordpress亲你迷路了
  • 个人做电影网站有什么风险南山最专业的网站建设
  • 「用Python来学微积分」4. 极坐标方程与参数方程
  • 第六章 路由基础
  • P1049 装箱问题 题解(四种方法)附DP和DFS的对比
  • Windows下Vscode连接到WSL的方法
  • R语言系列入门教程:什么是R语言?与传统编程语言有什么区别?
  • 商务网站建设的主流程网页设计排版作品分析
  • Altium Designer(AD24)原理图菜单栏详细介绍
  • 【JavaWeb学习】关于mysql-connector-j版本过高引起的问题
  • Eudemon1000E-F_V600R024C00SPC100
  • 建设工程资质录入是在那个网站机械类网站模板
  • 手机网站建站用哪个软件好字体样式 网站
  • ESMO中国之声丨徐兵河教授:芦康沙妥珠单抗再奏ADC中国之声,HR阳性HER2阴性晚期乳腺癌迎来CDK4/6抑制剂治疗后新希望
  • 模板网站禁止右键wordpress描述代码
  • pyhton(大厂笔试/面试)最长子序列(哈希-回溯-中等)含源码(二十三)
  • 做淘宝浏览单的网站菏泽外贸网站建设公司
  • Linux:理解操作系统和进程
  • 单片机开发工具篇:(六)STM32CubeMX 的使用,包括软件和固件包的下载、以及基础使用
  • 网站建设费是多少常州高端网站建设
  • 20.UE-游戏逆向-绘制所有对象坐标
  • jsp网站建设作业泗阳县建设局网站
  • Springboot整合IoTB
  • 个人做网站哪种类型的网站好男生做男生网站在那看
  • 从 0 到 1 学 C 语言队列:链表底层实现(初始化 / 入队 / 出队 / 销毁),代码可直接复用!