《操作系统导论》学习笔记(六):逻辑地址空间管理

目录

    • 逻辑地址空间管理 — 段式(segmentation)
        • 1. 段地址空间
        • 2.逻辑地址结构
        • 2. 硬件支持
        • 3. 物理地址生成
    • 逻辑地址生成
        • 1. 栈(stack)
        • 2. 堆(heap)
        • 3. 静态区、常量区、代码区
    • 逻辑地址空间管理 — 页式(paging)
        • 1.页地址空间
        • 2. 逻辑地址结构
        • 3. 硬件支持
        • 4. 物理地址生成
    • 页式存储管理优化
        • 1. 快表
        • 2. 二级页表
    • 逻辑地址空间管理 — 段页式
        • 1. 段页式地址空间
        • 2. 逻辑地址结构
        • 3. 硬件支持
        • 4. 物理地址生成

逻辑地址空间管理 — 段式(segmentation)

逻辑地址空间管理根据内存分区的大小、是否固定、对程序员是否透明可分为 段式存储管理(segmentation)页式存储管理(paging)。段式存储管理的内存分区较大,分区大小可动态增长,程序员负责段的分配及回收。

1. 段地址空间

段地址空间按照用户进程的内容进行划分,一般从低地址到高地址可划分为代码段、常量区、全局存储区、堆、栈。
《操作系统导论》学习笔记(六):逻辑地址空间管理_第1张图片
段表示访问方式和存储数据等性质相同的一段地址空间,所以段内地址要求连续以便管理;而段式存储管理属于逻辑地址空间管理,可以通过地址转换机制将逻辑地址转换为物理地址,不要各段必须存储于连续的物理内存上。
《操作系统导论》学习笔记(六):逻辑地址空间管理_第2张图片

2.逻辑地址结构

内存块在段式存储管理中需要确定所在的段、段内的位置,再转换为物理地址,所以段的逻辑地址由 段号(segment)段内偏移量(offset) 组成的二元组 (s, offset)。
《操作系统导论》学习笔记(六):逻辑地址空间管理_第3张图片

2. 硬件支持

段表 存储在物理内存中,表示段地址空间与物理地址空间的映射关系,段表由操作系统控制更新。段表项对应进程的一个段,段表项目包括段号、段长、段基址。
《操作系统导论》学习笔记(六):逻辑地址空间管理_第4张图片

3. 物理地址生成

《操作系统导论》学习笔记(六):逻辑地址空间管理_第5张图片

  1. CPU从程序P中获取逻辑地址,前10位为段号,后10位为段内偏移
  2. 操作系统软件根据段号查询段表并保存段长到界地址寄存器(段长度寄存器),硬件MMU比较段内偏移是否超过段长,超过则发出越界异常给操作系统,否则继续执行
  3. 操作系统根据段号保存段基址到基地址寄存器(段基址寄存器),硬件MMU将段内偏移与段基址相加得到物理地址。

例:
在一个段式存储管理系统中,其段表及逻辑地址表如下,求逻辑地址所对应的物理地址。
表 1 段 表 表1 \quad段表 1

段号 段基址 段长
0 210 500
1 2350 20
2 100 90
3 1350 590
4 1938 95

表 2 逻 辑 地 址 表2 \quad 逻辑地址 2

段号 段内偏移
0 430
1 10
2 500
3 400
4 112
5 32

答:

  • 第0段的段长为500,段内偏移为430,逻辑地址(0,430)合法,段基址为210,所以物理地址为430+210=640。
  • 第1段的段长为20,段内偏移为10,逻辑地址(1,10)合法,段基址为2350,所以物理地址2350+10=2360。
  • 第2段的段长为90,段内偏移为500,发生越界异常,逻辑地址(2,500)非法。
  • 第3段的段长为590,段内偏移为400,逻辑地址(3,400)合法,段基址为1350,所以物理地址为1350+400=1750。
  • 第4段的段长为95,段内偏移为112,发生越界异常,逻辑地址(4,112)非法。
  • 段表内没有第5段,逻辑地址(5,32)非法。

逻辑地址生成

1. 栈(stack)

栈(stack) :编译器自动分配回收。运行函数 f u n c ( ) func() func() 时,编译器自动在栈内存放整型变量 x x x;当从函数返回时,编译器自动回收整型变量 x x x

