实时操作系统内存管理-TLSF算法

news2024/11/23 11:10:38

hhhtsdyzx

内存管理-TLSF算法

  • 前言
    • TLSF算法:
      • 为什么内存又叫内存“块”:
      • O(1)查找空闲块:
        • 确定fl:
        • 确定sl:
        • 提级申请:
        • 分割块:
      • 空闲块如何串成链表?
      • 减少外部碎片:
        • 查找上下块:
      • 合并块:
    • 疑问:

前言

Vue框架:从项目学Vue
OJ算法系列:神机百炼 - 算法详解
Linux操作系统:风后奇门 - linux
C++11:通天箓 - C++11
Python常用模块:通天箓 - Python

TLSF算法:

  • 实时操作系统的内存管理算法
  • TLSF的分配速度快,但是相对内存利用率低,容易出现内部碎片,比较常用于嵌入式场景。

为什么内存又叫内存“块”:

  • 假设我们现在新买了一个16G内存条,在我们没安装到主板前,其实使用内存就不足16G:

    • 初始内存中有两个指针,一个指向首个可用空间首地址,一个指向内存条“末尾”
    • 首个可用空间大小为16G-2*sizeof(pointer),而内存条末位的这个“哨兵块”大小为0
      在这里插入图片描述
  • 由此有了内存“块”称呼来源1:初始内存只有两块,“空闲块”&“哨兵块”

  • 在16G内存中找空闲块指针很简单,直接111…111111,但是找哨兵块指针就需要技巧了

    • 看图中哨兵块指针近邻内存块指针,所以只需要111…111111 - sizeof(pointer)就找到了
    • 这里sizeof(pointer)是指以111…111111作为首地址的单元的size
    • 也就是说我们需要知道当前指针所在“单元”的size,而这个size可不always等于sizeof()的结果,如分配拿到了64字节,但是我们没用完64字节,只存了一个int,此时sizeof()=4
    • 所以除了这两个指针之外,其他内存块都需要一个size字段,表明自己大小
    • 到这,我们可画出内存的物理“块”的结构初稿(哨兵块size不准确):
      size
  • 由此有了内存“块”称呼来源2:每个内存块都有size字段规定本块大小

  • 我们借助一个物理块的size字段可以轻松找到其物理地址的下一块,那么上一块还需要借助其他信息吗?

    • 如果只要求到达上一块,则当前块块首指针-1就到了上一块的最后一个字节
    • 但是要求到达上一块的块首地址,我们还是需要借助指针,这个指针就在上一块的最后sizeof(Pointer)个字节
    • 由此我们可以画出物理空间上的内存块定稿:
      在这里插入图片描述
  • 为了防止用户直接操作物理内存,OS给用户可操控的地址/指针,其实是size邻接的下一地址:
    在这里插入图片描述

  • 当用户用满了这个Busy Block区域,prev_phys_block字段被用作用户数据,就不再指向物理块首地址了,

  • 这个时候我们需要通知下一个物理块,prev_phys_block指针失效

  • 通知的方法很简单,由于每个物理块大小size+sizeof(size字段)都是8Bytes或者4Bytes的倍数,所以每个物理块的size最后两位必定是0。

  • 用size字段第0位标识当前物理块是不是空闲,第1位标识上一物理块是不是空闲。1是空闲,0是被用户使用。这就是为什么要求申请到的内存必须是4或者8的整数倍。

  • 当上一个物理块被malloc()给用户使用,走之前需要把自己的size第0位置为0,把下一个物理块的size第1位置为0

  • 每个物理块在查找上一个物理块块首时,必须先看size第1位是不是1,是1的话pre_phy_block才有效

  • 由此有了内存“块”称呼来源3:每个内存块都要知道上一个块是不是被用户使用

  • 最后,就像我们写链表时候的逻辑地址和物理地址一样,在控制物理块的时候,我们要借助struct做一些逻辑上的改变:

    • 每个物理块开头是size,结尾是pre_phys_blocks
    • 逻辑Block struct在物理块基础上多加了上一块的结尾,prev_phys_blocks,用于找到上一块的struct起始地址
    • struct的起始地址始终在物理块的起始地址前一个指针大小
      在这里插入图片描述
  • 由此有了内存“块”称呼来源4:os通过struct来管理每个内存块

