浅析Linux 64位系统虚拟地址和物理地址的映射及验证方法

前言

有好久没更新了,这段时间发生了挺多大喜事哈。但是也还是有挺久没更新了,不得不意识到自己是个小菜鸡,就算是小菜鸡也要做一只快乐小菜鸡。就算更新慢但是我依然会持续更新,因为更文使我快乐。

虚拟内存

先简单介绍一下操作系统中为什么会有虚拟地址和物理地址的区别。因为Linux中有进程的概念,那么每个进程都有自己的独立的地址空间。

现在的操作系统都是64bit的,也就是说如果在用户态的进程中创建一个64位的指针,那么在这个进程中,这个指针能够指向的范围是0~0xFFFFFFFFFFFFFFFF(总共有16个F,每个F是4个bit)。

每个进程“理论上”都有这样的地址范围(-,-这里的”理论“是指猜测一下,指针乱指向未定义的范围会引发段错误,下文中会写明64bit的用户空间的地址范围)。

我们看到了,Linux为了让每个进程空间的独立,创造了虚拟地址这个概念。但是计算机最终还是需要操作物理的内存的。

那么虚拟地址和物理地址的映射关系是怎样的?也只能用映射表了。比如说:进程A虚拟空间中的第0x1234个字节,对应于物理内存中的第0x823ABC个字节。这个一个字节和一个字节对应,理论上是可以的,但是太消耗资源了,为了映射这“一个字节”,仅映射这“一个字节”的表项的大小也远超过了一个字节的大小(大约四十个字节左右)。这是不行的,这就像几十个产品和项目经理去管一个程序员工作,这是效率低下的。

所以页这个概念产生了,一个页一个页映射总还可以了吧,我们将页作为最小单位去映射就好了。大多数32位体系结构支持4KB的页,而64位体系结构一般会支持8KB的页。在linux使用命令获取当前系统的页大小:

getconf PAGE_SIZE

在我的ubuntu 16.04 x86_64上的系统得到的结果是 4096。目前大部分64位的系统的页大小都是4096个字节。

系统中每个物理页都会建立一个类似映射表的结构体,但是依然会有人觉得这有点浪费内存。我们来算一下,比如一个物理页的属性和映射表的内容占用40个字节(linux代码中是struct page)。假设如当前大部分Linux上的页为4KB大小,系统有4GB物理内存,那么就有1048576个页,这么多页的映射表消耗的内存是1048576 * 40byte = 40MB。用40MB去管理4GB,还是可以接受的。

64位系统的虚拟内存布局

在AArch64下,页大小为4KB时,页管理为四级架构时的Linux的进程中的虚拟内存布局如下:

可以看到即使是虚拟地址,用户态下能用的地址也就只是0 ~ 0000ffffffffffff,不过也有256TB大小了。也就是说每个进程都有自己独立的0 ~ 0000ffffffffffff的地址空间。0x0000ffffffffffff是12个f,也就是48个bit。

每个进程都有自己的虚拟地址到物理地址的映射关系表。Linux内核会根据每个不同的进程去查找表:如进程A的虚拟空间地址K的物理地址是哪个。为了加快查找效率,虚拟内存的地址的不同段映射到了不同的entry上,页管理表有4级的也有3级的。最常用的4级页管理映射表如下:

可以看到[47:0]这48个bits的虚拟地址,被分成了五段,前四段的每一份长度都是9 bits,最后一段是12 bits。

每个9 bits的段都是2^9 = 512,也就是说每个分级段都有512个entry。

最后一段[11:0],大小是12 bits的即2^12 = 4096,4096就是一个页的大小,所以最后一段是页内偏移(因为映射是以页为单位,所以虚拟地址和物理地址的页内偏移都是一样的)。前四段合在一起就是虚拟页号

我们举一个48 bit 虚拟地址的例子,这个地址以八进制表示:

003 010 007 413 1056

上面所述的每个Entry的结构体如下:

可以看到物理地址的页号是40 bits,也就是说最多有2^40个物理页,每个页是4096个字节,也就是最多4PB(4096TB)。

虚拟地址到物理地址的验证方法

说了这么多,如何验证上面说的这些是真的。就算推导出物理地址了,那又有啥用呢?

