聊聊glibc中malloc函数的unlink

news2024/11/20 2:26:15

unlink的意思其实就是删除。在介绍这个函数之前,我们得介绍一点概念。在程序中,如果我们使用malloc申请的内存在不用或者不需要的时候,是需要程序员手动去释放,也就是free操作。我们知道malloc操作free操作都是涉及到内存管理的。那么涉及到硬件操作一定是要通过系统调用。系统调用对性能的消耗是很大的,glibc为了减少频繁的系统调用。在用户free掉一块内存空间的时候,并不会急着把这片内存归还给操作系统。ptmalloc会统一管理heap和mmap区域中的空间chunk。等到下一次用户需要再次申请的时候,会优先从这些空闲的chunk分配给用户。这样做很大程度上降低了内存分配的开销。

那么ptmalloc是怎么操作的呢?首先在ptmalloc中有一个名词叫bin。它是一个链表结构,表示空闲的chunk,在ptmalloc中讲大小差不多的空闲chunk用这样的bin链表来链接起来。根据大小的不同分成了很多种bin。这些bin组合在一起叫bins。是由一个数组表示的,如图:

这个数组是按照数组下标来区分bin的分类。数组中每两个数组下标为一个空闲chunk。因为在这个bins数组中是用双向链表把这些空闲chunk给串起来的。这有一张更生动的图:

数组中的第一个为 unsorted bin ,数组中从 2 开始编号的前 64 bin 称为 small bins ,同
一个 small bin 中的 chunk 具有相同的大小。两个相邻的 small bin 中的 chunk 大小相差 8bytes
small bins 中的 chunk 按照最近使用顺序进行排列,最后释放的 chunk 被链接到链表的头部,
而申请 chunk 是从链表尾部开始,这样,每一个 chunk 都有相同的机会被 ptmalloc 选中。
Small bins 后面的 bin 被称作 large bins。有了这些基础就能介绍一个宏了:
#define bin_at(m, i) \
  (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2]))			      \
             - offsetof (struct malloc_chunk, fd))

这个宏其实是用于定位bin的,m是指向malloc_state的指针,因为bins数组在这个结构体里定义的:

其次i就是一个索引。乘以2是因为2个数组下标为一个bin。假设i=1,我们就找到了unsorted bin的fd,假设i=2,我们就找到了第一个small bins的fd。以此类推而来。

接着- offsetof (struct malloc_chunk, fd)就是减去fd在malloc_chunk的偏移,就能得到这个chunk头。

最后强转为malloc_chunk结构体指针,这个指针的宏定义在这:

typedef struct malloc_chunk *mbinptr;

 通过这个宏我们能找到相应空闲chunk的头部指针。


#define next_bin(b)  ((mbinptr) ((char *) (b) + (sizeof (mchunkptr) << 1)))

这个宏是用来找到下一个bin的。这个b其实就是&((m)->bins[((i) - 1) * 2])。就是这个数组的某个bin的地址,语言表达不够,画图来描述:

 (mchunkptr)<<1相当于乘以2,因此直接能找到下一个bin所在的位置。图应该很好理解。


#define first(b)     ((b)->fd)
#define last(b)      ((b)->bk)

这两个宏很简单吧就不解释了。


接下来就是我们的unlink宏的解析了。代码如下:

#define unlink(AV, P, BK, FD) {                                            \
    FD = P->fd;								      \
    BK = P->bk;								      \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {								      \
        FD->bk = BK;							      \
        BK->fd = FD;							      \
        if (!in_smallbin_range (P->size)				      \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {		      \
	    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      \
		|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
	      malloc_printerr (check_action,				      \
			       "corrupted double-linked list (not small)",    \
			       P, AV);					      \
            if (FD->fd_nextsize == NULL) {				      \
                if (P->fd_nextsize == P)				      \
                  FD->fd_nextsize = FD->bk_nextsize = FD;		      \
                else {							      \
                    FD->fd_nextsize = P->fd_nextsize;			      \
                    FD->bk_nextsize = P->bk_nextsize;			      \
                    P->fd_nextsize->bk_nextsize = FD;			      \
                    P->bk_nextsize->fd_nextsize = FD;			      \
                  }							      \
              } else {							      \
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;		      \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;		      \
              }								      \
          }								      \
      }									      \
}

