精华内容
下载资源
问答
  • 操作系统原理:动态内存分配

    千次阅读 2017-04-12 23:21:13
    动态内存分配背后的机制深刻的体现了计算机科学中的这句名言: All problem in CS can be solved by another level of indirection. — Butler Lampson ...

    动态内存分配背后的机制深刻的体现了计算机科学中的这句名言:

    All problem in CS can be solved by another level of indirection. — Butler Lampson

    用户层

    malloc的实现

    malloc的底层调用sbrk和mmap

    malloc是C语言标准库函数,是在用户层实现的。在Linux里,malloc编译好,是在run-time的动态库so中,通过标准库头文件把api声明给调用者。mallocreallocfree是C语言的用户层的标准库函数,底层调用的是mmapsbrk函数。大致如下图:
    这里写图片描述
    malloc 底层调用的 sbrk 和 mmap. 申请内存小的时候用sbrk,增量扩展heap段;申请内存大的时候是调 mmap,进程重新开一块VMA(后面会介绍)。这些都是可配置的,malloc 的默认触发值是128k,可以看标准的源代码。

    /* malloc.c */
    #ifndef DEFAULT_MMAP_THRESHOLD_MIN
    #define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024)
    #endif
    
    #ifndef DEFAULT_MMAP_THRESHOLD
    #define DEFAULT_MMAP_THRESHOLD DEFAULT_MMAP_THRESHOLD_MIN
    #endif

    malloc 一般实现所用的数据结构 – 双向链表

    本质上,malloc就是管理一块连续的可读写的进程虚拟内存。作为一个内存分配器,要做到: (1)最大化吞吐效率; (2)最大化内存利用率。同时不可避免的就会有所限制,即申请过的内存块不能被修改和移动。管理申请的内存块,在具体实现,上要考虑下面几点:

    • 组织:如何记录空闲块;
    • 选择:如何选择一个合适的空闲块来作为一个新分配的内存块;
    • 分割:如何处理一个空闲快分配过内存后剩下的部分;
    • 合并:如何处理一个刚刚被释放的块;

    下面介绍两种malloc实现常用的数据结构:

    隐含链表方式 – 隐含链表方式即在每一块空闲或被分配的内存块中使用一个字的空间来保存此块大小信息和标记是否被占用。根据内存块大小信息可以索引到下一个内存块,这样就形成了一个隐含链表,通过查表进行内存分配。优点是简单,缺点就是慢,需要遍历所有。meta-data如图:
    这里写图片描述
    显示空闲链表 – 显示空闲链表的方法,和隐含链表方式相似,唯一不同就是在空闲内存块中增加两个指针,指向前后的空闲内存块。相比显示链表,就是分配时只需要顺序遍历空闲块。meta-data如图:
    这里写图片描述
    还有一些其他的方法不一一介绍。

    内核层

    brk系统调用的实现原理

    Linux kernel通过 VMA : vm_area_struct来分块管理进程地址空间。每个进程的进程控制块 task_struct 里都有一个 mm_struct指向了进程可使用的所有的VMA。sys_brk 操作系统调用本质就是调整这些VMA, 是mmap家族的一个特例,sbrk则是glibc针对brk的基础上实现的。详细可以看Linux kernel和glibc的的源码。

    /* 大致的结构 */
    struct mm_struct {
             struct vm_area_struct * mmap;  /* 指向虚拟区间(VMA)链表 */
             rb_root_t mm_rb;         /* 指向red_black树 */
             struct vm_area_struct * mmap_cache;     /* 指向最近找到的虚拟区间*/
             pgd_t * pgd;             /* 指向进程的页目录 */
             atomic_t mm_users;                   /* 用户空间中的有多少用户*/
             atomic_t mm_count;               /* 对"struct mm_struct"有多少引用*/
             int map_count;                        /* 虚拟区间的个数*/
             struct rw_semaphore mmap_sem;
             spinlock_t page_table_lock;        /* 保护任务页表和 mm->rss */
             struct list_head mmlist;            /*所有活动(active)mm的链表 */
             unsigned long start_code, end_code, start_data, end_data;
             unsigned long start_brk, brk, start_stack; /*堆栈相关*/
             unsigned long arg_start, arg_end, env_start, env_end;
             unsigned long rss, total_vm, locked_vm;
             unsigned long def_flags;
             unsigned long cpu_vm_mask;
             unsigned long swap_address;
             unsigned dumpable:1;
             /* Architecture-specific MM context */
             mm_context_t context;
    };

    下面是一个 mm_struct 管理进程内存的示意图:
    这里写图片描述

    Linux进程VMA的管理 - 红黑树

    每个VMA包括VMA的起始和结束地址, 访问权限等. 其中的 vm_file 字段表示了该区域映射的文件(如果有的话)。有些不映射文件的VMA是匿名的,比如的heap, stack都分别对应于一个单独的匿名的VMA. 进程的VMA存放在一个List和一个rb_tree中, 该List根据VMA的起始地址排序。红黑树结构是为了加快查找速度,快速查找某一地址是否在进程的某一个VMA中. 通过命令读取/proc/pid/maps文件查看进程的内存映射, 这个实现也是通过查存放VMA的List打印出来的。
    这里写图片描述

    物理页内存管理-伙伴算法

    操作系统是按页来管理物理内存的。伙伴算法每次只能分配2的幂次页的空间,比如一次分配1页,2页,4页,…, 1024页等。伙伴的定义:

    1. 两个块大小相同;
    2. 两个块地址连续;
    3. 两个块必须是同一个大块中分离出来的;

    分配内存:

    1. 寻找大小合适的内存块(大于等于所需大小并且最接近2的幂,比如需要27,实际分配32)。如果找到了,分配给应用程序,没有执行下一步;
    2. 对半分离出高于所需大小的空闲内存块;
    3. 如果分到最低限度,分配这个大小;
    4. 回到步骤1,寻找合适大小的块;
    5. 重复该步骤直到一个合适的块;

    释放内存:

    1. 释放该内存块;
    2. 寻找相邻的块,看其是否释放了;
    3. 如果相邻块也释放了,合并这两个块,重复上述步骤直到遇上未释放的相邻块,或者达到最高上限(即所有内存都释放了);

    参考

    展开全文
  • 操作系统——内存分配管理

    千次阅读 2019-08-29 15:39:51
    1. 连续分配 1.1 单一连续分配 1.2 固定分区分配 1.3 动态分区分配 2. 非连续分配 2.1 基本分页存储管理方式 2.2 基本分段存储管理方式 2.3 段页式存储管理方式

    1. 连续分配

    连续分配方式,是指为一个用户程序分配一个连续的内存空间。

    分配策略作业道数内部碎片外部碎片硬件支持解决碎片解决空间不足提高作业道数
    单道连续分配1界地址寄存器、越界检查机构覆盖交换
    多道固定连续分配不超过用户空间分区数上下界寄存器、越界检查机构、基地址寄存器、长度寄存器、动态地址转换机构覆盖交换
    多道动态连续分配可变上下界寄存器、越界检查机构、基地址寄存器、长度寄存器、动态地址转换机构紧凑覆盖交换

    内部碎片是已经被分配出去的的内存空间大于请求所需的内存空间。

    外部碎片是指还没有分配出去,但是由于大小太小而无法分配给申请空间的新进程的内存空间空闲块。

    固定分区分配存在内部碎片,动态分区分配会存在外部碎片;

    页式虚拟存储系统存在内部碎片,段式虚拟存储系统存在外部碎片。

    1.1 单一连续分配

    内存在此方式下分为系统区用户区,系统区仅提供给操作系统使用,通常在低地址部分;用户区是为用户提供的、除系统区之外的内存空间。这种方式无需进行内存保护,因为内存中永远只有一道程序,不会因为访问越界而干扰其他程序。

    优点:简单、无外部碎片,可以采用覆盖技术,不需要额外的技术支持。

    缺点:只能用于单用户、单任务的操作系统中,有内部碎片,存储器的利用率极低。

    1.2 固定分区分配

    固定分区与页式分区思想类似,都是预先分配内存,但是页式分区有页表。

    固定分区分配是最简单的一种多道程序存储管理方式,它将用户内存空间划分为若干个固定大小的区域,每个分区只装入一道作业。当有空闲分区时,便可以再从外存的后备作业队列中,选择适当大小的作业装入该分区,如此循环。

    固定分区分配在划分分区时,有两种不同的方法:

    • 分区大小相等:用于利用一台计算机去控制多个相同对象的场合,缺乏灵活性;
    • 分区大小不等:划分为含有多个较小的分区、适量的中等分区及少量的大分区。

    为便于内存分配,通常将分区按大小排队,并为之建立一张分区说明表,其中各表项包括每个分区的起始地址、大小及状态(是否已分配)。当有用户程序要装入时,便检索该表,以找到合适的分区给予分配并将其状态置为已分配;未找到合适分区则拒绝为该用户程序分配内存。

    这种分区方式存在两个问题:一是程序可能太大而放不进任何一个分区中,这时用户不得不使用覆盖技术来使用内存空间;二是主存利用率低,当程序小于固定分区大小时,也占用了一个完整的内存分区空间,这样分区内部有空间浪费,这种现象称为内部碎片

    固定分区是可用于多道程序设计最简单的存储分配,无外部碎片。固定分区分配很少用于现在通用的操作系统中,但在某些用于控制多个相同对象的控制系统中仍发挥着一定的作用。

    1.3 动态分区分配

    动态分区与段式分区思想类似,根据用户所需内存分配资源,段式分区有段表,且多为段式分区与页式分区结合使用,先为进程进行段式分配,然后在每段中进行页式分配。

    动态分区分配又称为可变分区分配,是一种动态划分内存的分区方法。这种分区方法不预先将内存划分,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统中分区的大小和数目是可变的。

    动态分区在开始分配时是很好的,但是之后会导致内存中出现许多小的内存块。随着时间的推移,进程不断地换入/换出,内存中会产生越来越多的碎片,内存的利用率随之下降。这些小的内存块称为外部碎片,指在所有分区外的存储空间会变成越来越多的碎片,这与固定分区中的内部碎片正好相对。克服外部碎片可以通过紧凑技术来解决,就是操作系统不时地对进程进行移动和整理。但是这需要动态重定位寄存器的支持,且相对费时。

    在进程装入或换入主存时,如果内存中有多个足够大的空闲块,操作系统必须确定分配哪个内存块给进程使用,这就是动态分区分配策略,考虑以下几种算法:

    • 首次适应算法:空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小能满足要求的第一个空闲分区;
    • 最佳适应算法:空闲分区按容量递增形成分区链,分配时找到第一个能满足要求的空闲分区;
    • 最坏适应算法:又称最大适应算法,空闲分区以容量递减的次序链接。找到第一个能满足要求的空闲分区,也就是挑选出最大的分区。
    • 邻近适应算法:又称循环首次适应算法,由首次适应算法演变而成。不同之处是分配内存时从上次查找结束的位置开始继续查找。

    首次适应算法不仅是最简单的,而且通常也是最好和最快的。UNIX 系统的最初版本,就是使用首次适应算法为进程分配内存空间,并且使用数组 (而非链表)来实现。首次适应算法会使得内存的低地址部分出现很多小的空闲分区,每次分配查找时,都要经过这些分区,增加查找的开销。

    邻近适应算法试图解决这个问题,但实际上,它常常会导致在内存的末尾分配空间分裂成小碎片(因为在一遍扫描中,内存前面部分使用后再释放时,不会参与分配)。它通常比首次适应算法的结果要差。

    最佳适应算法性能通常很差,因为每次最佳的分配会留下很小的难以利用的内存块,它会产生最多的外部碎片。

    最坏适应算法与最佳适应算法相反,选择最大的可用块,这看起来最不容易产生碎片,但是却把最大的连续内存划分开,会很快导致没有可用的大的内存块,因此性能也非常差。

    在算法实现时,最佳适应法和最大适应法需要对可用块进行排序或遍历查找,而首次适应法和邻近适应法只需要简单查找;回收操作中,当回收的块与原来的空闲块相邻时(有三种相邻的情况,比较复杂),需要将这些块合并。

    2. 非连续分配

    非连续分配允许一个程序分散地装入到不相邻的内存分区中,解决因连续空闲空间不足,导致程序无法装入的问题。由于需要额外的空间存储分散内存的索引,非连续分配方式的存储密度低于连续分配方式。

    根据分区的大小是否固定分为分页存储管理方式和分段存储管理方式;根据运行作业时是否要把作业的所有页面都装入内存才能运行分为基本分页存储管理方式和请求分页存储管理方式。

    分配策略逻辑地址结构页表段表设计目的硬件支持
    页式存储管理[页号]:[页内偏移量][页号]:[块号]-提高内存的利用率页表寄存器
    段式存储管理[段号]:[段内偏移量]-[段号]:[段长]:[起始地址]信息保护和共享段表寄存器
    段页式存储管理[段号]:[页号]:[页内偏移量][页号]:[块号][段号]:[页表长度]:[页表起始地址]兼顾二者段表寄存器

    透明性:分页对程序员透明,分段需要显式划分每个段(一般由编译器完成);
    地址空间:分页是一维地址空间,分段是二维地址空间;
    大小是否可变:页的大小不可变,段的大小可以动态改变;
    出现原因:分页主要用于提高内存利用率,实现虚拟内存,获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间,有助于共享和保护。

    2.1 基本分页存储管理方式

    固定分区会产生内部碎片,动态分区会产生外部碎片,这两种技术对内存的利用率都比较低。

    把主存空间划分为大小相等且固定的块,块相对较小,作为主存的基本单位。每个进程也以块为单位进行划分,进程在执行时,以块为单位逐个申请主存中的块空间。

    块的大小相对分区要小很多,而且进程也按照块进行划分,进程运行时按块申请主存可用空间并执行。这样,进程只会在为最后一个不完整的块申请主存空间时,才产生主存碎片,所以尽管会产生内部碎片,但是这种碎片相对较小,每个进程平均只产生半个块大小的内部碎片(也称页内碎片)。

    1. 页面/页框

    进程中的块称为页面(Page),内存中的块称为页框或页帧(Page Frame)。外存也以同样的单位进行划分,直接称为块(Block)。进程在执行时需要申请主存空间,为每个页分配主存中的可用页框,产生页和页框的一一对应关系。

    为方便地址转换,页面大小应是2的整数幂。同时,页面大小应该适中,如果页面太小,会使进程的页面数过多,页表过长,占用大量内存,也会增加硬件地址转换的开销,降低页面换入/换出的效率;页面过大会使页内碎片增大,降低内存的利用率。

    2. 逻辑地址结构

    逻辑地址结构包含两部分:前一部分为页号,后一部分为页内偏移量。对于32位操作系统,逻辑地址长度为32 位,其中0-11位为页内地址,每页大小为4KB;12-31位为页号,最多允许有 2 20 2^{20} 220页。逻辑地址长度决定虚拟内存的寻址空间大小

    3. 页表

    为了便于在内存中找到进程的每个页面所对应的物理块,系统为每个进程建立一张页表,记录页面在内存中对应的物理块号,页表一般存放在内存中。页表的作用是实现从页号到物理块号的地址映射。

    逻辑地址 [页号]:[页内偏移量]

    页表结构 [页号]:[物理内存块号]

    物理地址 [物理内存块号]:[页内偏移量]

    在这里插入图片描述

    这种分区方式存在两个问题:一是每次访存操作都需要进行逻辑地址到物理地址的转换,地址转换过程必须足够快,否则访存速度会降低;二是为每个进程引入页表,用于存储映射机制,页表不能太大,否则内存利用率会降低。

    2.1.1 基本地址变换机构

    基本地址变换机构的任务是将逻辑地址转换为物理地址,地址变换是借助于页表实现的,整个过程由硬件自动完成。通常设置一个页表寄存器(PTR),存放页表在内存的起始地址 F 和页表长度 M。进程未执行时,页表的起始地址和长度存放在进程控制块中,进程执行时,才将二者存入页表寄存器。

    页面大小为 L,逻辑地址 A 到物理地址 E 的转换过程如下:

    1. 计算页号 P=A/L 和页内偏移量 W=A%L
    2. 比较页号和页表长度,若页号大于等于页表长度则产生越界中断,否则继续执行;
    3. 页表中页号对应的页表项地址 = 页表起始地址 + 页号 * 页表项长度,该页表项内容 b,即为物理块号;
    4. 计算物理地址 E=b*L+W

    在这里插入图片描述

    2.1.2 具有快表的地址变换机构——解决地址变换效率问题

    在基本地址变换机构中,页表全部放在内存中,存取一个数据或一条指令至少要访问两次内存:第一次是访问页表,确定所存取的数据或指令的物理地址,第二次才是根据该地址存取数据或指令。这种方法比通常执行指令的速度慢了一半。

    在地址变换机构中增设一个具有并行查找能力的高速缓冲存储器——快表,又称联想寄存器(TLB),存储当前访问的若干页表项,以加速地址变换的过程。与此对应,主存中的页表也常称为慢表。

    具有快表的地址转换过程如下:

    1. CPU 给出逻辑地址后,由硬件计算出页号,并将其送入高速缓存寄存器,将此页号与快表中的所有页号进行比较;
    2. 如果找到匹配的页号,则直接从中取出该页对应的页框号,与页内偏移量拼接形成物理地址。存取数据仅一次访存便可实现;
    3. 如果没有匹配成功,则需要访问主存中的页表,读出页表项后,应将其存入快表,以便可能的再次访问。若快表已满,则按照一定的算法对旧的页表项进行替换。

    在这里插入图片描述

    有些处理机设计为快表和慢表同时查找,如果在快表中查找成功则终止慢表的查找。

    一般快表的命中率可以达到90%以上,分页带来的速度损失就降低到10%以下。快表的有效性是基于著名的局部性原理

    2.1.3 二级页表——解决页表过大问题

    大多数情况下,映射所需要的页表项都在页表的同一个页面中。为了查询的方便,顶级页表最多只能有1个页面,容纳1K个页表项,需要10位编址

    在32位操作系统中,全部32位逻辑地址空间4GB,页面大小4KB,最大页面数1M。若采用一级页表,至少需要20位对页号编址,页表项长度至少为3B(一般取4B),页表总大小4MB,需要占用1024个页面。若采用二级页表,对1K个页面建立顶级页表,1K个页表项正好是一页的大小,即顶级页表占用1页。32位逻辑地址结构为:顶级页表10位,二级页表10位,页内偏移量12位

    在64位操作系统中,全部64位逻辑地址空间16EB(K、M、G、T、P、E、Z、Y、D、N),页面大小4KB,最大页面数4PB,至少需要20位对页号编址,页面项长度至少为7B(一般取8B)。对上一级分页时,每个页面只能存储512个页表项,页面号9位,以此类推。64位逻辑地址结构为:7+9+9+9+9+9+12,需要六级分页

    使用层次结构的页表:将页表的10页空间进行地址映射,建立上一级页表,用于存储页表的映射关系。对页表的10个页面进行映射只需要10个页表项,上一级页表只需要1页就足够(1页最大可存储1024个页表项)。进程执行时,只需要将这1页的上一级页表调入内存,进程的页表和进程本身的页面,可以在后面的执行中再调入内存。

    建立多级页表的目的在于建立索引,这样不用浪费主存空间去存储无用的页表项,也不用盲目地顺序式查找页表项,建立索引的要求是最高一级页表项不超过一页的大小

    在这里插入图片描述

    2.2 基本分段存储管理方式

    分页管理方式是从计算机的角度考虑设计的,以提高内存的利用率,提升计算机的性能, 且分页通过硬件机制实现,对用户完全透明;分段管理方式的提出则是考虑了用户和程序员,以满足方便编程、信息保护和共享、动态增长及动态链接等多方面的需要。

    段式管理方式按照用户进程中的自然段划分逻辑空间。例如,用户进程由主程序、两个子程序、栈和一段数据组成,于是可以把这个用户进程划分为5个段,每段从0开始编址,并分配一段连续的地址空间(段内要求连续,段间不要求连续,因此整个作业的地址空间是二维的),逻辑地址由段号 S 与段内偏移量 W 两部分组成。

    在页式系统中,逻辑地址的页号和页内偏移量对用户是透明的,但在段式系统中,段号和段内偏移量必须由用户显式提供,在髙级程序设计语言中,这个工作由编译程序完成。

    每个进程都有一张逻辑空间与内存空间映射的段表,每一个段表项对应进程的一个段,段表项由段号、段长和该段在内存中的起始地址组成。段表用于实现从逻辑段到物理内存的映射。

    在这里插入图片描述

    为了实现进程从逻辑地址到物理地址的变换功能,在系统中设置了段表寄存器,用于存放段表在内存的起始地址 F 和段表长度 M。

    逻辑地址 A 到物理地址 E 的转换过程如下:

    1. 从逻辑地址中取出前几位为段号,后几位为段内偏移量;
    2. 比较段号和段表长度,若段号大于等于段表长度,则产生越界中断,否则继续执行;
    3. 段表中段号对应的段表项地址 = 段表起始地址 + 段号 * 段表项长度,取出该段表项的前几位得到段长。若段内偏移量大于等于段长,则产生越界中断,否则继续执行;
    4. 取出段表项中该段的起始地址b,计算物理地址 E = b+W

    在这里插入图片描述

    段的共享是通过两个作业的段表中相应表项指向被共享的段的同一个物理副本来实现的。当一个作业正从共享段中读取数据时,必须防止另一个作业修改此共享段中的数据。不能修改的代码称为纯代码或可重入代码(它不属于临界资源),这样的代码和不能修改的数据是可以共享的,而可修改的代码和数据则不能共享。

    分段管理的保护方法主要有两种:一种是存取控制保护,另一种是地址越界保护。地址越界保护是利用段表寄存器中的段表长度与逻辑地址中的段号比较,若段号大于段表长度则产生越界中断;再利用段表项中的段长和逻辑地址中的段内位移进行比较,若段内位移大于段长,也会产生越界中断。

    2.3 段页式存储管理方式

    在段页式系统中,作业的地址空间首先被分成若干个逻辑段,每段都有自己的段号,然后再将每一段分成若干个大小固定的页。对内存空间的管理仍然和分页存储管理一样,将其分成若干个和页面大小相同的存储块,对内存的分配以存储块为单位。

    在段页式系统中,作业的逻辑地址分为三部分:段号、页号和页内偏移量。

    为了实现地址变换,系统为每个进程建立一张段表,而每个分段有一张页表。段表项中至少包括段号、页表长度和页表起始地址,页表表项中至少包括页号和块号。此外,系统中还应有一个段表寄存器,指出作业的段表起始地址和段表长度。在一个进程中,段表只有一个,而页表可能有多个

    在这里插入图片描述

    在进行地址变换时,首先通过段表查到页表起始地址,然后通过页表找到页帧号,最后形成物理地址。进行一次访问实际需要三次访问主存,同样可以使用快表以加快查找速度,其关键字由段号、页号组成,值是对应的页帧号和保护码。

    在这里插入图片描述

    展开全文
  • 内存的作用 内存是计算机的一个重要组成部分,它的主要作用在于配合 CPU 的高速运转,使得计算机的运行速度得到大大地提升 我们应该知道,计算机上的一切都是程序,我们使用计算机其实就是在运行计算机上的各种...

    一. 内存的作用

    内存是计算机的一个重要组成部分,它的主要作用在于配合 CPU 的高速运转,使得计算机的运行速度得到大大地提升
    我们应该知道,计算机上的一切都是程序,我们使用计算机其实就是在运行计算机上的各种程序,而这些程序都存储在我们的硬盘中(外存),硬盘中的数据内容是几乎可以永久存储的但是 它的读取速度相较于 CPU 的处理速度是十分缓慢的 **
    如果没有内存,CPU 在处理完一段程序后,要空闲很长一段时间等待硬盘继续传送数据,这样一来,极大地降低了 CPU 的效率
    而内存由于其构造原理与硬盘不同,它的
    读写速度非常快**(虽然不及 CPU,但可以远高于硬盘),但是它内部的数据断电后就会消失,不具有永久存储性,因此,内存的主要作用就相当于 CPU 与硬盘之间的中转站,内存中会暂存即将要执行的程序,等待着 CPU 的调度

    二. 内存分配

    基于内存的工作原理,你一定想问:既然内存速度那么快,为什么不把硬盘中的所有数据全部放入内存等待 CPU 调度呢?
    因为内存不仅读取速度是硬盘的很多倍,制造成本也远高于硬盘,所以必须省着点用啊!
    那么新的问题来了,内存空间有限,那么在有限的空间中该怎么存取数据呢?这就是内存的分配算法了

    内存分配算法,大体来说分为:连续式分配 与 非连续式分配
    顾名思义连续式分配就是把所以要执行的程序 完整的,有序的 存入内存,连续式分配又可以分为固定分区分配 和 动态分区分配
    非连续式分配就是把要执行的程序按照一定规则进行拆分,显然这样更有效率,现在的操作系统通常也都是采用这种方式分配内存

    关于内存的原理部分本文只讲到这里,本文主要关注动态分区分配算法

    三. 动态分区分配

    所谓动态分区分配,就是指内存在初始时不会划分区域,而是会在进程装入时,根据所要装入的进程大小动态地对内存空间进行划分,以提高内存空间利用率,降低碎片的大小
    动态分区分配算法有以下四种:

    1. 首次适应算法(First Fit)

    空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小满足要求的第一个空闲分区就进行分配
    这里写图片描述

    2. 邻近适应算法(Next Fit)

    又称循环首次适应法,由首次适应法演变而成,不同之处是分配内存时从上一次查找结束的位置开始继续查找
    这里写图片描述

    3. 最佳适应算法(Best Fit)

    空闲分区按容量递增形成分区链,找到第一个能满足要求的空闲分区就进行分配
    这里写图片描述

    4. 最坏适应算法(Next Fit)

    又称最大适应算法(Largest Fit),空闲分区以容量递减的次序链接,找到第一个能满足要求的空闲分区(也就是最大的分区)就进行分配
    这里写图片描述

    四. 代码实现

    package com.dht.memory;
    
    import java.util.LinkedList;
    import java.util.Scanner;
    
    /**
     * 内存类
     * @author dht925nerd@126.com
     */
    public class Memory{
        /**
         * 内存大小
         */
        private int size;
        /**
         * 最小剩余分区大小
         */
        private static final int MIN_SIZE = 5;
        /**
         * 内存分区
         */
        private LinkedList<Zone> zones;
        /**
         * 上次分配的空闲区位置
         */
        private int pointer;
    
        /**
         * 分区节点类
         */
        class Zone{
            /**
             * 分区大小
             */
            private int size;
            /**
             * 分区始址
             */
            private int head;
            /**
             * 空闲状态
             */
            private boolean isFree;
    
            public Zone(int head, int size) {
                this.head = head;
                this.size = size;
                this.isFree = true;
            }
        }
    
        /**
         * 默认内存大小为 100 KB
         */
        public Memory(){
            this.size = 100;
            this.pointer = 0;
            this.zones = new LinkedList<>();
            zones.add(new Zone(0, size));
        }
        public Memory(int size) {
            this.size = size;
            this.pointer = 0;
            this.zones = new LinkedList<>();
            zones.add(new Zone(0, size));
        }
    
        /**
         * 内存分配
         * @param size 指定需要分配的大小
         */
        public void allocation(int size){
            System.out.println("1.FirstFit 2.NextFit 3.BestFit 4.WorstFit");
            System.out.print("请选择分配算法:");
            Scanner in = new Scanner(System.in);
            int algorithm = in.nextInt();
            switch (algorithm){
                case 1:
                    fristFit(size);break;
                case 2:
                    nextFit(size);break;
                case 3:
                    bestFit(size);break;
                case 4:
                    worstFit(size);break;
                default:
                    System.out.println("请重新选择!");
            }
        }
    
        /**
         * 首次适应算法
         * @param size 指定需要分配的大小
         */
        private void fristFit(int size){
            //遍历分区链表
            for (pointer = 0; pointer < zones.size(); pointer++){
                Zone tmp = zones.get(pointer);
                //找到可用分区(空闲且大小足够)
                if (tmp.isFree && (tmp.size > size)){
                    doAllocation(size, pointer, tmp);
                    return;
                }
            }
            //遍历结束后未找到可用分区, 则内存分配失败
            System.out.println("无可用内存空间!");
        }
    
        /**
         * 循环首次适应算法
         * @param size 指定需要分配的大小
         */
        private void nextFit(int size){
            //从上次分配空闲区位置开始遍历分区链表
            Zone tmp = zones.get(pointer);
            if (tmp.isFree && (tmp.size > size)){
                doAllocation(size, pointer, tmp);
                return;
            }
            int len = zones.size();
            int i = (pointer + 1) % len;
            for (; i != pointer; i = (i+1) % len){
                tmp = zones.get(i);
                //找到可用分区(空闲且大小足够)
                if (tmp.isFree && (tmp.size > size)){
                    doAllocation(size, i, tmp);
                    return;
                }
            }
            //遍历结束后未找到可用分区, 则内存分配失败
            System.out.println("无可用内存空间!");
        }
    
        /**
         * 最佳适应算法
         * @param size 指定需要分配的大小
         */
        private void bestFit(int size){
            int flag = -1;
            int min = this.size;
            for (pointer = 0; pointer < zones.size(); pointer++){
                Zone tmp = zones.get(pointer);
                if (tmp.isFree && (tmp.size > size)){
                    if (min > tmp.size - size){
                        min = tmp.size - size;
                        flag = pointer;
                    }
                }
            }
            if (flag == -1){
                System.out.println("无可用内存空间!");
            }else {
                doAllocation(size, flag, zones.get(flag));
            }
        }
    
        /**
         * 最坏适应算法
         * @param size 指定需要分配的大小
         */
        private void worstFit(int size){
            int flag = -1;
            int max = 0;
            for (pointer = 0; pointer < zones.size(); pointer++){
                Zone tmp = zones.get(pointer);
                if (tmp.isFree && (tmp.size > size)){
                    if (max < tmp.size - size){
                        max = tmp.size - size;
                        flag = pointer;
                    }
                }
            }
            if (flag == -1){
                System.out.println("无可用内存空间!");
            }else {
                doAllocation(size, flag, zones.get(flag));
            }
        }
    
        /**
         * 执行分配
         * @param size 申请大小
         * @param location 当前可用分区位置
         * @param tmp 可用空闲区
         */
        private void doAllocation(int size, int location, Zone tmp) {
            //如果分割后分区剩余大小过小(MIN_SIZE)则将分区全部分配,否则分割为两个分区
            if (tmp.size - size <= MIN_SIZE){
                tmp.isFree = false;
            } else {
                Zone split = new Zone(tmp.head + size, tmp.size - size);
                zones.add(location + 1, split);
                tmp.size = size;
                tmp.isFree = false;
            }
            System.out.println("成功分配 " + size + "KB 内存!");
        }
    
        /**
         * 内存回收
         * @param id 指定要回收的分区好号
         */
        public void collection(int id){
            if (id >= zones.size()){
                System.out.println("无此分区编号!");
                return;
            }
            Zone tmp = zones.get(id);
            int size = tmp.size;
            if (tmp.isFree) {
                System.out.println("指定分区未被分配, 无需回收");
                return;
            }
            //如果回收分区不是尾分区且后一个分区为空闲, 则与后一个分区合并
            if (id < zones.size() - 1 && zones.get(id + 1).isFree){
                Zone next = zones.get(id + 1);
                tmp.size += next.size;
                zones.remove(next);
            }
            //如果回收分区不是首分区且前一个分区为空闲, 则与前一个分区合并
            if (id > 0 && zones.get(id - 1).isFree){
                Zone previous = zones.get(id - 1);
                previous.size += tmp.size;
                zones.remove(id);
                id--;
            }
            zones.get(id).isFree = true;
            System.out.println("内存回收成功!, 本次回收了 " + size + "KB 空间!");
        }
    
        /**
         * 展示内存分区状况
         */
        public void showZones(){
            System.out.println("------------------------------------");
            System.out.println("分区编号\t分区始址\t分区大小\t空闲状态\t");
            System.out.println("------------------------------------");
            for (int i = 0; i < zones.size(); i++){
                Zone tmp = zones.get(i);
                System.out.println(i + "\t\t" + tmp.head + "\t\t" +
                                    tmp.size + "  \t" + tmp.isFree);
            }
            System.out.println("------------------------------------");
        }
    }
    
    展开全文
  • 3.1.4 操作系统内存分配与回收

    千次阅读 多人点赞 2020-04-23 21:32:09
    动态分区分配(可变分区分配)(1)系统要用怎样的数据结构记录内存的使用情况呢?(2)当多个空闲分区都能满足要求时,应该选择哪个分区进行分配?(3)如何进行分区的分配和回收操作?4.内部碎片与外部碎片 0....


    0.思维导图

    在这里插入图片描述
    在这里插入图片描述

    1.单一连续分配

    在这里插入图片描述

    2.固定分区分配

    在这里插入图片描述

    (1)分区说明表

    在这里插入图片描述

    3.动态分区分配(可变分区分配)

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    (1)系统要用怎样的数据结构记录内存的使用情况呢?

    在这里插入图片描述

    (2)当多个空闲分区都能满足要求时,应该选择哪个分区进行分配?

    在这里插入图片描述

    (3)如何进行分区的分配和回收操作?

    如何分配?
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    如何回收?
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    4.内部碎片与外部碎片

    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 特别是开发者经常接触的Malloc/Free接口的实现,对许多开发者来说,是一个永远的话题,而且有时候也是一个比较迷惑的问题,本文根据自己的理解,尝试简单的探究一下在嵌入式系统中,两类典型系统动态内存分配以及...
  • C语言——操作系统内存分配过程

    千次阅读 2018-03-27 13:49:08
    内存分配机制的发展过程: 第一阶段——程序直接操作物理内存。 某台计算机总的内存大小是128M,现在同时运行两个程序A和B,A需占用内存10M,B需占用内存100。计算机在给程序分配内存时会采取这样的方法:先将...
  • 最近分享了关于内存分配的相关知识,整理了一下PPT,总结成博客 在操作系统中,内存管理的目的是什么呢? 其中最主要的就是提高内存的利用率,所谓的提高内存利用率,就是尽可能的在内存中多存储进程,这就涉及到...
  • 操作系统分配内存

    千次阅读 2016-07-05 14:01:26
    操作系统内存的最小访问单位是 字节 ,也就是8bit。 通常我们所说的计算机系统是32位的总线,所谓的32位总线就是说一次读写可以从内存当中读或者写32位(也就是4字节)。 因为一次读写是32位,所以需要...
  • C/C++ 动态内存分配

    万次阅读 多人点赞 2019-06-30 18:03:06
    首先我们看看 c 语言是如何进行动态内存分配的; c 语言主要是使用malloc / calloc / realloc 来进行内存申请的。 malloc / realloc / calloc三者的共同点与不同点: 共同点 1、都是从堆上进行动态内存分配 2、...
  • 操作系统中有专门负责分配内存的进程,它会为那些在操作系统级别上使用内存的软件系统分配一块内存。 所谓的编译时内存分配(静态内存分配),即是为程序本身能在下一个阶段(运行阶段)的运行而在内存上的程序区分...
  • 常见内存分配算法及优缺点如下:  (1)首次适应算法。使用该算法进行内存分配时,从空闲分区链首开始查找,直至找到一个能满足其大小需求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存分配给请求...
  • 操作系统内存管理内存空间的连续分配方式1.概述、分类内存空间的连续分配方式,是指为一个用户程序(作业)分配一个连续的内存空间。 按照内存空间划分方式的不同可将连续分配方式划分为以下四种方式: 1. 单一...
  • 操作系统 内存分配与回收

    千次阅读 2018-11-28 17:23:14
    操作系统 内存分配与回收 无论什么进程,想要运行的就需要申请内存的空间,操作系统把我们的内存空间分割成化成一个个页表, 现在的一般一个页表的大小是4kb,而每个进程申请的空间都是以页为单位的。 比如我们...
  • 详解操作系统分配内存

    万次阅读 多人点赞 2017-07-08 16:37:29
    操作系统内存的最小访问单位是 字节 ,也就是8bit。 通常我们所说的计算机系统是32位的总线,所谓的32位总线就是说一次读写可以从内存当中读或者写32位(也就是4字节)。 因为一次读写是32位,所以需要...
  • 特别是开发者经常接触的Malloc/Free接口的实现,对许多开发者来说,是一个永远的话题,而且有时候也是一个比较迷惑的问题,本文根据自己的理解,尝试简单的探究一下在嵌入式系统中,两类典型系统动态内存分配以及...
  • 一个程序操作系统为他分配哪些空间,都是干什么的,还有,操作系统在程序运行之前做些什么
  • 浅析动态内存分配及Malloc/free的实现 作者:gogofly 邮箱:gogofly_lee@yahoo.com.cn 一、概述: 动态内存分配,特别是开发者经常接触的Malloc/Free接口的实现,对许多开发者来说,是一个永远的话题,而且有时候也...
  • 操作系统——动态分区分配方式模拟

    万次阅读 多人点赞 2015-01-11 12:52:41
    这里直接给出代码,如果不理解请参考左万历版《计算机操作系统教程》,先在给出四中模拟算法。 1. 设计目的 了解动态分区分配中使用的数据结构和分配算法,并进一步加深对动态分区存储管理方式及其实现过程的...
  • 动态内存分配

    千次阅读 2012-02-01 19:56:40
    2.但是我们也可以使用动态内存分配在运行时为它分配内存。3.为什么使用动态内存分配1>当使用数组时,必须用一个常量来指定数组的长度。但是,有时候,数组的长度常常在运行时才知道。因此,在某些情况下,我们通常...
  • 动态内存分配(C语言)

    千次阅读 多人点赞 2021-05-13 20:43:50
    动态内存分配 不放过每一个值得关注的点 文章目录动态内存分配前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结 前言 我们创建一个一维的数组的时候,可以是静态分配的,也可以是动态分配的。 静态分配...
  • 上一篇:操作系统from清华大学向勇,陈渝 笔记(二)操作系统的启动、中断、异常、系统调用 3-1 计算机体系结构&内存分层体系 3-2地址空间和地址生成 3-3连续内存分配:内存碎片与分区的动态分配 3-4 连续内存...
  • 算法思想:将内存块中的所有的块按照地址递增的顺序连接成一个链表,每次要将新的作业放入内存的时候就按顺序查找内存块链表,每次都是用找到的...插入操作时依照地址递增的顺序检查可以装入的第一个内存块若找到,...
  • 动态分区分配算法 一、实验目的 用高级语言编写和调试一个内存连续分配中动态...内存连续分配动态分区分配流程图如下 回收分区时应考虑分区合并的情况,三种情况,如下图 #include<bits/stdc++.h> using ...
  • 七、操作系统——动态分区分配算法(详解)

    千次阅读 多人点赞 2020-05-20 17:45:48
    每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。 三、最佳适应算法(Best Fit) 算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片...
  • C语言中手把手教你动态内存分配

    万次阅读 多人点赞 2017-02-06 23:21:07
    C语言中手把手教你动态内存分配动态内存分配常见的内存分配的错误先上一个内存分配的思维导图:便于联想想象,理解: 首先我们介绍一下内存分配的方式:1:在静态存储区域中进行分配 内存在程序编译的时候就已经分配...
  • C语言中的动态内存分配

    千次阅读 2018-10-17 15:34:51
    为什么使用动态内存分配? C语言中的一切操作都是基于内存的 变量和数组都是内存的别名,如何分配这些内存由编译器在编译期间决定 定义数组的时候必须指定数组长度 而数组长度是在编译期就必须决定的 需求:...
  • 操作系统-内存管理

    万次阅读 多人点赞 2019-06-06 09:20:53
    有何作用1.1.2 进程运行的原理-指令1.1.3 逻辑地址VS物理地址1.1.4 进程运行的基本原理(从写程序到程序运行)1.1.5 装入内存的三种方式1.1.5 链接的三种方式1.1.6 总结1.2 内存管理的概念1.2.1 内存空间的分配与回收...
  • DSP里动态内存分配malloc函数的使用

    千次阅读 2015-11-26 15:54:14
    DSP里的动态内存分配,其分配的内存区域在在堆(heap)中。同时DSP里动态分配内存的函数还有calloc以及reclloc。这些动态分配的内存放置在.system段的全局池或堆(heap)中。因此当我们需要动态分配大尺寸的内存时,我们...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 795,256
精华内容 318,102
关键字:

操作系统动态内存分配