内存2019-11-29

 虚拟内存:虚拟内存是一种逻辑上扩充物理内存的技术。基本思想是用软、硬件技术把内存与外存这两级存储器当做一级存储器来用。虚拟内存技术的实现利用了自动覆盖和交换技术。简单的说就是将硬盘的一部分作为内存来使用。

 如果CPU启用了MMU,CPU核发出的地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,以下简称VA),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。

C++ 内存管理

在 C++ 中,内存分为:栈、堆、自由存储区、全局静态存储区、常量存储区

栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结果结束也是在栈上进行释放

堆,就是那些由 malloc 等分配的内存块,用 free 来释放

STL 中的内存分配策略

Linux 进程分配内存的两种方式 brk 和 mmap

如何查看进程发生缺页中断的次数

用ps -o majflt,minflt -C program命令查看

其中,majflt 表示 majflt fault,大错误

发生缺页中断后,执行了哪些操作

检查要访问的虚拟地址是否合法

查找/分配一个物理页

填充物理页内容(读取磁盘,或者直接置 0,或者啥也不干)

建立映射关系(虚拟地址到物理地址)

如果第三步需要读取磁盘,那么这次缺页中断就是 majflt,否则就是 minflt

内存分配原理

brk 是将数据段的最高地址指针 break 往高地址推(小于 128 K)

mmap 是在进程的虚拟地址空间中(堆和栈中间,成为文件映射区域的地方)找一块空闲的虚拟内存(大于 128 K)

这两种都是分配虚拟内存,没有分配物理内存,在第一次访问已分配的虚拟地 址空间时,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系

Linux-malloc 底层实现原理

前言

首先 malloc 肯定在堆上分配内存,下来我们讨论一下

在 32 位系统下,寻址空间 4G,Linux 系统下 0-3G 是用户模式,3-4G 是内核模式,而在用户 模式下又分为:代码段、数据段、.bss 段、堆、栈,其中 bss 段存放未初始化的全局变量和局部 静态变量,数据段存放已经初始化的全局变量和局部静态变量,局部变量存放在栈中

堆区位于 bss 段下方,Linux 维护一个 break 指针,这个指针指向堆空间的某个地址,从 堆起始到 break 之间的地址为映射好的,可以供进程使用,而 break 指针往上,是为映射的地址空 间,如果访问这段空间,程序则会报错,我们利用 malloc 就是从 break 往上进行分配的

进程面对的虚拟内存空间,只有按页映射到物理内存,才能真正使用,受物理内存容量限制, 整个堆虚拟内存空间不可能全部映射到实际的物理内存

malloc 描述

malloc 函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。 调 用 malloc() 函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内 存块。 然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩 下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的 那块(如果有的话)返回到连接表上。 调用 free 函数时,它将用户释放的内存块连接 到空闲链表上。 到最后,空闲链会被切成很多的小内存片段,如果这时用户 申请一个大的内存片段, 那么空闲链表上可能没有可以满足用户要求的 片段了。于是,malloc() 函数请求延时,并开始在空闲链表上检查各内存片段,对它们 进行内存整理,将相邻的小空闲块合并成较大的内存块。

malloc 分配内存前的初始化

malloc_init 是初始化内存分配程序的函数,它完成三个目的,将分配程序标识为已 初始化;找到操作系统中最后一个有效的内存地址,然后建立起指向需要管理的内存 的指针,这里需要用到三个全局变量

inthas_initialized =0;//初始化标记void* mananed_memory_start;//管理内存的起始地址void* last_valid_address;//操作系统中最后一个有效的地址

被映射的内存边界常称为系统中断点或当前中断点,为了指出当前系统中断点,必须 使用 sbrk(0) 函数,sbrk 函数根据参数中给出的字节数移动当前系统中断点,然后 返回新的系统中断点,使用参数 0,只是返回当前中断点,Linux 通过 brk 和 sbrk 系 统调用操作 break 指针,brk 将 break 指针直接设定为某个地址,而 sbrk 将 break 从 当前位置移动参数所指定的增量,前者成功返回 0,否则 -1 并且 设置 error 为 ENOMEM,sbrk 成功 返回 break 移动之前所指向的地址,否则 (void*)-1

这里给出内存初始化 malloc_init 函数

voidmalloc_init() {//用 sbrk 函数在操作系统中取得最后一个有效地址last_valid_address =sbrk(0);//将最后一个有效地址作为管理内存的起始地址mananed_memory_start = last_valid_address;//初始化成功标记has_initialized =1;}

内存块的获取

我们所要申请的内存是由多个内存块构成的链表

内存块的大致结构:每个块由 meta 区和数据区组成,meta 区记录数据块的元信息(数据区大小、空闲标 标志位、指针等等),数据区是真实分配的内存区域,并且数据区的第一个字节地址即为 malloc 返回的地址

typedefstructs_block* t_block;structs_block{size_tsize;//数据区大小t_block next;//指向下个字节的指针intfree;//是否为空闲块intpadding;//填充 4 字节,保证 meta 块长度为 8 的倍数chardata[1];//这是一个虚拟字段,标识数据区的第一个字节,长度不计入 meta};

现在,为了完全管理内存,我们需要能够追踪要分配和回收哪些内存,在对内存快进行了 free 调用 之后,我们需要做的是如何把他们标记为未被使用的内存块,并且在调用 malloc 时,我们要 将能够定位未被使用的内存块,因此,malloc 返回的每块内存的起始都要有如下这个结构

structmem_control_block{intis_available;//是否空闲intsize;//内存块大小};

寻找合适的 block

上述工作准备完成,我们应该考虑如何选择合适的 block

从头开始,使用第一个数据区大小大于 size 的块

从头开始,遍历所有快,使用数据区大小大于 size 并且差值最小的块

两种方法各有千秋:前者具有更好的运行效率,后者具有更高的内存使用率

find_block 从第一个块开始,查找第一个符合要求的 block 并返回其地址,如果找不到就返回 NULL,这 里在遍历时会更新一个叫 last 的指针,这个指针始终指向当前遍历的 block,这是为了如果找不到合适的块而开辟新的 block 使用的

如果现有的 block 都不满足 size 要求,则需要在链表的最后开辟一个新的 block,开辟新的 block 示意代码,

#defineBLOCK_SIZE24//由于存在虚拟的data字段,sizeof不能正确计算meta长度,这里手工设置t_blockextend_heap(t_block last,size_ts) {    t_block b;    b =sbrk(0);if(sbrk(BLOCK_SIZE + s) == (void*)-1)returnNULL;    b->size= s;    b->next=NULL;if(last)        last->next= b;    b->free=0;returnb;

你可能感兴趣的:(内存2019-11-29)