百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

Linux下虚拟内存与物理内存之间的关系

wptr33 2025-01-23 21:51 23 浏览

在介绍 HugePages 之前,我们先来回顾一下 Linux 下 虚拟内存物理内存 之间的关系。

  • 物理内存:也就是安装在计算机中的内存条,比如安装了 2GB 大小的内存条,那么物理内存地址的范围就是 0 ~ 2GB。
  • 虚拟内存:虚拟的内存地址。由于 CPU 只能使用物理内存地址,所以需要将虚拟内存地址转换为物理内存地址才能被 CPU 使用,这个转换过程由 MMU(Memory Management Unit,内存管理单元) 来完成。在 32 位的操作系统中,虚拟内存空间大小为 0 ~ 4GB。

嵌入式进阶教程分门别类整理好了,看的时候十分方便,由于内容较多,这里就截取一部分图吧。

需要的朋友私信【内核】即可领取

我们通过图1描述虚拟内存地址转换成物理内存地址的过程:

如 图1 所示,页表 保存的是虚拟内存地址与物理内存地址的映射关系,MMU页表 中找到虚拟内存地址所映射的物理内存地址,然后把物理内存地址提交给 CPU,这个过程与 Hash 算法相似。

内存映射是以内存页作为单位的,通常情况下,一个内存页的大小为 4KB(如图1所示),所以称为 分页机制

一、内存映射

我们来看看在 64 位的 Linux 系统中(英特尔 x64 CPU),虚拟内存地址转换成物理内存地址的过程,如图2:

从图2可以看出,Linux 只使用了 64 位虚拟内存地址的前面 48 位(0 ~ 47倍),并且 Linux 把这 48 虚拟内存地址分为 5 个部分,如下:

  • PGD索引:39 ~ 47 位(共9个位),指定在 页全局目录(PGD,Page Global Directory)中的索引。
  • PUD索引:30 ~ 38 位(共9个位),指定在 页上级目录(PUD,Page Upper Directory)中的索引。
  • PMD索引:21 ~ 29 位(共9个位),指定在 页中间目录(PMD,Page Middle Directory)中的索引。
  • PTE索引:12 ~ 20 位(共9个位),指定在 页表(PT,Page Table)中的索引。
  • 偏移量:0 ~ 11 位(共12个位),指定在物理内存页中的偏移量。

把 图1 中的 页表 分为 4 级:页全局目录页上级目录页中间目录页表 目的是为了减少内存消耗(思考下为什么可以减少内存消耗)。

注意:页全局目录、页上级目录、页中间目录 和 页表 都占用一个 4KB 大小的物理内存页,由于 64 位内存地址占用 8 个字节,所以一个 4KB 大小的物理内存也可以容纳 512 个 64 位内存地址。

另外,CPU 有个名为 CR3 的寄存器,用于保存 页全局目录 的起始物理内存地址(如图2所示)。所以,虚拟内存地址转换成物理内存地址的过程如下:

  • CR3 寄存器中获取 页全局目录 的物理内存地址,然后以虚拟内存地址的 39 ~ 47 位作为索引,从 页全局目录 中读取到 页上级目录 的物理内存地址。
  • 以虚拟内存地址的 30 ~ 38 位作为索引,从 页上级目录 中读取到 页中间目录 的物理内存地址。
  • 以虚拟内存地址的 21 ~ 29 位作为索引,从 页中间目录 中读取到 页表 的物理内存地址。
  • 以虚拟内存地址的 12 ~ 20 位作为索引,从 页表 中读取到 物理内存页 的物理内存地址。
  • 以虚拟内存地址的 0 ~ 11 位作为 物理内存页 的偏移量,得到最终的物理内存地址。

二、HugePages 原理

