操作系统内存管理概述
单一连续存储
早期的程序计算机指令访问的内存地址直接就是物理地址,内存的管理方法是单一连续存储管理,内存被分为系统区和用户区,应用程序被放在用户区运行
操作系统分区
固定分区&动态分区
为了支持多道程序,多个程序并发执行,引入了分区操作系统,把内存分为很多区,操作系统占一个区,还剩下了好多个区,每个程序占一个或几个区,但不能进行内存共享,会产生内外碎片,静态分区每个区大小相同,动态分区每个区大小不同,根据程序需要操作系统动态分配大小,没有内碎片,但有外碎片
伙伴系统
为了综合考虑固定分区和静态分区,引入了伙伴系统,分区大小均为2 ^ k大小,1≤k≤m,最开始一块分区,大小为2 ^ m,大小相同的分区,用同一个链表维护,当需要分配一个长度为n的连续空间时,找到最能满足的大小,在对应的空闲分区链表中查找,如果找到了就分配,如果没有找到就找大一号的,如果在大一号的里面找到了,那把大一号的拆分成两个小一号的,把一个分配出去,把另一个放进空闲链表里,回收的时候如果是拆出来的,那么回收后把两个小一号的合并成大一号的
覆盖
在多道程序技术中,还有一个问题是,如果现在可用内存确实很小,程序员在写程序时会对程序分区,分为常驻内存和可选部分,可选部分平时存放在覆盖文件中,需要时才装入内存
交换
为了解决同样的问题,另一种方法是交换技术,整个程序不再需要划分,而是直接把不能运行的程序换出内存,这样获得的空闲内存用来跑新程序或外存里就绪的老程序
离散内存管理
这些思想都基于程序的运行需要在一段连续的内存地址内,这样的问题在于,可能物理地址有很大,但1.在32位机里,cpu只能寻址2^32个地址也就是4G,因此会有些浪费,2.当内存里运行了多个程序后,内存里都是碎片内存,3.各个进程之间共享一个物理内存,一个进程可以恶意修改别的进程的信息4.现代操作系统需要实现内存的访问控制,有的地址不是谁都能访问的,5.当一个进程在运行时,另一个进程抢占了,此时原进程需要被全部拷贝到硬盘中,然后将新程序装入内存中运行(交换)。
因此提出了将程序放在不连续的地址空间内的思想
页式
页式存储管理的思想是把内存和物理内存都划分为相同大小的分页,每个程序的内存地址和实际物理地址一样大,每个程序有一个页表,用来记录自己的页对应在物理地址中的页在哪,当然每个程序都维护一个页表是不现实的,因此实际上还有二级页表三级页表
因此,操作系统为了维护内存,给每个进程都分配4G大小的内存,这4G的虚拟地址和实际的物理地址,先被分为相同大小的一页一页(例如4k),通过页表,建立逻辑地址与物理地址之间的关系,根据程序的局部性原理,当一块内存被访问时,可能附近的内存也要被访问,因此这个页表里并不是每个逻辑地址都真的映射物理地址,只有部分确实有映射,其余页表的数据被保存在硬盘里(swap & C),当访问某一页没有映射时就会缺页中断,操作系统就赶紧把硬盘里的数据拿出来放到物理内存里,并建立好映射关系,如果确实没有空闲内存了,那就涉及到页面置换算法
段式
另一种内存管理思想是不把程序内存和物理内存分页,而是像动态分配一样,只是把程序的地址空间分为若干个段,各个段依据动态分配的思想分配,而程序整体的地址空间不必连续
############################################################################
内存分区
代码段 只读数据(字符串常量)、文本存储(程序机器代码)
数据段 已初始化的全局变量和静态变量
bss段 未初始化的全局变量和静态变量以及初始化为0的全局变量和静态变量
堆区 new malloc出来的
栈区 函数、局部变量
hello world在哪个区:
文字常量区
堆和栈的区别
栈和堆的生长方向为啥不同
程序的地址空间从低地址到高地址的顺序依次是程序、数据、堆、栈、环境参数变量,堆和栈之间有很大一部分地址空间,如果堆和栈刚好相向生长就不用管堆栈中间的分界线了
内存池
深浅拷贝
浅拷贝就是指,当我们拷贝对象的时候,两个对象的指针指向了同一个内容,这时候不论是析构还是改变这个内容的值都会出错,而深拷贝是指,两个对象指向两个不同的地址,但这个地址里的内容都是一样的,具体做法是利用原对象的构造函数构造一个temp,构造函数不仅会开空间还会数据拷贝,然后将temp的指针和目的对象的指针交换即可
移动和拷贝的区别
对于不含指针的类,移动和拷贝都是值的复制,对于含有指针的类,移动与拷贝相似,只是移动会把原指针置为nullptr
申请动态内存时操作系统怎么做
malloc在分配时,实际上分配的空间大小为:用户需要的空间+用来管理的空间,一般需要满足[用户空间+sizeof(控制块)]%16 == 0。在操作系统中,所有的块都有一个用于管理的数据结构,用来记录该内存块是否可用以及块的大小,
因此malloc的过程为:先在堆中找空闲的内存块,如果找到了空闲,那么大小是否满足,如果满足直接用,如果一直没找到,那就向操作系统申请新的内存(sbrk),把新的内存加到堆中,如果申请失败,直接返回NULL,如果有,那么越过内存控制块的大小,把实际地址返回给用户, free操作相反,直接把内存块的可用信息设置为可用就可以了