当前位置: 首页 > news >正文

Linux下brk、sbrk实现一个简易版本的malloc

目录

一.内存四区回顾

二.brk、sbrk

三.使用brk、sbrk模拟实现malloc和free


一.内存四区回顾

  • 程序代码区:该区域在程序运行时存放程序的二进制代码。

全局数据区:该区域主要存放全局变量,静态变量和各种常量。

  • 堆:堆用于在程序运行时动态分配内存,比如new一个新的对象,或者malloc一个新数组,就是在堆中分配存储空间的,一般由程序员手动控制,但也容易造成内存泄漏。
  • 栈:该区域主要存放程序运行时函数的参数与局部变量等,当程序员完成某个软件的编译时,一般该软件对应内存栈的大小也就由编译器确定了,但直到程序真正运行时,操作系统才会在内存栈中为其分配空间

二.brk、sbrk

下面让我们一起来看看brk和sbrk的函数原型

 #include <unistd.h>

 int brk(void *addr);

void *sbrk(intptr_t increment);

sbrk/brk都是通过增量的方式来分配虚拟内存。首先我们先来解释一下这个sbrk函数,sbrk函数当中的increment的取值有三种情况

1.大于0增加内存空间

2.等于0获取末尾地址

3.小于0释放空间

函数返回值:返回上一调用brk/sbrk后末尾地址,失败返回-1.

sbrk其实内部维护了一个指针,指向当前堆内存的最后一个字节的下一个位置,sbrk函数根据增量参数调整该指针原来的位置如果发现页耗尽了,则会自动追加或者取消页映射

下面我们来通过一张简图来看看sbrk是如何实现分配内存的

下面我们调用sbrk申请四个字节的空间那么此时top就会往右边移动四个字节(假设地址从左往右依次递增)

 

 如果我们此时再调用sbrk(0)那么此时返回的就是top'指向的地址。如果我们想要释放这块空间此时我们需要调用sbrk(-4)即可。此时top'指针就回到了top所指向的位置。sbrk这种分配内存空间的方式也就意味着,无法做到释放任意申请的空间比如说我们依次申请了3次4个字节的空间,此时我们无法通过sbrk释放中间这块空间。

下面我们通过代码进行演示一下,sbrk的函数的使用:

#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<cstdio>
using namespace std;
int main()
{
    char*ptr1=(char*)sbrk(1);//申请一个字节大小的空间

    printf("ptr1的地址为:%p\n",ptr1);
    void*ptr2=sbrk(0);//获取动态内存的末尾位置
    printf("ptr2的地址为:%p\n",ptr2);
    int *ptr3=(int*)sbrk(4);
    printf("ptr3的地址为:%p\n",ptr3);

    return 0;
}

运行结果:

 2.下面我们来看一下brk这个函数,同样的这个函数也是通过增量分配虚拟内存

    brk函数内部维护了一个指针指向当前堆内存最后一个字节的下一个位置这一点和sbrk很相似。 

    brk函数会根据指针参数设置该指针的位置,如果发现页耗尽则自动追加或者取消映射

   函数返回值:成功返回0,失败返回-1

   由于这个非常的简单再这里就不演示代码了。

总结:

1.sbr/brk通过增量方式管理动态内存,申请内存是在动态内存后面申请,释放是从后往前释放不能够选择性的释放增量动态内存,只能从后往前释放动态内存。

2.一般来说我们使用sbrk来分配内存,使用brk来释放内存,这是因为使用sbrk来释放内存我们需要记住我们之前申请了多少大小的空间

3.sbrk/brk都有申请和释放动态内存的功能

三.使用brk、sbrk模拟实现malloc和free

在模拟实现一个简易版本的malloc和free之前,需要说明一下在这里我们只是模拟和真实的malloc的区别很大。我们在使用malloc的时候,比如说我们申请了4个字节的大小但是在底层真的只是分配了4个字节的大小吗?下面给出一张图来解释一下大致是个什么情况

 我们在使用malloc进行申请内存的时候并不是你申请了多少个字节,底层给你分配多少个字节。你申请空间的时候会多分配一个控制块空间的大小用来管理。malloc的底层采用的是双向链表的方式来进行管理。在这里我们使用单链表来进行模拟

