29 虚拟地址到物理地址的转换

news2024/11/13 8:54:21

前言

呵呵 这是 linux 中内存管理中很基础的一环 

用户程序 操作的地址都是虚拟地址, 虚拟地址通过 mmu 转换为物理地址 

用户程序 看到的地址都是一个完整的世界, 只有具体需要使用的时候 产生缺页中断, 然后 分配具体的物理页 

这里 要说的就是 虚拟地址 到 物理地址 的转换 

体验一下 虚拟地址 转换 为物理地址

主要是来自于 内核模块 来体验, 测试的 模块代码 来自于如下链接 

Linux内核学习3——虚拟地址转换成物理地址z

执行的容器是 centos 7.x 桌面版本, 原来的 模块代码 做了一定的调整 

#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>
#include <linux/delay.h>


static unsigned long cr0,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);   //指示page offset字段,映射的是一个页面的大小,一个页面大小是4k,转换成以2为底的对数就是12,其他的宏类似

    //下面的这些宏是用来指示相应的页目录表中的项的个数的,这些宏都是为了方便寻页时进行位运算的
    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);
    printk("PAGE_MASK = 0x%lx\n", PAGE_MASK);   //page_mask,页内偏移掩码,用来屏蔽掉page offset字段
}

static unsigned long 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 = pgd_offset(current->mm,vaddr);  //第一个参数是当前进程的mm_struct结构(我们申请的线性地址空间是内核,所以应该查内核页表,又因为所有的进程都共享同一个内核页表,所以可以用当前进程的mm_struct结构来进行查找)
    printk("pgd_addr = 0x%lx, *pgd = 0x%lx, pgd_page_addr = 0x%lx, pgd_val = 0x%lx, pgd_index = %lu\n", pgd, *pgd, pgd_page_vaddr(*pgd), pgd_val(*pgd),pgd_index(vaddr));
    if (pgd_none(*pgd)){
        printk("not mapped in pgd\n");
        return -1;
    }

//    p4d = p4d_offset(pgd, vaddr);  //查找到的页全局目录项pgd作为下级查找的参数传入到p4d_offset中
//    printk("p4d_val = 0x%lx, p4d_index = %lu\n", p4d_val(*p4d),p4d_index(vaddr));
//    if(p4d_none(*p4d))
//    {
//        printk("not mapped in p4d\n");
//        return -1;
//    }

    pud = pud_offset(pgd, vaddr);
    printk("pud_addr = 0x%lx, *pud = 0x%lx, pud_page_addr = 0x%lx, pud_val = 0x%lx, pud_index = %lu\n", pud, *pud, pud_page_vaddr(*pud), pud_val(*pud),pud_index(vaddr));
    if (pud_none(*pud)) {
        printk("not mapped in pud\n");
        return -1;
    }

    pmd = pmd_offset(pud, vaddr);
    printk("pmd_addr = 0x%lx, *pmd = 0x%lx, pmd_page_addr = 0x%lx, pmd_val = 0x%lx, pmd_index = %lu\n", pmd, *pmd, pmd_page_vaddr(*pmd), pmd_val(*pmd),pmd_index(vaddr));
    if (pmd_none(*pmd)) {
        printk("not mapped in pmd\n");
        return -1;
    }

    pte = pte_offset_kernel(pmd, vaddr);  //与上面略有不同,这里表示在内核页表中查找,在进程页表中查找是另外一个完全不同的函数   这里最后取得了页表的线性地址
    printk("pte_addr = 0x%lx, *pte = 0x%lx, pte_val = 0x%lx, ptd_index = %lu\n", pte, *pte, pte_val(*pte),pte_index(vaddr));

    if (pte_none(*pte)) {
        printk("not mapped in pte\n");
        return -1;
    }
    //从页表的线性地址中取出该页表所映射页框的物理地址
    page_addr = pte_val(*pte) & PAGE_MASK;    //取出其高48位
    //取出页偏移地址,页偏移量也就是线性地址中的低12位
    page_offset = vaddr & ~PAGE_MASK;
    //将两个地址拼接起来,就得到了想要的物理地址了
    paddr = page_addr | page_offset;
    printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);
    printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);
    return paddr;
}