O(1)查找空闲块:

  • 根据牺牲空间才能换取时间的原则,TLSF有着设计优越的管理结构便于查找空闲块

    • 空闲内存(上图size字段)相近的空闲块串成链表,最终有许多条空闲链表
    • blocks[][]:二维数组,一级索引成为fl,二级索引称为sl,每个一级索引fl下都有同样个sl,一般是2^5个,blocks[fl][sl]存储空闲链表头
    • fl_bitmap:位图,为1的位说明blocks[fl]下有空闲链表,0则无。
    • sl_bitmap[]:位图数组,当确定fl后,sl_bitmap[fl]是位图,为1的位说明blocks[fl][sl]下有空闲链表
    • 当blocks[fl][sl]不为None,则sl_bitmap[fl]第sl位必须1,则fl_bitmap第fl位必须1。
    • 位图和blocks[][]必须时刻相符合,不然说明OS已经奔溃,内存条也受伤。
        pub fn init_on_heap(mut tmp : Box<TLSFControl,Global>) -> Box<Self,Global>{
            // TODO: YOUR CODE HERE
            tmp.fl_bitmap = 0;
            tmp.sl_bitmap = [0; FL_INDEX_COUNT];
            for i in 0..FL_INDEX_COUNT {
                for j in 0..SL_INDEX_COUNT {
                    tmp.blocks[i][j] = RawList::new();
                }
            }
            tmp
            // END OF YOUR CODE
        }
    
  • TLSF规定,空闲内存不同块按照下面的原则链接到不同的Blocks[fl][sl]中:

    1. fl = 0层存放空闲内存不超过256Bytes的空闲块

      fl = 0,sl 有32个索引,256 / 32 = 8,则不同空闲范围对应sl如下:

      /*fl = 0
      sl[0]: 0  ~  7
      sl[1]: 8  ~  15
      sl[2]: 16  ~  23
      ...
      sl[30]: 240 ~ 247
      sl[31]: 248 ~ 255
      */
      
    2. fl = 1层存放空闲内存介于256到511Bytes(256和511二进制最高位1占位同)的空闲块

      511 - 256 + 1 = 256,256 / 32 = 8,则不同空闲范围对应sl如下:

      /*fl = 1
      sl[0]: 256  ~  263
      sl[1]: 264  ~  271
      sl[2]: 272  ~  279
      ...
      sl[30]: 596 ~ 403
      sl[31]: 404 ~ 511
      */
      
    3. fl = 2层存放空闲内存介于512到1023Bytes(512和1023二进制最高位1占位同)的空闲块

      1023 - 512 + 1 = 512,512 / 32 = 16,则不同空闲范围对应sl如下:

      /*fl = 2
      sl[0]: 512  ~  527
      sl[1]: 528  ~  543
      sl[2]: 544  ~  559
      ...
      sl[30]: 992 ~ 1007
      sl[31]: 1008 ~ 1023
      */
      
    4. 其余fl层分配相同

