虚拟内存管理-分页-页面置换算法

页面置换算法

Page replacement happens when a requested page is not in memory (page fault) and a free page cannot be used to satisfy the allocation, either because there are none, or because the number of free pages is lower than some threshold. --Wikepedia

页面置换算法用于当 被请求的页面不在内存空闲空间不足以满足页面载入需求 时,选择内存中原有的页面与被请求页面进行交换。

文章目录

    • 页面置换算法
      • 缺页率
      • 几种页面置换算法
        • 先进先出(First-in, first-out)
          • 实现
        • 最近最少使用(Least recently used)
          • 实现
        • 源码

缺页率

缺页率 = 失败次数 / 加载页面的总次数

失败/成功次数 : 当被请求页面已经在内存中时,该次加载成功,否则失败并执行算法交换页面

几种页面置换算法

  • 页替换算法
  • 理论最优(OPT)[略]
  • 先进先出(First-in, first-out)
  • 最近最少使用(Least recently used)
  • 两次机会(Second-chance)
  • 最近未使用(Not recently userd)
  • 其他

本文的重点内容在于FIFO与LRU算法的原理与实现

先进先出(First-in, first-out)

先进先出算法作为一种十分简单的页面置换算法实现,和它的名字一样,其实现原理十分简单容易理解:

按顺序替换出进入内存最久的页面

在先进先出算法中,我们不需要费劲心思的考虑如何选择换出页面可以获得最高的性价比,我们只遵循一个原则,“先进,先出” ,即当需要换出页面时,永远换出在内存中呆了最长时间的页面,固执,但是简单

实现

先进先出算法有两种较为容易理解的实现,我们依次来看一下:

1.循环队列
在该算法的经典实现中,我们使用 顺序的(数组的)循环队列作为内存的模拟
数据结构定义如下,为了清晰,这里隐藏了内部的具体实现:

/*c++*/
#include

class Queue{
public:
    Queue(const int capacity = 4):capacity(capacity),cur_front(0),cur_rear(0){
        /*初始化队列大小为capacity,将cur_front与cur_rear的值初始化为0*/
        for(int i = 0; i != this->capacity; ++i)
            myqueue.push_back(0);
    }
    bool push(const int e){
    	/*添加元素e至队列尾部*/
    }
    bool pop(void){
		/*将队列头部位置的元素弹出*/
    }
    bool existed(const int e){
    	/*如果队列中存在元素e,返回true,否则返回false*/
    }
    int front(void) const{
        //返回队列的首元素
        return myqueue[cur_front];
    }
private:
    int capacity;//队列大小
    vector<int> myqueue;//数组
    int cur_front;//游标,指向队列的头部位置
    int cur_rear;//游标,指向队列的尾部位置
};

将待执行的页面看作一个顺序数组pages,则对于其中的待执行页面page,先进先出算法的实现步骤如下:

  1. 在内存中寻找page是否存在
  2. 若找到页面,则页面曾经被加载过,无需重复加载,返回步骤1处理下一个待执行页面
  3. 若未找到,则表示该页面未被加载,试着将页面加载入内存
  4. 若内存未满,加载成功后返回步骤1处理下一个页面
  5. 若加载失败,说明内存已满,此时将内存头部的页面弹出,再次加载页面至尾部即可

当内存的capacity=3
待执行页面={1, 2, 3, 4, 3, 1, 4, 2, 3, 1, 4}时,算法的执行过程如下:

The pages waiting to be loaded into memory: 1 -> 2 -> 3 -> 4 -> 3 -> 1 -> 4 -> 2 -> 3 -> 1 -> 4 ->end
page 1 replace in memory.
page 2 replace in memory.
page 3 replace in memory.
page 4 replace in memory, page 1 was swapped out.
page 3 is already in memory.
page 1 replace in memory, page 2 was swapped out.
page 4 is already in memory.
page 2 replace in memory, page 3 was swapped out.
page 3 replace in memory, page 4 was swapped out.
page 1 is already in memory.
page 4 replace in memory, page 1 was swapped out.
缺页率为:0.727273

完整代码在 目录-源码

2.数组

和使用循环队列的实现方法不同,使用一些巧妙地技巧可以十分简单的使用数组完成FIFO算法的实现

/*C*/
#define CAPACITY 3//内存的容量

int memory[CAPACITY];//数组,模拟内存
int cur;//游标,标记下一个处理(加载页面、页面换出)的内存位置

/* 约定:所有的page都不为-1 */

在这个实现中,很重要的一点在于约定:所有的page页号都不为-1,利用这个约定,我们仅使用1个游标就可以判断当前内存位置的状态为空还是已经存在一个之前加载的页面