上面介绍了以 4KB 的内存页作为内存映射的单位,但有些场景我们希望使用更大的内存页作为映射单位(如 2MB)。使用更大的内存页作为映射单位有如下好处:

  • 减少 TLB(Translation Lookaside Buffer) 的失效情况。
  • 减少 页表 的内存消耗。
  • 减少 PageFault(缺页中断)的次数。

Tips:TLB 是一块高速缓存,TLB 缓存虚拟内存地址与其映射的物理内存地址。MMU 首先从 TLB 查找内存映射的关系,如果找到就不用回溯查找页表。否则,只能根据虚拟内存地址,去页表中查找其映射的物理内存地址。

因为映射的内存页越大,所需要的 页表 就越小(很容易理解);页表 越小,TLB 失效的情况就越少。

使用大于 4KB 的内存页作为内存映射单位的机制叫 HugePages,目前 Linux 常用的 HugePages 大小为 2MB 和 1GB,我们以 2MB 大小的内存页作为例子。

要映射更大的内存页,只需要增加偏移量部分,如 图3 所示:

如 图3 所示,现在把偏移量部分扩展到 21 位(页表部分被覆盖了,21 位能够表示的大小范围为 0 ~ 2MB),所以 页中间目录 直接指向映射的 物理内存页地址

这样,就可以减少 页表 部分的内存消耗。由于内存映射关系变少,所以 TLB 失效的情况也会减少。

三、HugePages 使用

了解了 HugePages 的原理后,我们来介绍一下怎么使用 HugePages。

HugePages 的使用不像普通内存申请那么简单,而是需要借助 Hugetlb文件系统 来创建,下面将会介绍 HugePages 的使用步骤:

1. 挂载 Hugetlb 文件系统

Hugetlb 文件系统是专门为 HugePages 而创造的,我们可以通过以下命令来挂载一个 Hugetlb 文件系统:

$ mkdir /mnt/huge
$ mount none /mnt/huge -t hugetlbfs

执行完上面的命令后,我们就在 /mnt/huge 目录下挂载了 Hugetlb 文件系统。

2. 声明可用 HugePages 数量

要使用 HugePages,首先要向内核声明可以使用的 HugePages 数量。/proc/sys/vm/nr_hugepages 文件保存了内核可以使用的 HugePages 数量,我们可以使用以下命令设置新的可用 HugePages 数量:

$ echo 20 > /proc/sys/vm/nr_hugepages

上面命令设置了可用的 HugePages 数量为 20 个(也就是 20 个 2MB 的内存页)。

3. 编写申请 HugePages 的代码

要使用 HugePages,必须使用 mmap 系统调用把虚拟内存映射到 Hugetlb 文件系统中的文件,如下代码:

#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdio.h>

#define MAP_LENGTH (10*1024*1024) // 10MB

int main()
{
    int fd;
    void * addr;

    // 1. 创建一个 Hugetlb 文件系统的文件
    fd = open("/mnt/huge/hugepage1", O_CREAT|O_RDWR);
    if (fd < 0) {
        perror("open()");
        return -1;
    }

    // 2. 把虚拟内存映射到 Hugetlb 文件系统的文件中
    addr = mmap(0, MAP_LENGTH, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap()");
        close(fd);
        unlink("/mnt/huge/hugepage1");
        return -1;
    }

    strcpy(addr, "This is HugePages example...");
    printf("%s\n", addr);

    // 3. 使用完成后,解除映射关系
    munmap(addr, MAP_LENGTH);
    close(fd);
    unlink("/mnt/huge/hugepage1");

    return 0;
 }

编译上面的代码并且执行,如果没有问题,将会输出以下信息:

   This is HugePages example...

四、总结

本文主要介绍了 HugePages 的原理和使用,虽然 HugePages 有很多优点,但也有其不足的地方。比如调用 fork 系统调用创建子进程时,内核使用了 写时复制 的技术,在父子进程内存发生改变时,需要复制更大的内存页,从而影响性能。

导读-最新发表 - 我爱内核网 - 构建全国最权威的内核技术交流分享论坛

