mirror of
https://github.com/Didnelpsun/CS408.git
synced 2026-06-16 23:17:21 +08:00
更新内存管理
This commit is contained in:
@@ -63,8 +63,8 @@ public:
|
||||
// 后插入
|
||||
bool NextInsert(element_type* elem, int start, int length);
|
||||
// 删除
|
||||
virtual bool Delete(int index);
|
||||
virtual bool Delete(int index, int length) = 0;
|
||||
element_type Delete(int index);
|
||||
virtual element_type* Delete(int index, int length) = 0;
|
||||
};
|
||||
|
||||
class LinkListWithHead : public LinkList {
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
// 插入
|
||||
bool Insert(int index, element_type data) override;
|
||||
// 删除
|
||||
bool Delete(int index, int length) override;
|
||||
element_type * Delete(int index, int length) override;
|
||||
};
|
||||
|
||||
class LinkListWithoutHead : public LinkList {
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
// 插入
|
||||
bool Insert(int index, element_type data) override;
|
||||
// 删除
|
||||
bool Delete(int index, int length) override;
|
||||
element_type * Delete(int index, int length) override;
|
||||
};
|
||||
|
||||
bool LinkListNode::SetData(element_type data) {
|
||||
@@ -383,18 +383,19 @@ bool LinkList::NextInsert(element_type* elem, int start, int length) {
|
||||
}
|
||||
}
|
||||
|
||||
bool LinkList::Delete(int index) {
|
||||
this->Delete(index, 1);
|
||||
return true;
|
||||
element_type LinkList::Delete(int index) {
|
||||
return *(this->Delete(index, 1));
|
||||
}
|
||||
|
||||
bool LinkListWithHead::Delete(int index, int length) {
|
||||
element_type* LinkListWithHead::Delete(int index, int length) {
|
||||
auto* data = (element_type*)malloc(length * sizeof(element_type));
|
||||
if (index < 1) {
|
||||
cout << "Delete:删除索引值" << index << "过小!" << endl;
|
||||
return false;
|
||||
return data;
|
||||
}
|
||||
if (length < 1) {
|
||||
cout << "Delete:删除长度" << length << "过小!" << endl;
|
||||
return data;
|
||||
}
|
||||
// 定义一个结点指针start指向当前扫描到的结点,即要删除第一的元素的前一个
|
||||
LinkListNode* start;
|
||||
@@ -407,7 +408,7 @@ bool LinkListWithHead::Delete(int index, int length) {
|
||||
// 如果链表没有任何数据
|
||||
if(start == nullptr) {
|
||||
cout << "Delete:链表为空!" << endl;
|
||||
return false;
|
||||
return data;
|
||||
}
|
||||
// 循环遍历到达指定索引号的单链表的结点
|
||||
// 条件是当前结点的下一个不为空且索引号到达,所到达的结点一定不是空结点
|
||||
@@ -423,10 +424,11 @@ bool LinkListWithHead::Delete(int index, int length) {
|
||||
// 此时i==index-1,start到达,求end
|
||||
end = start;
|
||||
for (int i = 0; i < length; i++) {
|
||||
data[i] = end->GetData();
|
||||
end = end->GetNext();
|
||||
if (end == nullptr) {
|
||||
cout << "Delete:删除索引最大值" << index + length - 1 << "大于链表最大索引" << length << endl;
|
||||
return false;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
if (index == 1) {
|
||||
@@ -436,13 +438,14 @@ bool LinkListWithHead::Delete(int index, int length) {
|
||||
start->SetNext(end->GetNext());
|
||||
}
|
||||
this->SetLength(this->GetLength() - length);
|
||||
return true;
|
||||
return data;
|
||||
}
|
||||
|
||||
bool LinkListWithoutHead::Delete(int index, int length) {
|
||||
element_type* LinkListWithoutHead::Delete(int index, int length) {
|
||||
auto* data = (element_type*)malloc(length * sizeof(element_type));
|
||||
if (index < 0) {
|
||||
cout << "Delete:删除索引值" << index << "过小!" << endl;
|
||||
return false;
|
||||
return data;
|
||||
}
|
||||
if (length < 1) {
|
||||
cout << "Delete:删除长度" << length << "过小!" << endl;
|
||||
@@ -458,8 +461,9 @@ bool LinkListWithoutHead::Delete(int index, int length) {
|
||||
// 如果链表没有任何数据
|
||||
if (this->GetData() == NULL) {
|
||||
cout << "Delete:链表为空!" << endl;
|
||||
return false;
|
||||
return data;
|
||||
}
|
||||
data[0] = this->GetData();
|
||||
// 循环遍历到达指定索引号的单链表的结点
|
||||
// 条件是当前结点的下一个不为空且索引号到达,所到达的结点一定不是空结点
|
||||
while (start->GetNext() != nullptr && i < index - 1) {
|
||||
@@ -469,15 +473,18 @@ bool LinkListWithoutHead::Delete(int index, int length) {
|
||||
// 如果此时i小于index-1,表示遍历完还没有到达对应的索引
|
||||
if (i < index - 1) {
|
||||
cout << "Delete:删除索引值" << index << "过大!" << endl;
|
||||
return false;
|
||||
return data;
|
||||
}
|
||||
// 从1开始遍历
|
||||
end = this->GetNext();
|
||||
for (int i = 1; i < index + length - 1; i++) {
|
||||
if (i > index) {
|
||||
data[i - index] = end->GetData();
|
||||
}
|
||||
end = end->GetNext();
|
||||
if (end == nullptr) {
|
||||
cout << "Delete:删除索引最大值" << index + length - 1 << "大于链表最大索引" << length << endl;
|
||||
return false;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
if (index == 0) {
|
||||
@@ -491,5 +498,5 @@ bool LinkListWithoutHead::Delete(int index, int length) {
|
||||
start->SetNext(end->GetNext());
|
||||
}
|
||||
this->SetLength(this->GetLength() - length);
|
||||
return true;
|
||||
return data;
|
||||
}
|
||||
@@ -36,9 +36,9 @@ public:
|
||||
// 循环插入函数
|
||||
bool LoopInsert(element_type *elem, int index, int length);
|
||||
// 删除函数
|
||||
bool Delete(int index, element_type &elem);
|
||||
element_type Delete(int index);
|
||||
// 多个删除函数
|
||||
bool LoopDelete(int index, int len, element_type *elem);
|
||||
element_type* LoopDelete(int index, int length);
|
||||
// 按位获取元素
|
||||
element_type GetElem(int index) const;
|
||||
// 按值获取元素
|
||||
@@ -249,32 +249,37 @@ bool SequenceList::LoopInsert(element_type *elem, int index, int length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SequenceList::Delete(int index, element_type &elem) {
|
||||
element_type SequenceList::Delete(int index) {
|
||||
if (index >= this->GetLength() || index < 0) {
|
||||
cout << "Delete:删除索引" << index << "超过索引范围!" << endl;
|
||||
return false;
|
||||
}
|
||||
elem = this->GetData(index);
|
||||
for (int i = index; i < this->GetLength(); i++) {
|
||||
this->SetData(i, this->GetData(i+1));
|
||||
}
|
||||
this->SetLength(this->GetLength()-1);
|
||||
return true;
|
||||
return this->GetData(index);
|
||||
}
|
||||
|
||||
bool SequenceList::LoopDelete(int index, int length, element_type *elem) {
|
||||
element_type* SequenceList::LoopDelete(int index, int length) {
|
||||
if (index + length > this->GetLength() || index < 0) {
|
||||
cout << "LoopDelete:删除索引" << index + length << "超过索引范围!" << endl;
|
||||
return false;
|
||||
}
|
||||
for (int i = index; i <= this->GetLength() - length; i++) {
|
||||
if (i < index + length) {
|
||||
elem[i - index] = this->GetData(i);
|
||||
auto* elem = (element_type*)malloc(length * sizeof(element_type));
|
||||
if (elem) {
|
||||
for (int i = index; i <= this->GetLength() - length; i++) {
|
||||
if (i < index + length) {
|
||||
elem[i - index] = this->GetData(i);
|
||||
}
|
||||
this->SetData(i, this->GetData(i + length));
|
||||
}
|
||||
this->SetData(i, this->GetData(i + length));
|
||||
this->SetLength(this->GetLength() - length);
|
||||
}
|
||||
this->SetLength(this->GetLength()-length);
|
||||
return true;
|
||||
else {
|
||||
cout << "LoopDelete:申请空间失败!" << endl;
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
element_type SequenceList::GetElem(int index) const {
|
||||
|
||||
@@ -8,8 +8,7 @@ int SequenceListTest() {
|
||||
element_type a[6] = {'1','2','3','4','5','6'};
|
||||
list.LoopInsert(a, 0, 6);
|
||||
list.Print();
|
||||
element_type b[4];
|
||||
list.LoopDelete(1, 3, b);
|
||||
element_type* b = list.LoopDelete(1, 3);
|
||||
list.Print();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
cout << b[i] << endl;
|
||||
@@ -33,7 +32,11 @@ int LinkListTest() {
|
||||
auto* list = new LinkListWithoutHead();
|
||||
list->NextInsert(a, 0 ,5);
|
||||
list->Print();
|
||||
list->Delete(2,4);
|
||||
int len = 3;
|
||||
element_type* b = list->Delete(2, len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
cout << b[i] << endl;
|
||||
}
|
||||
list->Print();
|
||||
return 0;
|
||||
}
|
||||
@@ -42,6 +42,8 @@ $D.$以用户的逻辑记录为单位
|
||||
|
||||
解:$B$。注意这里指存储的访问,所以以字或字节为单位,而不是$D$。
|
||||
|
||||
#### 位示图
|
||||
|
||||
**例题** 现有一个容量为$10GB$的磁盘分区,磁盘空间以簇为单位进行分配,簇的大小为$4KB$,若采用位图法管理该分区的空闲空间,即用一位标识一个簇是否被分配,则存放该位图所需的簇为()个。
|
||||
|
||||
$A.80$
|
||||
@@ -98,6 +100,44 @@ $D.$循环首次适应算法
|
||||
|
||||
第三步,物理地址$E=B\times L+W=8×1024+452=8848$。
|
||||
|
||||
**例题** 某计算机主存按字节编址,逻辑地址和物理地址都是$32$位,页表项大小为$4B$。请回答下列问题:
|
||||
|
||||
1)若使用一级页表的分页存储管理方式,逻辑地址结构为(页号$20$位,页内偏移量$12$位),则页的大小是多少字节?页表最大占用多少字节?
|
||||
|
||||
2)若使用二级页表的分页存储管理方式,逻辑地址结构为(页目录号$10$位,页表索引$10$位,页内偏移量$12$位),设逻辑地址为$LA$,请分别给出其对应的页目录号和页表索引的表达式。
|
||||
|
||||
3)采用1)中的分页存储管理方式,一个代码段的起始逻辑地址为$0000\,8000H$,其长度为$8KB$,被装载到从物理地址$0090\,0000H$开始的连续主存空间中。页表从主存$0020\,0000H$开始的物理地址处连续存放。请计算出该代码段对应的两个页表项的物理地址、这两个页表项中的页框号,以及代码页面$2$的起始物理地址。
|
||||
|
||||
解:
|
||||
|
||||
1)因为主存按字节编址,页内偏移量是$12$位,所以页大小为$2^{12}B=4KB$。物理空间为$32$位,需要用足够的页表项来表示,所以页表项数为$2^{32}\div2^{12}=2^{20}$,因此该一级页表最大的大小为$2^{20}\times4B=4MB$。
|
||||
|
||||
2)页目录号可表示为$(((unsigned\,int)(LA))>>22)\&0x3FF$。页表索引可表示为$(((unsigned\,int)(LA))>>12)\&0x3FF$。
|
||||
|
||||
3)首先代码段长度为$8KB$,而一页为$4KB$,所以代码段被分为两页。代码页面$1$的逻辑地址为$0000\,8000H$,根据一级页表的结构,前五位表示页号,$0000\,8H$表明其位于第$8$个页处,对应页表中的第$8$个页表项,所以第$8$个页表项的物理地址=页表始址+$8$×页表项的字节数=$0020\,0000H+8\times4=0020\,0020H$。所以第一个页面的物理地址就是$0020\,0020H$,页表项长度为$4B$,所以第二个页面的物理地址就是$0020\,0024H$。
|
||||
|
||||
程序段被装载到物理地址$0090\,0000H$,所以代码页面$1$的物理地址就是$0090\,0000H$,而一个页面$4KB=2^12B$,所以地址的后三位是页内偏移量,第二个代码页面物理地址就是倒数第四位加一$0090\,1000H$。而前五位就是页框号(逻辑地址就是页号,物理地址就是页框号),所以两个页表的页框号就是对应物理地址的前五位:$00900H$和$00901H$。
|
||||
|
||||
**例题** 页式存储管理允许用户的编程空间为$32$个页面(每页$1KB$),主存为$16KB$。如有一用户程序为$10$页长,且某时刻该用户程序页表见表。
|
||||
|
||||
逻辑页号|物理块号
|
||||
:------:|:------:
|
||||
0|8
|
||||
1|7
|
||||
2|4
|
||||
3|10
|
||||
|
||||
若分别遇到三个逻辑地址$0AC5H$,$1AC5H$,$3AC5H$处的操作,计算并说明存储管理系统将如何处理。
|
||||
|
||||
解:首先对存储地址结构进行分析。逻辑页面$32=2^5$,而每页$1KB$,所以逻辑地址一共为$32\times1KB=2^{15}$,即一共$15$位。而此时主存为$16KB=2^{14}B$,只有$16$个页面,即物理地址一共只有$14$位,逻辑地址和物理地址不等长。而页号即页内偏移量不变,所以低$10$位是页内偏移量,逻辑地址高$5$位是虚页号,物理地址高$4$位是物理块号。
|
||||
|
||||
逻辑地址$OAC5H$转换为二进制是$000\,1010\,1100\,0101B$,虚页号为$2=00010B$,映射至物理块号$4$,因此系统访问物理地址$12C5H=01\,0010\,1100\,0101B$。
|
||||
|
||||
逻辑地址$1AC5H$转换为二进制是$001\,1010\,1100\,0101B$,虚页号为$6=00110B$,不在页面映射表中,会产生缺页中断,系统进行缺页中断处理。
|
||||
|
||||
逻辑地址$3AC5H$转换为二进制是$011\,1010\,1100 0101B$,页号为$14$,而该用户程序只有$10$页,因此系统产生越界中断。
|
||||
|
||||
|
||||
#### 快表
|
||||
|
||||
**例题** 某系统使用基本分页存储管理,并采用了具有快表的地址变换机构。访问一次快表耗时$1\mu s$,访问一次内存耗时$100\mu s$。若快表的命中率为$90\%$,那么访问一个逻辑地址的平均耗时是多少?
|
||||
@@ -108,7 +148,7 @@ $D.$循环首次适应算法
|
||||
|
||||
若该系统支持快表慢表同时查找,则为$(1+100)\times0.9+(100+100)\times0.1=110.9\mu s$。
|
||||
|
||||
#### 两级页表
|
||||
#### 多级页表
|
||||
|
||||
**例题** 某计算机采用二级页表的分页存储管理方式,按字节编址,页大小为$2^{10}B$,页表项大小为$2B$,逻辑地址结构为(页目录号,页号,页内偏移量),逻辑地址空间大小为$2^{16}$页,则表示整个逻辑地址空间的页目录表中包含表项的个数至少是()。
|
||||
|
||||
@@ -132,6 +172,18 @@ $D.512$
|
||||
|
||||
所以各级页表最多包含$2^{10}$个页表项,需要$10$位二进制位才能映射到$2^{10}$个页表项,因此每一级的页表对应页号应该为$10$位,$28$位的页号至少分为三级。
|
||||
|
||||
**例题** 已知系统为$32$位实地址,采用$48$位虚拟地址,页面大小为$4KB$,页表项大小为$8B$。假设系统使用纯页式存储,则要采用()级页表。
|
||||
|
||||
$A.1$
|
||||
|
||||
$B.2$
|
||||
|
||||
$C.3$
|
||||
|
||||
$D.4$
|
||||
|
||||
解:$D$。页面大小为$4KB=2^{12}B$,因此页内偏移为$12$位。系统采用$48$位虚拟地址,因此虚页号$48-12=36$位。采用多级页表时,最高级页表项不能超出一页大小。每页能容纳的页表项数为$4KB\div8B=512=2^9$,$36\div9=4$,因此应采用四级页表,最高级页表项正好占据一页空间。$32$位实地址这里是无用条件,因为实地址与物理地址相关,而页表关注的是逻辑地址。
|
||||
|
||||
#### 段式存储管理
|
||||
|
||||
**例题** 采用分页或分段管理后,提供给用户的物理地址空间()
|
||||
@@ -185,3 +237,61 @@ $C.$处于临界段
|
||||
$D.$死锁
|
||||
|
||||
解:$B$。交换技术就是内存紧张时把暂时用不到的程序移动到外存中。进程正在进行$I/O$操作时不能换出主存,否则其$I/O$数据区将被新换入的进程占用,导致错误。不过可以在操作系统中开辟$I/O$缓冲区,将数据从外设输入或将数据输出到外设的$I/O$活动在系统缓冲区中进行,这时系统缓冲区与外设$I/O$时,进程交换不受限制。其中比较模糊的是$C$。只有**内核临界区**不能被换出,普通临界区可以。
|
||||
|
||||
## 虚拟内存管理
|
||||
|
||||
### 虚拟内存的基本概念
|
||||
|
||||
### 请求分页管理方式
|
||||
|
||||
#### 缺页中断机构
|
||||
|
||||
**例题** 若用户进程访问内存时产生缺页,则下列选项中,操作系统可能执行的操作是()。
|
||||
|
||||
Ⅰ.处理越界错
|
||||
|
||||
Ⅱ.置换页
|
||||
|
||||
Ⅲ.分配内存
|
||||
|
||||
$A.$仅Ⅰ、Ⅱ
|
||||
|
||||
$B.$仅Ⅱ、Ⅲ
|
||||
|
||||
$C.$仅Ⅰ、Ⅲ
|
||||
|
||||
$D.$Ⅰ、Ⅱ和Ⅲ
|
||||
|
||||
解:$B$。用户进程访问内存时缺页,会发生缺页中断。发生缺页中断时,系统执行的操作可能是置换页面或分配内存。越界错误是编程时访问内存越界,系统内没有越界错误,不会进行越界出错处理。
|
||||
|
||||
### 页面置换算法
|
||||
|
||||
**例题** 考虑页面置换算法,系统有$m$个物理块供调度,初始时全空,页面引用串长度为$p$,包含了$n$个不同的页号,无论用什么算法,缺页次数不会少于()。
|
||||
|
||||
$A.m$
|
||||
|
||||
$B.p$
|
||||
|
||||
$C.n$
|
||||
|
||||
$D.\min(m,n)$
|
||||
|
||||
解:$C$。这个题目是对页面置换的基本概念,即最少要置换多少次。首先页面引用串就代表一共要调入$p$个页面,要调入$p$个页面,假设最坏情况所有页面都缺页,就得到缺页的最大值为$p$,所以$p$不可能是缺页的最小值。包含了$n$个不同的页号,所以就一定会缺$n$次页,所以最小值是$n$,即缺页次数一定$\geqslant n$。而对于$m$而言,若$n>m$,则由于初始为空,所以次数$>m$,综合$>n$,若$n<m$,则$m$块肯定用不完,所以全部调入$n$个页面后不会缺页,缺页次数$=n$,综上$C$。
|
||||
|
||||
#### 最佳置换算法
|
||||
|
||||
#### 先进先出置换算法
|
||||
|
||||
#### 最近最久未使用置换算法
|
||||
|
||||
**例题** 导致$LRU$算法实现起来耗费高的原因是()。
|
||||
|
||||
$A.$需要硬件的特殊支持
|
||||
|
||||
$B.$需要特殊的中断处理程序
|
||||
|
||||
$C.$需要在页表中标明特殊的页类型
|
||||
|
||||
$D.$需要对所有的页进行排序
|
||||
|
||||
解:$D$。最近最久未使用置换算法需要根据调入页面顺序进行排序找到最久未使用的,而$A$是$D$的结果。
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
2. 如果找到匹配的页号(即命中),说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。
|
||||
3. 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照一定的页面置换算法对旧的页表项进行替换。因此,若快表未命中,则访问某个逻辑地址需要两次访存。
|
||||
|
||||
#### 两级页表
|
||||
#### 多级页表
|
||||
|
||||
单级页表的缺点:
|
||||
|
||||
@@ -301,7 +301,7 @@
|
||||
|
||||
1. 覆盖技术。
|
||||
2. 交换技术。
|
||||
3. 虚拟存储技术。
|
||||
3. 虚拟存储技术。使用覆盖和交换可以实现虚拟存储。
|
||||
|
||||
前两种技术不是重点。
|
||||
|
||||
@@ -349,14 +349,14 @@
|
||||
|
||||
### 虚拟内存的基本概念
|
||||
|
||||
+ 基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
|
||||
+ 基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
|
||||
+ 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
|
||||
+ 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
|
||||
+ 在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存。
|
||||
|
||||
+ 虚拟内存的最大容量是由计算机的地址结构($CPU$寻址范围)确定的。
|
||||
+ 虚拟内存的实际容量= min(内存和外存容量之和,$CPU$寻址范围)。
|
||||
+ 某计算机地址结构为32位,按字节编址,内存大小为512MB,外存大小为2GB。则虚拟内存的最大容量为$2^{32}$B= 4GB,而实际内存是2GB+512MB。
|
||||
+ 虚拟内存的最大容量是由计算机的**地址结构**($CPU$寻址范围)确定的。
|
||||
+ 虚拟内存的实际容量=$\min$(内存和外存容量之和,$CPU$寻址范围)。
|
||||
+ 某计算机地址结构为$32$位,按字节编址,内存大小为$512MB$,外存大小为$2GB$。则虚拟内存的最大容量为$2^{32}B=4GB$,而实际内存是$2GB+512MB$。
|
||||
|
||||
虚拟内存特征:
|
||||
|
||||
@@ -364,8 +364,6 @@
|
||||
+ 对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
|
||||
+ 虚拟性:从逻辑上扩充可内存的容量,使用户看到的内存容量,远大于实际的容量。
|
||||
|
||||
### 请求分页管理方式
|
||||
|
||||
虚拟内存实现需要基于离散分配的内存管理方式基础上。所以根据传统的非连续分配存储管理,可以将虚拟存储的实现分为:
|
||||
|
||||
+ 请求分页存储管理。
|
||||
@@ -374,25 +372,36 @@
|
||||
|
||||
其主要区别是:在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序;若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。所以操作系统需要提供请求调页或请求调段的功能与页面置换或段置换的功能。
|
||||
|
||||
不管哪种方式,都需要有一定的硬件支持。一般需要的支持有以下几个方面:
|
||||
|
||||
+ 一定容量的内存和外存。
|
||||
+ 页表机制(或段表机制),作为主要的数据结构。
|
||||
+ 中断机构,当用户程序要访问的部分尚未调入内存时,则产生中断。
|
||||
+ 地址变换机构,逻辑地址到物理地址的变换。
|
||||
|
||||
### 请求分页管理方式
|
||||
|
||||
是最常用的实现虚拟存储器的方式。基于基本分页系统,增加了请求调页功能和页面置换功能。
|
||||
|
||||
#### 页表机制
|
||||
|
||||
+ 与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存:如果还没调入,那么也需要知道该页面在外存中存放的位置。
|
||||
+ 与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存:如果还没调入,那么也需要知道该页面在外存中存放的位置。
|
||||
+ 当内存空间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面:有的页面没有被修改过,就不用再浪费时间写回外存。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。
|
||||
|
||||
其中基本分页存储管理的页表项分为隐藏的页号与内存块号,所以请求分页管理存储的页表项分为:
|
||||
|
||||
+ 页号(隐藏)。
|
||||
+ 内存块号。
|
||||
+ 状态位:表示是否已调入内存,0表示为调入,1表示已调入。
|
||||
+ 访问字段:可记录最近被访问过几次,或记录上次访问的时间,供置换算法选择换出页面时参考。
|
||||
+ 修改位:页面调入内存后是否被修改过,0表示没有,1表示修改过。
|
||||
+ 外存地址:页面在外存中的存放地址。
|
||||
+ 内存块号(物理块号)。
|
||||
+ 状态位$P$:表示是否已调入内存,$0$表示未调入,$1$表示已调入,供访问时参考。
|
||||
+ 访问字段$A$:可记录最近被访问过几次,或记录上次访问的时间,供置换算法选择换出页面时参考。
|
||||
+ 修改位$M$:页面调入内存后是否被修改过,$0$表示没有,$1$表示修改过。
|
||||
+ 外存地址:页面在外存中的存放地址,一般是物理块号,供调入参考。
|
||||
|
||||
#### 缺页中断机构
|
||||
|
||||
缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此属于内中断的故障。一条指令在执行期间可能出现多次缺页中断。
|
||||
|
||||
1. 在请求分页系统中,每当要访问的页面不在内存时(状态位为0),便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。
|
||||
1. 在请求分页系统中,每当要访问的页面不在内存时(状态位为$0$),便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。
|
||||
2. 此时缺页的进程阻塞,放入阻塞队列。
|
||||
3. 如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项,如内存块号。
|
||||
4. 如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
|
||||
@@ -402,28 +411,26 @@
|
||||
|
||||
与普通页表的地址变换机构不同的是,增加了请求调页、页面置换和请求修改内容三个部分。
|
||||
|
||||
1. 要算出逻辑地址A对应的页号P与页内偏移量W。页号P=逻辑地址A÷页面长度L(取除法的整数部分)。页内偏移量W=A逻辑地址%页面长度L(取除法的余数部分)。
|
||||
2. 检测页号P是否越界。如果页号P大于等于页表长度M,则内中断(因为页号从0开始,页表长度至少为1,从而P=M页会越界)。
|
||||
1. 要算出逻辑地址$A$对应的页号$P$与页内偏移量$W$。页号$P$=逻辑地址$A$÷页面长度$L$(取除法的整数部分)。页内偏移量$W$=$A$逻辑地址%页面长度$L$(取除法的余数部分)。
|
||||
2. 检测页号$P$是否越界。如果页号$P$大于等于页表长度$M$,则内中断(因为页号从$0$开始,页表长度至少为$1$,从而$P=M$页会越界)。
|
||||
3. 在快表中查找对应页号,如果找到匹配的页号(即命中),说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。
|
||||
4. 快表中有的页面一定是在内存中的。若某个页面被换出外存,则快表中的相应表项也要删除,否则可能访问错误的页面。
|
||||
5. 如果没有找到匹配的页号,则需要访问内存中的页表。
|
||||
6. 找到对应页表项后,若对应页面未调入内存,则产生缺页中断,之后由操作系统的缺页中断处理程序进行处理,包括调页与页面置换。
|
||||
7. 根据页表寄存器中的页表项地址PA=页表起始地址F+页号P×页表项长度PL,得到页表中对应的页表项,从而确定页面存放的内存块号B。
|
||||
8. 最后物理地址E=内存块号B×页面大小L+页内偏移量W(如果内存块号和业内偏移量用二进制表示,则直接拼接起来就是最终物理地址了)。
|
||||
7. 根据页表寄存器中的页表项地址$PA$=页表起始地址$F$+页号$P$×页表项长度$PL$,得到页表中对应的页表项,从而确定页面存放的内存块号$B$。
|
||||
8. 最后物理地址$E$=内存块号$B$×页面大小$L$+页内偏移量$W$(如果内存块号和业内偏移量用二进制表示,则直接拼接起来就是最终物理地址了)。
|
||||
|
||||
简单而言,在具有快表机构的请求分页系统中,访问一个逻辑地址时,若发生缺页,则地址变换步骤是:查快表(未命中)――查慢表(发现未调入内存)—―调页(调入的页面对应的表项会直接加入快表)―—查快表(命中)――访问目标内存单元。
|
||||
|
||||
### 页面置换算法
|
||||
|
||||
假设一个例子,系统为某进程分配了三个内存块,并考虑到有一下页面号引用串(会依次访问这些页面):$7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1$。使用算法如何置换?
|
||||
|
||||
#### 最佳置换算法
|
||||
|
||||
即OPT算法。
|
||||
即$OPT$算法。
|
||||
|
||||
每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。这是不可能实际实现的,因为不可能超强预测所有页面请求,一般用来评价其他算法效果。
|
||||
|
||||
**例题** 假设系统为某进程分配了三个内存块,并考虑到有一下页面号引用串(会依次访问这些页面):7,0,1,2,0, 3,0,4,2,3,0,3,2,1,2,0,1,7,0,1。
|
||||
|
||||
若是不发生缺页则不标出该内存块的页面情况。
|
||||
每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。这是不可能实际实现的,因为不可能预测本进程所有页面请求,一般用来评价其他算法效果。
|
||||
|
||||
访问页面|7|0|1|2|0|3|0|4|2|3|0|3|2|1|2|0|1|7|0|1
|
||||
:------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:
|
||||
@@ -432,55 +439,73 @@
|
||||
内存块3| | |1|1| |3| |3| | |3| | |1| | | |1| |
|
||||
是否缺页|√|√|√|√| |√| |√| | |√| | |√| | | |√| |
|
||||
|
||||
所以缺页中断发生了9次,页面置换发生了6次,缺页率=9÷20=45%。
|
||||
所以缺页中断发生了$9$次,页面置换发生了$6$次,缺页率$=9\div20=45\%$。
|
||||
|
||||
#### 先进先出置换算法
|
||||
|
||||
即FIFO算法。
|
||||
即$FIFO$算法。
|
||||
|
||||
每次选择淘汰的页面是最早进入内存的页面。只考虑进入时间而不考虑访问次数。
|
||||
|
||||
实现方法:把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面即可。队列的最大长度取决于系统为进程分配了多少个内存块。
|
||||
|
||||
如给定一串页面号:3,2,1,0,3,2,4,3,2,1,0,4,使用FIFO算法进行置换,会发现分配三个内存块缺页次数为9次,而分配四个内存块缺页次数为10次。这种当为进程分配的物理块数增大时,缺页次数不减反增的异常现象就是**Belady异常**。
|
||||
访问页面|7|0|1|2|0|3|0|4|2|3|0|3|2|1|2|0|1|7|0|1
|
||||
:------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:
|
||||
内存块1|7|7|7|2| |2|2|4|4|4|0| | |0|0| | |7|7|7
|
||||
内存块2| |0|0|0| |3|3|3|2|2|2| | |1|1| | |1|0|0
|
||||
内存块3| | |1|1| |1|0|0|0|3|3| | |3|2| | |2|2|1
|
||||
是否缺页|√|√|√|√| |√|√|√|√|√|√| | |√|√| | |√|√|√
|
||||
|
||||
只有FIFO算法会产生Belady异常。另外,FIFO算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因为先进入的页面也有可能最经常被访问。因此该算法性能差。
|
||||
缺页了$15$次,置换了$12$次,所以缺页率为$15\div20=75\%$。
|
||||
|
||||
如给定一串页面号:$3,2,1,0,3,2,4,3,2,1,0,4$,使用$FIFO$算法进行置换,会发现分配三个内存块缺页次数为$9$次,而分配四个内存块缺页次数为$10$次。这种当为进程分配的物理块数增大时,缺页次数不减反增的异常现象就是**Belady异常**。
|
||||
|
||||
只有$FIFO$算法会产生$Belady$异常,使用队列实现,是队列类算法。另外,$FIFO$算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因为先进入的页面也有可能最经常被访问。因此该算法性能差。
|
||||
|
||||
#### 最近最久未使用置换算法
|
||||
|
||||
即LRU算法。
|
||||
即$LRU$算法。
|
||||
|
||||
每次淘汰的页面是最近最久未使用的页面。
|
||||
每次淘汰的页面是最近最久未使用的页面。(是检查过去的,而$OPT$是检查未来的)
|
||||
|
||||
所以这里就需要使用页表中访问字段这一项,用来记录该页面自上次被访问以来所经历的时间t,当需要淘汰时,就选择t值最大的,即最近最久未使用的页面。
|
||||
所以这里就需要使用页表中访问字段这一项,用来记录该页面自上次被访问以来所经历的时间$t$,当需要淘汰时,就选择$t$值最大的,即最近最久未使用的页面。
|
||||
|
||||
访问页面|7|0|1|2|0|3|0|4|2|3|0|3|2|1|2|0|1|7|0|1
|
||||
:------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:
|
||||
内存块1|7|7|7|2| |2| |4|4|4|0| | |1| |1| |1| |
|
||||
内存块2| |0|0|0| |0| |0|0|3|3| | |3| |0| |0| |
|
||||
内存块3| | |1|1| |3| |3|2|2|2| | |2| |2| |7| |
|
||||
是否缺页|√|√|√|√| |√| |√|√|√|√| | |√| |√| |√| |
|
||||
|
||||
所以缺页中断发生了$12$次,页面置换发生了$9$次,缺页率$=12\div20=60\%$。
|
||||
|
||||
在手动做题时,若需要淘汰页面,可以逆向检查此时在内存中的几个页面号。在逆向扫描过程中最后一个出现的页号就是要淘汰的页面。
|
||||
|
||||
该算法的实现需要专门的硬件支持,虽然算法性能好,最接近OPT算法,但是实现困难,开销大。
|
||||
该算法的实现需要专门的硬件支持,如寄存器和栈,虽然算法性能好,最接近$OPT$算法,但是实现困难,开销大。
|
||||
|
||||
#### 时钟置换算法
|
||||
|
||||
即CLOCK算法。是一种性能和开销较均衡的算法,又称最近未用算法NRU。
|
||||
即$CLOCK$算法。是一种性能和开销较均衡的算法,又称最近未用算法$NRU$。
|
||||
|
||||
实现方法:
|
||||
|
||||
1. 为每个页面设置一个访问位:访问位为1,表示最近访问过;访问位为0,表示最近没访问过。
|
||||
1. 为每个页面设置一个访问位:访问位为$1$,表示最近访问过;访问位为$0$,表示最近没访问过。
|
||||
2. 再将内存中的页面都通过链接指针链接成一个循环队列。
|
||||
3. 当某页被访问时,其访问位置为1。当需要淘汰一个页面时,只需检查页的访问位。如果是0,就选择该页换出。
|
||||
4. 如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描。
|
||||
5. 第二轮扫描中一定会有访问位为0的页面,因此简单的CLOCK算法选择一个淘汰页面最多会经过两轮扫描。
|
||||
3. 当某页被访问时,其访问位置为$1$。当需要淘汰一个页面时,只需检查页的访问位。如果是$0$,就选择该页换出。
|
||||
4. 如果是$1$,则将它置为$0$,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是$1$,则将这些页面的访问位依次置为$0$后,再进行第二轮扫描。
|
||||
5. 第二轮扫描中一定会有访问位为$0$的页面,因此简单的$CLOCK$算法选择一个淘汰页面,最多会经过两轮扫描。
|
||||
|
||||
值得注意的是:访问和置换是不同的,扫描指针用于置换,只有缺页中断才会发生指向的变化,而访问和修改数据是另一个指针,会随着访问而不断移动,且是访问指针会影响访问位而不是扫描指针。
|
||||
|
||||
**例题** 假设系统为某进程分配了五个内存块,并考虑到有以下页面号引用串:1,3,4,2,5,6,3,4,7。
|
||||
假设系统为某进程分配了五个内存块,并考虑到有以下页面号引用串:$1,3,4,2,5,6,3,4,7$。
|
||||
|
||||
首先第一步将1,3,4,2,5链接成为循环队列,此时访问位全部为1。
|
||||
首先第一步将$1,3,4,2,5$链接成为循环队列,代表置入内存的内存块,此时访问位全部为$1$,表示全部被访问并置入内存。
|
||||
|
||||
然后访问到6,需要置换出一个页面,所以循环访问1,3,4,2,5,将访问位全部改为0。
|
||||
然后访问到$6$,需要置换出一个页面,所以循环访问$1,3,4,2,5$,将访问位全部改为$0$。
|
||||
|
||||
第二轮循环开始,第一个的1访问位为0,则将1换出,6换入,访问位置为1,变为6,3,4,2,5,扫描指针指向3。
|
||||
第二轮循环开始,第一个的$1$访问位为$0$,则将$1$换出,$6$换入,访问位置为$1$,变为$6,3,4,2,5$,扫描指针指向$3$。
|
||||
|
||||
接着访问3和4,将访问位都置为1,然后是7,因为7不在内存中,从3开始扫描,34访问位为1,而2访问位为0,所以7换入2换出,7的访问位置为1,变为6,3,4,7,5,扫描指针指向5。
|
||||
接着访问$3$和$4$,将访问位都置为$1$,然后是$7$,因为$7$不在内存中,从$3$开始扫描,$3$、$4$访问位为$1$,而$2$访问位为$0$,所以$7$换入$2$换出,$7$的访问位置为$1$,变为$6,3,4,7,5$,扫描指针指向$5$。
|
||||
|
||||
#### 改进型时钟置换算法
|
||||
|
||||
@@ -488,18 +513,18 @@
|
||||
|
||||
因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免$I/O$操作。这就是改进型的时钟置换算法的思想。
|
||||
|
||||
需要利用到修改位,修改位=0,表示页面没有被修改过;修改位=1,表示页面被修改过。
|
||||
需要利用到修改位,修改位$=0$,表示页面没有被修改过;修改位$=1$,表示页面被修改过。
|
||||
|
||||
为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过,且被修改过。
|
||||
为方便讨论,用(访问位,修改位)的形式表示各页面状态。如$(1,1)$表示一个页面近期被访问过,且被修改过。
|
||||
|
||||
算法规则:
|
||||
|
||||
1. 将所有可能被置换的页面排成一个循环队列。初始的(访问位,修改位)可能是任何的10组合,这和简单时钟算法初始为全0不同。
|
||||
2. 第一轮(没访问没修改):从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位。
|
||||
3. 第二轮(没访问有修改):若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0。
|
||||
4. 第三轮(有访问没修改):若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位。
|
||||
5. 第四轮(有访问有修改):若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。需要第四轮扫描只有全部页面都被访问都被修改过这一种情况。
|
||||
6. 由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描一定会有一个帧被选中,因此改进型CLOCK置换算法选择一个淘汰页面最多会进行四轮扫描。
|
||||
1. 将所有可能被置换的页面排成一个循环队列。初始的(访问位,修改位)可能是任何的$10$组合,这和简单时钟算法初始为全$0$不同。
|
||||
2. 第一轮(没访问没修改):从当前位置开始扫描到第一个$(0,0)$的帧用于替换。本轮扫描不修改任何标志位。
|
||||
3. 第二轮(没访问有修改):若第一轮扫描失败,则重新扫描,查找第一个$(0,1)$的帧用于替换。本轮将所有扫描过的帧访问位设为$0$。
|
||||
4. 第三轮(有访问没修改):若第二轮扫描失败,则重新扫描,查找第一个$(0,0)$的帧用于替换。本轮扫描不修改任何标志位。
|
||||
5. 第四轮(有访问有修改):若第三轮扫描失败,则重新扫描,查找第一个$(0,1)$的帧用于替换。需要第四轮扫描只有全部页面都被访问都被修改过这一种情况。
|
||||
6. 由于第二轮已将所有帧的访问位设为$0$,因此经过第三轮、第四轮扫描一定会有一个帧被选中,因此改进型$CLOCK$置换算法选择一个淘汰页面最多会进行四轮扫描。
|
||||
|
||||
### 页面分配策略
|
||||
|
||||
@@ -511,7 +536,7 @@
|
||||
|
||||
页面分配策略:
|
||||
|
||||
+ 固定分配:操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。即,驻留集大小不变入。
|
||||
+ 固定分配:操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。即,驻留集大小不变。
|
||||
+ 可变分配:先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。即,驻留集大小可变。
|
||||
|
||||
页面置换策略:
|
||||
@@ -537,20 +562,23 @@
|
||||
|
||||
调入页面时机:
|
||||
|
||||
+ 预调页策略(运行前):根据局部性原理,一次调入若干个相邻的页面可能比一次调入一个页面更高效。但如果提前调入的页面中大多数都没被访问过,则又是低效的。因此可以预测不久之后可能访问到的页面,将它们预先调入内存,但目前预测成功率只有50%左右。故这种策略主要用于进程的首次调入,由程序员指出应该先调入哪些部分。
|
||||
+ 预调页策略(运行前):根据局部性原理,一次调入若干个相邻的页面可能比一次调入一个页面更高效。但如果提前调入的页面中大多数都没被访问过,则又是低效的。因此可以预测不久之后可能访问到的页面,将它们预先调入内存,但目前预测成功率只有$50\%$左右。故这种策略主要用于进程的首次调入,由程序员指出应该先调入哪些部分。
|
||||
+ 请求调页策略(运行时):进程在运行期间发现缺页时才将所缺页面调入内存。由这种策略调入的页面一定会被访问到,但由于每次只能调入一页,而每次调页都要磁盘$I/O$操作,因此$I/O$开销较大。
|
||||
|
||||
调入页面位置:
|
||||
|
||||
+ 系统拥有足够的对换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保证页面的调入、调出速度很快。在进程运行前,需将进程相关的数据从文件区复制到对换区。
|
||||
+ 系统拥有足够的对换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保证页面的调入、调出速度很快。在进程运行前,需将进程相关的数据从文件区复制到对换区。
|
||||
+ 系统缺少足够的对换区空间:凡是不会被修改的数据都直接从文件区调入,由于这些页面不会被修改,因此换出时不必写回磁盘,下次需要时再从文件区调入即可。对于可能被修改的部分,换出时需写回磁盘对换区,下次需要时再从对换区调入。
|
||||
+ UNIX方式:运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可从文件区调入。若被使用过的页面需要换出,则写回对换区,下次需要时从对换区调入。
|
||||
+ $UNIX$方式:运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可从文件区调入。若被使用过的页面需要换出,则写回对换区,下次需要时从对换区调入。
|
||||
|
||||
#### 抖动(颠簸现象)
|
||||
|
||||
+ 刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动,或颠簸。
|
||||
+ 产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数(分配给进程的物理块不够)。所以需要合适的物理块数量。
|
||||
+ 工作集:指在某段时间间隔里,进程实际访问页面的集合。
|
||||
+ 窗口尺寸就是驻留集的大小,约束工作集大小,工作集大小小于等于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作集大小给进程分配若干内存块。如窗口尺寸为5,经过一段时间的监测发现某进程的工作集最大为3,那么说明该进程有很好的局部性,可以给这个进程分配3个以上的内存块即可满足进程的运行需要。
|
||||
|
||||
### 工作集
|
||||
|
||||
+ 窗口尺寸就是驻留集的大小,约束工作集大小,工作集大小小于等于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作集大小给进程分配若干内存块。如窗口尺寸为$5$,经过一段时间的监测发现某进程的工作集最大为$3$,那么说明该进程有很好的局部性,可以给这个进程分配$3$个以上的内存块即可满足进程的运行需要。
|
||||
+ 驻留集大小不能小于工作集大小,否则会产生抖动现象。
|
||||
+ 基于局部性原理可知,进程在一段时间内访问的页面与不久之后会访问的页面是有相关性的。因此,可以根据进程近期访问的页面集合(工作集)来设计一种页面置换算法――选择一个不在工作集中的页面进行淘汰。
|
||||
|
||||
Reference in New Issue
Block a user