void func() {
	int x; // declares an integer on the stack
}

2. 堆(heap)

堆(heap):程序员通过函数 malloc()/free() 分配回收。

(1) 头文件及函数原型

#include 
void *malloc(unsigned int size);
void free(void *ptr);

m a l l o c ( ) malloc() malloc() 分配成功则返回指向被分配内存的指针,否则返回空指针NULL;而 f r e e ( ) free() free()不返回任何值

(2) 函数使用
m a l l o c ( ) malloc() malloc() 返回了一个指向void类型的指针,需要根据内存块的数据类型进行强制类型转换; f r e e ( ) free() free() 的参数是需要回收内存的指针,所以需要事先调用 m a l l o c ( ) malloc() malloc() 分配内存。

int *x = (int *) malloc(sizeof(int));
free(x);

(3) 常见错误
野指针(wild pointer):没有被初始化过的指针。指针 s r c src src 指向内容为 “hello” 的内存块,而指针 d s t dst dst 是所谓的“野指针”,因为未预先分配内存而指向不明区域。当使用字符串拷贝函数 s t r c p y ( ) strcpy() strcpy() 时会产生段错误。

char *src = "hello";
char *dst; 			// 野指针
strcpy(dst, src); 	// 段错误

缓存区溢出(buffer overflow):计算机向缓冲区填充数据时超出了缓冲区本身的容量,溢出的数据覆盖在合法数据上。例如 s t r c p y ( ) strcpy() strcpy() 函数有时会将一个字节写得太远,超过所分配空间的末尾,覆盖其他变量所占内存,从而修改该变量的值。

char *src = "hello";
char *dst = (char *) malloc(strlen(src)); // 缓存区溢出	
strcpy(dst, src); 	

正确做法:指针定义时需要分配内存,且最存时需多分配一点。

char *src = "hello";
char *dst = (char *) malloc(strlen(src) + 1);	
strcpy(dst, src); 

未初始化读取(uninitialized read) m a l l o c ( ) malloc() malloc() 分配的内存后,内存应该使用 m e m s e t ( ) memset() memset() 初始化。
内存泄漏(memory leak): 分配的堆内存未回收,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。指针 p p p 指向的内存使用完未回收,发生内存泄漏的现象。正确的做法是在内存使用完后 f r e e ( ) free() free()

正确做法:内存使用前 m e m s e t ( ) memset() memset() 初始化,内存使用完后 f r e e ( ) free() free()

char *src = "hello";
char *dst = (char *) malloc(strlen(src) + 1);	
memset(dst, '0', sizeof(dst));						
strcpy(dst, src); 
free(dst);										

悬空指针(dangling pointer): 指针最初指向的内存已经被释放了的指针。指针 s r c src src 指向字符串 “hello”,指针 d s t dst dst 指向一块类型为 c h a r char char 的内存,但 d s t dst dst 指向的内存被回收, d s t dst dst 成为悬空指针。

char *src = "hello";
char *dst = (char *) malloc(strlen(src) + 1);	
memset(dst, '0', sizeof(dst));						
strcpy(dst, src); 
free(dst);										

正确做法:指针使用完均设置为 NULL。

char *src = "hello";
char *dst = (char *) malloc(strlen(src) + 1);	
memset(dst, '0', sizeof(dst));						
strcpy(dst, src); 
free(dst);		  
dst = NULL;    
src = NULL;

除此之外,多次使用 f r e e ( ) free() free() 函数或传递给 f r e e ( ) free() free() 参数非指针均会导致错误。

3. 静态区、常量区、代码区

static int a;
int b;
void main() {
	const int c = 0;
}

静态变量 a a a 和全局变量 b b b 存放在静态区,常量 c c c 保存在常量区,整段代码都保存在代码区,函数名 m a i n main main 保存在栈中。

逻辑地址空间管理 — 页式(paging)

逻辑地址空间管理根据内存分区的大小、是否固定、对程序员是否透明可分为 段式存储管理(segmentation)页式存储管理(paging)。页式存储管理的内存分区较小,分区大小固定不变,编译器负责页的分配及回收。

1.页地址空间

