页表的实现与地址转换

页表是软件实现的,但是页表的查找是MMU完成的,所以硬件定义了页表的实现规则,软件做的只有选择页表的级别,是否使用huge page以及填充对应的权限标志位。每个进程都拥有一个自己的页表,在linux中,有一个页目录数组,这是分页机制的最高层,每个进程的页表对应其中的一个页目录项,通过cr3寄存器(存放页目录项的物理地址)可以访问。一个进程的页表,对应的页表项中对应页的物理地址。

分页机制

两级页表举例:

两级表的第一级表称为页目录,存储在一个4K字节的页中,页目录表共有1K个表项,每个表项为4个字节,线性地址最高的10位(22-31)用来产生第一级表索引,由该索引得到的表项中的内容定位了两级页表中的一个表的地址,即下级页表所在的内存块号。

第二级页表称为页表,存储在一个4K字节页中,它包含了1K字节的表项,每个表项包含了一个页的物理地址。二级页表由线性地址的中间10位(12-21)位进行索引,定位页表表项,获得页的物理地址。页物理地址的高20位与线性地址的低12位形成最后的物理地址。

四级页表举例如图:

页表PGD的首地址是通过mm_struct中的pgd得到的:pgd_t *pgd,pgd_offset(),pmd_offset(),pmd_offset()分别用于从线性地址中提取PGD,PMD和PTE表所需的index.

PGD,PUD,PMD,PTE分别都是一个4k的page,其实,PGD,PUD,PMD,PTE是四张table,table的大小都是4k,其中table的entry分别是pgd_t,pud_t,pmd_t,pte_t,都是unsigned long类型(8字节),4k(2的12次方)/8字节=512个entry(2的9次方);PTE的table大小也是4k,entry大小也是8字节,所以,PTE表中可以存放512个entry(也就是512个物理地址),PTRS_PER_***宏表示表中项(entry)的个数。

linux中用PAGE_SHIFT表示一个PTE所对应的内存范围,表示线性地址offset字段的位数。该宏的值被定义为12位,即页的大小为4KB。

#define PAGE_SHIFT 12

PMD_SHIFT和PGDIR_SHIFT分别表示一个PMD和一个PGD所对应的内存所需要的bit数。在四级分页模型中,PGDIR_SHIFT占据39位,即9位页上级目录,9位中间目录,9位页表和12位偏移。页全局目录同样占线性地址的9位,因此PTRS_PER_PGD为512.

PAGE_SIZE,PMD_SIZE,PGDIR_SIZE则分别表示一个PTE,PMD,PGD所对应的内存范围的大小。PAGE_MASK,PMD_MASK,PGDIR_MASK则是用于提取地址位的掩码,MASK与SIZE的大小如下图所示:

关于类似的相关宏参考:https://blog.csdn.net/weixin_34186128/article/details/93170177

地址转换过程(代码实现MMU转换过程)

