1. 原理

程序的链接&装入

将用户程序变为内存中可执行程序

  • 编译

  • 链接

    • 静态链接:运行前链接成完整的装入模块
    • 装入时动态链接
    • 运行时动态链接:只在需要它时才进行链接
  • 装入(逻辑地址→物理地址)

    • 绝对装入:绝对地址

    • 可重定位装入:

      相对地址,从0开始

      装入内存时进行重定位,若起始物理地址为100,所有地址+100

    • 动态重定位

      装入时并不会立即将逻辑地址→物理地址,等程序真正执行时才进行

进程的内存映像

当一个程序调入内存,就构成进程的内存映像

  • 代码段:机器指令、只读数据
  • 数据段:读/写数据
  • 进程控制块PCB
  • 堆:
    • 存放动态分配的变量。
    • 调用malloc动态向高地址分配空间
    • 实现函数调用
    • 从用户空间最大地址→低地址增长

宏定义常量不专门分配空间,在预编译阶段直接读入

image-20251109105714680

内存保护

  • ①设上下限寄存器

  • ②重定位寄存器+界地址寄存器

    例:进程A物理地址100-279,逻辑地址0-179

    • 重定位寄存器=100
    • 界地址寄存器=179
    • 此时访问逻辑地址80的内存单元
      • 80<179→未越界(否则抛出异常)
      • 80+100=180→访问物理地址180内存单元

2.连续分配

单一连续分配

内存(低→高):系统区、用户区

  • 只有一道用户程序
  • 无外部碎片

固定分区分配

用户区划分成若干固定大小分区

动态分区分配

不会预先划分,在进程装入时根据进程大小动态建立分区

  • 无内部碎片,有外部碎片(可采用“紧凑技术”解决)

动态分区分配算法

  • ①首次适应(⭐综合性能最好)

    低地址开始找,找到第一个能够满足大小的空闲分区

  • ②最佳适应

    优先使用更小的空闲区(但会有很多外部碎片)

  • ③最坏适应

    优先使用更大的空闲区 (这样分完剩下的那块仍然会比较大,也许以后还能用。但如果之后有大进程可能无处放

  • ④临近适应

    从上次查找结束的地方开始查找,仍然按顺序排列

3.分页

  • 将内存分为一个个大小相等的分区——“页框”

页框=页帧=内存块=物理块=物理页面

  • 将进程逻辑地址也分为和页框大小相等的“页”

  • 页表

    • 一个进程对应一张页表,存放在PCB中
    • 记录页面和实际存放的页框之间的映射关系

内存块号→物理地址;页号→逻辑地址

页号=逻辑地址/页面大小页号=逻辑地址/页面大小 页内偏移量=逻辑地址页内偏移量=逻辑地址%页面大小

物理地址E=内存号b页面大小L+页内偏移量w物理地址E=内存号b*页面大小L+页内偏移量w

基本地址变换机构

[例题]

image-20251109153040919

页号P=2500/1024=2页号P=2500/1024=2 页内偏移量W=2500%1024=452页内偏移量W=2500 \% 1024=452 物理地址E=bL+W=81024+452=8644物理地址E=b*L+W=8*1024+452=8644

具有快表的地址变换机构

什么是快表

TLB,不是内存,是访问速度>>内存的高速缓存

内存中的表称为慢表

变换过程

  • CPU给出逻辑地址,由硬件转换,比较快表中所有页号
  • 匹配→直接取出对应页框号(一次访存即实现存取数据)
  • 未匹配→访问主存页表,同时存入快表(如果满了就删一个旧页表)

两级页表

在系统中增设外层页表寄存器——存放页目录始值

①逻辑地址的页目录号→页目录索引→对应页表初始值

②二级页号→页表分页的索引→对应页表项

③页表项中 物理块号+页内偏移 拼接→物理地址→访问内存单元

4.分段

将进程按照自身逻辑关系划分为若干段

分段系统逻辑地址=段号+段内地址

段表

每个进程有一个段表(每一行:段号+⭐段长+基址)

image-20251109164904545

  • 需要检查段号是否越界、段内地址是否超过段长(和分页地址变换的最大区别)

分页vs.分段

  • 物理单位。用户不可见
  • 逻辑单位。用户可见

  • 大小固定
  • 大小不固定

  • 分页的用户进程地址空间是一维(直接给一个数,即逻辑地址)
  • 分段的用户进程地址空间是二维(段号、段内地址两个数)

5.段页式

逻辑地址结构

段号+页号+页内偏移量

  • 二维:用户只给出段号+段内地址,“分页”对用户不可见