static int __init v2p_init(void)    //内核模块的注册函数
{
    unsigned long vaddr = 0 ;
    printk("vaddr to paddr module is running..\n");
    get_pgtable_macro();
    printk("\n");
    vaddr = __get_free_page(GFP_KERNEL);   //在内核的ZONE_NORMAL中申请了一块页面,GFP_KERNEL标志指示优先从内核的ZONE_NORMAL中申请页框
    if (vaddr == 0) {
        printk("__get_free_page failed..\n");
        return 0;
    }
    sprintf((char *)vaddr, "hello world from kernel");   //在地址中写入hello
    printk("get_page_vaddr=0x%lx\n", vaddr);
    vaddr2paddr(vaddr);
    return 0;
}
static void __exit v2p_exit(void)    //内核模块的卸载函数
{
    printk("vaddr to paddr module is leaving..\n");
    free_page(vaddr);   //将申请的线性地址空间释放掉
}


module_init(v2p_init);
module_exit(v2p_exit);
MODULE_LICENSE("GPL");


Makefile 如下

PROJ=Test05Virt2Physical
KERN_DIR=/lib/modules/$(shell uname -r)/build

obj-m+=$(PROJ).o

all:
	make ARCH=x86_64 -C $(KERN_DIR) M=$(shell pwd) modules

clean:
	make -C $(KERN_DIR) M=$(shell pwd)  clean
	rm -rf modules.order

添加内核模块输出日志如下, 这里展示的就是一个 虚拟地址 转换为 物理地址 的过程 

待调试虚拟地址为 0xffff9346f8bc6000 

mm->pgd 中存储的是 pgd 的首部, 加上 pgd 的索引 294, 计算得到 pgd 的地址, 0xffff9346c7dc8930, pgd 的值为 0x29275067 
pgd 的值为 0x29275067, 存储数据的页地址为 0xffff9346e9275000, 加上 pud 的索引 283, 计算得到 pud 的地址, 0xffff9346e9275000 + 283 * 8 = 0xffff9346e92758d8, pud 的值为 0x29276067 
pud 的值为 0x29276067, 存储数据的页地址为 0xffff9346e9276000, 加上 pmd 的索引 453, 计算得到 pmd 的地址, 0xffff9346e9276000 + 453 * 8 = 0xffff9346e9276e28, pmd 的值为 0x29276067
pmd 的值为 0x29276067, 存储数据的页地址为 0xffff9346f6f5a000, 加上 pte 的索引为 454, 计算得到 pte 的地址, 0xffff9346f6f5a000 + 454 * 8 = 0xffff9346f6f5ae30, pte 的值为 0x8000000038bc6063

pte 指向的即为对应的物理页信息, mask 掉后面 12bit, 加上偏移即为物理地址 8000000038bc6000
[ 3319.130839] vaddr to paddr module is running..
[ 3319.130842] cr0 = 0x80050033, cr3 = 0x7dc8000
[ 3319.130843] PGDIR_SHIFT = 39
[ 3319.130844] PUD_SHIFT = 30
[ 3319.130845] PMD_SHIFT = 21
[ 3319.130846] PAGE_SHIFT = 12
[ 3319.130847] PTRS_PER_PGD = 512
[ 3319.130848] PTRS_PER_PUD = 512
[ 3319.130848] PTRS_PER_PMD = 512
[ 3319.130849] PTRS_PER_PTE = 512
[ 3319.130850] PAGE_MASK = 0xfffffffffffff000

[ 3319.130853] get_page_vaddr=0xffff9346f8bc6000
[ 3319.130854] pgd_addr = 0xffff9346c7dc8930, *pgd = 0x29275067, pgd_page_addr = 0xffff9346e9275000, pgd_val = 0x29275067, pgd_index = 294
[ 3319.130856] pud_addr = 0xffff9346e92758d8, *pud = 0x29276067, pud_page_addr = 0xffff9346e9276000, pud_val = 0x29276067, pud_index = 283
[ 3319.130857] pmd_addr = 0xffff9346e9276e28, *pmd = 0x36f5a063, pmd_page_addr = 0xffff9346f6f5a000, pmd_val = 0x36f5a063, pmd_index = 453
[ 3319.130912] pte_addr = 0xffff9346f6f5ae30, *pte = 0x8000000038bc6063, pte_val = 0x8000000038bc6063, ptd_index = 454
[ 3319.130914] page_addr = 8000000038bc6000, page_offset = 0
[ 3319.130916] vaddr = ffff9346f8bc6000, paddr = 8000000038bc6000
[ 3326.641119] vaddr to paddr module is leaving..

基于 linux 的调试

处理缺页中断的时候, 根据 虚拟地址计算 pgd_index, 获取目标 pgd 的地址信息 

根据虚拟地址 计算 pud_index, 如果给定的 pud 不存在, 则新建, 获取 pud 的地址信息 