确定fl:

  • 提出需求size大小空间,如何确定fl & sl:

    • 首先要对size进行8字节对齐:

          pub fn malloc<T>(&mut self, size: usize) -> *mut T {
              let adjust = if size > 0 {
                  align_up(size, ALIGN_SIZE)
              } else {
                  panic!("size must be greater than 0");
              };
              //size = adjust 或之后直接以adjust传参...
      
  • 当size < 256Bytes时,先到fl = 0查找空闲块。

  • 当size > 256Bytes时,根据公式查找空闲块:[ ]表示向下取整
    f l = [ l o g x s i z e ] fl = [log_{x}^{size}] fl=[logxsize]

  • 上述公式从值相等角度可用取二进制最高位1所在位代替:

    #[inline]
    fn ffs(word: usize) -> u32 {
        (word as u32).trailing_zeros() as u32
    }
    

确定sl:

  • 当size < 256Bytes时,sl = (size - 0) / 8

  • 当size > 256Bytes时,根据公式查找空闲块:SLI就是每个fl对应sl数目取log2
    s l = ( s i z e − 2 f l ) 2 f / 2 S L I sl = \frac{(size - 2^{fl})}{2^{f}/2^{SLI}} sl=2f/2SLI(size2fl)

  • 也就是特殊在size<256时候,2^fl = 2^0 = 1 != 0

提级申请:

  • 若此时申请大小为519的空闲内存:
    1. 519对8向上取整得到520
    2. 520二进制最高位是第9位,因为fl=0对应的256最高位是第8位,所以fl = 9-8+1=2
    3. sl = (520 - 512)/16 = 0
    4. 看上面表:sl[0]: 512 ~ 527,仿佛可用blocks[2][0]
    5. 但是由于不确定blocks[2][0]内空闲大小是512还是527,就有可能容不下520Bytes内容
    6. 确保每次申请的空闲块都能存储下size,我们向空闲更大的blocks发起申请
  • 寻找空闲更大且最靠近size的空闲块:
    1. 从低到高查询sl_bitmap[fl]中第x位,要求x>sl,且x位上是1
    2. 如果在sl_bitmap[fl]中没有查到x,则要到fl_bitmap中查询
    3. 从低到高查询fl_bitmap中第x位,要求x>fl,且x位上是1

分割块:

  • 假设现在内存条1024字节,初始两个指针占用8字节,则可用1008字节

    1008字节分为大小是1008的可用块和大小是0的哨兵块

    现在只申请8字节,根据上面查找fl和sl方法,肯定查到的空闲块是1008

  • 我们不可能把远大于用于申请大小的空闲块直接给用户使用,这样内部碎片很多,所以需要分割块,将一个且不能分割出size是0的块。

  • 给定Block struct A,分割为大小为a的B块 & 和大小不为0的C块:

    1. B块直接改改使用原A的size字段,分割后我们在物理层要为C块增加size,而这个size的开销是8
    2. 要确保A.size > a + 8,由于最小单元8字节,所以等价于A.size >= a + 8 + 8
  • 确定C块size的起始位置:

    • 用户data_ptr + 当前块size = 下一物理块size结尾
    • 下一块size的起始位置 = 结尾 - 8

    在这里插入图片描述

    • 由于用户申请malloc到的内存块要写入用户数据,所以分割得到的C块的prev_phys_blocks失效,要设置C块size第1位为0,防止C块通过混乱的prev_phys_blocks访问其他内存。

    还要设置C块的pre_phys_block,原A块下一块的pre_phys_block

  • 如果在malloc阶段没有和8字节对齐,那么当前块的size最后2位必定失效,引起内存访问混乱。

空闲块如何串成链表?

  • 上面我们分析之后发现size字段和用户使用的busy block已经占据了所有物理块。

    那么链表的next & prev在哪里存储?

  • 空闲块的busy block此时没有写入用户数据,那么我们可将next & prev写入size下面的busy block:

在这里插入图片描述

  • 这样,拿到任意内存块,通过看其size最后两位,我们即确定有无prev & next,又确定可否访问上一物理块

减少外部碎片:

  • malloc的时候,我们分割的目的是减少内部碎片。

    free的时候,我们尝试将物理上当前块和上下块合并为更大的块,目的是减少外部碎片。

查找上下块:

  • 查找上一块:
    1. 先看size第0位是不是1
    2. 再利用prev_phys_block访问上一块的起始地址
  • 查找下一块:
    1. 先向后移动当前块首地址指针,移动size-8个单位
    2. 再利用下一块size第1位判断下一块是不是空闲

合并块:

  • 合并相当于BC块合成A块,A的大小是B.size + C.size + sizeof(size字段)=8

  • 合并之后的A块空闲,除了本身的size最后两位要修改之外

    还有C块下一块的pre_phys_block及其size最后2位

疑问:

  • 开头说TLSF管理器在堆上,堆也在内存里面,为什么init_block()不考虑减去TLSF管理器的开销?
  • 哨兵块的size字段到底占用内存吗?
    我的理解哨兵块极其可能只是一个字节

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

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

相关文章

Java补充之MySQL入门必备知识

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/java-tutorial 】或者公众号【AIShareLab】回复 java 也可获取。 文章目录 零基础学MySQL解决之道文件、数据库MySQL 数据库的安装和配置使用命令行窗口连接MYSQL 数据库操作示意图 数据库三层结构数…

使用Intellij IDEA创建新项目时,maven路径总是默认的,一创建maven就卡死

目录 使用Intellij IDEA创建新项目时&#xff0c;maven路径总是默认的 弄了老半天&#xff0c;终于把这个破玩意给弄好了&#xff0c;真的没有意思&#xff0c;真的很恶心 我经历了两个过程&#xff0c;一个是 使用Intellij IDEA创建新项目时&#xff0c;maven路径总是默认的…

用免费蜜罐工具配置Modbus工控蜜罐

导语&#xff1a;本文将用DecoyMini免费蜜罐工具来配置自定义的ModbusTCP工控仿真模板&#xff0c;并介绍部署后的Modbus蜜罐的使用效果。 DecoyMini是一个免费的蜜罐工具&#xff0c;其特色是仿真能力采用与软件松耦合的仿真模板来进行管理。通过一键式导入云端仿真模板库里的…

【Linux】多线程 --- 线程同步与互斥+生产消费模型

人生总是那么痛苦吗&#xff1f;还是只有小时候是这样&#xff1f; —总是如此 文章目录 一、线程互斥1.多线程共享资源访问的不安全问题2.提出解决方案&#xff1a;加锁&#xff08;局部和静态锁的两种初始化/销毁方案&#xff09;2.1 对于锁的初步理解和实现2.2 局部和全局锁…

计算机中丢失msvcr120.dll怎么办,电脑找不到msvcr120.dll怎么办

电脑提示msvcr120.dll丢失是一个常见的问题&#xff0c;这个问题通常会在你尝试打开某些程序或游戏时出现。这个问题的原因是因为你的电脑缺少了一个名为msvcr120.dll的文件&#xff0c;这个文件是微软Visual C Redistrle for Visualv 2013的一部分。如果你遇到了这个问题&…

算法设计 || 第5题:田忌赛马-杭州电子科技大学(贪心算法)

目录 &#xff08;一&#xff09;杭电原题 &#xff08;二&#xff09;Please speak Chinese: &#xff08;三&#xff09;手写草稿理解思路核心算法 第一款代码&#xff1a; 第二款代码&#xff1a; &#xff08;一&#xff09;杭电原题 Tian Ji -- The Horse Racing Pro…

【分治法】

目录 知识框架No.1 分治法基本思想No.2 合并排序No.3 快速排序一、基本思想三、效率分析四、快速排序不稳定例子 No.4 二叉树遍历及其相关特性一、基本概念二、中序遍历三、前序遍历四、二叉树的高度计算(高度不是深度) 知识框架 No.1 分治法基本思想 将规模为N的问题分解为k…

Spring MVC:常用参数(注解)的使用和参数绑定的验证

Spring MVC&#xff1a;常用参数&#xff08;注解&#xff09;的使用和参数绑定的验证 一、学习资源二、基础源码三、实验结果3.1 Spring MVC常用参数Controller和RequestMappingRequestMappingRequestParamPathVariableCookie ValueRequestHeader 3.2 Spring MVC参数绑定3.2.1…

一路对标顶级产品,奇遇XR为何仍不见起色?

临近6月&#xff0c;再度遇冷的XR行业&#xff0c;又让很多人充满期待。外界普遍认为&#xff0c;基于苹果酝酿多年的MR头显产品&#xff0c;将于6月举行的WWDC 2023全球开发者大会正式亮相&#xff0c;XR行业或将迎来“iPhone时刻”。 在一派期待中&#xff0c;一家国内XR企业…

代码审计之PHP基础铺垫

目录 1、标记 2、注释 3、输出语句 4、关键字 5、常量的定义与使用 6、预定义常量 7、变量的赋值&#xff08;传参赋值与引用赋值&#xff09; 8、可变变量 9、双引号和单引号的区别 10、heredoc结构和nowdoc结构 11、其他符号 1、标记 <?php 和 ?> 是PHP标…

第十一届蓝桥杯青少组省赛Python中/高级组编程题真题,包含答案解析

第十一届蓝桥杯青少组省赛Python中/高级组编程题真题 编程实现 第一题&#xff1a; 输入一个字符串&#xff0c;如果该字符串以er、Iy或者ing后缀结尾的&#xff0c;则删除该字符串后缀&#xff0c;并输出删除后的字符串&#xff0c;否者将原字符串输出。 输入描述 输入一个…

零知识证明:应用和具体用例

零知识证明&#xff08;Zero-Knowledge Proofs&#xff0c;ZKPs&#xff09;是应用密码学中令人兴奋的突破&#xff0c;将在各个行业中解锁新的用例&#xff0c;从 Web3 到供应链再到物联网。通过在不揭示信息的情况下验证其真实性&#xff0c;ZKPs 可以增强数字系统的隐私、安…

【Unity-UGUI控件全面解析】| Slider 滑动条组件详解

🎬【Unity-UGUI控件全面解析】| Slider 滑动条组件详解一、组件介绍二、组件属性面板三、代码操作组件四、组件常用方法示例4.1 充当 进度条控制灯光亮度4.2 模拟 血条 使用💯总结🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CS…

【Spring全家桶系列】面向切面编程AOP

⭐️前面的话⭐️ 本文已经收录到《Spring框架全家桶系列》专栏&#xff0c;本文将介绍面向切面编程的思想和相关概念&#xff0c;附加一个小案例。 &#x1f4d2;博客主页&#xff1a;未见花闻的博客主页 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&a…

基于Kubernetes集群构建大中型企业CICD应用平台(2)--code阶段工具gitlab安装

这里我们为gitlab服务器准备一台虚拟机&#xff1a;192.168.19.6-gitlab服务器 在code阶段&#xff0c;我们需要将不同版本的代码存储到一个仓库中&#xff0c;常见的版本控制工具就是SVN或者Git&#xff0c;这里我们采用Git作为版本控制工具&#xff0c;GitLab作为远程仓库。…

django常用命令/vue-cli图形化界面创建方式/vueo-cli命令行创建方式/若依框架/elementui

django常用命令 django-admin.exe startproject mysite python .\manage.py startapp app01 python manage.py makemigrations python manage.py migrate python manage.py runserver python manage.py runserver 0.0.0.0:8000 from django.shortcuts import render,Http…

【Linux学习】多线程——同步 | 条件变量 | 基于阻塞队列的生产者消费者模型

多线程——同步 | 条件变量 | 基于阻塞队列的生成者消费者模型 &#x1f3d3;生产者消费者模型&#x1f3f8;同步概念&#x1f3f8;生产者消费者模型的特点 &#x1f3d3;同步的应用&#x1f3f8;条件变量&#x1f3f8;条件变量接口 &#x1f3d3;基于阻塞队列的生产者消费者模…

文心一言 VS chatgpt (16)-- 算法导论4.1 1~2题

一、当A的所有元素均为负数时&#xff0c;FIND-MAXIMUM-SUBARRAY返回什么? 文心一言&#xff1a; 当A的所有元素均为负数时&#xff0c;FIND-MAXIMUM-SUBARRAY返回-1。 这是因为在二进制中&#xff0c;当所有元素均为负数时&#xff0c;A的每个元素都对应一个负数&#xff…

基于Kubernetes集群构建大中型企业CICD应用平台(4)--Operate阶段工具

还是在192.168.19.7-CentOS-7.6-jenkins-new这台服务器上进行操作 部署过程&#xff0c;会采用Docker进行部署&#xff0c;暂时只安装Docker即可&#xff0c;后续还需安装Kubenetes 4.1 Docker安装 准备测试环境&生产环境 下载Docker依赖组件 # yum -y install yum-uti…

vue+express+mysql做一个简单前后端交互,从数据库中读取数据渲染到页面

1.下载上次的包 npm I &#xff0c;同时下载新的包 axios 2.打开数据库服务器&#xff0c;同时使用新建数据库一样&#xff0c;数据包名 3.新建一个项目 4.全局注册axios 5.新建一个server文件夹&#xff08;里面在建一个index.js的主文件&#xff09;用来放我们后端写的东西 …