#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/sched.h>
#include <linux/export.h>static unsigned long cr0;
static unsigned long cr3;static unsigned long vaddr = 0;static void get_pgtable_macro(void)
{cr0 = read_cr0();cr3 = read_cr3_pa();printk("cr0 = 0x%lx, cr3 = 0x%lx\n", cr0, cr3);printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT);printk("P4D_SHIFT = %d\n", P4D_SHIFT);printk("PUD_SHIFT = %d\n", PUD_SHIFT);printk("PMD_SHIFT = %d\n", PMD_SHIFT);printk("PAGE_SHIFT = %d\n", PAGE_SHIFT);//页目录表中项的个数printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD);printk("PTRS_PER_P4D = %d\n", PTRS_PER_P4D);printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD);printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD);printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE);//页内偏移掩码,用来屏蔽 page_offsetprintk("PAGEMASK = 0x%lx\n", PAGE_MASK);
}void vaddr2paddr(unsigned long vaddr)
{pgd_t *pgd;p4d_t *p4d;pud_t *pud;pmd_t *pmd;pte_t *pte;unsigned long paddr = 0;unsigned long page_addr = 0;unsigned long page_offset = 0;//取得页全局目录项//pgd_index(addr):返回PGD包含的项中,地址字段值为addr的项的索引,下同//pgd_val(pgd)获得pgd所指的页全局目录项,下同pgd = pgd_offset(current->mm, vaddr);printk("pgd_val = 0x%lx, pgd_index = %lu\n", pgd_val(*pgd), pgd_index(vaddr));//取得 p4dp4d = p4d_offset(pgd, vaddr);printk("p4d_val = 0x%lx, p4d_index = %lu\n", p4d_val(*p4d), p4d_index(vaddr));//取得页上级目录项pud = pud_offset(p4d, vaddr);printk("pud_val = 0x%lx, pud_index = %lu\n", pud_val(*pud), pud_index(vaddr));//取得页中间目录项pmd = pmd_offset(pud, vaddr);printk("pmd_val = 0x%lx, pmd_index = %lu\n", pmd_val(*pmd), pmd_index(vaddr));//取得页表(在主内核页表中查找)pte = pte_offset_kernel(pmd, vaddr);printk("pte_val = 0x%lx, pte_index = %lu\n", pte_val(*pte), pte_index(vaddr));//取得的页表项的低 12 位放的是页属性//所以用 PAGE_MASK 来清除page_addr = pte_val(*pte) & PAGE_MASK;//取得页内偏移 page_offset//也就是只取虚拟地址的低 12 位page_offset = vaddr & ~PAGE_MASK;//然后和并页表地址和页内偏移取得物理地址paddr = page_addr | page_offset;printk("page_addr = 0x%lx, page_offset = 0x%lx\n", page_addr, page_offset);printk("virtual address = 0x%lx, physical address = 0x%lx\n", vaddr, paddr);}static int __init v2p_init(void)
{unsigned long vaddr = 0;printk("vritual address to physical address module is running..\n");get_pgtable_macro();printk("\n");vaddr = __get_free_page(GFP_KERNEL);if(vaddr == 0){printk("__get_free_page failed..\n");return 0;}printk("get_page_vaddr = 0x%lx\n", vaddr);vaddr2paddr(vaddr);return 0;
}static void __exit v2p_exit(void)
{free_page(vaddr);printk("module is leaving...\n");
}module_init(v2p_init);
module_exit(v2p_exit);

【Linux】页表的实现与地址转换相关推荐

  1. Linux内核内存管理:地址转换和MMU

    地址转换和MMU 虚拟内存是一个概念,是给进程的一种错觉,因此它认为自己拥有巨大的.几乎无限的内存,有时甚至比系统实际拥有的内存还要多.每次访问内存位置时,由CPU将虚拟地址转换为物理地址.这种机制称 ...

  2. Linux网络编程笔记 - 05 地址转换函数 32位整数,转换为点分十进制

    #include <arpa/inet.h> const char *inet_ntop(int af, const void *src,char *dst, socklen_t size ...

  3. Linux操作系统实验1——地址转换

    实验要求: 1.在内核中先申请一个页面,使用内核提供的函数,按照寻页的步骤一步步的找到物理地址.这些步骤就相当于我们手动的模拟了mmu的寻页过程.(paging_lowmem.c) 2.通过mmap将 ...

  4. 【Linux网络编程学习】预备知识(网络字节序、IP地址转换函数、sockaddr数据结构)

    此为牛客Linux C++课程和黑马Linux系统编程笔记. 1. 网络字节序 我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分. 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小 ...

  5. Linux 网络编程详解一(IP套接字结构体、网络字节序,地址转换函数)

    IPv4套接字地址结构 struct sockaddr_in {uint8_t sinlen;(4个字节)sa_family_t sin_family;(4个字节)in_port_t sin_port ...

  6. 【linux网络编程】网络字节序、地址转换

    网络字节序 故事的起源 "endian"这个词出自<格列佛游记>.小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endia ...

  7. linux的基础知识——网络字节序转化,ip地址转换函数,sockaddr数据结构

    1.网络字节序 TCP/IP协议规定,网络数据流采用大端字节序,即低地址高字节.为了使网络程序具有可移植性,使得同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机 ...

  8. linux如何实现端口复用nat,NAT地址转换和端口复用PAT

    什么是端口复用动态地址转换(PAT) 介绍配置实例 端口多路复用(Port address Translation,PAT)是指改变外出数据包的源端口并进行端口转 换,即端口地址转换(PAT,Port ...

  9. linux页表,arm linux 页表(转)

    最近在看arm linux 的mm部分,看的是2.6.8.1,芯片是INTEL PXA255,参考资料有arm linux演艺.<情景分析>等.一遍看下来只能说似懂非懂.这里有几个基础的问 ...

最新文章

  1. Rhel6-heartbeat配置文档
  2. OpenAI新研究:扩散模型在图像合成质量上击败BigGAN,多样性还更佳
  3. postgresql 可调试
  4. 优秀的词云展示第三方库——wordcloud
  5. Impress.js教程
  6. open_links_per_instance 和 open_links 参数说明
  7. Codeup墓地-问题 A: 还是畅通工程
  8. Hadoop端口介绍及各种启动命令列表
  9. atitit。获取表格的字段注释metadata的原理以及AND 字段表格描述文档方案
  10. sql2012 数据库连接错误
  11. [unity3d]Assetbundle使用示例2(支持多平台)
  12. 游标sql server_SQL Server游标属性
  13. CentOS6.X安装10G需要额外安装的软件包
  14. .Net MVC控制器中进行页面跳转并传递多个参数
  15. 【机械仿真】基于matlab GUI机械臂运动控制【含Matlab源码 063期】
  16. 免费直播编码软件应用技巧
  17. 「Python|场景案例」如何获取音视频中声音片段的起止时间?
  18. bootstrap 黑边框表格样式_bootstrap4 使用及常用样式详细整理
  19. 音频 ----- DRC
  20. C++编写COM组件

热门文章

  1. 摩托罗拉下注Android 不成功便成仁
  2. MySQL数据库基础与安装
  3. Go 语言入门系列:基本语法介绍之变量的声明与初始化
  4. 关于SEO的13种经典方法
  5. ubuntu控制台访问u盘_解决ubuntu无法挂在u盘的问题
  6. 新手练车小白的自我提升
  7. Intellij IDEA 破解方法和地址
  8. 使用Apache文件上传控件实现文件上传
  9. vue使用高德地图,精确定位ip定位,获取城市、地区位置
  10. 未明学院:7天金融量化分析初阶集训营,紧跟Fintech数据时代潮流!