一个简单的内存分配器-《深入理解计算机操作系统》

  我相信很多人都知道《深入理解计算机操作系统》这本书,并且很多人都会对它研读。实际本人刚开始看的时候,只是加深了对操作系统的理解,别的到是没有感觉的到,

但是在看到公司的软件框架里面对于内存堆的管理,才发现和书上讲的异曲同工。于是乎,自己对利用隐式的空闲链表实现分配器做了总结,并且和自己想到的架构做了对比分析。

  我们知道一个实际的分配器,不仅要考虑好吞吐率和内存利用率之间的平衡,还要考虑:

  ①空闲块组织:我们如何记录空闲块(一般刚开始会把一整段堆当做空闲块,然后再分割)

  ②放置:我们如何选择一个合适的空闲块来放置一个新分配的块?

  ③分割:在一个空闲块里面放置一个块后,剩余的空闲部分怎么处理?

  ④合并:我们如何处理刚刚释放的块?假如有两个块都是空闲的,需要合并吗 ?

  管理:于是书中定义一个数据结构来区分块边界,用来记录空闲块,这个数据结构是:

块大小包括头、有效负荷和填充部分,这样就可以记录出空闲和以分配的块,对应的隐式空闲链表如下图所示:

  放置和分割:假如我们想要放置已经分配块时,分配器会搜索链表,寻求一个足够大的空闲块来放置,有首次放配的策略、下一次分配的策略和最佳适配的策略,为了方便起见,我们将使用首次分配,从堆头开始搜索空闲链表,选择第一个合适的空闲块,这样的优点是趋向于将大的空闲块放在最后,缺点是开始的地方有小空闲块的“碎片”。当你找到一个匹配的空闲块时,你要做决定是否需要分割,这样的话第一部分变成分配块,而剩下的部分还为空闲块,如下图所示:

  合并:合并的问题最需要考虑的,当分配器释放一个分配块时,假如其他的空闲块于之相连,这个时候需要合并,我们假设是立即合并(实际快速的分配器不会是立即合并,会选择推迟合并)。如果是下一个块时空闲块,可以让释放掉的空闲块的头部,加上块大小,指向下一个块,然后读出下一个块的大小,就将它的大小简单的加到当前块头部的大小上。但是如果是前面的块时空闲块的话,就需要搜索整个链表,记录前面的块再进行合并,这样free需要的时间与堆的大小成线性关系,对于吞吐率是有很大的影响,这个时候Knuth记录提出了边界标记(boundary tag),在脚部添加一个头部的副本,这个脚部总是在距当前块开始位置一个字的距离。实际这样是牺牲了内存来实现吞吐率的。

当然了,为了我们结构的可读性更好,我们可以定义一个头部结构体,不用放脚部标记了

typedef struct

 {

  size_t sec_size;//块的大小,用于比较和申请内存的块,找到能放的下的空闲块(在已经分配的块上,这个值一般等于alloc_size)

size alloc_size;//有效负载的大小,由于知道结构体的大小,相当于上文中知道了块大小

size_t prev_sec_addr;//上一个块的起始地址,是从头部开始,用于和前合并

}

原文地址:https://www.cnblogs.com/kwdeblog/p/9417029.html

时间: 2024-10-10 18:24:26

一个简单的内存分配器-《深入理解计算机操作系统》的相关文章

简单的内存分配器

采用自定义的operator运算符实现自己的内存分配策略,在某些时候可以提高程序的效率.   C++中的new运算符,具体工作流程如下: 1.调用operator new申请原始内存 2.调用place new表达式,执行类的构造函数 3.返回内存地址 而delete操作符的工作是: 1.调用对象的析构函数 2.调用operator delete释放内存 例如: #include <iostream> using namespace std; class Test { public: Test

深入理解计算机操作系统--读书笔记-第八章异常

