前面分配给进程的内存空间时连续的,那就出现了一个问题,每个进程的堆和栈中间总是有一块没有使用的内存,而这一块内存又不能分配给其它的进程,当进程足够多的时候,就会导致,即使内存中有大量的空间,也没有办法分配给新的进程。 为了解决这个问题,本章提出了一个 Segmentation 的概念。

0KB +----------------+
    |  Program Code  |
2KB +----------------+
    |      Heap      |
4KB +----------------+
    |       ↓        |
    |                |
    |                |
    |                |
    |      Free      |
    |                |
    |                |
    |                |
    |       ↑        |
14KB+----------------+
    |     Stack      |
16KB+----------------+

Segmentation

由于一整块连续的内存空间会造成资源的浪费,同时通过虚拟地址翻译,使得进程不需要在意地址空间在内存中的位置。那么,将每一个部分独立分配到内存不同的地方,就可以解决这个问题,这就是 Segmentation,而每一个独立的部分都被分配为连续的地址空间,称为 Segment。每一个 Segment 都会有一对 base 和 bound 寄存器,来进行虚拟地址的翻译。

Segment 的虚拟地址

13   12   11   10   9    8    7    6    5    4    3    2    1    0
+----+----+----+----+----+----+----+----+----+----+----+----+----+
|    |    |    |    |    |    |    |    |    |    |    |    |    |
+----+----+----+----+----+----+----+----+----+----+----+----+----+
|←Segment→|←                       Offset                       →|

举个例子,如上面所示的虚拟地址中,使用了前两位作为段标识,用来确定这个虚拟地址是哪个 Segment 的。如 00 表示代码段。每一个段都有 base 和 bound,可以得到下面的物理地址计算方法。

Segment = (VirtualAddress & SEG_MASK) >> SEG_SHIFT
Offset  = VirtualAddress & OFFSET_MASK
if (Offset >= Bounds[Segment])
    RaiseException(PROTECTION_FAULT)
else
    PhysAddr = Base[Segment] + Offset
    Register = AccessMemory(PhysAddr)

需要判断 Offset 是不是合法的,也就是不能超出 Bound 所标识的范围。这里是 heap 是正向增长的。对于 stack 是负向增长的,在硬件中用一位存储来标识是正向增长还是负向增长。如果是 stack,那上面的就变成 PhysAddr = Base[Segment] - Offset

共享内存

对于代码段来说,如果多个进程运行同一个程序,或者一些 lib 代码段,没有必要在内存中分配多个代码段,可以让这些进程公用同一个代码段,这样就避免了不必要的内存分配,节省内存。我们使用一个 bit 来标记段的状态。例如标记了一个 Segment 是只读的,那么用户进程如果试图向这个段中写入,就会触发一个异常。

上下文切换

进程并不是一直都在运行的,所以操作系统在进程上下文切换的时候,对 base-bound 寄存器进行切换。

Segment 增长

进程所需要的内存空间是在不断变化的。比方说程序使用 malloc 向操作系统申请空间的时候,就会涉及到 heap segment 的增长。但是,内存空间毕竟是有限的,所以操作系统会根据判断是不是有足够多的内存,如果有,就会调用 sbrk syscal 进行空间的增长,如果没有足够多的空间,就会拒绝进程的请求。

问题

每个进程,都有自己的 segment,这些 segment 是分布在内存的各个地方的。但是这样就会造成一个问题,那就是内存的碎片化,这些 segment 会将内存割裂,留下来的非连续空间会变得很多。