如果你知道共享库和静态库的区别的话,那么就会知道不同的进程如果用了同一个共享库,那么其实这两个不同的进程使用的共享库是指向同一个物理地址!如果能验证这一点,那么从虚拟地址推导到物理地址的方法大体是正确的,以上所述大体也是对的。

借助proc下的maps和pagemap

通过man命令

man proc

可以找到以下条目:以上我们知道通过/proc/[pid]/maps就能够知道一个进程的虚拟地址。以上我们知道通过/proc/[pid]/pagemap就能够将一个进程的虚拟地址页转成物理地址页。

测试代码

下面上硬菜。小伙子你要讲武德,你不能闪!

代码如下:

#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>size_t virtual_to_physical(pid_t pid, size_t addr)
{char str[20];sprintf(str, "/proc/%u/pagemap", pid);int fd = open(str, O_RDONLY);if(fd < 0){printf("open %s failed!\n", str);return 0;}size_t pagesize = getpagesize();size_t offset = (addr / pagesize) * sizeof(uint64_t);if(lseek(fd, offset, SEEK_SET) < 0){printf("lseek() failed!\n");close(fd);return 0;}uint64_t info;if(read(fd, &info, sizeof(uint64_t)) != sizeof(uint64_t)){printf("read() failed!\n");close(fd);return 0;}if((info & (((uint64_t)1) << 63)) == 0){printf("page is not present!\n");close(fd);return 0;}size_t frame = info & ((((uint64_t)1) << 55) - 1);size_t phy = frame * pagesize + addr % pagesize;close(fd);printf("The phy frame is 0x%zx\n", frame);printf("The phy addr is 0x%zx\n", phy);return phy;
}int main(void)
{while(1){uint32_t pid;uint64_t virtual_addr;printf("Please input the pid in dec:");scanf("%u", &pid);printf("Please input the virtual address in hex:");scanf("%zx", &virtual_addr);printf("pid = %u and virtual addr = 0x%zx\n", pid, virtual_addr);virtual_to_physical(pid, virtual_addr);}return 0;
}

首先,我编译一下!

gcc test.c -o haha

然后,我拷贝一下!

cp haha hahatest1; cp haha hahatest2; cp haha hahamonitor

接着,我运行一下!

nohup  ./hahatest1 &
[1] 3943
nohup  ./hahatest2 &
[2] 3944
sudo ./hahamonitor 

这里你可能已经发现我的意图了,我是用进程hahamonitor查看进程hahatest1和进程hahatest2的内存地址。

但是你不能大意,运行hahamonitor 一定要加sudo或者root权限,不然读出来就都是0了。

先看看hahatest1和hahatest2进程的地址空间:

zbf@zbf:~$ cat /proc/3943/maps 
00400000-00401000 r-xp 00000000 08:06 11150436                           /home/zbf/physic_virtual_memory/hahatest1
00600000-00601000 r--p 00000000 08:06 11150436                           /home/zbf/physic_virtual_memory/hahatest1
00601000-00602000 rw-p 00001000 08:06 11150436                           /home/zbf/physic_virtual_memory/hahatest1
011ad000-011cf000 rw-p 00000000 00:00 0                                  [heap]
7ffbf1b64000-7ffbf1d24000 r-xp 00000000 08:06 20714662                   /lib/x86_64-linux-gnu/libc-2.23.so
7ffbf1d24000-7ffbf1f24000 ---p 001c0000 08:06 20714662                   /lib/x86_64-linux-gnu/libc-2.23.so
7ffbf1f24000-7ffbf1f28000 r--p 001c0000 08:06 20714662                   /lib/x86_64-linux-gnu/libc-2.23.so
7ffbf1f28000-7ffbf1f2a000 rw-p 001c4000 08:06 20714662                   /lib/x86_64-linux-gnu/libc-2.23.so
7ffbf1f2a000-7ffbf1f2e000 rw-p 00000000 00:00 0 
7ffbf1f2e000-7ffbf1f54000 r-xp 00000000 08:06 20714659                   /lib/x86_64-linux-gnu/ld-2.23.so
7ffbf2133000-7ffbf2136000 rw-p 00000000 00:00 0 
7ffbf2153000-7ffbf2154000 r--p 00025000 08:06 20714659                   /lib/x86_64-linux-gnu/ld-2.23.so
7ffbf2154000-7ffbf2155000 rw-p 00026000 08:06 20714659                   /lib/x86_64-linux-gnu/ld-2.23.so
7ffbf2155000-7ffbf2156000 rw-p 00000000 00:00 0 
7ffd2529f000-7ffd252c0000 rw-p 00000000 00:00 0                          [stack]
7ffd25302000-7ffd25305000 r--p 00000000 00:00 0                          [vvar]
7ffd25305000-7ffd25307000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]zbf@zbf:~$ cat /proc/3944/maps 
00400000-00401000 r-xp 00000000 08:06 11150444                           /home/zbf/physic_virtual_memory/hahatest2
00600000-00601000 r--p 00000000 08:06 11150444                           /home/zbf/physic_virtual_memory/hahatest2
00601000-00602000 rw-p 00001000 08:06 11150444                           /home/zbf/physic_virtual_memory/hahatest2
01e8b000-01ead000 rw-p 00000000 00:00 0                                  [heap]
7fe786964000-7fe786b24000 r-xp 00000000 08:06 20714662                   /lib/x86_64-linux-gnu/libc-2.23.so
7fe786b24000-7fe786d24000 ---p 001c0000 08:06 20714662                   /lib/x86_64-linux-gnu/libc-2.23.so
7fe786d24000-7fe786d28000 r--p 001c0000 08:06 20714662                   /lib/x86_64-linux-gnu/libc-2.23.so
7fe786d28000-7fe786d2a000 rw-p 001c4000 08:06 20714662                   /lib/x86_64-linux-gnu/libc-2.23.so
7fe786d2a000-7fe786d2e000 rw-p 00000000 00:00 0 
7fe786d2e000-7fe786d54000 r-xp 00000000 08:06 20714659                   /lib/x86_64-linux-gnu/ld-2.23.so
7fe786f33000-7fe786f36000 rw-p 00000000 00:00 0 
7fe786f53000-7fe786f54000 r--p 00025000 08:06 20714659                   /lib/x86_64-linux-gnu/ld-2.23.so
7fe786f54000-7fe786f55000 rw-p 00026000 08:06 20714659                   /lib/x86_64-linux-gnu/ld-2.23.so
7fe786f55000-7fe786f56000 rw-p 00000000 00:00 0 
7fffd3388000-7fffd33a9000 rw-p 00000000 00:00 0                          [stack]
7fffd33ce000-7fffd33d1000 r--p 00000000 00:00 0                          [vvar]
7fffd33d1000-7fffd33d3000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

可以看到这两个进程都链接了/lib/x86_64-linux-gnu/libc-2.23.so这个动态库,在进程3943(hahatest1)中的虚拟地址是:7ffbf1b64000,但在进程3944中的虚拟地址是:7fe786964000

我们用hahamonitor康康它们的最终的物理地址都是什么?

zbf@zbf:~/$ sudo ./hahamonitor 
Please input the pid in dec:3943
Please input the virtual address in hex:7ffbf1b64000
pid = 3943 and virtual addr = 0x7ffbf1b64000
The phy frame is 0x12ee58
The phy addr is 0x12ee58000Please input the pid in dec:3944
Please input the virtual address in hex:7fe786964000
pid = 3944 and virtual addr = 0x7fe786964000
The phy frame is 0x12ee58
The phy addr is 0x12ee58000

可以看到物理地址是一样的,都是0x12ee58000。另外我也实验过这两个进程对应的堆栈的物理地址都是不一样的,这就对了!

有兴趣的朋友可以自行下载代码跑一下。

参考资料:

  1. https://www.kernel.org/doc/html/v4.19/admin-guide/mm/pagemap.html

  2. https://www.kernel.org/doc/Documentation/vm/pagemap.txt

  3. https://www.kernel.org/doc/html/latest/arm64/memory.html

  4. https://constantsmatter.com/posts/virtual-address/

  5. 程序喵大人:https://mp.weixin.qq.com/s?__biz=MzI3NjA1OTEzMg==&mid=2247484681&idx=1&sn=45b7d8f38402622718fcdc10ba77f443&chksm=eb7a039adc0d8a8cc6bb635fcb8a3f2f567e064f9c0ee863297c90f486394b788de5c3fe6dbd&mpshare=1&scene=1&srcid=1129bC44tMBu7lpXza2ki1k6&sharer_sharetime=1606655711296&sharer_shareid=741c39217c916aaf06bf9827e80dbff6&exportkey=AX19wECY41gfhbceNfjn7ws%3D&pass_ticket=Tv1TS4ibFzi6ZvNrbr2emqQu9boZCHYlwz5dSAFLvlJHUrIsSAibiRbzFP%2FmiurU&wx_header=0#rd

  6. https://zhou-yuxin.github.io/articles/2017/Linux%20%E8%8E%B7%E5%8F%96%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80%E5%AF%B9%E5%BA%94%E7%9A%84%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80/index.html

