内存优化总结:ptmalloc、tcmalloc和jemalloc:https://blog.csdn.net/junlon2006/article/details/77854898  

malloc的底层实现(ptmalloc):https://blog.csdn.net/z_ryan/article/details/79950737

从操作系统层面来说,malloc 是考察面试者对操作系统底层的存储管理理解的一个很好的方式,涉及到虚拟内存、分页/分段等。下面逐个细说。

1. 虚拟内存

首先需要知道的是程序运行起来的话需要被加载的物理内存中,具体到计算机硬件就是内存条。操作系统启动的时候先把自己加载到物理内存的固定位置(一般为底部),物理内存的其他位置就用来运行用户程序。程序就是一堆指令,程序运行可以简单抽象为把指令加载到内存中,然后 CPU 将指令从内存载入执行。

1. 为什么需要虚拟内存?

CPU 对内存的寻址最简单的方式就是直接使用物理内存地址,这种方式一般叫做物理寻址。早期的 PC 使用物理寻址,而且像数字信号处理器、嵌入式微控制器也使用物理寻址。物理寻址的好处是简单,坏处也有很多,比如:

不安全:操作系统的地址直接暴露给用户程序,用户程序可以破坏操作系统。这种解决方案是采用特殊的硬件保护。

同时运行多个程序比较困难:多个用户程序如果都直接引用物理地址,很容易互相干扰。那么是不是可以通过不断交换物理内存和磁盘来保证物理内存某一时间自由一个程序在运行呢?当时是可以的,但是这引入很多不必要和复杂的工作。

用户程序大小受限:受制于物理内存大小。我们现在的错觉是应用程序大小都小于物理内存,这主要是因为现在 PC 的物理内存都比较大。实际上只有 1G 物理内存的 PC 是可以运行 2G 的应用程序的。

综合上面各种缺点,虚拟内存出现了。

2. 虚拟内存概览

虚拟内存的基本思想是:每个程序拥有独立的地址空间(也就是虚拟内存地址,或者称作虚拟地址),互不干扰。地址空间被分割成多个块,每一块称作一页(page),每一页有连续的地址范围。虚拟地址的页被映射到物理内存(通过 MMU,Memory Management Unit),但是并不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将确实的部分装入物理内存。虚拟地址寻址(也叫做虚拟寻址)的示意图如下。

3.虚拟内存实现

1.虚拟内存大小

一般是和 CPU 字长相关,比如 32 位对应的虚拟地址空间大小为:0 ~ 2^31。

2. MMU

CPU 将虚拟地址发送给 MMU,然后 MMU 将虚拟地址翻译成物理地址,再寻址物理内存。那么虚拟地址和物理地址具体是怎么映射的呢?完成映射还需要另一个重要的数据结构的参与:页表(page table)。页表完成虚拟地址和物理地址的映射,MMU 每次翻译的时候都需要读取页表。页表的一种简单表示如下。

这里页大小为 p 位。虚拟内存的页和物理内存的页大小一样。虚拟地址的高 n-p 位,又叫做虚拟页号(Virtual Page Number, VPN),用来索引物理页号(Physical Page Number,PPN),最后将 PPN 和低 p 位组合在一起就得到了物理地址。

3. 页表的两个问题

前面说到用 VPN 来做页表索引,也就是说页表的大小为虚拟地址位数 / 页的大小。比如 32 位机器,页大小一般为 4K ,则页表项有 2^32 / 2^12 = 2^20 条目。如果机器字长 64 位,页表项就更多了。那么怎么解决呢?一般有两种方法:

  1. 倒排页表。物理页号做索引,映射到多个虚拟地址。通过虚拟地址查找的时候就需要通过虚拟地址的中间几位来做索引了。

  2. 多级页表。以两级页表为例。一级页表中的每个 PTE (page table entry)映射虚拟地址空间的一个 4MB 的片,每一片由1024 个连续的页面组成。一级 PTE 指向二级页表的基址。这样 32 位地址空间使用 1024 个一级 PTE 就可以表示。需要的二级页表总条目还是 2^32 / 2^12 = 2^20 个。这里的关键在于如果一级 PTE i 中的页面都未被分配,一级 PTE 就为空。多级页面的一个简单示意图如下。

