虚拟内存

先简单介绍一下操作系统中为什么会有虚拟地址和物理地址的区别。因为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 0x12ee58000 Please 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 64位系统虚拟地址和物理地址的映射及验证方法的更多相关文章

  1. 手把手VirtualBox虚拟机下安装rhel6.4 linux 64位系统详细文档

    下面演示安装的是在VirtualBox里安装rhel 6.4 linux 64位系统. 一.VirtualBOX 版本. 二.虚拟机的配置. 1.现在开始演示安装,一起从零开始.点击“新建”,创建新的 ...

  2. 在Linux 64位系统下使用hugepage

    首先,为什么要介绍/使用HugePage? 在步入正题之前,先讲一个非常普遍的数据库性能问题. 众所周知,Oracle数据库使用共享内存(SGA)来管理可以共享的一些资源;比如shared pool中 ...

  3. 64位系统使用Access 数据库文件的彻底解决方法

    最近,有PDF.NET用户问我怎么在64位系统下无法访问Access数据库的问题,我第一反应是我怎么没有遇到呢?今天一看自己的VS和Office都是32位版本的,所以在VS里面调试访问Access是没 ...

  4. Adobe/Flash Media Server 5.0 linux 64位系统下的安装

    一.下载 Adobe/Flash MS5.0下载地址: http://fs1.d-h.st/download/00036/VOt/adobemediaserver_5_ls1_linux64.tar. ...

  5. centos7.2 linux 64位系统上安装mysql

    1.在线安装mysql 在终端中命令行下输入(在官网下载mysql): # wget https://dev.mysql.com/downloads/repo/yum/mysql57-communit ...

  6. PL/SQL Developer 在windows7 64位系统下连Oaracle11g64位系统的解决经验

    PL/SQL Developer 在windows7 64位系统下连Oaracle11g64位系统的解决经验 一.问题现象及解决方法 现象: 1.PL/SQL 无法登录64位数据库 2.在PL/SQL ...

  7. 【转】将 Linux 应用程序移植到 64 位系统上

    原文网址:http://www.ibm.com/developerworks/cn/linux/l-port64.html 随着 64 位体系结构的普及,针对 64 位系统准备好您的 Linux® 软 ...

  8. linux 下 查看是32位还是64位系统 命令

    文章引自:http://zhidao.baidu.com/question/583981849.html 方法1:getconf LONG_BIT 查看 如下例子所示: 32位Linux系统显示32, ...

  9. 20135337——Linux实践三:ELF文件格式(64位系统,简单分析)

    ELF文件格式简单分析 (具体分析见上一篇ELF文件格式32位系统) ELF-header 第一行: 457f 464c :魔数: 0201 :64位系统,小端法 01 :文件头版本 剩余默认0: 第 ...

随机推荐

  1. 安装卸载nginx

    http://www.nginx.cn/install ubuntu和debain下的apt方式安装软件很方便,特别是对于新手安装和卸载nginx. 由于nginx不能动态添加模块,所以会经常安装和卸 ...

  2. python测试代码

    前言: 编写函数或者类时,需要编写测试代码,来保证其的功能运行是否按预期的那样工作.在程序添加新的代码时,用来测试是否会破坏本身的功能. 我们使用python自带的unittest模块来测试代码. 编 ...

  3. vue前端静态页面Github Pages线上预览实现

    一.前期准备之项目编译 此处记录如何解决vue2.0 打包之后,打开index.html出现空白页的问题,附上@参考地址 打包之前修改三个文件 第一步,找到build文件,在webpack.prod. ...

  4. dubbo与zk

    一.总体流程: 1.服务提供者启动时,会向注册中心写入自己的元数据信息,同时会订阅配置元数据信息: 2.消费者启动时,也会向注册中心写入自己的元数据信息,并订阅服务提供者.路由和配置元数据信息: 3. ...

  5. Spring Boot + RabbitMQ 使用示例

    基础知识 虚拟主机 (Virtual Host): 每个 virtual host 拥有自己的 exchanges, queues 等 (类似 MySQL 中的库) 交换器 (Exchange): 生 ...

  6. Java虚拟机之内存区域

    原创文章,转载请标明出处! 目录 一.背景 二.运行时内存区域概述 1.官方描述 2.中文翻译 3.内存区域简述 4.运行时数据区简图 5.运行时数据区详图 三.JVM线程 JVM数据区域与线程关系 ...

  7. 第12.5节 Python time模块导览

    一.时间相关的概念 time模块模块提供了各种时间相关的函数,在介绍时间相关功能前,先介绍一些术语和惯例: epoch 是时间开始的点,并且取决于平台.对于Unix, epoch 是1970年1月1日 ...

  8. Leetcode学习笔记(6)

    题目1 ID112 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和. 说明: 叶子节点是指没有子节点的节点. 示例: 给定如下二叉树,以及目标 ...

  9. Python 常用方法和模块的使用(time & datetime & os &random &sys &shutil)-(六)

    1 比较常用的一些方法 1.eval()方法:执行字符串表达式,并返回到字符串. 2.序列化:变量从内存中变成可存储或传输到文件或变量的过程,可以保存当时对象的状态,实现其生命周期的延长,并且需要时可 ...

  10. CSS初识- 选择器 &背景& 浮动& 盒子模型

    # CSS初识-目标: > 1. 学会使用CSS选择器 > 2. 熟记CSS样式和外观属性 > 3. 熟练掌握CSS各种基础选择器 > 4. 熟练掌握CSS各种复合选择器 &g ...