该文章是我对深入理解计算机操作系统这本书的读书笔记 异常概念:异常就是控制流的突变,用来响应处理器状态中的某些变化,当处理器状态发生变化时,处理器正在执行某个当前指令,在处理器中状态编码为不同的位和信号,状态变化称之为事件,事件可能与当前指令有关如缺页,算术溢出,也可能无关定时器产生信号或者io请求 异常处理完成的三种情况 1)处理程序将控制返回给当前指令 2)处理程序将控制返回给下一条指令 3)处理程序终止被中断的程序 异常处理和过程调用的区别: 1)过程调用时,在跳转处理程序之前,处理器将返

深入理解计算机操作系统(笔记)

1.实时系统概念 2.编译连接 3.AT&T汇编指令学习(GCC) 4.内存对齐 5.Big-Endian大端模式和Little-Endian小端模式 6.过程调用 1.实时系统概念 前后台系统 后台是各种面向硬件的程序,如中断,定时器,gpio等. 前台是: main() { while(1) { ; } } 循环中不断调用各种函数实现功能. 代码临界段 指处理时不可分割的代码,一旦这部分代码运行就不可以打断. 为了确保代码能正常运行,进入临界段代码钱需要关中断,执行完后再开中断. 任务 一个

《深入理解计算机操作系统》

第1章 计算机系统漫游 1.1 信息就是比特+上下文1.2 程序被其他程序翻译成不同的格式1.3 了解编译系统如何工作是大有益处的1.4 处理器读并解释储存在存储器中的指令1.5 高速缓存1.6 形成层次结构的存储设备1.7 操作系统管理硬件1.8 利用网络系统和其他系统通信1.9 下一步1.10 小结 第2章 信息的表示和处理 2.1 信息存储2.2 整数表示2.3 整数运算2.4 浮点2.5 小结 第3章 程序的机器级表示 3.1 历史观点3.2 程序编码3.3 数据格式3.4 访问信息3.

深入理解计算机操作系统——第3章:数组,异质的数据结构,指针

3.8 数组的分配和访问 (1)基本原则: int A[10] 首先,它在存储器中分配一个sizeof(int)*10字节的连续区域. 其次,它引入了标识符A.可以用A作为指向数组开头的指针.

深入理解计算机操作系统——第9章:缓存,存储器管理

9.3 虚拟存储器作为缓存工具 (1)虚拟存储器被组织为一个由存放在磁盘上的N个连续字节大小的单元组成的数组. (2)每个字节都有一个唯一的虚拟地址,作为数组索引 (3)磁盘上的内容被缓存到主存中 (4)磁盘上的数据被分割成块,作为磁盘与主存之间的传输单元 虚拟页: (1)VM系统将虚拟存储器分割为虚拟页的大小的固定块 (2)每个虚拟页的大小为P=2^p 物理页: (1)类似的物理存储器也被分割为物理页,大小也为P称为页帧 虚拟页面的集合: (1)未分配的:没有任何数据与它相关联,也就不占用任何

深入理解计算机操作系统——第8章:进程

进程: (1)进程的经典概念就是执行中的一个程序的实例. (2)系统中的每一个程序都是运行在进程的上下文中. (3)上下文:由程序正确运行所需的状态组成,这个状态包括:存放在存储器中的代码和数据,它的栈,通用目的寄存器, 程序计数器,环境变量,以及打开的文件描述符等等. 进程给程序提供的关键抽象: (1)独立的逻辑控制流:好像程序在运行时独占cpu处理器 (2)私有的地址空间:好像程序独占存储器一样. 逻辑控制流: 程序的运行时会有一系列的程序计数器(PC)的值,这些值唯一的对应着可执行程序的指

深入理解计算机操作系统——第11章:全球IP地址

全球IP英特网 (1)每台英特网主机都运行实现TCPIP协议的软件. (2)英特网的客户端和服务器混合使用套接字接口函数和Unix IO函数来进行通信. (3)套接字函数典型的是作为陷入内核的系统调用来实现的,并调用各种内核模式的TCPIP函数. 13.3.1 IP地址

深入理解计算机操作系统——第8章:进程控制

8.4 进程控制 8.4.1 获取进程ID (1)pid_t getpid(void)和pid_t gettpid(void)函数,其中返回的是pid_t整数值 (2)pid_t在types.h文件中被定义 8.4.2 创建和终止进程