第一步是保存要删除节点的前后指向的位置:

 因为fd和bk其实都是指向一个chunk的头部的,千万不要以为是指向其fd或者bk的位置,我图画不好请见谅。


 if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);

这一段是判断这个P的完整性,用下一个chunk的bk指针和前一个chunk的fd指针来验证是否都指向p,如果验证不完整则会调用malloc_printerr函数来报告错误。


如果P是完整的,那么将执行如下步骤:

这两步操作俗称断链。就是双向链表的断链操作。


 

if (!in_smallbin_range (P->size)				      \
            && __builtin_expect (P->fd_nextsize != NULL, 0))

in_smallbin_range (P->size): 这部分是一个条件判断语句,判断节点 P 的大小是否不在小块范围内。

in_smallbin_range 是一个函数或宏,用于确定节点 P 的大小是否在小块范围内。
如果节点 P 的大小不在小块范围内,条件成立。
__builtin_expect (P->fd_nextsize != NULL, 0): 这部分是另一个条件判断语句,使用了内置函数 __builtin_expect 来提高条件判断的预测性能。它判断节点 P 的 fd_nextsize 成员是否不为 NULL。

如果条件成立(即 fd_nextsize 不为 NULL),表达式的值为非零。
如果条件不成立(即 fd_nextsize 为 NULL),表达式的值为零。

如果不是小块,那就是大块,大块会多两个指针,这在chunk结构里面讲过:

struct malloc_chunk* fd_nextsize; 
struct malloc_chunk* bk_nextsize;

如果上述的判断成立接着判断双向链表的完整性:

if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      \
		|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
	      malloc_printerr (check_action,				      \
			       "corrupted double-linked list (not small)",    \
			       P, AV);		

和前面的判断其实是类似的,只不过这是执行大块的逻辑。如果其中有一个不成立,则表明链表出现异常,则会执行malloc_printerr函数报告错误。


if(FD->fd_nextsize == NULL){

     代码块;

}

如果P指向chunk的下一个chunk是最后一个(这里有点绕,可以回看上面的图),将执行代码块内的内容。如果不是最后一个则执行如下代码:

else {							      
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;		      
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;	
}

 

 

忽略P的fd_nextsize和bk_nextsize的指向后图示如下: 


如果上述条件不成立将执行如下逻辑:

if (P->fd_nextsize == P)				      \
                  FD->fd_nextsize = FD->bk_nextsize = FD;		      \
                else {							      \
                    FD->fd_nextsize = P->fd_nextsize;			      \
                    FD->bk_nextsize = P->bk_nextsize;			      \
                    P->fd_nextsize->bk_nextsize = FD;			      \
                    P->bk_nextsize->fd_nextsize = FD;			      \
                  }	

如果 P->fd_nextsize == P 成立,意味着节点 P 的 fd_nextsize 成员指向自身。这在双向链表中表示链表中只有一个节点。将执行:

FD->fd_nextsize = FD->bk_nextsize = FD;

 否则执行:

FD->fd_nextsize = P->fd_nextsize;			      
FD->bk_nextsize = P->bk_nextsize;			      
P->fd_nextsize->bk_nextsize = FD;			     
P->bk_nextsize->fd_nextsize = FD;

这就是unlink实现的基本过程(可能理解不过透彻有错误欢迎指出),主要就是设计不同大小的bin实现不同的断链。有一丢丢复杂吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/691473.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

USB Monitor只抓数据时的设置

