精华内容
下载资源
问答
  • 内存分配算法

    2014-03-10 14:35:45
    内存分配算法 实验目的:  本实验要求利用事先已经编写好的图形包软件模拟内存的分配。  实现动态分区的分配算法。最佳适配算法选择内存空闲块中最适合进程大小的块分配。邻近适配算法从上一次分配的地址开始...
  • 内存分配方式与内存分配算法

    千次阅读 2018-03-14 20:24:56
    内存分配算法,其实就是:有一大块空闲的资源,如何合理地分配资源?内存分配的思想可以用到很多其他的领域。比如Java虚拟机是如何将内存分配与回收的?再比如文件系统是如何将磁盘块分配与回收的?其本质就是如何把...

    内存分配方式有两种,连续内存分配方式和离散内存分配方式。不同的分配方式又有不同的分配算法。

    内存分配算法,其实就是:有一大块空闲的资源,如何合理地分配资源?内存分配的思想可以用到很多其他的领域。比如Java虚拟机是如何将内存分配与回收的?再比如文件系统是如何将磁盘块分配与回收的?其本质就是如何把空闲的资源分配出去,分配之后又如何回收?目标就是分配快,回收也快,而且还不浪费。那么,就需要根据资源的特点、以及应用场景做权衡从而选择何种方式进行分配与回收。

    ①连续内存分配方式

    1)固定分区分配

    将内存划分成若干个固定大小的块。将程序装入块中即可。内存划分成各个块之后,块大小不再改变。当然,划分块的方式有:所有的块大小相等;划分的块大小不相等。

    这种方式,在实际的内存分配之前,就已经知道了所有的内存块大小了。

    2)动态分区分配

    需要一个空闲表 或者 空闲链 来记录目前系统中空间的内存区域。在内存分配时,需要查找空间表或空闲链找到一块内存分配给当前进程。

    动态分区分配算法:

    a)首次适应法

    b)循环首次适应法

    c)最佳适应法

    d)最坏适应法

    e)快速适应法

    3)可重定位分区分配

    说白了,就是增加了内存移动的功能。由于若干次内存分配与回收之后,各个空闲的内存块不连续了。通过“重定位”,将已经分配的内存“紧凑”在一块(就类似于JVM垃圾回收中的复制算法)从而空出一大块空闲的内存出来。

    ”紧凑“是需要开销的,比如需要重新计算 地址,这也为什么JVM垃圾回收会导致STW的原因。

    而离散分配方式–不管是分页还是分段,都是直接将程序放到各个离散的页中。从而就不存在“紧凑”一说了。

    连续内存分配方式涉及两种操作:内存分配操作 和 内存回收操作

    ②离散内存分配方式

    内存资源是有限的,程序要运行,必须得加载到内存。如果内存已经满了,而现在又有新的程序要运行,怎么办?—SWAP

    把当前不用的程序(数据)先换出内存,从而就有空间 加载当前需要运行的程序的一部分数据进入内存,这样大大提高了内存的利用率。

    由于牵涉到换入与换出,前面的连续内存分配方式就有点不适用了。因为,最明显的一个问题:对于连续内存分配方式,究竟换出哪部分数据呢?

    而这种只装入部分”数据”就可以使程序运行的机制,就是虚拟存储器的本质。

    1)分页存储管理

    将进程的逻辑地址空间分成若干大小相等的页面;同时,也将物理内存分成相等大小的页面(称为块或frame)。在为进程分配内存时,以块为单位将进程的若干页 可以 装入到内存中多个不邻接的物理块中。

    从上可以看出:“离散” 体现在:进程在内存中分配的空间(物理块)是不连续的。而对于连续分配方式,进程在内存的分配的空间是连续的。

    现在考虑32位系统,每个物理块的大小为4KB。如何把逻辑地址 转换成 物理地址?

    对每个进程而言,都有着自己的页表。页表的本质就是逻辑地址到物理地址的映射。

    分页存储中的逻辑地址的结构如下:

    这里写图片描述

    1)由于进程的逻辑页面大小与物理块(页帧)大小相同,故都为4K,因此需要12个位表示4K的大小(2^12=4K),即图中的【0-11】

    2)【12-31】表示的是页号。一共有20个位表示页号,也即:对于一个进程而言,一共可以有1M(2^20=1M)个页。

    3)每个进程的逻辑地址空间范围为0-2^32-1,因为:每个页大小为4K,一共有1M个页。故进程可用的逻辑空间为2^32B

    逻辑地址到物理地址的转换需要用到页表。具体细节是有一个“地址变换机构”,它有一个寄存器保存页表在内存的起始地址 以及 页表的长度。

    上面提到,一个进程最多可以有1M个页,故页表就有1M个页表项。假设每个页表项只有1B,那页表的大小也有1MB,所以:一般而言,页表也是很大的,不能全放在寄存器中,故页表也是存储在内存中的。(有些机器有“快表”,快表就是一个寄存器,它保存了页表中的部分表项);其次,也可以使用多级页表以解决单个页表太大的问题。

    那现在给定一个逻辑地址,怎么知道其物理地址呢?

    ①将【12-31】位的页号与 页表的长度比较。页号不能大于页表长度,否则越界。

    ②根据页号 找到 该页号所在的页表项,即该页号对应着哪个页表项。因为,页表项里面就存放着物理地址。

    那如何查找页表项呢?将页号乘以页表项的长度(每个页表项,其实就是一个逻辑的页 到 物理页 的映射信息),就知道了该逻辑页对应着哪个页表项(根据页号匹配页表项一般是由硬件完成的)

    然后,正如前面提到,页表也是保存在内存中的,故需要页表的内存始址(这是也为什么地址变换机构 保存 页表在内存的起始地址的原因),将页表始址 与 上面的乘积相加,就得到了该逻辑页对应的页表项的物理地址。读这个页表项的物理地址中的内容,就知道了该逻辑页对应的物理块地址(物理地址)。从而,就完成了逻辑地址到物理地址的转换。

    从上面可以看出,CPU每存取一个数据时,需要两次访问主存。一次是访问页表项的物理地址,得到了数据的物理块地址。第二次拿着物理块地址去取数据。

    在分页存储管理方式下:由于取一个数据,需要二次访存,CPU处理速度降低了一半,正由于这个原因:引入了“快表”(又称TLB(Translation Lookaside Buffer)),快表是个寄存器,用来保存那些当前访问过的页表项。从而,读页表项时,不需要再访存了,而是直接从寄存器中读取。

    虚拟存储器

    谈到虚拟存储器,总是说它从逻辑上扩充了内存的容量,why?

    内存是有限的,作业初始时保存在磁盘上的,如果要运行,必须得将相应的程序(数据)加载到内存中。那如果要运行的作业特别多,无法一下子装入内存,怎么办?

    一种方式是加内存条,这是从物理上扩充内存的容量。

    另一种方式是:先把作业的一部分程序(数据)装入内存,先让它运行着,运行过程中发现: 咦,我还需要其他的数据,而这些数据还未装入内存,因此就产生中断(缺页中断)再将数据加载到内存。

    采用这种方式,系统一次就可以将很多作业装入内存运行了。这时,从物理上看,内存还是原来的大小,但是它能运行的作业多了,因此说从逻辑上扩充了内存。

    将虚拟存储器这种思想与分页存储管理结合,一次只将作业的部分页面加载到内存中,形成了一个强大的内存分配与管理系统了。引入了虚拟存储器,同样需要有页表,记录逻辑地址到物理地址的映射,只不过此时的页表更复杂了,因为,有些页可能还在磁盘上。;还需要有缺页中断处理机构,因为毕竟只将一部分数据装入内存,会引起缺页中断嘛,就需要处理中断嘛;还需要地址变换机构,这里的地址变换机构功能更多,因为需要处理中断情况下的地址变换。

    转载自:https://www.cnblogs.com/hapjin/p/5689049.html

    展开全文
  • 四种常见的内存分配算法,简要介绍其优缺点以及代码实现
  • TLSF是一种动态内存分配算法,本资源是C语言的编写的,包含例程。
  • 改进的 Spark Shuffle 内存分配算法
  • buddy内存分配算法浅析

    千次阅读 2016-08-02 20:46:37
    buddy内存分配算法技术是一种内存分配算法,将内存划分分区,试图以适当地满足内存请求。buddy内存分配算法是比较容易实行。它支持有限,高效的分裂和内存块的合并。目的是为了解决内存的外碎片。 避免外碎片的...

    因为今天遇到这个问题,所以上网搜了下,看了觉得还是很有用处,便写了这篇博文。

    buddy内存分配算法技术是一种内存分配算法,将内存划分分区,试图以适当地满足内存请求。buddy内存分配算法是比较容易实行。它支持有限,高效的分裂和内存块的合并。目的是为了解决内存的外碎片


    避免外碎片的方法有两种
    1,利用分页单元把一组非连续的空闲页框映射到非连续的线性地址区间。
    2,开发适当的技术来记录现存的空闲连续页框块的情况,以尽量避免为满足对小块的请求而把大块的空闲块进行分割。


    这里给出伙伴的概念,满足以下三个条件的称为伙伴:
    1)两个块大小相同;
    2)两个块地址连续;
    3)两个块必须是同一个大块中分离出来的



    当存储请求时如下发生了什么:
    如果内存要分配
    1.寻找一个合适的大小(最小2的存储器插槽ķ块是大于或等于所请求的存储器的)
           1.如果它被找到,它被分配给该程序
           2.如果不是,它试图使一个合适的存储器插槽。该系统这样做尝试以下:
                     1.拆分一个免费的内存插槽比请求存储器大小成半较大
                      2.如果达到了下限值时,则分配的存储器的量
                       3.回到步骤1(找一个合适大小的内存插槽)

                        4.直到一个合适的内存插槽中发现重复此过程



    如果内存被释放
    1.空闲内存块
    2.看相邻块 - 它是免费的吗?
    3.如果是,将二者结合起来,并回到步骤2,重复这个过程既达到上限,直到(所有内存被释放),或直到遇到一个非自由相邻块


    实施和效率

    相较于其他更简单的技术,如动态分配,好友记忆系统几乎没有外部碎片,并允许压实与小的开销内存。释放内存的哥们方法快速,与登录所需的平等压实的最大数目2(最高等级)。通常好友存储器分配系统与使用的实施二叉树来表示使用或不使用的分割存储器块。每个区块的“哥们”,可以与发现异或块的地址和块的大小。
    但是,仍然存在内部碎片的问题,因为请求的内存比小块稍大,但比一大块小了就有很多内存浪费 。因为伙伴存储器分配技术的工作方式,请求的存储器66K将被分配128K,这导致的存储器62K浪费的程序。这个问题可以通过解决板坯分配,其可以在更粗的好友分配器的顶部分层,以提供更精细的分配。

    在Linux内核还使用好友系统,以进一步修改,以尽量减少外部碎片,以及其他各种分配器来管理模块中的存储器。


    展开全文
  • 动 态 内 存 分 配 算 法 实 验 报 告 院系:计算机与通信工程学院 班级:计科08-1班 姓名:胡太祥 学号:200807010112 一实验题目动态内存分配算法 二实验目的 深入了解动态分区存储管理方式内存分配与回收的实现 三...
  • 文章目录目录内存分配算法物理内存分配内存碎片伙伴(Buddy)分配算法申请和回收反碎片机制Slab 算法slab 分配器的结构slab 高速缓存分区页框分配器非连续内存区内存的分配虚拟内存的分配内核空间内存分配...

    目录

    前文列表

    Linux 操作系统原理 — 内存 — 物理存储器与虚拟存储器
    Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术
    Linux 操作系统原理 — 内存 — 基于局部性原理实现的内/外存交换技术

    内存碎片

    Linux 操作系统的页式虚拟存储器文内存管理带来了很多好处。但如果就这样直接把内存分页使用的话还是会存在一些问题,例如:内存空间碎片化严重。

    物理内存页分配会出现外部碎片和内部碎片问题,所谓的内部和外部是针对 “页内外” 而言的,一个页内的内存碎片是内部碎片,多个页间的碎片是外部碎片。

    • 内部碎片:当实际只需要很小内存的时候,也会分配一张至少 4K 的页面,而内核中有很多需要以字节为单位分配内存的场景,这样本来只想要几个字节而已却不得不分配一页内存,除去用掉的字节剩下的就形成了内部碎片。
      在这里插入图片描述

    • 外部碎片:当需要分配大块内存的时候,需要用好几页组合起来才够,而系统分配物理内存页的时候会尽量分配连续的内存页面,频繁的分配与回收物理页导致大量的小块内存夹杂在已分配页面中间,形成外部碎片。
      在这里插入图片描述

    避免内存碎片的办法主要由客观手段和主观手段两种。

    客观手段,即良好的编程习惯

    • 少用动态内存分配的函数,尽量使用栈空间。
    • 分配内存和释放的内存尽量实现在同一个函数中。
    • 尽量一次性申请较大的内存,而不要反复申请小内存。
    • 尽可能申请大块的 2 的指数幂大小的内存空间。
    • 自己进行内存管理工作,设计内存池程序模块。

    主观手段,即操作系统自身的内存分配算法

    • 伙伴系统算法:避免外部碎片。
    • Slab 算法:避免内部碎片。

    其中伙伴算法和 Slab 高速缓存都在物理内存映射区分配物理内存,而 vmalloc 机制则在高端内存映射区分配物理内存。

    伙伴(Buddy)分配算法

    伙伴系统算法(Buddy system),顾名思义,就是把相同大小的页通过链表串起来,多张页就像手拉手的好伙伴(算法名称的由来)。伙伴算法负责大块连续物理内存的分配和释放,以页为基本单位。

    伙伴分配算法的原理就是:将所有的空闲页框分组为 11 个块链表,每个块链表分别包含大小为 1,2,4,8,16,32,64,128,256,512 和 1024 个连续页的页块。最大可以申请 1024 个连续页,对应 4MB 大小的连续内存。因为任何正整数都可以由 2^n 的和组成,所以总能找到合适大小的内存块分配出去,减少了外部碎片产生 。

    在这里插入图片描述

    如此的,假设需要申请 4 个页,但是长度为 4 个连续页块链表没有空闲的页块,伙伴系统会从连续 8 个框块的链表获取一个,并将其拆分为两个连续 4 个页块,取其中一个,另外一个放入连续 4 个页块的空闲链表中。释放的时候会检查,释放的这几个页前后的页框是否空闲,能否组成下一级长度的块。

    在 Linux 操作系统中可以通过命令查看:

    $ cat /proc/buddyinfo
    Node 0, zone      DMA      1      0      0      0      2      1      1      0      1      1      3
    Node 0, zone    DMA32    189    209    119     80     38     17     11      1      1      2    627
    Node 0, zone   Normal   1298   1768   1859    661    743    461    275    133     68     61   2752
    

    Slab 算法

    一般来说,内核对象的生命周期是:分配内存 -> 初始化 -> 释放内存。而内核中会有大量的小对象,比如:文件描述结构对象、任务描述结构对象,如果按照伙伴系统按页分配和释放内存,就会对小对象频繁的执行上述过程,会非常消耗性能。另外,伙伴系统分配出去的内存还是以页为单位,而对于内核的很多场景都是分配小片内存,远用不到一页内存大小的空间。

    为了解决伙伴算法的不足,Jeff Bonwick 最初在 SunOS 操作系统引入了 Slab 算法,并实现了 Slab 分配器。它的基本思想是:将内核中经常使用的对象放到高速缓存中,并且由系统保持为初始的可利用状态。比如进程描述符,内核中会频繁对此数据进行申请和释放

    Slab 分配器为每一种对象建立高速缓存,通过将内存按使用对象不同再划分成不同大小的空间,应用于内核对象的缓存,内核对该对象的分配和释放均是在这块高速缓存中操作。

    可见,Slab 内存分配器是对伙伴分配算法的补充。Slab 缓存负责小块物理内存的分配,并且它也作为高速缓存,主要针对内核中经常分配并释放的对象。

    Slab 的好处

    • Slab 内存管理基于内核小对象,不用每次都分配一页内存,充分利用内存空间,避免内部碎片。
    • Slab 对内核中频繁创建和释放的小对象做缓存,重复利用一些相同的对象,减少内存分配次数。

    在这里插入图片描述

    kmem_cache 是一个cache_chain 的链表组成节点,代表的是一个内核中的相同类型对象的高速缓存,每个kmem_cache 通常是一段连续的内存块,包含了三种类型的 slabs 链表:

    • slabs_full:完全分配的 slab 链表
    • slabs_partial:部分分配的 slab 链表
    • slabs_empty:没有被分配对象的 slab 链表

    slab 是 Slab 分配器的最小单位,一个 slab 由一个或多个连续的物理页组成(通常只有一页)。单个 slab 可以在 slab 链表之间移动,例如:如果一个半满 slabs_partial 链表被分配了对象后变满了,就要从 slabs_partial 中删除,同时插入到全满 slabs_full 链表中去。内核 slab 对象的分配过程是这样的:

    在这里插入图片描述

    1. 如果 slabs_partial 链表还有未分配的空间,分配对象,若分配之后变满,移动 slab 到 slabs_full 链表。
    2. 如果 slabs_partial 链表没有未分配的空间,进入下一步。
    3. 如果 slabs_empty 链表还有未分配的空间,分配对象,同时移动 slab 进入 slabs_partial 链表。
    4. 如果 slabs_empty 为空,请求伙伴系统分页,创建一个新的空闲 slab, 按步骤 3 分配对象。

    在 Linux 上查看 Slab 内存的信息:

    [root@c-dev ~]# cat /proc/slabinfo
    slabinfo - version: 2.1
    # name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
    isofs_inode_cache     50     50    640   25    4 : tunables    0    0    0 : slabdata      2      2      0
    kvm_async_pf           0      0    136   30    1 : tunables    0    0    0 : slabdata      0      0      0
    kvm_vcpu               0      0  14976    2    8 : tunables    0    0    0 : slabdata      0      0      0
    xfs_dqtrx              0      0    528   31    4 : tunables    0    0    0 : slabdata      0      0      0
    xfs_dquot              0      0    488   33    4 : tunables    0    0    0 : slabdata      0      0      0
    xfs_ili            40296  40296    168   24    1 : tunables    0    0    0 : slabdata   1679   1679      0
    xfs_inode          41591  41616    960   34    8 : tunables    0    0    0 : slabdata   1224   1224      0
    xfs_efd_item        1053   1053    416   39    4 : tunables    0    0    0 : slabdata     27     27      0
    

    显示内核中的 Slab 内存缓存信息:

    $ slabtop
    

    Slab 高速缓存分为两类:

    1. 通用高速缓存:slab 分配器中用 kmem_cache 来描述高速缓存的结构,它本身也需要 slab 分配器对其进行高速缓存。cache_cache 保存着对高速缓存描述符的高速缓存,是一种通用高速缓存,保存在 cache_chain 链表中的第一个元素。另外,slab 分配器所提供的小块连续内存的分配,也是通用高速缓存实现的。通用高速缓存所提供的对象具有几何分布的大小,范围为 32 到 131072 字节。内核中提供了 kmalloc() 和 kfree() 两个接口分别进行内存的申请和释放。slab 分配器所提供的小块连续内存的分配是通过通用高速缓存实现的。

    2. 专用高速缓存:内核为专用高速缓存的申请和释放提供了一套完整的接口,根据所传入的参数为指定的对象分配 Slab 缓存。内核为专用高速缓存的申请和释放提供了一套完整的接口,根据所传入的参数为具体的对象分配 slab 缓存。kmem_cache_create() 用于对一个指定的对象创建高速缓存。它从 cache_cache 普通高速缓存中为新的专有缓存分配一个高速缓存描述符,并把这个描述符插入到高速缓存描述符形成的 cache_chain 链表中。kmem_cache_alloc() 在其参数所指定的高速缓存中分配一个 slab。相反, kmem_cache_free() 在其参数所指定的高速缓存中释放一个 slab。

    随着大规模多处理器系统和 NUMA 系统的广泛应用,Slab 也暴露出了一下问题:

    • 复杂的队列管理。
    • 管理数据和队列存储开销较大。
    • 长时间运行 partial 队列可能会非常长。
    • 对 NUMA 支持非常复杂。

    为了解决问题,基于 Slab 推出了 Slub:改造 Page 结构来削减 Slab 管理结构的开销、每个 CPU 都有一个本地活动的 slab(kmem_cache_cpu),对于小型的嵌入式系统存在一个 Slab 模拟层 Slob,在这种系统中它更有优势。

    虚拟内存的分配

    虚拟内存的分配,包括用户态虚拟内存和内核态虚拟内存分配。需要注意的是:分配的虚拟内存还没有映射到物理内存,只有当访问申请的虚拟内存时,才会发生缺页异常,再通过上面介绍的伙伴系统和 slab 分配器申请物理内存

    内核态内存分配

    先来回顾一下内核地址空间。

    在这里插入图片描述

    在内核态申请内存比在用户态申请内存要更为直接,它没有采用用户态那种延迟分配(通过缺页机制来反馈)内存技术。一旦有内核函数申请内存,那么就必须立刻满足该申请内存的请求,并且这个请求一定是正确合理的。相反,对于用户态申请内存的请求,内核总是尽量延后分配物理内存,用户进程总是先获得一个虚拟内存区的使用权,最终通过缺页异常获得一块真正的物理内存。

    内核虚拟地址空间只有 1GB 大小,因此可以直接将 1GB 大小的物理内存映射到内核地址空间,但超出 1GB 大小的物理内存(高端内存)就不能映射到内核空间。为此,内核采取了下面的方法使得内核可以使用所有的物理内存:

    1. 高端内存不能全部映射到内核空间,也就是说这些物理内存没有对应的线性地址。不过,内核为每个物理页都分配了对应的页描述符,所有的页框描述符都保存在 mem_map 数组中,因此每个页描述符的线性地址都是固定存在的。内核此时可以使用 alloc_pages() 和 alloc_page() 来分配高端内存,因为这些函数返回页框描述符的线性地址。

    2. 内核地址空间的后 128MB 专门用于映射高端内存,否则,没有线性地址的高端内存不能被内核所访问。这些高端内存的内核映射显然是暂时映射的,否则也只能映射 128MB 的高端内存。当内核需要访问高端内存时就临时在这个区域进行地址映射,使用完毕之后再用来进行其他高端内存的映射。

    由于要进行高端内存的内核映射,因此直接能够映射的物理内存大小只有 896MB,该值保存在 high_memory 中。内核地址空间的线性地址区间如下图所示:

    在这里插入图片描述

    从图中可以看出,内核采用了三种机制将高端内存映射到内核空间:永久内核映射、固定映射和 vmalloc 机制。

    内核 API:

    • mempool_create:创建内存池对象
    • mempool_alloc:分配函数获得该对象
    • mempool_free:释放一个对象
    • mempool_destroy:销毁内存池

    在这里插入图片描述

    vmalloc 函数

    在这里插入图片描述
    kmalloc 和 vmalloc 分别用于分配不同映射区的虚拟内存。

    vmalloc 分配的虚拟地址区间,位于 vmalloc_start 与 vmalloc_end 之间的动态内存映射区。一般用分配大块内存,释放内存对应于 vfree,分配的虚拟内存地址连续,物理地址上不一定连续。函数原型在 <linux/vmalloc.h> 中声明。一般用在为活动的交换区分配数据结构,为某些 I/O 驱动程序分配缓冲区,或为内核模块分配空间。

    内核通过它来申请非连续的物理内存,若申请成功,该函数返回连续内存区的起始地址,否则,返回 NULL。vmalloc() 和 kmalloc() 申请的内存有所不同,kmalloc() 所申请内存的线性地址与物理地址都是连续的,而 vmalloc() 所申请的内存线性地址连续而物理地址则是离散的,两个地址之间通过内核页表进行映射。vmalloc() 使得内核通过连续的线性地址来访问非连续的物理页,这样可以最大限度的使用高端物理内存。

    vmalloc() 的工作方式理解起来很简单:

    1. 寻找一个新的连续线性地址空间;
    2. 依次分配一组非连续的页框;
    3. 为线性地址空间和非连续页框建立映射关系,即修改内核页表;

    vmalloc() 的内存分配原理与用户态的内存分配相似,都是通过连续的虚拟内存来访问离散的物理内存,并且虚拟地址和物理地址之间是通过页表进行连接的,通过这种方式可以有效的使用物理内存。但是应该注意的是,vmalloc() 申请物理内存时是立即分配的,因为内核认为这种内存分配请求是正当而且紧急的;相反,用户态有内存请求时,内核总是尽可能的延后,毕竟用户态跟内核态不在一个特权级。

    可见,vmalloc 的主要目的就是用多个碎片来拼凑出一个大内存,相当于收集一些 “边角料”,组装成一个成品后再 “出售”。

    在这里插入图片描述

    kmalloc

    kmalloc() 分配的虚拟地址范围在内核空间的直接内存映射区。按字节为单位虚拟内存,一般用于分配小块内存,释放内存对应于 kfree ,可以分配连续的物理内存。函数原型在 <linux/kmalloc.h> 中声明,一般情况下在驱动程序中都是调用 kmalloc() 来给数据结构分配内存。

    kmalloc 是基于 Slab 分配器的,同样可以用 cat /proc/slabinfo 命令,查看 kmalloc 相关 slab 对象信息,下面的 kmalloc-8、kmalloc-16 等等就是基于 Slab 分配的 kmalloc 高速缓存。

    在这里插入图片描述

    用户态内存分配

    用户态内存分配函数:

    • alloca 是向栈申请内存,因此无需释放。
    • malloc 所分配的内存空间未被初始化,使用 malloc() 函数的程序开始时(内存空间还没有被重新分配)能正常运行,但经过一段时间后(内存空间已被重新分配)可能会出现问题。
    • calloc 会将所分配的内存空间中的每一位都初始化为零。
    • realloc 扩展现有内存空间大小。
      • 如果当前连续内存块足够 realloc 的话,只是将 p 所指向的空间扩大,并返回 p 的指针地址。这个时候 q 和 p 指向的地址是一样的。
      • 如果当前连续内存块不够长度,再找一个足够长的地方,分配一块新的内存,q,并将 p 指向的内容 copy 到 q,返回 q。并将 p 所指向的内存空间删除。

    malloc 申请内存

    malloc 用于申请用户空间的虚拟内存:

    • 当申请小于 128KB 小内存的时,malloc 使用 sbrk 或 brk 分配内存;
    • 当申请大于 128KB 的内存时,使用 mmap 函数申请内存。

    存在的问题:由于 brk/sbrk/mmap 属于系统调用,如果每次申请内存都要产生系统调用开销,CPU 在用户态和内核态之间频繁切换,非常影响性能。而且,堆是从低地址往高地址增长,如果低地址的内存没有被释放,高地址的内存就不能被回收,容易产生内存碎片。

    解决:因此,malloc 采用的是内存池的实现方式,先申请一大块内存,然后将内存分成不同大小的内存块,然后用户申请内存时,直接从内存池中选择一块相近的内存块分配出去。

    在这里插入图片描述

    调用 malloc 函数时,它沿 free_chuck_list 连接表寻找一个大到足以满足用户请求所需要的内存块。

    在这里插入图片描述

    free_chuck_list 连接表的主要工作是维护一个空闲的堆空间缓冲区链表。如果空间缓冲区链表没有找到对应的节点,需要通过系统调用 sys_brk 延伸进程的栈空间。

    在这里插入图片描述

    用户进程内存分配示例

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    
    
    int main(void)
    {
        char *s;
        unsigned long int i;
    
        s = strdup("test_memory");
        if (NULL == s) {
            fprintf(stderr, "Can't allocate mem with malloc.\n");
            return EXIT_FAILURE;
        }
    
        i = 0;
        while (s) {
            printf("[%lu] %s (%p)\n", i, s, (void *)s);
            sleep(1);
            i++;
        }
        return EXIT_SUCCESS;
    }
    

    编译运行:

    $ gcc -Wall -Wextra -pedantic -Werror test4.c -o test4; ./test4
    [0] test_memory (0xcba010)
    [1] test_memory (0xcba010)
    ...
    

    执行环节是 IA64,所以示例程序的虚拟内存高地址为 0xffffffffffffffff、低地址为 0x0,程序主动申请的堆(Heap)地址为 0xcba010。我们知道堆地址是靠近低地址的,所以从数值上看也符合。

    在这里插入图片描述

    查看程序进程:

    $ ps -ef | grep test
    root     18539 16745  0 13:18 pts/1    00:00:00 ./test4
    

    查看进程内存信息:

    $ cat /proc/18539/maps
    00400000-00401000 r-xp 00000000 fd:01 1065575                            /root/workspace/test/test4
    00600000-00601000 r--p 00000000 fd:01 1065575                            /root/workspace/test/test4
    00601000-00602000 rw-p 00001000 fd:01 1065575                            /root/workspace/test/test4
    00cba000-00cdb000 rw-p 00000000 00:00 0                                  [heap]
    7f28da826000-7f28da9e9000 r-xp 00000000 fd:01 1051055                    /usr/lib64/libc-2.17.so
    7f28da9e9000-7f28dabe9000 ---p 001c3000 fd:01 1051055                    /usr/lib64/libc-2.17.so
    7f28dabe9000-7f28dabed000 r--p 001c3000 fd:01 1051055                    /usr/lib64/libc-2.17.so
    7f28dabed000-7f28dabef000 rw-p 001c7000 fd:01 1051055                    /usr/lib64/libc-2.17.so
    7f28dabef000-7f28dabf4000 rw-p 00000000 00:00 0
    7f28dabf4000-7f28dac16000 r-xp 00000000 fd:01 1053320                    /usr/lib64/ld-2.17.so
    7f28dae07000-7f28dae0a000 rw-p 00000000 00:00 0
    7f28dae13000-7f28dae15000 rw-p 00000000 00:00 0
    7f28dae15000-7f28dae16000 r--p 00021000 fd:01 1053320                    /usr/lib64/ld-2.17.so
    7f28dae16000-7f28dae17000 rw-p 00022000 fd:01 1053320                    /usr/lib64/ld-2.17.so
    7f28dae17000-7f28dae18000 rw-p 00000000 00:00 0
    7ffe852fa000-7ffe8531b000 rw-p 00000000 00:00 0                          [stack]
    7ffe85331000-7ffe85333000 r-xp 00000000 00:00 0                          [vdso]
    ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
    

    在 Linux 操作系统中,可以通过 /proc/[pid]/mem 访问和修改进程的内存页,可以通过 /proc/[pid]/maps 看到进程当前已映射的内存区域,如上所示。从 maps 中可以看到堆空间(00cba000-00cdb000)靠近低地址,而栈空间(7ffe852fa000-7ffe8531b000)靠近高地址。还可以看到 heap 的访问权限是 rw(可读写)。

    所以我们可以通过堆地址找到示例程序中字符串的地址,并通过修改 mem 文件对应地址的数据内容,从而可以修改字符串。这里我们使用下述的 Python 脚本进行试验。

    #!/usr/bin/env python3
    '''            
    Locates and replaces the first occurrence of a string in the heap
    of a process    
    
    Usage: ./read_write_heap.py PID search_string replace_by_string
    Where:          
    - PID is the pid of the target process
    - search_string is the ASCII string you are looking to overwrite
    - replace_by_string is the ASCII string you want to replace
      search_string with
    '''
    
    import sys
    
    def print_usage_and_exit():
        print('Usage: {} pid search write'.format(sys.argv[0]))
        sys.exit(1)
    
    # check usage  
    if len(sys.argv) != 4:
        print_usage_and_exit()
    
    # get the pid from args
    pid = int(sys.argv[1])
    if pid <= 0:
        print_usage_and_exit()
    search_string = str(sys.argv[2])
    if search_string  == "":
        print_usage_and_exit()
    write_string = str(sys.argv[3])
    if search_string  == "":
        print_usage_and_exit()
    
    # open the maps and mem files of the process
    maps_filename = "/proc/{}/maps".format(pid)
    print("[*] maps: {}".format(maps_filename))
    mem_filename = "/proc/{}/mem".format(pid)
    print("[*] mem: {}".format(mem_filename))
    
    # try opening the maps file
    try:
        maps_file = open('/proc/{}/maps'.format(pid), 'r')
    except IOError as e:
        print("[ERROR] Can not open file {}:".format(maps_filename))
        print("        I/O error({}): {}".format(e.errno, e.strerror))
        sys.exit(1)
    
    for line in maps_file:
        sline = line.split(' ')
        # check if we found the heap
        if sline[-1][:-1] != "[heap]":
            continue
        print("[*] Found [heap]:")
    
        # parse line
        addr = sline[0]
        perm = sline[1]
        offset = sline[2]
        device = sline[3]
        inode = sline[4]
        pathname = sline[-1][:-1]
        print("\tpathname = {}".format(pathname))
        print("\taddresses = {}".format(addr))
        print("\tpermisions = {}".format(perm))
        print("\toffset = {}".format(offset))
        print("\tinode = {}".format(inode))
    
        # check if there is read and write permission
        if perm[0] != 'r' or perm[1] != 'w':
            print("[*] {} does not have read/write permission".format(pathname))
            maps_file.close()
            exit(0)
    
        # get start and end of the heap in the virtual memory
        addr = addr.split("-")
        if len(addr) != 2: # never trust anyone, not even your OS :)
            print("[*] Wrong addr format")
            maps_file.close()
            exit(1)
        addr_start = int(addr[0], 16)
        addr_end = int(addr[1], 16)
        print("\tAddr start [{:x}] | end [{:x}]".format(addr_start, addr_end))
    
        # open and read mem
        try:
            mem_file = open(mem_filename, 'rb+')
        except IOError as e:
            print("[ERROR] Can not open file {}:".format(mem_filename))
            print("        I/O error({}): {}".format(e.errno, e.strerror))
            maps_file.close()
            exit(1)
    
        # read heap  
        mem_file.seek(addr_start)
        heap = mem_file.read(addr_end - addr_start)
    
        # find string
        try:
            i = heap.index(bytes(search_string, "ASCII"))
        except Exception:
            print("Can't find '{}'".format(search_string))
            maps_file.close()
            mem_file.close()
            exit(0)
        print("[*] Found '{}' at {:x}".format(search_string, i))
    
        # write the new string
        print("[*] Writing '{}' at {:x}".format(write_string, addr_start + i))
        mem_file.seek(addr_start + i)
        mem_file.write(bytes(write_string, "ASCII"))
    
        # close files
        maps_file.close()
        mem_file.close()
    
        # there is only one heap in our example
        break
    

    执行脚本对进程的堆空间的内容进行修改:

    $ python3 loop.py 18539 test_memory test_hello
    [*] maps: /proc/18539/maps
    [*] mem: /proc/18539/mem
    [*] Found [heap]:
    	pathname = [heap]
    	addresses = 00cba000-00cdb000
    	permisions = rw-p
    	offset = 00000000
    	inode = 0
    	Addr start [cba000] | end [cdb000]
    [*] Found 'test_memory' at 10
    [*] Writing 'test_hello' at cba010
    

    结果 test_memory 给修改为 test_helloy:

    267] test_memory (0xcba010)
    [268] test_memory (0xcba010)
    [269] test_memory (0xcba010)
    [270] test_helloy (0xcba010)
    [271] test_helloy (0xcba010)
    [272] test_helloy (0xcba010)
    [273] test_helloy (0xcba010)
    [274] test_helloy (0xcba010)
    [275] test_helloy (0xcba010)
    
    展开全文
  • 动态内存分配算法实验报告包括:实验题目,实验目的,实验要求,实验内容,实验结果,实验总结及后附有详细源代码 实验内容 1,确定定内存空闲分配表和进程内存分配表 2,采用首次适应算法完成内存空间的分配 3,...
  • 详细介绍了TLSF(Two Level Segregated Fit)动态内存分配算法的实现过程,包括内存池的创建初始化、动态内存的分配与释放。把TLSF移植到μC/OSII实时操作系统上,移植后的系统在基于CortexM3内核的LPC1768处理器上...
  • 1.前言: PooledByteBufAllocator 实现相当复杂,其中涉及许多复杂的数据结构类: 1)PoolArena ...其核心思想是利用了为 FreeBSD 设计的 jemalloc 内存分配算法和 buddy 分配算法。为了更好地解...

    1.前言:

    PooledByteBufAllocator 实现相当复杂,其中涉及许多复杂的数据结构类:

    1)PoolArena

    2)PoolChunk 

    3)PoolSubpage

    5)PoolThreadCache

    还有其他相关辅助类包括 PoolChunkList

    其核心思想是利用了为 FreeBSD 设计的 jemalloc 内存分配算法和 buddy  分配算法。为了更好地解读 netty 内存分配,本节首先着重介绍 buddy  分配算法。

    2.算法

    内存管理,特别是内存分配一直是操作系统一个基本问题。固定的划分模式会限制活跃进程的数量,而且如果进程请求的大小与可用的分割大小匹配效果不佳,会导致内存空间的使用效率很低。动态划分模式使得维护更复杂,包括内存合并的开销。而伙伴算法就是权衡折中的一种算法。最早由贝尔电话实验室的 Ken C Knowlton 在1965年的《A fast storage allocator》一文中提出。

    在伙伴算法中,把用来分配的内存作为一整块2次幂大小的空间。当请求到达时,如果请求的大小大于初始大小空间的一半,那么整块内存都会被分配。否则,这个内存块会一分为二(这两块内存块互为伙伴,算法名即来源于此),这时候再一次判断请求的大小是否大于其中一块子内存块的一半,如果大于,则这块子块将被分配。如果小于,那么再将其中一个子块一分为二,以此类推,直到请求的大小大于分割后的子块的一半,若未找到,则直到分割系统允许的最小单位为止,然后再将相应内存块分配。

    在此算法中,当一个进程结束时,分配给进程的内存块会被释放。只要有可能,一个未分配的内存块就会试图与其伙伴块合并以便形成一个更大的空闲内存块。如果两个内存块是由同一个父内存块一分为二产生的,那么他们就互为伙伴。

    下面的例子展示了伙伴算法的具体分配过程。假设初始内存块大小为1024KB,表左侧为进程每次请求的内存大小。

    分配时优先考虑从低地址开始适配内存块。

    展开全文
  • 同时,提出了内存分配算法,该算法能够更好地为并行实时任务管理内存资源。实验结果表明新的调度算法具有更高的调度成功率。另外,在内存资源竞争的前提下,内存分配算法可以保证并行任务的实时性与系统稳定性。
  • 银行家算法--进程调度算法--内存分配算法java实现
  • 研究了V x Wo r k s系统内存分配算法, 指出了常用内存管理算法的局限性。 在此基础上, 提 出了一种改进的内存分配算 法。改进的内存分配算法包括优化的内存块分配算法和快速高效的动态内存分配算法。 两者结合...
  • 内存分配算法的模拟实现 页面置换算法的实现 多级反馈队列调度算法模型
  • 用java写的Buddy System 内存分配算法,是applet小程序,包括分配和释放演示,没有源码。
  • 操作课程设计,动态内存分配算法实现。包括可视化演示,可单步操作和自动执行。
  • Java在Eclipse可视化界面编写的关于操作系统的内存分配算法的课程设计。主要用来跟大家分享,互相学习。算法简单,结构清晰,适合新手参考。
  • 一种基于VXWORKS的内存分配算法.pdf
  • 最差适配内存分配算法模拟 1、目的 用程序实现可变分区内存管理过程,并按最差适配算法进行分配。 2、内容 (1)基本思想 可变分区是指系统不预先划分固定分区,而是在装入程序的时候划分内存区 域,使得为程序...
  • buddy内存分配算法

    千次阅读 2017-06-23 16:45:57
    1 普通内存分配方式的缺点以及解决方法: 1.1 普通的动态内存分配方式(如c语言运行库的内存分配malloc、free):分配时,是在堆的空闲链表上,查找到第一块空闲的并且足够大的内存,然后对这块内存进行分割,分割一...
  • 通过链表记录当前固定内存区域的使用情况,分配内存时自动寻找最合适的内存块,并将其分割成两个新内存块;释放内存时通过内存块合并有效减少内存碎片的问题。
  • DynamicAllocate.rar 动态内存分配算法(源代码&报告)

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 380,018
精华内容 152,007
关键字:

内存分配算法