多级页表减少内存占用的关键在于:

  1. 如果一级页表中的一个 PTE 为空,那么相应的二级页表就根本不会存在。这是一种巨大的潜在节约。

  2. 只有一级页表才需要常驻内存。虚拟内存系统可以在需要时创建、页面调入或者调出二级页表,从而减轻内存的压力。

第二个问题是页表是在内存中,而 MMU 位于 CPU 芯片中,这样每次地址翻译可能都需要先访问一次内存中的页表(CPU L1,L2,L3 Cache Miss 的时候访问内存),效率非常低下。对应的解决方案是引入页表的高速缓存:TLB(Translation Lookaside Buffer)。加入 TLB,整个虚拟地址翻译的过程如下两图所示。

什么是page fault

当进程访问它的虚拟地址空间中的PAGE时,如果这个PAGE目前还不在物理内存中,此时CPU是不能干活的,Linux会产生一个hard page fault中断。系统需要从慢速设备(如磁盘)将对应的数据PAGE读入物理内存,并建立物理内存地址与虚拟地址空间PAGE的映射关系。然后进程才能访问这部分虚拟地址空间的内存。

page fault 又分为几种,major page fault、 minor page fault、 invalid(segment fault)。

major page fault 也称为 hard page fault, 指需要访问的内存不在虚拟地址空间,也不在物理内存中,需要从慢速设备载入。从swap 回到物理内存也是 hard page fault。

minor page fault 也称为 soft page fault, 指需要访问的内存不在虚拟地址空间,但是在物理内存中,只需要MMU建立物理内存和虚拟地址空间的映射关系即可。

  1. 当一个进程在调用 malloc 获取虚拟空间地址后,首次访问该地址会发生一次soft  page fault。
  2. 通常是多个进程访问同一个共享内存中的数据,可能某些进程还没有建立起映射关系,所以访问时会出现soft page fault

invalid fault 也称为 segment fault,指进程需要访问的内存地址不在它的虚拟地址空间范围内,属于越界访问,内核会报 segment fault错误。

抖动在分页存储管理系统中,内存中只存放了那些经常使用的页面, 而其它页面则存放在外存中,当进程运行需要的内容不在内存时, 便启动磁盘读操作将所需内容调入内存,若内存中没有空闲物理块, 还需要将内存中的某页面置换出去。也就是说,系统需要不断地在内外存之间交换信息。 若在系统运行过程中,刚被淘汰出内存的页面,过后不久又要访问它, 需要再次将其调入。而该页面调入内存后不久又再次被淘汰出内存,然后又要访问它。 如此反复,使得系统把大部分时间用在了页面的调入/换出上, 而几乎不能完成任何有效的工作,这种现象称为抖动。

2. 分段

1. 分段概述

前面介绍了分页内存管理,可以说通过多级页表,TLB 等,分页内存管理方法已经相当不错了。那么分页有什么缺点呢?

  1. 共享困难:通过共享页面来实现共享当然是可以的。这里的问题在于我们要保证页面上只包含可以共享的内容并不是一件容易的事儿,因为进程空间是直接映射到页面上的。这样一个页面上很可能包含不能共享的内容(比如既包含代码又包含数据,代码可以共享,而数据不能共享)。早期的 PDP-11 实现的一种解决方法是为指令和数据设置分离的地址空间,分别称为 I 空间和 D 空间(其实这已经和分段很像了)。

  2. 程序地址空间受限于虚拟地址:我们将程序全部映射到一个统一的虚拟地址的问题在于不好扩张。不如我们程序的地址按先代码放在一起,然后把数据放在一起,然后再放 XXX,这样其中某一部分的空间扩张起来都会影响到相邻的空间,非常不方便。

上面的问题一个比较直观的解决方法是提供多个独立的地址空间,也就是段(segment)。每个段的长度视具体的段不同而不同,而且是可以在运行期动态改变的。因为每个段都构成了一个独立的地址空间,所以它们可以独立的增长或者减小而不会影响到其他的段。如果一个段比较大,把它整个保存到内存中可能很不方便甚至是不可能的,因此可以对段采用分页管理,只有那些真正需要的页面才会被调入内存。