一&#xff0c;简介 在抓HID数据时&#xff0c;只关注数据的收发&#xff0c;不太关注其他的数据例如SOF等信息&#xff0c;所以要对上位机软件的过滤设置进行勾选。 二&#xff0c;过滤设置 原则&#xff1a;带data的都要&#xff0c;不带data的可以不要。 点击“设置”-&…

挽输出和开漏输出

GPIO口配置为输出时会有两种模式&#xff0c;一种叫推挽输出&#xff0c;一种叫开漏模式。 三种输出状态 如下图所示为将GPIO配置为输出时的内部示意图&#xff1a; 由上图可以看出&#xff0c;GPIO的输出状态完全取决于两个MOS管Q1和Q2的导通状态&#xff1a; Q1导通、Q2关断…

js 数组中和为 0 的三个数

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 示例 …

Makerbase SimpleFOC ESP32 例程10 步进电机开环速度测试

Makerbase SimpleFOC ESP32 例程10 步进电机开环速度测试 第一部分 硬件介绍 1.1 硬件清单 序号品名数量1ESP32 FOC V1.0 主板1235HB27-401A步进电机1312V电源适配器14USB 线1 注意&#xff1a; 35HB27-401A是两相1.8步进电机&#xff0c;对应极对数为50。   硬件清单如下…

[框架]Spring框架

目录 关于Spring框架 Spring框架创建对象 Spring框架创建对象的方式之一--组件扫描 Spring框架创建对象的方式之二--Bean方法 Spring框架创建对象的方式的选取 Spring Bean的名称 Spring Bean的作用域 Spring Bean的生命周期 Spring的自动装配 关于为属性注入值的做法…

stm32或gd32移植libcanard实现UAVCAN协议

一、源码下载 1、git下载 点击我下载 2、csdn下载 自己上传的点击下载 二、源码移植 我自己是使用rt-thread操作系统移植的。但是不局限与操作系统&#xff0c;裸机也可以。 1、首先将源码加入到工程 2、分别实现一个内存的分配与释放函数&#xff0c;他是一个指针函数&…

Keras-深度学习-神经网络-电影评论情感分析模型

目录 模型搭建 模型训练 模型搭建 使用到的数据集为IMDB电影评论情感分类数据集&#xff0c;该数据集包含 50,000 条电影评论&#xff0c;其中 25,000 条用于训练&#xff0c;25,000 条用于测试。每条评论被标记为正面或负面情感&#xff0c;因此该数据集是一个二分类问题。…

AD利用嘉立创的封装

1.首先&#xff0c;打开元件库&#xff0c;搜索元器件 2.点开它的封装&#xff08;符号&#xff09; 3.文件-->导出-->Altium Designer 4.然后在AD上面打开这个文件 5.将其复制&#xff0c;粘贴放到PCB库中 6.然后在原理图中的封装管理器中&#xff0c;添加封装&#xf…

ODrive引脚排列

对引脚配置的更改仅在odrv0.save_configuration()和odrv0.reboot()之后生效 如果 GPIO 设置为不支持的模式,它将保持未初始化状态。 当将 GPIO 设置为特殊用途模式(例如GpioMode.UART_A)时,您还必须启用相应的功能(例如<odrv>.config.enable_uart_a)。 数字模式是一…

如何创新玩转元服务开发-趋势、分类与我们实践的方向!

一、软件发展分类与元服务&#xff08;一&#xff09;软件分类发展简要分析 软件总体分为系统软件和应用软件两大类。用户、设备、操作系统系统软件、流量入口、应用形态应用软件关系及发展见下表—— 从表中分析得知&#xff0c;从互联网时期到移动互联网主导的发展&#xff…

前端开发两年半,我裸辞了

☀️ 前言 一晃两年半过去了&#xff0c;我离开了我的第一份前端开发工作&#xff0c;当你看到这篇文章&#xff0c;我已经离职两个月了&#xff0c;目前仍在艰难求职中&#xff0c;想记录分享一下我的经历&#xff0c;感兴趣的可以继续往下看&#xff0c;希望能给大家一些启示…