物理地址空间划分为大小相同且固定的块,作为内存分配的基本单位称为 页帧(Page Frame);进程的逻辑地址空间也划分为相同大小的基本分配单位,称为 页面(Page)。进程执行时需要占据物理地址空间,就是要为每个页面分配页帧,所以页面和页帧一一对应。
《操作系统导论》学习笔记(六):逻辑地址空间管理_第6张图片

2. 逻辑地址结构

内存块在页式存储管理中需要确定所在的页、页内的位置,所以页的逻辑地址由 虚页号(virtual page number)页内偏移(offset) 组成的二元组 (, offset),而页面与页帧大小相同且页号和帧号一一对应,所以物理地址和逻辑地址结构相同,由 物理帧号(physical frame number)页内偏移(offset) 组成的二元组 (pfn, offset)。
《操作系统导论》学习笔记(六):逻辑地址空间管理_第7张图片

3. 硬件支持

页表 存储在物理内存中,表示页地址空间和物理地址空间的映射关系,页表由操作系统更新。页表项对应进程的一个页面,页表项目包括虚页号()、物理帧号(pfn)。区别于段表,页表项不包括页基址、页长的字段,因为页长可以由偏移量所占位数,页基址可以由物理帧号*页长得到。
《操作系统导论》学习笔记(六):逻辑地址空间管理_第8张图片
页表项只包含虚页号、物理帧号,多余的位数作为控制位,记录页面的使用情况。控制位作为为内存虚拟化的基础,其中最后一位P为存在位(present),表示页面是否有对应的页帧。
《操作系统导论》学习笔记(六):逻辑地址空间管理_第9张图片

4. 物理地址生成

《操作系统导论》学习笔记(六):逻辑地址空间管理_第10张图片

  1. CPU从程序获取逻辑地址,前10位为虚页号,后10位为页内偏移
  2. 操作系统软件根据页号查询对应的控制位,若存在位为0,则发出缺页异常,否则继续执行
  3. 操作系统软件根据页号查询对应的物理帧号,物理帧号与页内偏移组合成为物理地址。

例:
页式存储管理,允许用户编程空间为32个页面(每页1KB),主存为16KB,如果以用户程序有10页长,且某时刻该用户程序页表如下:

虚页号 物理帧号
0 8
1 7
2 4
3 10

若果分别遇到以下三个逻辑地址:0AC5H、1AC5H、3AC5H处的操作,试计算并说明存储管理系统将如何处理。

答:
页面大小为 1 K B = 2 10 B 1KB=2^{10}B 1KB=210B,所以低10位为页内偏移;用户编程空间为32 ( 2 5 2^5 25) 个页面,所以前5位为虚页号;主存为16 ( 2 4 2^4 24) 个页面,所以前4位为物理帧号。

  • 逻辑地址 0AC5H 转换为二进制为 000 1010 1100 0101B,虚页号为 2 (00010B),映射至物理帧号为 4 (0100B),故物理地址为12C5H (01 0010 1100 0101B)。
    逻辑地址 1AC5H 转换为二进制为 001 1010 1100 0101B,虚页号为 6 (00110B),不在页表范围内,产生缺页中断异常,操作系统进行中断处理。
    逻辑地址 3AC5H 转换为二进制为 011 1010 1100 0101B,虚页号为 14 (01110B),而该用户程序只有10页,产生越界异常,操作系统进行中断处理。

页式存储管理优化

页式访问机制会产生两个问题

  • 内存访问性能:访问一个内存单元需要2次内存访问,第一次访问页表,确定数据的物理地址,第二次访问数据。
  • 页表大小:页表可能很大,占据大量内存,导致内存利用效率降低。

内存访问性和页表大小的问题可以通过更新硬件支持的方法进行优化。

1. 快表

内存访问性能可以利用局部性原理,将常访问的页面保存至缓存,该缓存称为地址变换高速缓存(Translation Look-aside Buffer, TLB),又可称为快表。配置快表后,CPU访问 页面会先去查看快表内是否存在,然后再去查看内存的页面。

《操作系统导论》学习笔记(六):逻辑地址空间管理_第11张图片

  1. CPU从程序获取逻辑地址,前10位为虚页号,后10位位页内偏移
  2. 操作系统软件根据虚页号与快表内的虚页号进行比较,如果找到匹配的页号,则将对应的物理帧号与页内偏移组合成为物理地址,从而实现一次访存
  3. 如果没有匹配到,则需要访问主存的页表,读出对应的物理帧号时,同时将该页表项存入快表