采用分段和分页结合的方式管理内存,一个地址由两个部分组成:段和段内地址。段内地址又进一步分为页号和页偏移。在进行内存访问时,过程如下:

  1. 根据段号找到段描述符(存放段基址)。

  2. 检查该段的页表是否在内存中。如果在,则找到它的位置,如果不在,则产生段错误。

  3. 检查所请求的虚拟页面的页表项,如果该页面不在内存中则产生缺页中断,如果在内存中就从页表项中取出这个页面在内存中的起始地址。

  4. 将页面起始地址和偏移量进行拼接得到物理地址,然后完成读写。

2. 进程的段

每个 Linux 程序都有一个运行时内存映像,也就是各个段的布局,简单如下图所示。

注意上图只是一个相对位置图,实际上这些段并不是相邻的。主要的段包括只读代码段、读写段、运行时堆、映射区、用户栈。在分配栈、堆段运行时地址的时候,链接器会使用空间地址空间布局随机化(ASLR),但是相对位置不会变。上图中 .data 等是对应进程中的不同数据的 section ,或者叫做节。简介如下。

  • .text: 已编译程序的机器代码。

  • .rodata: 只读数据。

  • .data: 已初始化的全局和静态变量。局部变量保存在栈上。

  • .bss: 未初始化的全局和静态变量,以及所有被初始化为 0 的全局或者静态变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。

3. malloc 实现

1. 堆内存管理

我们常说的 malloc 函数是 glibc 提供的库函数。

glibc 的内存管理使用的方法是 ptmalloc,除此之后还有很多其他内存管理方案,比如 tcmalloc (golang 使用的类似 tcmalloc)。

ptmalloc 对于申请内存小于 128KB 时,分配是在堆段,使用系统调用 brk() 或者 sbrk()。如果大于 128 KB 的话,分配在映射区,使用系统调用 mmap()

2. brk, sbrk

在堆段申请的话,使用系统调用 brk 或者 sbrk。

int brk(const void *addr);

void *sbrk(intptr_t incr);

brk 将 brk 指针放置到指定地址处,成功返回 0,否则返回 -1。sbrk 将 brk 指针向后移动指定字节,返回依赖于系统实现,或者返回移动前的 brk 位置,或者返回移动后的 brk 位置。下面使用 sbrk 实现一个巨简单的 malloc。

void *malloc(size_t size) {

void *p = sbrk(0);

void *request = sbrk(size);

if (request == (void*) -1) {

return NULL; // sbrk failed.

} else {

assert(p == request); // Not thread safe.

return p;

}

}

3. mmap

linux 系统调用 mmap 将一个文件或者其它对象映射进内存。

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

mmap 的 flags 可选多种参数,当选择 MAP_ANONYMOUS 时,不需要传入文件描述符,malloc 使用的就是 MAP_ANONYMOUS 模式。mmap 申请的内存在操作系统的映射区。比如 32 位系统,映射区从 3G 虚拟地址粗向下生长,但是因为程序的其他段也会占用空间(比如代码段必须以特定的地址开始),所以并不能申请 3G 的大小。

4. malloc 和物理内存有关系吗?

可以说没关系,malloc 申请的地址是线性地址,申请的时候并没有进行映射。访问到的时候触发缺页异常,这个时候才会进行物理地址映射。

5. ptmalloc

ptmalloc 只是 glibc 使用的内存管理策略,篇幅有限,这里就不细说了。

4. 参考:

  1. 《深入理解计算机系统》

  2. 《现代操作系统》

  3. StackOverFlow

  4. mmap manpage

  5. tcmalloc 介绍

  6. a malloc tutorial

  7. malloc manpage

  8. understanding glibc malloc

  9. advanced memory allocation