学Python能做哪些副业?我一般不告诉别人!建议存好

前两天一个朋友找到我吐槽&#xff0c;说工资一发交完房租水电&#xff0c;啥也不剩&#xff0c;搞不懂朋友圈里那些天天吃喝玩乐的同龄人钱都是哪来的&#xff1f;确实如此&#xff0c;刚毕业的大学生工资起薪都很低&#xff0c;在高消费、高租金的城市&#xff0c;别说存钱&a…

C++继承机制下析构和构造函数的执行分析

析构函数在下边3种情况时被调用&#xff1a; 对象生命周期结束&#xff0c;被销毁时&#xff1b;delete指向对象的指针时&#xff0c;或delete指向对象的基类类型指针&#xff0c;而其基类虚构函数是虚函数时&#xff1b;对象i是对象o的成员&#xff0c;o的析构函数被调用时&a…

SRM 供应商管理系统都有哪些模块?

3k字干货&#xff01; SRM必备6大模块&#xff1a;供应商管理、采购需求管理、采购寻源、采购履约、交付结算。下面针对环节中的核心场景进行讲解。 1、供应商全生命周期管理 过去&#xff0c;企业业务简单&#xff0c;对接供应商数量少&#xff0c;需求供给匹配、价格合适就…

如何使用KoodousFinder搜索和分析Android应用程序中的安全威胁

关于KoodousFinder KoodousFinder是一款功能强大的Android应用程序安全工具,在该工具的帮助下,广大研究人员可以轻松对目标Android应用程序执行安全研究和分析任务,并寻找出目标应用程序中潜在的安全威胁和安全漏洞。 账号和API密钥 在使用该工具之前,我们首选需要访问该…

适合初创企业租赁的办公模式-共享办公室

随着共享经济的兴起&#xff0c;共享办公室已经成为越来越多人的选择。共享办公室提供了一个灵活、高效、舒适的工作环境&#xff0c;能够帮助个人和团队提高工作效率和创造力。下面我将从三个角度来介绍共享办公室。 共享办公室的优势 首先&#xff0c;共享办公室具有成本效益…

合肥先进光源束测步进电机及驱动器的选择

大规模电机控制的方案选择-电机和驱动器篇 在上面文档的系统里选择的是免电池带绝对值编码器的步进伺服电机方案&#xff0c;现在有些场合只是普通的步进电机就好了&#xff0c;同样从电机控制的龙头企业鸣志的产品中选择&#xff0c;依然选择现成熟的ethercat总线技术的驱动器…

深度学习模型在图像识别中的应用:CIFAR-10数据集实践与准确率分析

文章目录 前言导入所需的库忽略证书验证下载并加载 CIFAR-10 数据集数据预处理构建深度学习模型编译模型模型训练模型评估进行图片识别测试图片运行效果完整代码完结 前言 深度学习模型在图像识别领域的应用越来越广泛。通过对图像数据进行学习和训练&#xff0c;这些模型可以自…

ChatGPT独家汇总:发现最优秀的人工智能对话体验

欢迎来到我们的 ChatGPT 镜像网站汇总博客&#xff01;在这个令人激动的人工智能时代&#xff0c;ChatGPT 作为一款顶尖的语言模型&#xff0c;已经引起了全球范围内的热议。但是&#xff0c;您是否曾经为了找到最佳的 ChatGPT 使用体验而苦苦搜寻&#xff1f;别担心&#xff0…

(15)第一人称视角视频

文章目录 前言 15.1 推荐的零件 15.2 连接图示 15.3 通过任务计划器最小化OSD设置 15.4 集成式OSD 15.5 用户视频/博客 15.6 与FPV飞行特别相关的安全警告 15.7 政府/地方法规 前言 第一人称视角在飞行时为你提供了真正的飞行员视角&#xff0c;它将视频摄像机和发射器…