Linux下虚拟内存与物理内存之间的关系 - 论坛 - 我爱内核网 - 构建全国最权威的内核技术交流分享论坛

相关推荐

redis的八种使用场景

前言:redis是我们工作开发中,经常要打交道的,下面对redis的使用场景做总结介绍也是对redis举报的功能做梳理。缓存Redis最常见的用途是作为缓存,用于加速应用程序的响应速度。...

基于Redis的3种分布式ID生成策略

在分布式系统设计中,全局唯一ID是一个基础而关键的组件。随着业务规模扩大和系统架构向微服务演进,传统的单机自增ID已无法满足需求。高并发、高可用的分布式ID生成方案成为构建可靠分布式系统的必要条件。R...

基于OpenWrt系统路由器的模式切换与网页设计

摘要:目前商用WiFi路由器已应用到多个领域,商家通过给用户提供一个稳定免费WiFi热点达到吸引客户、提升服务的目标。传统路由器自带的Luci界面提供了工厂模式的Web界面,用户可通过该界面配置路...

这篇文章教你看明白 nginx-ingress 控制器

主机nginx一般nginx做主机反向代理(网关)有以下配置...

如何用redis实现注册中心

一句话总结使用Redis实现注册中心:服务注册...

爱可可老师24小时热门分享(2020.5.10)

No1.看自己以前写的代码是种什么体验?No2.DooM-chip!国外网友SylvainLefebvre自制的无CPU、无操作码、无指令计数器...No3.我认为CS学位可以更好,如...

Apportable:拯救程序员,IOS一秒变安卓

摘要:还在为了跨平台使用cocos2d-x吗,拯救objc程序员的奇葩来了,ApportableSDK:FreeAndroidsupportforcocos2d-iPhone。App...

JAVA实现超买超卖方案汇总,那个最适合你,一篇文章彻底讲透

以下是几种Java实现超买超卖问题的核心解决方案及代码示例,针对高并发场景下的库存扣减问题:方案一:Redis原子操作+Lua脚本(推荐)//使用Redis+Lua保证原子性publicbo...

3月26日更新 快速施法自动施法可独立设置

2016年3月26日DOTA2有一个79.6MB的更新主要是针对自动施法和快速施法的调整本来内容不多不少朋友都有自动施法和快速施法的困扰英文更新日志一些视觉BUG修复就不翻译了主要翻译自动施...

Redis 是如何提供服务的

在刚刚接触Redis的时候,最想要知道的是一个’setnameJhon’命令到达Redis服务器的时候,它是如何返回’OK’的?里面命令处理的流程如何,具体细节怎么样?你一定有问过自己...

lua _G、_VERSION使用

到这里我们已经把lua基础库中的函数介绍完了,除了函数外基础库中还有两个常量,一个是_G,另一个是_VERSION。_G是基础库本身,指向自己,这个变量很有意思,可以无限引用自己,最后得到的还是自己,...

China&#39;s top diplomat to chair third China-Pacific Island countries foreign ministers&#39; meeting

BEIJING,May21(Xinhua)--ChineseForeignMinisterWangYi,alsoamemberofthePoliticalBureau...

移动工作交流工具Lua推出Insights数据分析产品

Lua是一个适用于各种职业人士的移动交流平台,它在今天推出了一项叫做Insights的全新功能。Insights是一个数据平台,客户可以在上面实时看到员工之间的交流情况,并分析这些情况对公司发展的影响...

Redis 7新武器:用Redis Stack实现向量搜索的极限压测

当传统关系型数据库还在为向量相似度搜索的性能挣扎时,Redis7的RedisStack...

Nginx/OpenResty详解,Nginx Lua编程,重定向与内部子请求

重定向与内部子请求Nginx的rewrite指令不仅可以在Nginx内部的server、location之间进行跳转,还可以进行外部链接的重定向。通过ngx_lua模块的Lua函数除了能实现Nginx...