实现步骤如下:

  1. 初始化cur = 0, memory={-1, -1, -1}
  2. 搜索page是否存在于memory中
  3. 若存在,则页面已被加载过,返回步骤1处理下一页面
  4. 若不存在,检查当前memory[cur]的值
  5. 若memory[cur] == -1, 说明当前内存未满,令memory[cur] = page 将页面加载进内存
  6. 若memory[cur] != -1,说明当前内存位置已有一个页面,依旧 memory[cur] = page,替换原有页面
  7. ++cur %= CAPACITY, 维护cur的值

**Tip:**要理解这个实现方法,重要的是牢记cur指向的位置始终是下一个待处理的内存位置,明白算法执行过程中cur值的变化,如何维护能达到我们所需要的效果

最近最少使用(Least recently used)

最近最少使用算法的工作原理有些令人费解:

在一段时间内跟踪页面使用情况,换出页面时选择使用最少的页面

事实上仅凭其工作原理我们很难明白为什么这样的算法能够很好的运转,因此要理解LRU,关键所在就是要明白其核心思想

用过去预测未来

这一预测算法思想在很多地方都被我们所不自觉地利用(等一辆没有时刻表的公交车时,我们总猜测下一趟公交抵达的时间和我们已经等待的时间差不多),而在LRU算法中,我们就利用了这样的思想,最近最少使用算法认为如果我们在过去的一段时间用到了该页面,那么在不远的未来,这个页面同样有可能被用到

实现

在模拟实现LRU的数据结构中,我们同样使用数组表示内存,而同FIFO不同,我们还需要一个与memory同样大小的tracker数组标记内存中页面的访问记录

	#include 
	
	vector<int> memory;//数组,模拟内存
	vector<unsigned int> tracker;//数组,标记内存中对应页面的访问记录
    int capacity;//内存的容量
    const unsigned int mask;/*掩码,对于4字节大小的unsigned int来说,
    						掩码的值将被初始化为 (unsigned int)INT_MIN,
    						十六进制表示为 	0x80 00 00 00, 
    						即二进制的 1000 0000 0000 0000 0000 0000 0000 0000*/

这里有一点要解释的是mask的值和tracker的工作原理,这部分略有一些复杂,如果不感兴趣的话可以跳过,后面有更为简单的实现方法:

掩码:此处对mask的初始化用到了一些关于二进制补码的基础知识,简要地说,int类型的取值范围为 -231 ~ 231-1 ,其中对于INT_MIN也就是 -231 在计算机中使用补码的保存方式,其值就是0x800000, 最高位为符号位

tracker的工作原理:在tracker中保存着与memory数组一一对应的标记,用以记录每次对内存中页面的调用,当内存中有页面被调用或者新页面被加载入内存时,内存中其他所有页面对应的记录均右移一位,并将被调用的页面记录的首位置为1

对于其他未产生调用的页面
tracker[page] >>= 1
被调用的页面
tracker[page] >>= 1右移1位接着tracker[page] |= mask将最高位置为1
新加入的页面
tracker[page] = mask

一个简单的例子
页面A:1001
页面B:0010
页面C:0110

当页面B被调用后,此时tracker中各页面对应的记录
页面A:0100
页面B:1001
页面C:0011

通过这样的方法,我们只需要比较tracker中各记录值,就可以决定哪个是我们将要换出的页面

更加简单的实现方法

#define CAPACITY 3

int memory[CAPACITY]
unsigned int tracker[CAPACITY]

在简单的实现方法中,我们不需要使用掩码从最高位进行右移,只需简单的转换一下思维,从左向右

在这样的实现中,当有新页面加载入内存或产生页面调用时,我们只需将其他所有页面tracker[page]的值×2,而对于产生调用的页面,新的记录值将等于tracker[page]×2+1,如此,即使不对二进制进行深入了解,我们也能直观的感觉到,频繁产生调用的页面,其记录值将会大于很久没调用的页面的记录值

源码

注: 这里的源码只实现了循环队列的FIFO和右移的LRU算法
Usage: ./pageReplace [ -FIFO | -LRU ]
将pageReplace替换为编译后可执行文件的名字, -FIFO-LRU 为可选参数,执行对应的算法

#include 
#include 
#include 
#include 
using namespace std;


/* *
 * -the page replacement algorithm implement 
 * -mer
 * -2018.12
 * */


//circular queue
class Queue{
public:
    Queue(const int capacity = 4):capacity(capacity),cur_front(0),cur_rear(0){
	for(int i = 0; i != this->capacity; ++i)
	    myqueue.push_back(0);
    }
    bool push(const int e){
	int temp = (cur_rear + 1) % capacity;
	if( temp == cur_front)//queue is full
	    return false;
	myqueue[cur_rear] = e;
	cur_rear = temp;
	return true;
    }
    bool pop(void){
	if( cur_front == cur_rear)//queue is empty
	    return false;
	++cur_front %= capacity;
	return true;
    }
    bool existed(const int e){
	for(int i = cur_front; i != cur_rear; ++i %= capacity)
	    if(myqueue[i] == e)
		return true;
	return false;
    }
    int front(void) const{
	return myqueue[cur_front];
    }
    
private:
    int capacity;
    vector<int> myqueue;
    int cur_front;
    int cur_rear;
};