:某一页式系统,其页表存放在主存中:
(1) 如果对主存的一次存取需要 1.5 μ s 1.5μs 1.5μs,试问实现一次页面访问时存取时间是多少?
答:页式访问需要访存两次,一次页表,一次数据,所以需要 1.5 ∗ 2 = 3 μ s 1.5 * 2=3μs 1.52=3μs

(2)如果系统有快表且其平均命中率为 85 85 85%,而页标项在快表查询的时间可忽略不计,试问此时的存取时间为多少?
答:快表命中时访存一次,未命中时访存两次,所以需要 0.85 ∗ 1.5 + 0.15 ∗ 3 = 1.725 μ s 0.85 * 1.5 + 0.15 * 3 = 1.725μs 0.851.5+0.153=1.725μs

2. 二级页表

页表大小问题可以通过二级页表的方法优化,庞大的页表按页面大小划分多张较小的页表,设置一张页目录表,每一个表项对应已划分的页表。进程执行开始时只需要将页目录表调入内存,然后次级页表和页面可以在后面执行需要时再调入。同时,逻辑地址的虚页号进一步划分为页目录号和虚页号,页目录号用于查询页表,虚页号用于查询物理帧号。

《操作系统导论》学习笔记(六):逻辑地址空间管理_第12张图片

  1. CPU从程序获取逻辑地址,前4位为页目录号,中间6位为虚页号,后10位位页内偏移
  2. 操作系统软件从 C R 3 CR3 CR3 寄存器获取页目录,查询页目录号并获取对应页表的起始地址
  3. 操作系统软件根据虚页号查询对应的物理帧号,物理帧号与页内偏移组合成为物理地址。

如果页目录表仍然过大,可以继续对页目录表进行划分,再设置更高一级的页表进行映射。

:某计算机主存按字节编址,逻辑地址和物理地址都是32位,页表项大小为4字节,请回答下列问题:
(1) 若使用一级页表的分页存储管理方式,逻辑地址结构为:

虚页号(20位) 页内偏移(12位)

则页的大小是多少?页表最大占用多少字节?

答:页面大小为 2 12 = 2 2 K B = 4 K B 2^{12}=2^2KB=4KB 212=22KB=4KB,页表项有 2 20 2^{20} 220 个,每个页表项占 4 B 4B 4B,所以页表最大占用 2 20 ∗ 4 B = 4 M B 2^{20}*4B=4MB 2204B=4MB

(2) 若使用二级页表的分页存储管理方式,逻辑地址结构为:

页目录号(10位) 虚页号(10位) 页内偏移(12位)

设逻辑地址为 L A LA LA,请分别给出对应的页目录号和虚页号的表达式。

答:0x3FF的低10位为1,页目录号可表示为:((unsigned int)(LA) >> 22) & 0x3FF
虚页号可表示为:((unsigned int)(LA) >> 12) & 0x3FF

逻辑地址空间管理 — 段页式

段式存储管理能够反映程序的逻辑结构,页式存储管理能有效地提高内存利用率,段页式存储管理将两者结合存储方式结合,从而获得两者的优势。

1. 段页式地址空间

逻辑地址空间首先划分为若干个逻辑段,每段都有自己的段号,然后每段分成若干个大小固定的页,相应的内存划分为和页面大小的页帧。

2. 逻辑地址结构

逻辑内存块在段页式存储管理需要确定所在的段、段内所在的页及页内的位置,所以段页式的逻辑地址由 段号(segment)虚页号(virtual page number)页内偏移(offset) 组成的三元组 (s, , offset)。

段号 虚页号 页内偏移

3. 硬件支持

段页式存储管理方式的硬件支持包括段表和页表。段表项仍然代表进程的一个段,段表项目包括段号、段长,但基址从段基址修改为页表基址。页表则与页式存储管理下的页表一致,页表项包括虚页号()和物理帧号(pfn)。

4. 物理地址生成

《操作系统导论》学习笔记(六):逻辑地址空间管理_第13张图片
首先段表查询到页表起始地址,然后通过页表找到物理帧号,最后物理帧号和页内偏移组合成物理地址。

你可能感兴趣的:(操作系统(理论))