下面我们来看一下这个控制块是什么样子的

//malloc底层维护的是一个双向链表,再这里我们用单向链表来模拟
//注意我们只是模拟和真实的malloc有很大的区别
struct mem_control_block
{
  size_t size_;//动态内存块的大小、
  bool free_;//该内存块是否被使用,为true则代表可以分配给用户使用
  mem_control_block*prev;//前一个控制块的指针
};

下面我们来看看这个malloc的模拟实现:

struct mem_control_block
{
    size_t size_;            //动态内存块的大小、
    bool free_;              //该内存块是否被使用,为true则代表可以分配给用户使用
    mem_control_block *prev; //前一个控制块的指针
};
typedef mem_control_block MCB;
MCB *g_top = nullptr; //维护的那个指针
#define MCBSIZE sizeof(MCB)
void *my_malloc(size_t size)
{
    //注意第一步需要从后往前找用没有合适的内存可以分配,没有开辟新的内存,真正的malloc是从前往后找
    MCB *mcb = g_top;
    while (mcb)
    {
        if (mcb->size_ >= size)
        {
            break;
        }
        mcb = mcb->prev;
    }
    if (mcb == nullptr) //之前的空间当中没有合适的控制块
    {
        mcb = (MCB *)sbrk(size + MCBSIZE);
        if (mcb == (void *)-1)
        {
            return nullptr;
        }
        mcb->size_ = size;
        mcb->prev = g_top;
        g_top = mcb;
    }

    mcb->free_ = false; //已经分配给用户了

    return (void *)(mcb + 1); //返回可用的地址
}

注意:我们申请空间的时候不是直接调用sbrk函数直接来申请,而是需要遍历一下有没有合适的空间可以分配给用户,如果没有我们才调用sbrk函数进行分配空间,注意在分配空间的时候我们需要把控制块的空间也算上。在这里我们是通过单链表进行链接。

下面我们来看看free的模拟实现是怎么样的

void my_free(void *ptr)
{
    if (ptr == nullptr)
        return;
    MCB *mcb = (MCB *)ptr - 1;
    mcb->free_ = true;
    //如果很多连续的动态内存释放了此时我们需要还给系统
    mcb = g_top;
    while (mcb->prev && mcb->free_)
    {
        //从最后往前找最后一块被使用的内存
        mcb = mcb->prev;
    }
    if (mcb->free_)
    {
        //释放内存
        g_top = mcb->prev;
        brk(mcb);
    }
    else //后面的可以释放但是mcb这块内存是不能够释放的
    {
        g_top = mcb;
        brk((char *)mcb + mcb->size_ + MCBSIZE);
    }
}

注意我们在释放内存的时候不是直接将它的控制块当中的free_置为true,我们需要从后往前遍历一下看一下有没有一骗连续的内存空间可以释放。如果有我们则将这一块连续的空间通过brk函数归还给OS。

对应总代码:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <cstdlib>
// malloc底层维护的是一个双向链表,再这里我们用单向链表来模拟
//注意我们只是模拟和真实的malloc有很大的区别
struct mem_control_block
{
    size_t size_;            //动态内存块的大小、
    bool free_;              //该内存块是否被使用,为true则代表可以分配给用户使用
    mem_control_block *prev; //前一个控制块的指针
};
typedef mem_control_block MCB;
MCB *g_top = nullptr; //维护的那个指针
#define MCBSIZE sizeof(MCB)
void *my_malloc(size_t size)
{
    //注意第一步需要从后往前找用没有合适的内存可以分配,没有开辟新的内存,真正的malloc是从前往后找
    MCB *mcb = g_top;
    while (mcb)
    {
        if (mcb->size_ >= size)
        {
            break;
        }
        mcb = mcb->prev;
    }
    if (mcb == nullptr) //之前的空间当中没有合适的控制块
    {
        mcb = (MCB *)sbrk(size + MCBSIZE);
        if (mcb == (void *)-1)
        {
            return nullptr;
        }
        mcb->size_ = size;
        mcb->prev = g_top;
        g_top = mcb;
    }

    mcb->free_ = false; //已经分配给用户了

    return (void *)(mcb + 1); //返回可用的地址
}