class LRUArray{
public:
    LRUArray(const int capacity = 3):capacity(capacity),mask((unsigned int)INT_MIN){}
    bool load(const int e){
	//load or swap page into memory if e not existed in, and update tracker before
	if(existed_(e)){
	    int i = getIndex_(e);
	    update_(i);
	}
	else{
	    update_();
	    if(this->isfull()){
		//if memory was full, swap out the least recently page
		int i = getLeastPage_();
	        if(i == -1) return false;
	    
		//Instead of least recently page, loading new page into memory
		vec[i] = e;
		tracker[i] = mask;

	    }else{
		//load page into memory
		vec.push_back(e);
		tracker.push_back(mask);
	    }
	}
	return true;
    }
    int getLeastPage(void) const{
	const int i = getLeastPage_();
	return i == -1 ? i : vec[i];
    }
    bool existed(const int e) const{ return existed_(e);}
    bool isfull(void) const{ return vec.size() == capacity;}

private:
    bool existed_(const int e) const{
	for(auto v: vec){
	    if(v == e) return true;
	}
	return false;
    }

    void update_(const int i = -1){
	for(int i = 0; i != tracker.size(); ++i)
	    tracker[i] = tracker[i]>>1;
	if(i != -1){
	    tracker[i] |= mask;
    
	}
    }
   int getIndex_(const int e)const{
	for(int i = 0; i != vec.size(); ++i){
	    if(vec[i] == e)
		return i;
	}
	return -1;
    }
    int getLeastPage_(void) const{
	int cur = -1;
	unsigned int cur_least = (unsigned int)(-1);
	for(int i = 0; i != tracker.size(); ++i){
	    if(tracker[i] <= cur_least){
		cur_least = tracker[i];
		cur = i;
	    } 
	}
	return cur;
    }

    vector<unsigned int> tracker;
    vector<int> vec;
    int capacity;
    const unsigned int mask;//with 4 bytes int, the mask's value is 0x80 00 00 00
};

class Manager{
public:
    Manager(const string s, const vector<int> pages):method(s),pages(pages),F(0),S(0){}
    void run(void){
	//Done: show detail in queue and these pages
	show();

	//Done: do page-replacement algorithm by 'FIFO' or 'LRU' method
	if(method == "FIFO")
	    FIFO_();
	else if(method == "LRU")
	    LRU_();
	double r = F/(double)(F + S);
	cout << "缺页率为:" << r << endl;
    }
    void show(void) const{
	cout << endl << "The pages waiting to be loaded into memory:";
	for(auto page: pages)
	  cout << " " << page << " ->";
	cout << "end" << endl; 
    }

private:
    void FIFO_(void){
	bool full = true;//check if queue is full
	bool exist = true;//check if page is already in memory
	for(auto page: pages){
	    exist = queue.existed(page);
	    if(exist){
		cout << "page " << page << " is already in memory."<<endl;
		++S;
		continue;
	    }
	    cout << "page " << page << " replace in memory";
	    full = queue.push(page);
	    if(!full){
		cout << ", page " << queue.front() << " was swapped out";
		queue.pop();
		queue.push(page);
	    }
	    cout << "." << endl;
	    ++F;
	}
    }
    void LRU_(void) {
	for(auto page: pages){
	    if(lru.existed(page)){
		cout << "page " << page << " is already in memory."<<endl;
		++S;
	    }
	    else{
		 
		cout << "page " << page << " replace in memory";
		
		if(lru.isfull()){
		    int t = lru.getLeastPage();
		    if(t != -1)
			cout << ", page " << t << " was swapped out";
		}
		cout << "." << endl;
		++F;
	    }		
	    
	    lru.load(page);

	}
    }
    
    vector<int> pages;
    string method;
    Queue queue;
    LRUArray lru;
    int F;//fault page-call
    int S;//success page-call
};

int main(int argc, char* argv[]){
    if(argc != 2 || !(argv[1] == string("-FIFO") || argv[1] == string("-LRU"))){
	cout << "Usage: ./pageReplace -[FIFO | LRU]" << endl;
	return 1;
    }
    
    cout << "Enter page number in order(stop when you press Ctrl~D):";
    
    vector<int> pages;
    int c;
    while(cin >> c)
	pages.push_back(c);
    
    Manager manager = Manager(string(argv[1]).substr(1),pages);
    manager.run();
    return 0;
}

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