在进程中,我们不直接对物理地址进行操作,CPU在运行时,指定的地址要经过MMU转换后才能访问到真正的物理内存。
地址转换的过程分为两部分,分段和分页。
分段机制简单的来说是将进程的代码、数据、栈分在不同的虚拟地址段上,从而避免进程间的互相影响。分段之前的地址我们称之为逻辑地址,它有两部分组成,高位的段选择符和低位的段内偏移。在分段时先用段选择符在相应的段描述符表中找到段描述符,也就是某一个段的基地址,再加上段内偏移量就得到了对应的线性地址,线性地址也称之为虚拟地址。
而在实际的应用中,Linux为了增加可移植性并没有完整的使用分段机制,它让所有的段都指向相同的段地址范围,段的基地址都为0,这样逻辑地址和线性地址在数值上就相同了。
所以,这里我们分析的重点在分页,也就是由线性地址到物理地址的转换过程。
我们使用一个虚拟地址对应一个物理地址的方式。
为了保证进程在各个页离散存储在物理块上的情况下,依旧可以正确找到对应。
由于虚拟内存中是连续的,而在物理内存中每个页都是不连续的,被离散的插在了不同地方。所以我们要有一个类似于目录的东西,来找到虚拟内存中的每一块对应到物理内存中的某一块。
也就是我给你一个虚拟地址,你就要给我一个对应的物理地址,指向某一个数据。
我们使用一个页表来存储物理内存对应的页表项。这个页表类似于一个目录。
那么我们页表共有1048576页也就是1M个条目,每个条目占4byte,里面存储的数据是物理页序号。这个物理页序号我们不需要4byte32位来存储,我们只需要其中的20位就可以了。因为只需要定位到是物理哪个页,一共有1M个页。所以20位就够了。其他位作为其他标记。
每个4byte条目对应表示物理内存中的4K内存空间。
这样页表就可以和实际物理内存映射起来了。
一个一级页表大小是4M。

给定一个虚拟地址转换成物理地址

举例:比如给定虚拟地址为0x1234,页部件就是MMU。首先虚拟地址分为两部分高20位和低12位
MMU取出0x1234的高20位,十六进制表示就是0x00001。他表示一级页表的索引,乘4并且加上一级页表的物理地址,可以定位到一级页表中的某一个PTE
从PTE中取出内容(存放的是物理地址)就是某个物理页的地址,0x9000,用它就可以定位到物理页的位置。
虚拟地址的后12位是offset,我们将物理页地址0x9000加上offset=0x234就得到最终的物理地址0x9234
一级页表大小位4M。多个进程占用4M也很大了。这是32位系统,如果64位系统那就是2的64次方除以10.非常大,放到内存中是不可能的。
使用二级页表,动态创建页表项。并不需要把页表一次性都创建好。
二级页表将1M个标准页4K。平均放到1024个页表中,而不是统一放到一个页表中(1M个PTE)。这样每个二级页表就被分为1024个PTE。也表示4G的内存空间。
我们用一个页目录表来进行管理1024个二级页表。

每个页表还是4K大小。
举例:
如果一个进程只使用了4M的空间。
虚拟地址转换为物理地址