void my_free(void *ptr)
{
    if (ptr == nullptr)
        return;
    MCB *mcb = (MCB *)ptr - 1;
    mcb->free_ = true;
    //如果很多连续的动态内存释放了此时我们需要还给系统
    mcb = g_top;
    while (mcb->prev && mcb->free_)
    {
        //从最后往前找最后一块被使用的内存
        mcb = mcb->prev;
    }
    if (mcb->free_)
    {
        //释放内存
        g_top = mcb->prev;
        brk(mcb);
    }
    else //后面的可以释放但是mcb这块内存是不能够释放的
    {
        g_top = mcb;
        brk((char *)mcb + mcb->size_ + MCBSIZE);
    }
}

int main()
{
    int *p1 = (int *)my_malloc(4);
    int *p2 = (int *)my_malloc(4);
    int *p3 = (int *)my_malloc(4);
    cout << p2 << endl;
    my_free(p2);
    int *p4 = (int *)my_malloc(4);
    cout << p4 << endl;

    return 0;
}

相关文章:

  • 一、CSS选择器与权重[基础选择器、结构选择器、属性选择器、伪类选择器]
  • flutter系列之:深入理解布局的基础constraints
  • 【C语言进阶】动态内存管理及柔性数组
  • 网课查题接口系统
  • C语言基础知识入门
  • 闲暇之际敲敲代码,记录Leetcode刷题Day-01
  • 2021年下半年信息安全工程师上午真题及答案解析
  • Dinky,让 Flink SQL 纵享丝滑
  • Docker | docker容器导出以及常见问题的处理
  • 【node进阶】深度解析之Express框架入门
  • 【重温Linux】一、Ubuntu系统一些常识性的东西(这节持续更新)
  • mysql group_concat 与 union 联合查询漏洞,数据列最大长度为341
  • ISO27001认证需要准备什么资料?
  • 腾讯研究成果登 Nature 子刊:scBERT 攻克单细胞测序数据分析痛点
  • Vue2和Vue3的区别——实例创建、响应式数据代理、v-for和v-if优先级、生命周期
  • JS 中的深拷贝与浅拷贝
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  •  D - 粉碎叛乱F - 其他起义
  • JavaScript 奇技淫巧
  • Laravel Mix运行时关于es2015报错解决方案
  • MYSQL 的 IF 函数
  • node学习系列之简单文件上传
  • php中curl和soap方式请求服务超时问题
  • Redis 懒删除(lazy free)简史
  • vue-cli在webpack的配置文件探究
  • Webpack 4x 之路 ( 四 )
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 面试遇到的一些题
  • 那些被忽略的 JavaScript 数组方法细节
  • 算法之不定期更新(一)(2018-04-12)
  • 携程小程序初体验
  • 赢得Docker挑战最佳实践
  • 【云吞铺子】性能抖动剖析(二)
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • (09)Hive——CTE 公共表达式
  • (求助)用傲游上csdn博客时标签栏和网址栏一直显示袁萌 的头像
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (一)Java算法:二分查找
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • 、写入Shellcode到注册表上线
  • .gitignore文件_Git:.gitignore
  • .NET 8.0 发布到 IIS
  • .NET的数据绑定
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • .NET委托:一个关于C#的睡前故事
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • [ 云计算 | AWS 实践 ] 基于 Amazon S3 协议搭建个人云存储服务
  • []T 还是 []*T, 这是一个问题
  • [Asp.net mvc]国际化
  • [HNCTF 2022 WEEK2]easy_include 文件包含遇上nginx
  • [leetcode] 61. 旋转链表
  • [MSSQL]GROUPING SETS,ROLLUP,CUBE初体验
  • [MySQL]SQL优化之索引的使用规则