根据虚拟地址 计算 pmd_index, 如果给定的 pmd 不存在, 则新建, 获取 pmd 的地址信息 

然后 接下来是 具体的分配物理页, 在 pmd 中记录 pte 的地址信息 

pte_alloc_one 是我们这里能够看到的分配物理页的操作, 基于 alloc_pages 分配物理页 

然后 pmd_populate 为记录当前 pmd 条目指向的 物理页的地址信息, 对应于上面 在 pmd 中存储 pte 的信息 

pgd, pud, pmd, pte 的结构 

此处结构为 x86_64 下的结构 

pgd/pud/pmd/pte 每一个结构为 8字节 的条目

typedef struct { pgdval_t pgd; } pgd_t;
typedef struct { pudval_t pud; } pud_t;
typedef struct { pmdval_t pmd; } pmd_t;
typedef struct page *pgtable_t;

/*
 * These are used to make use of C type-checking..
 */
typedef unsigned long	pteval_t;
typedef unsigned long	pmdval_t;
typedef unsigned long	pudval_t;
typedef unsigned long	pgdval_t;
typedef unsigned long	pgprotval_t;

参考 

Linux内核学习3——虚拟地址转换成物理地址z

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/660221.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1001router6-react

文章目录 1 一级路由2 Navigate3 NavLink 自定义高亮样式4 useRoutes()5 嵌套路由6 路由传参6.1 传递params参数6.2 传递search参数6.3 传递state参数 7 编程式导航7.1 路由跳转7.2 前进、后退 8 钩子函数8.1 useInRouterContext()8.2 useNavigationType()8.3 useOutlet()8.4 u…

手写Spring框架---AOP实现

目录 容器是OOP的高级工具 系统需求 关注点分离Concern Separation 原有实现 AOP的成员 Advice的种类 单个Aspect的执行顺序 多个Aspect的执行顺序 Introduction-引入型Advice 代理模式 JDK动态代理 Spring AOP的实现原理之JDK动态代理 Spring AOP的实现原理之CGL…

CSS基础学习--19 下拉菜单

一、基本下拉菜单 当鼠标移动到指定元素上时&#xff0c;会出现下拉菜单 <!DOCTYPE html> <html> <head> <title>下拉菜单实例</title> <meta charset"utf-8"> <style> .dropdown {position: relative;display: inline-…

UnityVR-项目的管理阶层

目录 概述 项目的总体架构 单例基类 继承MonoBehaviour的单例基类 概述 一个具备一定规模的项目&#xff0c;一般都需要由不同人员合作完成&#xff0c;每个人的想法风格不相同&#xff0c;如果一开始没有定下基本的框架&#xff0c;会让实现时混乱不堪&#xff0c;而且无法…

【CesiumJS入门】(5)GooJSON的加载、更新、监听与销毁——GeoJsonDataSource应用

前言 本篇&#xff0c;我们将较完整得介绍Cesium中GeoJSON/TopoJSON相关的方法。 GeoJSON规范地址&#xff1a;RFC 7946: The GeoJSON Format (rfc-editor.org) GeoJSON在线绘制&#xff1a;geojson.io CesiumJS提供了一个名为DataSource的类&#xff0c;它主要是用来加载和展…

Java-API简析_java.util.UUID类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131270140 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

《计算之魂》读书笔记——递归与堆栈的关系

进入梅雨季节&#xff0c;一周末雨水连绵不绝&#xff0c;空气中泛着潮湿的凉爽。这个天气最适合找个角落&#xff0c;安安静静地读书写字。 继续读《计算之魂》&#xff0c;前次读到递归&#xff0c;今天则了解递归地数据结构实现。递归算法的层层实现&#xff0c;需要保留从…

大数据周会-本周学习内容总结018

开会时间&#xff1a;2023.06.18 15:00 线下会议 01【调研-数据分析&#xff08;质量、ETL、可视化&#xff09;】 ETL&#xff0c;是英文Extract-Transform-Load的缩写&#xff0c;用来描述将数据从来源端经过抽取&#xff08;extract&#xff09;、转换&#xff08;transform…

Tcl常用命令备忘录-正则命令篇

正则表达式是一种用于匹配、查找、替换文本中特定模式的工具。在Tcl脚本中&#xff0c;可以使用正则表达式对字符串进行匹配、查找和替换。 regexp 语法&#xff1a; regexp ?选项? 正则表达式 字符串 ?变量1 变量2 ...? 其中&#xff0c;?选项?为可选项&#xff0c;…

