diff --git a/Operate-System/1-process-management.md b/Operate-System/1-process-management.md index 24a25f1..ad08488 100644 --- a/Operate-System/1-process-management.md +++ b/Operate-System/1-process-management.md @@ -16,8 +16,8 @@ #### 进程结构 -+ 程序段:包括程序代码,程序运行时使用、产生的运算数据。如全局变量、局部变量、宏定义的常量。 -+ 数据段:存放程序运行过程中处理的各种数据。 ++ 程序段(代码段):包括程序代码,程序运行时使用、产生的运算数据,往往是只读的。 ++ 数据段:存放程序运行过程中处理的各种数据。如全局变量、静态变量、宏定义的常量。 + $PCB$:操作系统用$PCB$来管理进程,所以$PCB$包含所有管理进程所需的信息。 + 进程描述信息: + 进程标识符$PID$:标识进程。 @@ -43,6 +43,8 @@ + 控制寄存器值。 + 标志寄存器值。 + 状态字。 ++ 堆:用来存放动态分配的变量,如调用`malloc`函数动态地向高地址分配空间。 ++ 栈:用来实现函数调用,保存局部变量、函数传递参数,从用户空间的高地址向低地址增长。 #### 进程特征 @@ -1621,7 +1623,7 @@ monitor Demo { #### 死锁的定义 -在并发环境下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各进程都阻塞,都无法向前推进的现象,就是“死锁”发生死锁后若无外力干涉,这些进程都将无法向前推进。 +在并发环境下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各进程都阻塞,都无法向前推进的现象,就是“死锁”。发生死锁后若无外力干涉,这些进程都将无法向前推进。 #### 死锁、饥饿、死循环 @@ -1777,7 +1779,7 @@ $Max-Allocation=Need[i,j]$矩阵,表示各进程最多还需要多少各类资 1. 某种数据结构来保存资源的请求和分配信息。 2. 提供一种算法,利用上述信息来检测系统是否已进入死锁状态。 -数据结构——资源分配图: +检测数据结构就是**资源分配图**: + 两种结点: + 进程结点(圆圈):对应一个进程。 @@ -1793,11 +1795,59 @@ $Max-Allocation=Need[i,j]$矩阵,表示各进程最多还需要多少各类资 + 如果最终不能消除所有边,那么此时就是发生了死锁。 + 最终还连着边的那些进程就是处于死锁状态的进程。 -总结上面的描述,所以检测死锁的算法: +总结上面的描述,所以检测死锁的算法:在资源分配图中,找出既不阻塞又不是孤点的进程$P_i$(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量。若所有的连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源)。消去它所有的请求边和分配边,使之称为孤立的结点。进程$P_i$所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。 -1. 在资源分配图中,找出既不阻塞又不是孤点的进程$P_i$(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量。若所有的连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源)。消去它所有的请求边和分配边,使之称为孤立的结点。 -2. 进程$P_i$所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。根据步骤一中的方法进行一系列简化后,若能消去途中所有的边,则称该图是可完全简化的。 -3. 死锁定理:若是不能完全简化,则系统死锁。 +1. 先看系统还剩下多少资源没分配,再看有哪些进程是不阻塞(“不阻塞”即:系统有足够的空闲资源分配给它)的。 +2. 把不阻塞的进程的所有边都去掉,形成一个孤立的点,再把系统分配给这个进程的资源回收回来。 +3. 看剩下的进程有哪些是不阻塞的,然后又把它们逐个变成孤立的点。 +4. 最后,所有的资源和进程都变成孤立的点。这样的图就叫做“可完全简化”。 + +死锁定理:根据步骤一中的方法进行一系列简化后,若能消去途中所有的边,则称该图是可完全简化的。若是不能完全简化,则系统死锁。 + +```txt + ----------→ p1 ------- + | ------→ | + | | ↓ +--------- ------- +| 0 0 0 | r1 | 0 0 | r2 +--------- ------- + ↑ | | + | -------→ | + ----------- p2 ←------ +``` + +首先先看$R1$资源,它有三个箭头是向外的,因此它一共给进程分配了三个资源,此时,$R1$没有空闲的资源剩余。 + +再看$R2$资源,它有一个箭头是向外的,给进程分配了一个资源,此时,$R2$还剩余一个空闲的资源没分配。 + +看完资源,再来看进程,先看进程$P2$,它只申请一个$R1$资源,但此时$R1$资源已经用光了,所以,进程$P2$进入阻塞状态,因此,进程$P2$暂时不能化成孤立的点。 + +再看进程$P1$,它只申请一个$R2$资源,此时,系统还剩余一个$R2$资源没分配,因此,可以满足$P1$的申请。这样,进程$P1$便得到了它的全部所需资源,所以它不会进入阻塞状态,可以一直运行,等它运行完后,我们再把它的所有的资源释放。相当于可以把$P1$的所有的边去掉,变成一个孤立的点: + +```txt + p1 + +--------- ------- +| 0 0 0 | r1 | 0 0 | r2 +--------- ------- + ↑ | | + | -------→ | + ----------- p2 ←------ +``` + +进程$P1$运行完后,释放其所占有的资源(两个$R1$资源和一个$R2$资源),系统回收这些资源后,空闲的资源便变成两个$R1$资源和一个$R2$资源,由于进程$P2$一直在申请一个$R1$资源,所以此时,系统能满足它的申请。这样,进程$P2$便得到了它的全部所需资源,所以它不会进入阻塞状态,可以一直运行,等它运行完后,我们再把它的所有的资源释放。相当于可以把$P2$的所有的边都去掉,化成一个孤立的点: + +```txt + p1 + +--------- ------- +| 0 0 0 | r1 | 0 0 | r2 +--------- ------- + + p2 +``` + +由于这个资源分配图可完全简化,因此,不会产生死锁。 #### 解除死锁 diff --git a/Operate-System/2-memory-management.md b/Operate-System/2-memory-management.md index 280d1d0..199ccaf 100644 --- a/Operate-System/2-memory-management.md +++ b/Operate-System/2-memory-management.md @@ -1,52 +1,71 @@ # 内存管理 -## 普通内存管理 +## 内存管理基础知识 -### 内存管理基础知识 +### 内存概念 -#### 内存概念 - -+ 内存是用于存放数据的硬件。程序执行前需要先放到内存中才能被$CPU$处理用于缓冲速度。 ++ 内存是用于存放数据的硬件。程序执行前需要先放到内存中才能被$CPU$处理。用于缓冲速度。 + 内存地址从$0$开始,每个地址对应一个存储单元。 + 存储单元的大小不定: + 按字节编址,则每个存储单元大小为$1B$,八个二进制位。 + 按字编址,每个存储单元大小为一个字,根据计算机的字长确定大小,若字长为$16$位,则一个字大小为$16$个二进制位。 -#### 内存管理功能 +### 内存管理功能 -1. 操作系统负责内存空间的分配与回收。 -2. 操作系统需要提供某种技术从逻辑上对内存空间进行扩充。 -3. 操作系统需要提供地址转换功能,负责程序的逻辑地址与物理地址的转换。 - + 相对地址(逻辑地址):从$0$号开始,程序员只用知道逻辑地址,可有相同逻辑地址。 - + 绝对地址(物理地址):内存物理单元的集合,是地址转换的最终地址。从逻辑地址到物理地址就是**地址重定位**。 -4. 操作系统需要提供内存保护功能。保证各进程在各自存储空间内运行,互不干扰: - + 在$CPU$中设置一对上、下限寄存器,存放进程的上、下限地址。进程的指令要访问某个地址时,$CPU$检查是否越界。 - + 采用重定位寄存器(又称基址寄存器,用于地址相加)和界地址寄存器(又称限长寄存器,用于地址比较)进行越界检查。重定位寄存器中存放的是进程的起始物理地址。界地址寄存器中存放的是进程的最大逻辑地址。 +#### 内存分配与回收 -#### 程序载入过程 +操作系统负责内存空间的分配与回收。即后面的普通内存管理内容。 + +#### 地址映射 + +操作系统需要提供某种技术从逻辑上对内存空间进行扩充。并且操作系统需要提供地址转换功能,通过内存管理部件$MMU$,负责程序的逻辑地址与物理地址的转换。 + ++ 相对地址(逻辑地址):从$0$号开始,程序员只用知道逻辑地址,可有相同逻辑地址。 ++ 绝对地址(物理地址):内存物理单元的集合,是地址转换的最终地址。从逻辑地址到物理地址就是**地址重定位**。 + +#### 内存保护 + +操作系统需要提供内存保护功能。保证各进程在各自存储空间内运行,互不干扰: + ++ 在$CPU$中设置一对上、下限寄存器,存放进程的上、下限地址。进程的指令要访问某个地址时,$CPU$检查是否越界。 ++ 采用重定位寄存器(又称基址寄存器,用于地址相加)和界地址寄存器(又称限长寄存器,用于地址比较)进行越界检查。重定位寄存器中存放的是进程的起始物理地址。界地址寄存器中存放的是进程的最大逻辑地址。 + +#### 内存共享 + +只有只读区域才能共享,这种代码就是**可重入代码**(纯代码)允许多个进程同时访问但是不允许任何进程修改的代码。可以复制副本后自己修改。 + +### 程序载入过程 + 程序运行过程: 1. 编译:由编译程序将用户源代码编译成若干个目标模块(编译就是把高级语言翻译为机器语言)。 2. 链接:由链接程序将编译后形成的一组目标模块,以及所需库函数链接在一起,形成一个完整的装入模块。 3. 装入(装载):由装入程序将装入模块装入内存运行。 -+ 链接的三种方式(将独立的逻辑地址合并为完整的逻辑地址。): - 1. 静态链接:在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。 - 2. 装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。 - 3. 运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。 -+ 装入的三种方式(将逻辑地址转换为物理地址): - 1. 绝对装入(单道程序阶段、未产生操作系统):在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。 - + 绝对装入只适用于单道程序环境。 - + 程序中使用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。 - + 通常情况下都是编译或汇编时再转换为绝对地址。 - 2. 静态重定位(可重定位装入)(多道批处理操作系统):编译、链接后的装入模块的地址都是从$0$开始的,指令中使用的地址、数据存放的地址都是相对于起始地址而言的逻辑地址。可根据内存的当前情况,使用单独的装入程序将装入模块装入到内存的适当位置。装入时对地址进行“重定位”,将逻辑地址变换为物理地址(地址变换是在装入时一次完成的)。 - + 静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。 - + 作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间。 - 3. 动态重定位(动态运行时装入)(现代操作系统):编译、链接后的装入模块的地址都是从$0$开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行。因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持。 - + 采用动态重定位时允许程序在内存中发生移动。 - + 可将程序分配到不连续的存储区中:在程序运行前只需裂入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存。 - + 便于程序段的共享,可以向用户提供一个比存储空间大得多的地址空间。 -### 内存空间分配回收 +#### 链接 + +将独立的逻辑地址合并为完整的逻辑地址。 + +1. 静态链接:在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。 +2. 装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。 +3. 运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。 + +#### 装入 + +将逻辑地址转换为物理地址): + +1. 绝对装入(单道程序阶段、未产生操作系统):在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。 + + 绝对装入只适用于单道程序环境。 + + 程序中使用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。 + + 通常情况下都是编译或汇编时再转换为绝对地址。 +2. 静态重定位(可重定位装入)(多道批处理操作系统):编译、链接后的装入模块的地址都是从$0$开始的,指令中使用的地址、数据存放的地址都是相对于起始地址而言的逻辑地址。可根据内存的当前情况,使用单独的装入程序将装入模块装入到内存的适当位置。装入时对地址进行“重定位”,将逻辑地址变换为物理地址(地址变换是在装入时一次完成的)。 + + 静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。 + + 作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间。 +3. 动态重定位(动态运行时装入)(现代操作系统):编译、链接后的装入模块的地址都是从$0$开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行。因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持。 + + 采用动态重定位时允许程序在内存中发生移动。 + + 可将程序分配到不连续的存储区中:在程序运行前只需裂入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存。 + + 便于程序段的共享,可以向用户提供一个比存储空间大得多的地址空间。 + +## 普通内存管理 分为: @@ -64,7 +83,7 @@ + 内部碎片,是已经被分配出去(能明确指出属于哪个进程)却不能被利用的内存空间。 + 外部碎片,是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。 -#### 单一连续分配 +### 单一连续分配 + 内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。 + 内存中只能有一道用户程序,用户程序独占整个用户区空间。 @@ -78,7 +97,7 @@ + 有内部碎片。即分配给某进程的内存区域中,如果有些区域没有用上的部分 + 存储器利用率极低。 -#### 固定分区分配 +### 固定分区分配 + 将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业。 + 分区的方式: @@ -93,7 +112,7 @@ + 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能。 + 会产生内部碎片,内存利用率低。 -#### 动态分区分配 +### 动态分区分配 + 动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。 + 记录分区的方法: @@ -108,44 +127,53 @@ + 若回收区的后面或前面都没有一个相邻的空闲分区,则增加一个表项。 + 动态分区分配会导致外部碎片,可用通过**紧凑**(拼凑)技术来移动进程位置合并空闲空间。 -动态分区分配算法,为了解决动态分区分配方式中如何从多个空闲分区中选择一个分区分配: +### 动态分区分配算法 -1. 首次适应算法($First\,Fit$): - + 算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。 - + 如何实现:空闲分区以**地址**递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。 - + 优点: - + 首次适应算法每次都要从头查找,每次都需要检索低地址的小分区。但是这种规则也决定了当低地址部分有更小的分区可以满足需求时,会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来。 - + 算法开销小,每次分区后不需要对分区队列重新排序。 - + 缺点: - + 造成低分区大量内存碎片。 - + 都要重复经过已经分配的底层区间,增加查找开销。 -2. 最佳适应算法($Best\,Fit$): - + 算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。 - + 如何实现:空闲分区按**容量**递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。当分配完后需要重新调整空闲分区链(或空闲分区表)。 - + 优点:容易保存大分区。 - + 缺点: - + 每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片且很难查找回收。 - + 算法开销大,每次分区外需要对分区队列进程重新排序。 -3. 最坏适应算法($Worst\,Fit$)或最大适应算法($Largest\,Fit$): - + 算法思想:为了解决最佳适应算法的问题――即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。 - + 如何实现:空闲分区按**容量**递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。当分配完后需要重新调整空闲分区链(或空闲分区表)。 - + 优点:可用减少难以利用的小碎片。 - + 缺点: - + 每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。 - + 算法开销大,每次分区外需要对分区队列进程重新排序。 -4. 临近适应算法($Next\,Fit$): - + 算法思想:首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。 - + 如何实现:空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。 - + 优点:减少了检索空闲分区的次数,提高了效率。 - + 缺点:邻近适应算法的规则可能会导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用,划分为小分区,最后导致无大分区可用。 +为了解决动态分区分配方式中如何从多个空闲分区中选择一个分区分配 + +首次适应算法($First\,Fit$): + ++ 算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。 ++ 如何实现:空闲分区以**地址**递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。 ++ 优点: + + 首次适应算法每次都要从头查找,每次都需要检索低地址的小分区。但是这种规则也决定了当低地址部分有更小的分区可以满足需求时,会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来。 + + 算法开销小,每次分区后不需要对分区队列重新排序。 ++ 缺点: + + 造成低分区大量内存碎片。 + + 都要重复经过已经分配的底层区间,增加查找开销。 + +最佳适应算法($Best\,Fit$): + ++ 算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。 ++ 如何实现:空闲分区按**容量**递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。当分配完后需要重新调整空闲分区链(或空闲分区表)。 ++ 优点:容易保存大分区。 ++ 缺点: + + 每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片且很难查找回收。 + + 算法开销大,每次分区外需要对分区队列进程重新排序。 + +最坏适应算法($Worst\,Fit$)或最大适应算法($Largest\,Fit$): + ++ 算法思想:为了解决最佳适应算法的问题――即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。 ++ 如何实现:空闲分区按**容量**递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。当分配完后需要重新调整空闲分区链(或空闲分区表)。 ++ 优点:可用减少难以利用的小碎片。 ++ 缺点: + + 每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。 + + 算法开销大,每次分区外需要对分区队列进程重新排序。 + +临近适应算法($Next\,Fit$): + ++ 算法思想:首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。 ++ 如何实现:空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。 ++ 优点:减少了检索空闲分区的次数,提高了效率。 ++ 缺点:邻近适应算法的规则可能会导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用,划分为小分区,最后导致无大分区可用。 所以综合效果来看,首次适应算法>最佳适应法?临近适应法>最大适应法。 首次适应法和临近适应法只用简单查找,最佳适应法和最大适应法都需要对可用块进行排序和遍历。 -#### 基本分页存储管理 +### 基本分页存储管理 -分页: +#### 分页 + 将内存空间分为一个个大小相等的分区(比如每个分区$4KB$),每个分区就是一个“页框”($Page\,Frame$),或称“页帧”、“内存块”、“物理块”。每个页框有一个编号,即“页框号”(或者“内存块号”、“页帧号”、“物理块号”)页框号从$0$开始。 + 将用户进程的地址空间也分为与页框大小相等的一个个区域,称为“页”($Page$)或“页面”。每个页面也有一个编号,即“页号”,页号也是从$0$开始。所以页面不同于页框,是进程的逻辑概念。(进程的最后一个页面可能没有一个页框那么大。) @@ -156,13 +184,13 @@ + 为了方便计算页号、页内偏移量、页面大小一般设为$2$的整数幂。 + 如果每个页面大小为$2^kB$,用二进制数表示逻辑地址,则末尾$k$位即为页内偏移量,其余部外就是页号。 -地址结构: +#### 地址结构 + 分为页面偏移量$W$和页号$P$两个部分。 + 长度为$32$位,$0\sim11$是页内地址,即页内偏移量,每页大小为$4KB$。 + $12\sim31$为页号,代表每个进程内的页的顺序,地址空间最多允许$2^{20}$页。 -页表: +#### 页表 + 因为允许将进程的各个页离散地存储在内存不同的物理块中,但系统应能保证进程的正确运行,即能在内存中找到每个页面所对应的物理块,所以为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表,其中页表大小也与页面一样被页框约束。 + 一个进程对应一张页表。 @@ -180,14 +208,14 @@ + 而如果每个页表项占$4$字节,则每个页框刚好能放下$1024$个页表项,从而没有余数,能减少查找的麻烦。 + 所以理论上$3B$就能表示内存块的范围,但是为了方便页表查找(对齐),实际上会多一些字节,使得每个页面能装下整数个页表项。 -页式基本地址变换机构: +#### 页式基本地址变换机构 + 可用借助页表进行转换,通常会在系统中设置一个页表寄存器($PTR$),存放页表在内存中的起始地址$F$和页表长度$M$(即这个进程里有多少页)。进程未执行时,页表的始址和页表长度放在进程控制块($PCB$)中,当进程被调度时,操作系统内核会把它们放到页表寄存器中。 + 在页式存储管理的系统中时,只用确定页面大小和逻辑结构就能得到物理地址。 基本地址变换机构需要先查询页表,再查询内存两次操作: -1. 要算出逻辑地址$A$对应的页号$P$与页内偏移量$W$。页号$P$=逻辑地址$A$÷页面长度$L$(取除法的整数部分)。页内偏移量$W$=$A$逻辑地址%页面长度$L$(取除法的余数部分)。 +1. 要算出逻辑地址$A$对应的页号$P$与页内偏移量$W$。页号$P$=逻辑地址$A\div$页面长度$L$(取除法的整数部分)。页内偏移量$W$=$A$逻辑地址\%页面长度$L$(取除法的余数部分)。 2. 检测页号$P$是否越界。如果页号$P$大于等于页表长度$M$,则内中断(因为页号从$0$开始,页表长度至少为$1$,从而$P=M$页会越界)。 3. 根据页表寄存器中的页表项地址$PA$=页表起始地址$F$+页号$P$×页表项长度$PL$,得到页表中对应的页表项,从而确定页面存放的内存块号$B$。 + 页表长度指的是这个页表中总共有几个页表项,即总共有几个页。 @@ -231,7 +259,9 @@ 2. 第二次访存:访问内存中的二级页表。 3. 第三次访存:访问目标内存单元。 -#### 基本分段存储管理 +### 基本分段存储管理 + +#### 分段 + 进程的地址空间:按照程序自身的逻辑关系划分为若干个不等长的段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从$0$开始编址。 + 内存分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。 @@ -244,7 +274,7 @@ 某系统按字节寻址,采用分段存储管理,逻辑地址结构为(段号$16$位,段内地址$16$位),因此用$16$位即可表示最大段长。物理内存大小为$4GB$(可用$32$位表示整个物理内存地址空间)。因此,可以让每个段表项占$16+32=48$位,即$6B$。由于段表项长度相同,因此段号可以是隐含的,不占存储空间。若段表存放的起始地址为$R$,则K号段对应的段表项存放的地址为$R+K\times6$,段号并不占内存空间。 -段式存储地址变换过程: +#### 段式存储地址变换过程 1. 根据逻辑地址得到段号$S$和段内地址$W$。 2. 根据段号$S$是否大于等于段表寄存器的段表长度$M$,判断上是否越界,若是则越界中断。(段表长度至少为$1$)。 @@ -252,7 +282,7 @@ 4. 检测段内地址$W$是否大于等于段长$C$(这也是段式存储与页式存储的最大不同,段式存储不定长),若是则越界中断。 5. 计算得到物理地址$E$=段基址$B$+段内地址$W$。 -分段、分页管理的区别: +#### 分段分页管理的区别  |与信息的关系|主要目的|是否对用户可见|长度|用户进程地址空间 :----:|:---------:|:-----:|:-----------:|:--:|:------------: @@ -264,28 +294,33 @@ + 不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)。 + 分页时页面不是按逻辑模块划分的。这就很难实现共享。 -#### 段页式存储管理 +### 段页式存储管理  |优点|缺点 :----:|:-:|:--: 分页管理|内存空间利用率高,不会产生外部碎片,只会有少量的页内碎片|不方便按照逻辑模块实现信息的共享和保护 分段管理|很方便按照逻辑模块实现信息的共享和保护|如果段长过大,为其分配很大的连续空间会很不方便。另外,段式管理会产生外部碎片 -+ 段页式存储的过程: - 1. 将进程按逻辑模块分段,再将各段分页(如每个页面$4KB$)。 - 2. 再将内存空间分为大小相同的内存块/页框/页帧/物理块。 - 3. 进程前将各页面分别装入各内存块中。 -+ 段页式系统逻辑地址结构: - + 由段号$S$、页号$P$、页内地址$W$(页内偏移量)组成。段号的位数决定了每个进程最多可以分几个段。页号位数决定了每个段最大有多少页。页内偏移量决定了页面大小、内存块大小是多少。 - + 系统含有一个段表寄存器,指出作业的段表始址和段表长度。 - + “分段”对用户是可见的,程序员编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的。系统会根据段内地址自动划分页号和页内偏移量。 - + 因此段页式管理的地址结构是二维的。 -+ 分为段表和页表: - + 每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始地址)组成。每个段表项长度相等,段号是隐含的。 - + 每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐含的。 - + 一个进程对应一个段表,对应一个或多个页表项,一个段表项就相当于只存一个的页表寄存器。 +#### 段页式存储的过程 -段页式存储地址变换过程: +1. 将进程按逻辑模块分段,再将各段分页(如每个页面$4KB$)。 +2. 再将内存空间分为大小相同的内存块/页框/页帧/物理块。 +3. 进程前将各页面分别装入各内存块中。 + +#### 段页式系统逻辑地址结构 + ++ 由段号$S$、页号$P$、页内地址$W$(页内偏移量)组成。段号的位数决定了每个进程最多可以分几个段。页号位数决定了每个段最大有多少页。页内偏移量决定了页面大小、内存块大小是多少。 ++ 系统含有一个段表寄存器,指出作业的段表始址和段表长度。 ++ “分段”对用户是可见的,程序员编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的。系统会根据段内地址自动划分页号和页内偏移量。 ++ 因此段页式管理的地址结构是二维的。 + +地址表分为段表和页表,段表只能有一个,页表可以有多个: + ++ 每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始地址)组成。每个段表项长度相等,段号是隐含的。 ++ 每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐含的。 ++ 一个进程对应一个段表,对应一个或多个页表项,一个段表项就相当于只存一个的页表寄存器。 + +#### 段页式存储地址变换过程 1. 根据逻辑地址得到段号$S$、页号$P$和页内偏移量$W$。 2. 根据段号$S$是否大于等于段表寄存器的段表长度$M$,判断上是否越界,若是则越界中断。(段表长度至少为$1$)。 @@ -332,7 +367,7 @@ + 暂时换出外存等待的进程状态是挂起状态。 + 处理机调度的中级调度(内存调度)就是交换技术的实现。进程的$PCB$常驻内存。 -覆盖用于同一个进程或程序中,二交换用于不同进程或作业之间。 +覆盖用于同一个进程或程序中,交换用于不同进程或作业之间。 ## 虚拟内存管理