推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎转发,在看,评论~

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

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

相关文章

C语言指针:从底层原理到花式技巧,用图文和代码帮你讲解透彻

一、前言二、变量与指针的本质三、指针的几个相关概念四、指向不同数据类型的指针五、总结一、前言如果问C语言中最重要、威力最大的概念是什么&#xff0c;答案必将是指针&#xff01;威力大&#xff0c;意味着使用方便、高效&#xff0c;同时也意味着语法复杂、容易出错。指针…

ARM

ARM 宗旨&#xff1a;技术的学习是有限的&#xff0c;分享的精神是无限的。 1 、ARM处理器的特点 ARM 处理器具有耗电少&#xff0c;功能强大&#xff0c;16 位/32 位双指令集等众多优点。主要有以下六个主要特点&#xff1a; ① 体积小、低功耗&#xff0c;低成本和高性能&am…

解决一个I2C读写问题

之前写关于I2C相关的文章排查一个触摸屏驱动问题MTK 平台TP调试遇坑1、问题今天遇到一个问题&#xff0c;我们有一个芯片&#xff0c;I2C读写失败&#xff0c;导致录音有问题&#xff0c;而且是偶现的。log提示看到是返回 -6<3>[ 730.336308] (3)[2085:tinycap]es7243_…

用多媒体库 Bass.dll 播放 mp3 [9] - 绘制波形图

本例效果图:代码文件:unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls, ExtCtrls, ComCtrls;typeTForm1 class(TForm)OpenDialog1: TOpenDialog;PaintBox1: TPaintBox;Button1: TButton;Button2: TBut…

我是如何使用wireshark软件的

长按二维码识别关注技术共享|资料共享|沟通交流01简介这篇文章介绍一个好用的抓包工具Wireshark&#xff0c; 用来获取网络数据封包&#xff0c;包括HTTP、TCP、UDP等网络协议包。开始界面wireshark是捕获机器上的某一块网卡的网络包&#xff0c;当你的机器上有多块网卡的时候&…

BZOJ4681 [jsoi2010]旅行

时间限制&#xff1a; 3S空间限制&#xff1a; 256M具体思路:DPf[i][j][k]表示1-i,前L条路上用了 j条,L后的路上换了k条的最小代价枚举一下L就有了一个复杂度O(nlognk^3)的做法AC代码#include<bits/stdc.h> using namespace std; #define INF 100000000 #define P pair&l…

IIC踩过的坑

读取IT8563WE时&#xff0c;读取第一次正确&#xff0c;第二次错误&#xff0c;第三次正确&#xff0c;第四次错误。。。。。。看到读取成功之后&#xff0c;SDA信号没有被正确拉高&#xff0c;电平大概只有一半。再次读取&#xff0c;主机设置读模式时&#xff0c;从机会发送N…

1万字30张图说清TCP协议

本篇文章较长&#xff0c;大家先看下目录1、简介2、TCP协议头3、TCP 数据包的编号&#xff08;SEQ&#xff09;4、三次握手建立连接5、四次挥手断开连接6、TCP可靠性的保证7、滑动窗口技术9、窗口滑动的数据重发9、TCP 流控制10、网线“断”了怎么办01简介TCP(Transmission Con…

Spring源码解析(二)BeanDefinition的Resource定位

IOC容器的初始化过程主要包括BeanDefinition的Resource定位、载入和注册。在实际项目中我们基本上操作的都是ApplicationContex的实现&#xff0c;我们比较熟悉的ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、XmlWebapplicationContext等。ApplicationCo…

单片机(MCU)如何才能不死机之对齐访问(Aligned Access)

