1. 栈、堆
1.1 程序的内存分配
栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。 全局区/静态区(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。 常量区:常量字符串就是放在这里的。 程序结束后由系统释放 程序代码区:存放函数体的二进制代码
int a = 0; // 全局初始化区
char *p1; // 全局未初始化区
int main{int b; // 栈char s[] = "abc"; // 栈char *p2; // 栈char *p3 = "123456"; // 123456\0在常量区,p3在栈区static int c = 0; // 全局(静态)初始化区p1 = (char *)malloc(10);p2 = (char *)malloc(20);// 分配得来的10和20字节的区域在堆区strcpy(p1, "123456"); // 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方return 0;
}
1.2 申请方式
stack:由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 heap:需要程序员自己申请,在c中使用malloc,在c++中使用new 注意:p1 = (char *)malloc(10);
这里的p1本身是在栈中的,分配的10个字节的区域在堆中
1.3 申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出 堆:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
1.4 申请大小的限制
栈:栈是向低地址扩展的数据结构,是一块连续的内存的区域,即栈顶的地址和栈的最大容量是系统预先规定好的,如果申请的空间超过栈的剩余空间时,将提示overflow。 堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。
1.5 申请效率
栈由系统自动分配,速度较快。但程序员是无法控制的。 堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
1.6 堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。 堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
1.7 栈溢出的情况
2. 缓冲区溢出的危害
缓冲区是指展示放置输入输出资料的内存 缓冲区溢出是指向缓冲区填充数据时超出了缓冲区本身的容量,溢出的数据覆盖了合法数据 危害:程序崩溃、跳转执行一段恶意代码
3. 分段和分页
分段和分页的出现,是为了解决连续分配内存管理带来的问题而设计出的一种新管理内存的方案
3.1 分段
一个进程不是完完整整的放入到连续的内存区域,而是把进程分段放入内存,因为把进程连续的放入内存带来的问题就是碎片问题很严重,所以分段放入内存就可以有效的缓解碎片问题; 分段允许逻辑地址空间的进程放入内存是不连续的,也就是说在内存的进程不是连续排列的,而是七零八落散落各个位置; 分段的基本思路: 分段机制的逻辑地址位由<段号,段内位移>组成,为了让逻辑地址映射到物理地址,OS需要维护一张表,该表名字就是段表; 段表里记录三个信息:1. 段号,2.段内基地址,3.段的大小 当我们逻辑地址需要转为物理地址时候,需要先查找逻辑地址的段号,根据段号去段表查找该段的基地址,还有段的大小;再把段的基地址,和段的大小放入两个寄存器:base 和 limit 两个寄存器中;再拿到limit寄存器的值,也就是段的大小,和逻辑地址的段内位移比较;段内唯一比段的大小大,就是非法地址,段内唯一比短的大小小就是合法地址; 合法地址就把段内位移加上base寄存器的值,也就是段内基地址,这样就找到了物理地址。
3.2 分页
分段管理虽然减少了内存碎片,但是减少的还是不够多,多多少少还是会有内存碎片,为进一步减少内存碎片问题提出分页机制 分页的基本思路: 把内存分为固定大小的分区,程序也被切成固定大小分区;内存固定大小的分区叫页框,程序的固定大小分区称为页面;页框的大小是等于页面的大小的 分页也会产生内存碎片,但是这个内存碎片式可控的; 假设固定大小为4kb,而程序只有39kb的大小,那么分10个页面,只有第10个页面的大小为3kb,其他页面的大小都是4kb;当把程序的每个页面都放进内存的也框中,只有第10个页面是占页框的3kb,页框就会剩下1kb,这也是内部碎片的产生,但是这个内部碎片很小,说明我们控制的还是相当合理的; 有了分页机制,我们的逻辑地址就可以表示为:页号+页内位移;逻辑地址转物理地址就可以通过逻辑地址的页号,找到对应的页框,然后页框号乘于页框大小加上页内位移就得到了物理地址; OS内部是直接通过:把逻辑地址的页号,替换成页号对应的页框,然后就和页内位移直接组合成物理地址。
3.3 分段与分页的区别
分段的是以信息逻辑单位划分内存空间,也就是每个程序的空间,是一个一个逻辑划分的,比如一个函数一个函数的逻辑单元;而分页就是固定大小的空间划分的; 分段的段长是任意的;分页的页长由系统确定; 分段中段的起始地址可以从主存任一地址开始;分页中页框起始地址只能以页框大小的整数倍开始 分段会产生外部碎片;分页消除了外部碎片但会出现内部碎片
4. 页面置换算法(发⽣缺⻚异常时调换⻚⾯)
页面置换算法主要用于解决进程运行过程中内存不足的问题。当进程需要访问的页面不在内存内,而内存又已满时,就需要通过页面置换算法选择一个页面进行替换,以腾出空间加载所需页面。 FIFO(先进先出)算法: 每次选择最早进⼊内存的⻆⾊进⾏切换。 这种策略很简单,只需要维护⼀个⻆⾊队列,每次淘汰队⾸的⻆⾊,然后把新的⻆⾊加⼊队尾。但是FIFO算法可能会淘汰⼀些经常被使⽤的⻆⾊,导致切换次数增加。 FIFO算法有可能出现⻉拉迪异常,即当分配给内存的空间增加时,切换次数反⽽增加。 LRU(最近最少使⽤)算法: 每次选择最⻓时间没有被使⽤的⻆⾊进⾏切换。 这种策略基于你对⻆⾊的喜好,认为最近被使⽤过的⻆⾊很可能还会被使⽤,⽽最久未被使⽤的⻆⾊很可能不会再被使⽤。 LRU算法可以有效地减少切换次数,但是实现起来⽐较复杂,需要记录每个⻆⾊的使⽤时间或者维护⼀个使⽤顺序的列表。 最佳⻚⾯置换算法(OPT): 置换在未来最⻓时间不访问的⻚⾯ 在实际系统中⽆法实现,因为程序访问⻚⾯时是动态的我们是⽆法预知每个⻚⾯在下⼀次访问前的等待时间,因此作为实际算法效率衡量标准 时钟⻚⾯置换算法: 把所有的⻚⾯都保存在⼀个类似钟⾯的环形链表中,⻚⾯包含⼀个访问位。当发⽣缺⻚中断时,顺时针遍历⻚⾯,如果访问位为1,将其改为0,继续遍历,直到访问到访问位为0⻚⾯,进⾏置换。 最不常⽤算法 : 记录每个⻚⾯访问次数,当发⽣缺⻚中断时候,将访问次数最少的⻚⾯置换出去 此⽅法需要对每个⻚⾯访问次数统计,额外开销。
5. 动态分区分配算法
所谓动态分区分配,就是指内存在初始时不会划分区域,而是会在进程装入时,根据所要装入的进程大小动态地对内存空间进行划分,以提高内存空间利用率,降低碎片的大小 首次适应算法: 空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小满足要求的第一个空闲分区就进行分配。
临近适应算法: 又称循环首次适应法,由首次适应法演变而成,不同之处是分配内存时从上一次查找结束的位置开始继续查找。
最佳适应算法: 空闲分区按容量递增形成分区链,找到第一个能满足要求的空闲分区就进行分配。(优先使用小的空闲区,这样会留下很多外部碎片)
最坏适应算法: 空闲分区以容量递减的次序链接,找到第一个能满足要求的空闲分区(也就是最大的分区)就进行分配
6. 虚拟内存
虚拟内存使得应⽤程序认为拥有连续的可⽤的内存,实际上,是多个物理内存碎⽚ 虚拟内存使⽤部分加载的技术,让⼀个进程或者资源的某些⻚⾯加载进内存(不是全部加载),从⽽能够加载更多的进程,甚⾄能加载⽐内存⼤的进程 优点: 较⼩的可⽤内存中执⾏较⼤的用户程序 在内存中容纳更多程序并发执⾏ 与覆盖技术相⽐,不会影响编程时的程序结构 缺点:占用了一定的物理硬盘空间 虚拟内存实现⽅式:请求分⻚存储管理、分段、段⻚式
7. 常见的内存分配错误
内存没有分配成功,却使⽤了 内存分配成功,但未初始化 内存分配成功且初始化,但操作越界 忘记释放内存,内存泄露 释放内存依旧使⽤:释放后没有置为NULL,产⽣野指针
8. 内存交换中,被换出的进程保存在哪?
保存在磁盘中,也就是外存中 磁盘 = 文件区 + 对换区,进程存放在对换区 文件区:存放⽂件、离散分配、追求空间利⽤率 对换区:存放进程数据、连续分配、提⾼对换速度
9. 抖动/颠簸
刚刚换出的⻚⾯⻢上换⼊内存,刚刚换⼊的⻚⾯⻢上换出外存,频繁的⻚⾯调度,就会出现抖动颠簸 原因:进程频繁访问的⻚⾯数⽬⽐分配的物理块多 抖动影响效率,虚拟内存管理器将⼀定量的内存⻚驻留在内存中,根据进程工作的指标,动态调整这个⻚⾯数量
10. 操作系统的局部性原理
程序常常会使⽤集中在⼀起的局部数据 时间局部性:被引⽤过⼀次的存储器位置会在未来被多次引⽤(通常在循环中) 空间局部性:⼀个存储器的位置被引⽤,那么将来他附近的位置也会被引⽤