元素码农
基础
UML建模
数据结构
算法
设计模式
网络
TCP/IP协议
HTTPS安全机制
WebSocket实时通信
数据库
sqlite
postgresql
clickhouse
后端
rust
go
java
php
mysql
redis
mongodb
etcd
nats
zincsearch
前端
浏览器
javascript
typescript
vue3
react
游戏
unity
unreal
C++
C#
Lua
App
android
ios
flutter
react-native
安全
Web安全
测试
软件测试
自动化测试 - Playwright
人工智能
Python
langChain
langGraph
运维
linux
docker
工具
git
svn
🌞
🌙
目录
▶
进程管理
▶
进程调度
调度策略
完全公平调度器
实时调度机制
▶
进程通信
信号机制
共享内存
管道与命名管道
▶
进程状态
上下文切换
僵尸进程处理
进程生命周期
▶
内存管理
▶
虚拟内存
页表机制
地址空间布局
TLB管理
▶
物理内存
伙伴系统
slab分配器
内存碎片处理
▶
文件系统
▶
VFS抽象层
inode机制
文件描述符
挂载机制
▶
具体文件系统
Ext4日志机制
Btrfs特性
Proc文件系统
▶
设备驱动
▶
驱动模型
设备树解析
sysfs接口
热插拔处理
▶
网络栈
▶
协议栈架构
sk_buff结构
Netfilter框架
套接字实现
▶
内核同步机制
▶
锁机制
自旋锁实现
RCU机制
Mutex与信号量
▶
启动过程
▶
引导阶段
GRUB交互
内核解压缩
早期初始化
▶
系统调用
▶
调用机制
陷入门实现
vsyscall优化
安全审计
▶
虚拟化和容器支持
▶
容器技术
命名空间隔离
cgroups实现
OverlayFS原理
发布时间:
2025-03-23 10:41
↑
☰
# Linux页表机制详解 页表机制是Linux虚拟内存管理的核心组件,它负责虚拟地址到物理地址的映射。本文将详细介绍Linux系统中的页表机制。 ## 基本概念 ### 地址转换 1. 虚拟地址结构 - 页目录索引 - 页表索引 - 页内偏移 2. 多级页表 ```text 虚拟地址 -> PGD -> PUD -> PMD -> PTE -> 物理地址 ``` 3. 页表项格式 ```c typedef struct { unsigned long pte; } pte_t; // 页表项的位域 #define _PAGE_PRESENT 0x001 // 页面是否存在 #define _PAGE_RW 0x002 // 读写权限 #define _PAGE_USER 0x004 // 用户态访问权限 #define _PAGE_ACCESSED 0x020 // 访问位 #define _PAGE_DIRTY 0x040 // 脏页标记 ``` ## 页表管理 ### 页表分配 1. 创建页表 ```c static inline pte_t *pte_alloc_one_kernel(struct mm_struct *mm) { return (pte_t *)get_zeroed_page(GFP_KERNEL); } static inline pgtable_t pte_alloc_one(struct mm_struct *mm) { struct page *page; page = alloc_page(GFP_KERNEL | __GFP_ZERO); if (!page) return NULL; if (!pgtable_page_ctor(page)) { __free_page(page); return NULL; } return page; } ``` 2. 页表初始化 ```c static inline void pgd_init(pgd_t *pgd) { // 初始化页全局目录项 memset(pgd, 0, PTRS_PER_PGD * sizeof(pgd_t)); } ``` ### 页表操作 1. 页表项修改 ```c static inline void set_pte(pte_t *ptep, pte_t pte) { *ptep = pte; } static inline void set_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep, pte_t pte) { set_pte(ptep, pte); } ``` 2. 页表遍历 ```c pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte; pgd = pgd_offset(mm, address); if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd))) goto out; pud = pud_offset(pgd, address); if (pud_none(*pud) || unlikely(pud_bad(*pud))) goto out; pmd = pmd_offset(pud, address); if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd))) goto out; pte = pte_offset_map(pmd, address); if (!pte) goto out; ``` ## 内存映射 ### 建立映射 1. 页表映射函数 ```c int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot) { // 建立虚拟地址到物理页框的映射 pgd_t *pgd; unsigned long end = addr + size; unsigned long next; // 遍历地址范围建立映射 do { next = pgd_addr_end(addr, end); if (pgd_none(*pgd)) { // 分配新的页表 if (pgd_alloc(vma->vm_mm) < 0) break; } // 递归建立映射 if (alloc_pte_range(vma, addr, next, pfn) < 0) break; } while (pgd++, addr = next, addr != end); return 0; } ``` 2. 内存分配映射 ```c void *vmalloc(unsigned long size) { struct vm_struct *area; // 分配虚拟内存区域 area = get_vm_area(size, VM_ALLOC); if (!area) return NULL; // 分配物理页面并建立映射 if (map_vm_area(area, PAGE_KERNEL, &pages)) { vfree(area->addr); return NULL; } return area->addr; } ``` ### 解除映射 1. 页表解映射 ```c static inline void pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *ptep) { // 清除页表项 *ptep = __pte(0); } ``` 2. 内存释放解映射 ```c void vfree(const void *addr) { struct vm_struct *area; // 查找虚拟内存区域 area = find_vm_area((void *)addr); if (!area) return; // 解除映射并释放页面 remove_vm_area(addr); kfree(area); } ``` ## 页表缓存 ### TLB管理 1. TLB刷新 ```c void flush_tlb_mm(struct mm_struct *mm) { // 刷新整个进程的TLB on_each_cpu(flush_tlb_mm_range, mm, 0, -1, MMU_FLAGS_FULL); } void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) { // 刷新指定地址范围的TLB flush_tlb_mm_range(vma->vm_mm, start, end, vma->vm_flags); } ``` 2. TLB优化 ```c // 延迟TLB刷新 void tlb_flush_mmu(struct mmu_gather *tlb) { // 批量处理TLB刷新请求 if (tlb->fullmm) { flush_tlb_mm(tlb->mm); return; } // 合并连续的地址范围 flush_tlb_range(tlb->vma, tlb->start, tlb->end); } ``` ## 大页支持 ### Huge Pages 1. 大页配置 ```c // 支持的大页大小 #define PMD_SHIFT 21 #define PMD_SIZE (1UL << PMD_SHIFT) #define PMD_MASK (~(PMD_SIZE-1)) // 大页映射标志 #define _PAGE_PSE 0x080 // 2MB/4MB页 ``` 2. 大页分配 ```c void *alloc_huge_page(struct vm_area_struct *vma, unsigned long addr) { struct page *page; // 分配大页 page = alloc_pages(GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOWARN, HPAGE_PMD_ORDER); if (!page) return NULL; // 建立大页映射 if (arch_make_huge_pte(vma, page, 1)) { put_page(page); return NULL; } return page_address(page); } ``` ## 调试技巧 ### 页表调试 1. 查看进程页表 ```bash # 查看进程页表 cat /proc/[pid]/maps # 查看页表统计信息 cat /proc/[pid]/numa_maps ``` 2. 内核调试 ```c // 打印页表项信息 void dump_pte(pte_t pte) { printk("PTE: %lx [present:%d rw:%d user:%d]", pte_val(pte), pte_present(pte), pte_write(pte), pte_user(pte)); } ``` ## 最佳实践 1. 页表管理 - 合理使用多级页表 - 及时释放不用的页表 - 注意页表项权限设置 2. 内存映射 - 使用合适的页大小 - 避免频繁映射/解映射 - 注意内存对齐 3. TLB优化 - 批量处理TLB刷新 - 使用大页减少TLB项 - 合理设置页面权限 ## 总结 Linux页表机制是虚拟内存管理的基础,通过多级页表实现了虚拟地址到物理地址的映射。理解页表机制对于开发高性能的Linux应用程序和进行系统优化非常重要。在实际应用中,我们应该合理使用页表特性,注意内存映射的效率,并利用TLB和大页等机制来提升系统性能。