操作系统内存最全解析!!!( 四 )


bss 段中数据的生存期随进程持续性
bss 段中的数据一般默认为0

  • rodata段:
只读数据比如 printf 语句中的格式字符串和开关语句的跳转表 。也就是常量区 。例如,全局作用域中的 const int ival = 10,ival 存放在 .rodata 段;再如,函数局部作用域中的 printf("Hello world %dn", c); 语句中的格式字符串 "Hello world %dn",也存放在 .rodata 段 。
  • 栈(stack):
可读可写
存储的是函数或代码中的局部变量(非 static 变量)
栈的生存期随代码块持续性,代码块运行就给你分配空间,代码块结束,就自动回收空间
  • 堆(heap):
可读可写
存储的是程序运行期间动态分配的 malloc/realloc 的空间
堆的生存期随进程持续性,从 malloc/realloc 到 free 一直存在
下面是我们用 Borland C++ 编译过后的结果
_TEXTsegment dword public use32 'CODE'_TEXTends_DATAsegment dword public use32 'DATA'_DATAends_BSSsegment dword public use32 'BSS'_BSSends
段定义( segment ) 是用来区分或者划分范围区域的意思 。汇编语言的 segment 伪指令表示段定义的起始,ends 伪指令表示段定义的结束 。段定义是一段连续的内存空间
所以内存针对自动增长的区域,会有三种处理方式
  • 如果一个进程与空闲区相邻,那么可把该空闲区分配给进程以供其增大 。
  • 如果进程相邻的是另一个进程,就会有两种处理方式:要么把需要增长的进程移动到一个内存中空闲区足够大的区域,要么把一个或多个进程交换出去,已变成生成一个大的空闲区 。
  • 如果一个进程在内存中不能增长,而且磁盘上的交换区也满了,那么这个进程只有挂起一些空闲空间(或者可以结束该进程)

操作系统内存最全解析!!!

文章插图
 
上面只针对单个或者一小部分需要增长的进程采用的方式,如果大部分进程都要在运行时增长,为了减少因内存区域不够而引起的进程交换和移动所产生的开销,一种可用的方法是,在换入或移动进程时为它分配一些额外的内存 。然而,当进程被换出到磁盘上时,应该只交换实际上使用的内存,将额外的内存交换也是一种浪费,下面是一种为两个进程分配了增长空间的内存配置 。
操作系统内存最全解析!!!

文章插图
 
如果进程有两个可增长的段,例如,供变量动态分配和释放的作为堆(全局变量)使用的一个数据段(data segment),以及存放局部变量与返回地址的一个堆栈段(stack segment),就如图 b 所示 。在图中可以看到所示进程的堆栈段在进程所占内存的顶端向下增长,紧接着在程序段后的数据段向上增长 。当增长预留的内存区域不够了,处理方式就如上面的流程图(data segment 自动增长的三种处理方式)一样了 。
空闲内存管理在进行内存动态分配时,操作系统必须对其进行管理 。大致上说,有两种监控内存使用的方式
  • 位图(bitmap)
  • 空闲列表(free lists)
下面我们就来探讨一下这两种使用方式
使用位图的存储管理使用位图方法时,内存可能被划分为小到几个字或大到几千字节的分配单元 。每个分配单元对应于位图中的一位,0 表示空闲, 1 表示占用(或者相反) 。一块内存区域和其对应的位图如下
操作系统内存最全解析!!!

文章插图
 
图 a 表示一段有 5 个进程和 3 个空闲区的内存,刻度为内存分配单元,阴影区表示空闲(在位图中用 0 表示);图 b 表示对应的位图;图 c 表示用链表表示同样的信息
分配单元的大小是一个重要的设计因素,分配单位越小,位图越大 。然而,即使只有 4 字节的分配单元,32 位的内存也仅仅只需要位图中的 1 位 。32n 位的内存需要 n 位的位图,所以1 个位图只占用了 1/32 的内存 。如果选择更大的内存单元,位图应该要更小 。如果进程的大小不是分配单元的整数倍,那么在最后一个分配单元中会有大量的内存被浪费 。
位图提供了一种简单的方法在固定大小的内存中跟踪内存的使用情况,因为位图的大小取决于内存和分配单元的大小 。这种方法有一个问题是,当决定为把具有 k 个分配单元的进程放入内存时,内容管理器(memory manager) 必须搜索位图,在位图中找出能够运行 k 个连续 0 位的串 。在位图中找出制定长度的连续 0 串是一个很耗时的操作,这是位图的缺点 。(可以简单理解为在杂乱无章的数组中,找出具有一大长串空闲的数组单元)


推荐阅读