从一个结构体说起。如下&#xff0c;在 STM32F0 的程序中&#xff0c;我们定义了一个结构体My_Struct &#xff0c;那么这个结构体占用多少内存呢&#xff1f;struct Struct_Def { uint8_t Var_B; uint16_t Var_W0; uint16_t Var_W1; uint32_t Var_DW; }; struct Struct_Def My…

小米的隔空充电,看起来好酷

昨天是1月29号&#xff0c;昨天小米发布了一个隔空充电技术&#xff0c;很火爆&#xff0c;大胆想&#xff0c;如果有一条无线充电的高速公路&#xff0c;那电动汽车还担心没有电吗&#xff1f;—— 雷总的微博原文隔空充电技术&#xff1a;如科幻电影一般&#xff0c;拿着手机…

同事用void把我给秀翻了!

1、聊一聊今天跟大家推荐的这首歌最近应该挺火的&#xff0c;不过没办法插入AGA的原版歌曲&#xff0c;大家觉得不错可以去找找原版歌曲收录一下。昨天建立了"最后一个bug"技术交流群,由于群成员超过200就无法直接通过群二维码加入&#xff0c;如果大家想加入扫描下面…

Spring Boot之自定义属性

选择Spring Boot主要是考虑到它既能兼顾Spring的强大功能&#xff0c;还能实现快速开发的便捷。我想大多数人也是出于这个原因选择了Spring Boot,如果不是特殊应用场景&#xff0c;就只需要在application.properties中完成一些属性配置就能开启各模块的应用。而不像传统的XML配…

一个老工程师的工作经历和思考

在这里不敢以”资深”工程师自居&#xff0c;因为学历和技术水平确实一般。为什么说“老”呢&#xff1f;因为工作时间确实够长&#xff0c;已经接近20年。下面把自身工作和学习经历和大家分享一下&#xff0c;使初学者能够得到一些有用的东西。2000年毕业&#xff0c;机械电子…

电子工程师都在看什么?送你一份“修炼宝典”

现如今&#xff0c;形形色色的公众号如繁星一般让人眼花缭乱。近几年科技的飞速发展&#xff0c;让更多人开始关注科技&#xff0c;甚至成为极客。然而学习是永无止境的&#xff0c;如何才能追赶如此高速的发展&#xff1f;曾经&#xff0c;我也是不知道去看哪些&#xff0c;便…

DataCleaner(4.5)第一章

Part1. Introduction to DataCleaner  介绍DataCleaner |--What is data quality(DQ)  数据质量&#xff1f;|--What is data profiling?   数据分析&#xff1f;|--What is datastore?     数据存储&#xff1f;   Composite datastore   综合性数据存储 |…

约瑟夫斯问题-java版数组解法和链表解法

10个人围成一圈&#xff0c;从1到10编号&#xff0c;从1开始数&#xff0c;数到3或3的倍数的位置&#xff0c;则该位置的人出局&#xff0c;求最后剩下哪一个号&#xff1f; 数组解法&#xff1a; 数组存放数组&#xff1a;a[10]存在1到10编号人 数组遍历到尾部又从头遍历&…

少写点if-else吧,它的效率有多低你知道吗?

# 干了这碗鸡汤我要再和生活死磕几年。要么我就毁灭&#xff0c;要么我就注定铸就辉煌。如果有一天&#xff0c;你发现我在平庸面前低了头&#xff0c;请向我开炮。--杰克凯鲁亚克if-else涉及到分支预测的概念&#xff0c;关于分支预测上篇文章《虚函数真的就那么慢吗&#xff…

为什么不能在中断上半部休眠?

这是一个老生常谈的问题。我们先简单说下什么是中断「因为最近在群里看到有人竟然不懂什么是中断」。中断是计算机里面非常核心的东西&#xff0c;我们可以跑OS&#xff0c;可以多任务运行都因为中断的存在。假设你是一个CPU&#xff0c;你正在睡觉。你突然觉得肚子疼&#xff…

j.u.c系列(08)---之并发工具类:CountDownLatch

写在前面 CountDownLatch所描述的是”在完成一组正在其他线程中执行的操作之前&#xff0c;它允许一个或多个线程一直等待“&#xff1a;用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法&#xff0c;所以在当前计数到达零之前&#xff0c;await 方法会一直受…