基于蒙特卡洛法的规模化电动汽车充电负荷预测(PythonMatlab实现)

目录 0 概述 1 蒙特卡洛模拟方法介绍 2 规模化电动汽车充电负荷预测计算方法 3 完整代码 0 概述 对于本文的研究,依据不同用途电动汽车影响因素的分布函数和设定参数&#xff0c;采用蒙特卡洛法,对各用途电动汽车的日行驶里程、起始充电时间概率分布参数进行随机抽样&#xff0…

linuxOPS系统服务_Linux下软件的安装方式之源码安装

Linux下有哪些软件安装方式 ① RPM软件包管理&#xff08;软件名称.rpm&#xff09; ② YUM软件包管理&#xff08;使用yum命令install 软件名称&#xff09; > 下载 安装一体化 ③ 源码编译安装&#xff08;相对来说是最复杂的一种方式&#xff09; 软件包类型 ☆ 二…

十二、docker学习-docker核心docker网络之bridge网络(2)

bridge网络 bridge网络表现形式就是docker0这个网络接口。容器默认都是通过docker0这个接口进行通信。也可以通过docker0去和本机的以太网接口连接&#xff0c;这样容器内部才能访问互联网。 # 查看docker0网络&#xff0c;在默认环境中&#xff0c;一个名为docker0的linux b…

Go语言的TCP和HTTP网络服务基础

目录 【TCP Socket 编程模型】 Socket读操作 【HTTP网络服务】 HTTP客户端 HTTP服务端 TCP/IP 网络模型实现了两种传输层协议&#xff1a;TCP 和 UDP&#xff0c;其中TCP 是面向连接的流协议&#xff0c;为通信的两端提供稳定可靠的数据传输服务&#xff1b;UDP 提供了一种…

NodeJSMongodbMVC管理开发⑨

文章目录 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持&#x1f618;前言MVC思想开发 服务器代码演示 M层 Services 或 Model V层 Views C层 Controllers总结 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c…

C语言笔记之结构体总结

C语言笔记之结构体总结 code review! 文章目录 C语言笔记之结构体总结一.介绍二.3种结构体类型变量说明1. 先定义结构&#xff0c;再定义结构变量2. 定义结构体类型的同时说明变量3. 直接说明结构变量(匿名结构体) 四.结构体成员表示方法五.结构体指针做参数六.结构体初始化1…

阵列信号处理笔记(2):均匀线阵、均匀加权线阵、波束方向图

阵列信号处理笔记&#xff08;2&#xff09; 文章目录 阵列信号处理笔记&#xff08;2&#xff09;均匀线阵&#xff08;Uniform Linear Array&#xff09;均匀加权线阵波束方向图的关键参数附polardb.m用来计算HPBW的Mathematica代码&#xff0c;以及用于拟合的数据拟合的MATL…

二、DSMP/OLS等夜间灯光数据贫困地区识别——MPI和灯光指数计算

一、前言 其实在计算MPI和灯光指数之前,最重要是DMSP/OLS等夜间灯光指数的校正还有就是MPI计算,那么校正分为DMSP/OLS和NPP/VIIRS夜间灯光数据,DMSP/OLS夜间灯光数据校正主要采取不变目标区域法原理进行校正,当前对其有很多优化后的做法,但是万变不离其宗,核心思想还是没…

LeetCode257. 二叉树的所有路径

写在前面&#xff1a; 题目链接&#xff1a;LeetCode257. 二叉树的所有路径 题目难度&#xff1a;简单 编程语言&#xff1a;C 一、题目描述 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的…

阿里P8架构师手码的Java工程师面试小抄在Github火了,完整版限时开源

网上的 JAVA 面试文档更是层出不穷。但是单单刷 JAVA 面试题就足够了吗&#xff1f; 答案显然是不够的&#xff01;那么为什么呢&#xff1f; 因为现在的程序员就业环境早就和两年前不可同日而语了。 如果你在两年前面试&#xff1a; 就拿 JVM 来说&#xff0c;刷面试题可能…

Springboot+vue.js+协同过滤推荐+余弦相似度算法实现新闻推荐系统

Springbootvue.js协同过滤推荐余弦相似度算法实现新闻推荐系统 - 简书 针对海量的新闻资讯数据&#xff0c;如何快速的根据用户的检索需要&#xff0c;完成符合用户阅读需求的新闻资讯推荐&#xff1f;本篇文章主要采用余弦相似度及基于用户协同过滤算法实现新闻推荐&#xff0…