举例:
虚拟地址0x1234567,二级页表虚拟地址分为三部分。
计算题:
假设页长为1KB(1024字节),逻辑地址为2500,请利用页表把逻辑地址转换成物理地址。
物理地址的计算公式为:物理地址=块的大小(即页的大小L)* 块号f+页内地址d
代入本题解答:
页号=int(2500/1024)=2;页内位移=2500mod1024=452;假设页号2对应块号1,则物理地址为:
物理地址=1024*1+452=1476
我们程序都是放在磁盘中,我们CPU执行程序的时候,程序需要加载到内存中,也就是我们如何从磁盘加载可执行程序到内存。
一种选择是:在程序执行时将整个程序加载到物理内存。(这个我们在前面讲过了,加载整个程序太浪费内存了,又不一定用的到)
另一种选择是:仅在需要时才加载页面。
不用想就知道肯定选最后一种,因为程序的局限性一般最近执行的都在这附近的几个页中,等到需要的时候才加载进内存,这种技术被称为请求调页,常常用于虚拟内存系统。
步骤1: 申请空闲的物理内存页;
步骤2: 从磁盘读取缺页数据到物理内存页;
步骤3: 建立虚拟内存与物理内存页的映射关系,即修改页表,新增一条映射记录;
1.最佳置换算法(OPT)(理想置换算法):从主存中移出永远不再需要的页面;如无这样的页面存在,则选择最长时间不需要访问的页面。于所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。
2.先进先出置换算法(FIFO):是最简单的页面置换算法。这种算法的基本思想是:当需要淘汰一个页面时,总是选择驻留主存时间最长的页面进行淘汰,即先进入主存的页面先淘汰。其理由是:最早调入主存的页面不再被使用的可能性最大。
3.最近最久未使用(LRU)算法:这种算法的基本思想是:利用局部性原理,根据一个作业在执行过程中过去的页面访问历史来推测未来的行为。它认为过去一段时间里不曾被访问过的页面,在最近的将来可能也不会再被访问。所以,这种算法的实质是:当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。
#include
#include
#include
int numbers[20]={7,0,1,2,
0,3,0,4,
2,3,0,3,
2,1,2,0,
1,7,0,1};//本地数据,与课本一致,方便测试
int nums=0;//输入栈的个数,为了方便使用,
int stack[20][7]={10};
void begin();
void randomnum();//用于产生随机数
void init();//初始化
void FIFO();//FIFO算法
void LRU();//LRU算法
void OPT();//最优页面置换算法(OPT)
void print();//输出
int main() {
begin();
FIFO();
LRU();
OPT();
return 0;
}
void begin()//开始菜单界面
{
int i,j,k;
printf("请输入页面帧的数量(1-7):");
scanf("%d",&nums);
for(k=0;;k++)
{
printf("是否使用随机数产生输入串(0:是,1:否)");
scanf("%d",&j);
if(j==0)
{
randomnum();
break;
}
else if(j==1)
{
break;
}
else
{
printf("请输入正确的选择!");
}
}
printf("页面引用串为:");
for(i=0;i<20;i++)
{
printf("%d ",numbers[i]);
}
printf("");
init();
}
void randomnum()//如果需要使用随机数生成输入串,调用该函数
{
srand(time(0));//设置时间种子
for(int i = 0; i < 20; i++) {
numbers[i] = rand() % 10;//生成区间0`9的随机页面引用串
}
}
void init()//用于每次初始化页面栈中内容,同时方便下面输出的处理
{
int i,j;
for(i=0;i<20;i++)
for(j=0;j<nums;j++)
stack[i][j]=10;
}
void print()//输出各个算法的栈的内容
{
int i,j;
for(i=0;i<nums;i++)
{
for(j=0;j<20;j++)
{
if(stack[j][i]==10)
printf("* ");
else
printf("%d ",stack[j][i]);
}
printf("");
}
}
void FIFO()//FIFO算法
{
init();
int i,j=1,n=20,k,f,m;
stack[0][0]=numbers[0];
for(i=1;i<20;i++)
{
f=0;
for(m=0;m<nums;m++)
{
stack[i][m]=stack[i-1][m];
}
for(k=0;k<nums;k++)
{
if(stack[i][k]==numbers[i])
{
n--;
f=1;
break;
}
}
if(f==0)
{
stack[i][j]=numbers[i];
j++;
}
if(j==nums)
j=0;
}
printf("");
printf("FIFO算法:");
print();
printf("缺页错误数目为:%d",n);
}
void LRU()//LRU算法
{
int i,j,m,k,sum=1,f;
int sequence[7]={0};//记录序列
init();
stack[0][0]=numbers[0];
sequence[0]=nums;
for(i=1;i<nums;i++)//前半部分,页面空置的情况
{
for(j=0;j<nums;j++)
{
stack[i][j]=stack[i-1][j];
}
for(j=0;j<nums;j++) //判断要插入的是否在栈中已经存在
{
f=0;
if(stack[i][j]==numbers[i])
{
f=1;
sum--;
sequence[j]=nums;
break;
}
}
for(j=0;j<nums;j++)
{
if(sequence[j]==0&&f==0)
{
stack[i][j]=numbers[i];
sequence[i]=nums;//最近使用的优先级列为最高
break;
}
}
for(j=0;j<i;j++)//将之前的优先级序列都减1
{
if(sequence[j]!=0)
sequence[j]--;
}
//sequence[i]=nums;
sum++;
}
for(i=nums;i<20;i++)//页面不空,需要替换的情况
{
int f;
f=0;
for(j=0;j<nums;j++)
{
stack[i][j]=stack[i-1][j];
}
for(j=0;j<nums;j++)//判断输入串中的数字,是否已经在栈中
{
if(stack[i][j]==numbers[i])
{
f=1;
k=j;
break;
}
}
if(f==0)//如果页面栈中没有,不相同
{
for(j=0;j<nums;j++)//找优先序列中为0的
{
if(sequence[j]==0)
{
m=j;
break;
}
}
for(j=0;j<nums;j++)
{
sequence[j]--;
}
sequence[m]=nums-1;
stack[i][m]=numbers[i];
sum++;
}
else//如果页面栈中有,替换优先级
{
if(sequence[k]==0)//优先级为最小优先序列的
{
for(j=0;j<nums;j++)
{
sequence[j]--;
}
sequence[k]=nums-1;
}
else if(sequence[k]==nums-1)//优先级为最大优先序列的
{
//无需操作
}
else//优先级为中间优先序列的
{
for(j=0;j<nums;j++)
{
if(sequence[k]<sequence[j])
{
sequence[j]--;
}
}
sequence[k]=nums-1;
}
}
}
printf("");
printf("LRU算法:");
print();
printf("缺页错误数目为:%d",sum);
}
void OPT()//OPT算法
{
int i,j,k,sum=1,f,q,max;
int seq[7]={0};//记录序列
init();
stack[0][0]=numbers[0];
seq[0]=nums-1;
for(i=1;i<nums;i++)//前半部分,页面空置的情况
{
for(j=0;j<nums;j++)
{
stack[i][j]=stack[i-1][j];
}
for(j=0;j<nums;j++) //判断要插入的是否在栈中已经存在
{
f=0;
if(stack[i][j]==numbers[i])
{
f=1;
sum--;
//b++;
seq[j]=nums;
break;
}
}
for(j=0;j<nums;j++)
{
if(seq[j]==0&&f==0)
{
stack[i][j]=numbers[i];
seq[j]=nums;//最近使用的优先级列为最高
break;
}
// else if(seq[j]==0&&f==1){
// b++;
// sum--;
// seq[j]=nums-1;
// break;
// }
}
for(j=0;j<nums;j++)//将之前的优先级序列都减1
{
if(seq[j]!=0)
seq[j]--;
}
sum++;
}
for(i=nums;i<20;i++)//后半部分,页面栈中没有空的时候情况
{
//k=nums-1;//最近的数字的优先级
for(j=0;j<nums;j++)//前面的页面中内容赋值到新的新的页面中
{
stack[i][j]=stack[i-1][j];
}
for(j=0;j<nums;j++)
{
f=0;
if(stack[i][j]==numbers[i])
{
f=1;
break;
}
}
if(f==0)//页面中没有,需要替换的情况
{
for(q=0;q<nums;q++)//优先级序列中最大的就是最久不会用的,有可能出现后面没有在用过的情况
{
seq[q]=20;
}
for(j=0;j<nums;j++)//寻找新的优先级
{
for(q=i+1;q<20;q++)
{
if(stack[i][j]==numbers[q])
{
seq[j]=q-i;
break;
}
}
}
max=seq[0];
k=0;
for(q=0;q<nums;q++)
{
if(seq[q]>max)
{
max=seq[q];
k=q;
}
}
stack[i][k]=numbers[i];
sum++;
}
else
{
//页面栈中有需要插入的数字,无需变化,替换的优先级也不需要变化
}
}
printf("");
printf("OPT算法:");
print();
printf("缺页错误数目为:%d",sum);
}
#include
#include
#include
//主要数据结构
#define total_instrucion 15//总的页面访问次数
#define max_block_num 10//最大内存分配
int Access_Series[total_instrucion];//内存访问序列
char Lack[total_instrucion];//记录是否缺页
struct one_block {//一个物理块
int page_no;//物理块内的页面号
int tim;//计时,用于确定最近最久未使用的页面(LRU)
}blocks[max_block_num];
int table[max_block_num][total_instrucion];//显示矩阵
void LRU(int block_num){
int diseffect;//缺页数
for (int i = 0; i < total_instrucion; i++)
Lack[i] = 'N';//初始化不缺页
//数据结构blocks的初始化
for (int i = 0; i < block_num; i++) {
blocks[i].page_no = -1;//初始化页号为-1,当前有空闲页面
blocks[i].tim = 0;//初始化计时
}
//开始访问页面
for (int i = 0; i < total_instrucion; i++) {
char full = 'T';//是否内存满
char hit = 'F';//是否命中
for (int j = 0; j < block_num; j++)
blocks[j].tim++;//计时器加1
for (int j = 0; j < block_num; j++) {
if (blocks[j].page_no == Access_Series[i]) {//命中的情况
hit = 'T';
blocks[j].tim=0;//刚刚访问,值变为0
break;
}else if (blocks[j].page_no == -1) {//未命中,且内存未满的情况
blocks[j].page_no = Access_Series[i];
blocks[j].tim=0;//刚刚访问,值变为0
Lack[i] = 'Y';//缺页
diseffect++;
full = 'F';//内存未满
break;
}
}
if (full == 'T') {//内存已满
if (hit == 'F') {//若未命中
diseffect++;
Lack[i] = 'Y';
int target = 0;//选中替换目标
for (int j = 1; j < block_num; j++) {
if (blocks[j].tim>blocks[target].tim)
target = j;
}
blocks[target].page_no = Access_Series[i];
blocks[target].tim=0;//刚刚访问,值变为0
}
}
for (int j = 0; j < block_num; j++)
table[j][i] = blocks[j].page_no;
}
/*输出运行过程及结果*/
printf("采用LRU页面置换算法结果如下:");
printf("\n");
printf("页号:");
for (int i = 0; i < total_instrucion; i++)
printf("%3d", Access_Series[i]);
printf("\n");
printf("-----------------------------------------------------\n");
for (int i = 0; i < block_num; i++) {
printf("块%2d:", i+1);
for (int j = 0; j < total_instrucion; j++)
printf("%3d", table[i][j]);
printf("\n");
}
printf("-----------------------------------------------------\n");
printf("缺页:");
for (int i = 0; i < total_instrucion; i++)
printf("%3c", Lack[i]);
printf("\n");
printf("-----------------------------------------------------\n");
printf("\t缺页次数:%d\t缺页率:%d/%d\n", diseffect, diseffect, total_instrucion);
printf("-----------------------------------------------------\n");
}
int main(){
//初始化
//父进程随机产生内存访问页面序列,存于数组Acess_Series[total_instruction]中
srand((unsigned)time(NULL));
for (int i = 0; i < total_instrucion; i++)
Access_Series[i] = rand() % total_instrucion;
int block_num;
while(1){
printf("请输入分配的物理块数(-1退出):");
scanf("%d",&block_num);
if(block_num==-1) break;
LRU(block_num);
}
return 0;
}