malloc 背后的系统知识(虚拟内存地址)相关推荐

  1. Docker背后的内核知识:命名空间资源隔离

    Docker背后的内核知识:命名空间资源隔离 Docker这么火,喜欢技术的朋友可能也会想,如果要自己实现一个资源隔离的容器,应该从哪些方面下手呢?也许你第一反应可能就是chroot命令,这条命令给用 ...

  2. iOS底层系统:虚拟内存

    关于作者 E-moss,程序员,爱好阅读和撸狗,主要从事iOS开发工作,公众号:知本集. 主要分享和编写技术方面文章,不定期分享读书笔记,亦可访问"知本集"Git地址:https: ...

  3. 【Binder 机制】进程通信 | 用户空间与内核空间 | MMU 与虚拟内存地址

    文章目录 一.进程通信 二.用户空间与内核空间 三.MMU 与虚拟内存地址 一.进程通信 进程隔离概念 : 系统中的进程存在 " 进程隔离 " , 出于对进程运行的保护 , 两个进 ...

  4. 虚拟内存的作用、分页系统实现虚拟内存原理

    文章目录 一.虚拟内存 二.虚拟内存的作用 三.分页系统实现虚拟内存原理 1.虚拟内存技术的实现 2.请求分页管理方式 一.虚拟内存   虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分 ...

  5. 华为系统云服务器地址,华为系统云服务器地址

    华为系统云服务器地址 内容精选 换一换 更新弹性云服务器的系统或者软件时,可以连接Internet,通过外部Pypi镜像源提供相关服务.但是,如果弹性云服务器无法访问Internet,或者外部Pypi ...

  6. 32位系统的虚拟内存空间最大容量

    表示32位系统的内存地址有32位,即4字节(表示64位系统的内存地址有64bit,即8字节) 答案:pow(2,32)表示有这么多虚拟内存空间的地址,每个地址指向1字节8bit的数据. 注:1024= ...

  7. Docker背后的内核知识之资源隔离

    文章目录 Docker 背后的内核知识-namespace 资源隔离 1.进行namespace API操作的4种方法 2.UTS namespace 3.IPC namespace 4.PID na ...

  8. 大数据技术之Hadoop分布式文件系统HDFS系统知识整理(从入门到熟练操作)

    系列博客 1.大数据技术之Hadoop完全分布式集群搭建+Centos7配置连通外网和主机 2.大数据技术之Hadoop编译源码 3.大数据技术之Hadoop分布式文件系统HDFS系统知识整理(从入门 ...

  9. UI设计培训之UI设计系统知识

    最近有很多小伙伴都在学习UI设计的相关知识,很多同学都是东边一学习一下,西边学习一下,根本没有明确的学习方法,对于这个问题小编为大家整理了一下学习UI设计的系统知识,一起看看吧! UI设计培训之UI设 ...

最新文章

  1. mmap映射大于4g的文件_iOS文件内存映射——MMAP
  2. JavaScript技术篇 - js的null值判断,js的undefined的判断,js的null与undefined的2种区分方法
  3. jQuery源码学习
  4. Self-training在目标检测任务上的实践
  5. 在同一个机器上布署两个JBOSS,要修改那些端口?
  6. 设计模式之_Strategy_05
  7. 前端学习(1910)vue之电商管理系统电商系统之完成用户的修改
  8. 数学通大道,算法合自然?
  9. android drawable资源调用使用心得
  10. (6)matplot去掉坐标轴
  11. 【数字图像处理课程设计】期中、期末综合考试题目整理总结(共四个图像处理算法应用题)
  12. 计算机登录密码保存,怎么查看电脑浏览器中保存的密码
  13. lora网关采集温室大棚温湿度数据案例
  14. 数据库原理(2)——数据模型与概念模型
  15. 麒麟v10服务器系统搭建本地源
  16. 查看linux负载的情况
  17. HEVC预测块(PU)模式划分显示
  18. 区块链培训就业方向多不多?
  19. 苹果5壁纸_元气壁纸软件-元气壁纸安卓版下载v1.0.2
  20. redistemplate文档用法_Spring学习笔记之RedisTemplate的配置与使用教程

热门文章

  1. sphinx全文检索功能 | windows下测试 (二)
  2. windows剪切板暂存
  3. C# 打开word 语法拼写错误太多 解决方案
  4. .Net中DataTable的保存
  5. 若一个月给你50000元,要你选下面一件事做,你会选?
  6. 操作系统实验报告4:Linux 下 x86 汇编语言3
  7. [Python人工智能] 二十四.易学智能GPU搭建Keras环境实现LSTM恶意URL请求分类
  8. [Python从零到壹] 三.语法基础之文件操作、CSV文件读写及面向对象
  9. [知识图谱实战篇] 三.Python提取JSON数据、HTML+D3构建基本可视化布局
  10. 【数据结构与算法】之深入解析“石子游戏”的求解思路与算法示例