一、内容与要求
Linux下C语言编程模拟内存管理单元。
通过本实验,学生应达到如下要求:
1)理解操作系统中内存管理的基本方法
2)理解逻辑地址空间与物理地址空间、地址保护与地址越界等概念
3)熟练掌握分页系统的逻辑地址到物理地址的转换过程
4) 理解页表的作用和快表(TLB)的作用
5)能够熟练使用C语言编程模拟实现MMU的地址映射算法。
二、思路分析
1)实验目的:
假设某小型分页系统的逻辑地址空间8KB,物理地址空间16KB,页面大小1KB按字节寻址.设计一个模拟程序,实现操作系统中内存管理单元(MMU)从逻辑地址到物理地址的转换算法.
2)系统分析和设计
a.逻辑地址构成分析:
逻辑地址空间8KB(0~8191B),故用13个二进制表示一个逻辑地址。页大小1K,逻辑地址空间分成8页,故逻辑地址的高3位表示页面号p,低1 0位表示页内偏移d。
编辑
b.物理地址构成分析:
物理地址空间16KB(0 ~16383B),故用14个二进制表示一个物理地址。帧大小与页大小相同,物理地址空间分成16帧,故物理地址的高4位为帧号f,低10位表示页内偏移d。
编辑
c.页表条目分析:
页表共8个条目,定义一个长度为8的整型数组来存放页表,故每个条目占32位。物理地址空间共分16帧,故每个条目需要4位存储帧号,其它位用于控制位和扩展位,本实验设计的模拟程序仅需维护低8位。页表的每个条目构成如下:编辑
有效无效位:小于进程所需的实际页面数的条目的有效无效位设置为1,表示有效页,反之,设置为0,表示无效页,MMU不解析无效页的地址。
修改位:某次访问地址是否修改所在页,本实验中对某地址随机决定是否修改页面,若地址访问将写页面,设置为1,若地址访问仅读页面设置为0。
访问位:访问地址时设置其所在页面的访问位为1,与修改位一起用于TLB的替换算法。
保护位:表示访问权限,1表示只读,0表示读/写,本实验全部页面设置为0。
d.TLB条目分析:
TLB共4个条目,可缓存刚才访问过的4个页面,存储其页面号和相应帧号。定义一个长度为4的整型数组存放TLB,每个条目32位,其中低8位与页表每个条目的低8位相同。逻辑地址空间共8页,故TLB中需要有3位存放刚刚访问过的页面号。TLB的每个条目构成如下:
编辑
3)定义数据结构
#define PSIZE 1024 //页大小
#define DLEN 10 //逻辑地址和物理地址中页内偏移所占的位数
#define PNUM 8 //逻辑页面数
#define PLEN 3 //逻辑地址中页号所占的位数
#define FNUM 16 //物理帧数
#define FLEN 4 //页表条目中物理帧数占4位,可表示16帧
#define TNUM 4 //TLB中缓存的条目数
int PageTable[PNUM]; //页表数组
页表中存储如下帧号(页号是数组下标):
高24位 |
低8位 |
数组赋值 |
|||||
页号 |
扩展位 |
保护位 |
修改位 |
访问位 |
有效位 |
帧号(4位) |
十六进制 |
第0页 |
0x000000 |
0 |
0 |
0 |
0 |
12 (1100) |
0x000C |
第1页 |
0x000000 |
0 |
0 |
0 |
0 |
8 (1000) |
0x0008 |
第2页 |
0x000000 |
0 |
0 |
0 |
0 |
5 (0101) |
0x0005 |
第3页 |
0x000000 |
0 |
0 |
0 |
0 |
10(1010) |
0x000A |
第4页 |
0x000000 |
0 |
0 |
0 |
0 |
15(1111) |
0x000F |
第5页 |
0x000000 |
0 |
0 |
0 |
0 |
0(0000) |
0x0000 |
第6页 |
0x000000 |
0 |
0 |
0 |
0 |
15(1111) |
0x000F |
第7页 |
0x000000 |
0 |
0 |
0 |
0 |
15(1111) |
0x000F |
开始地址转换之前,先根据进程实际页面数,刷新有效无效位。
int TLB[TNUM]; //快表数组
块表需要保存页号和帧号,高位中需要3个二进制位保存页号。
初始化全为0,无任何条目,假设依次访问第0,1,2,4,页,则TLB中的条目为0,若下一个地址需要范围第3页,则发生TLB替换。
11-31位 |
8-10位 |
低8位 |
|||||
扩展位 |
页号 |
保护位 |
修改位 |
访问位 |
有效位 |
帧号(4位) |
十六进制 |
全0 |
000 |
0 |
0 |
0 |
1 |
12 (1100) |
0x001C |
全0 |
001 |
0 |
0 |
0 |
1 |
8 (1000) |
0x0118 |
全0 |
010 |
0 |
0 |
0 |
1 |
5 (0101) |
0x0215 |
全0 |
100 |
0 |
0 |
0 |
1 |
10(1010) |
0x001A |
4)MMU地址转换算法
MMU地址转换过程(不含TLB):
算法描述:
MMU(){
Step1:解析逻辑地址,取得页号和帧号(使用移位>>和按位与&操作)
d=v_addr&0x03FF;//逻辑地址的低10位 p=v_addr%1024
p=v_addr>>DLEN;//逻辑地址右移10位,取页号 p=v_addr/1024
Step2: 根据p查找TLB,TLB命中返回f ,不命中返回-1;
f=SearchTLB();
Step3: 如果f==-1,直接访问PageTable[p],解析条目取得帧号f以及有效无效位v;
v=(PageTable[p]>>FLEN)&0x1; //取有效无效位
若v==0, 地址错误,退出
若 v==1, 则提取低4位,得到帧号f=PageTable[p]&0xF;
Step5: 计算物理地址
p_addr=(f<<DLEN)+d;
Step6: 更新页表的访问位和修改位
Step7: 将该页缓存到TLB中。UpdateTLB();
}
三、测试数据
1)输入进程大小:7000(字节)
查看页表,有效页面数量是否正确
2)依次输入逻辑地址:
9000:越界
8000:无效页
7000:内部碎片区域
6000:
6500:
5000:
4500:
0:
4000:
3000:
1000:
2000:
验证每个逻辑地址到物理地址的转换是否正确,以及页表和TLB的信息是否正确
四、结果与分析
初始值7000
编辑
分析:[7000/1024]=7(向上取整),慢表中确实有7条有效位为1的条目
8000
编辑
分析:8000超过了7000B进程的范围,内容正确
编辑
分析:由于存储在内存中的数据地址是0位开始的,而我存入的进程的大小为7000B,而现在我查询的数据逻辑地址为7000,就刚刚好超过了进程的逻辑地址的下表,所以虽然是在有效页面中,但是是子啊内部碎片区域
6000
编辑
分析:快表未满,更新快表的第一条
5000
编辑
分析:快表未满,更新快表的第二条
4500
编辑
0
编辑
分析:快表未满,更新快表的第三条
4000
编辑 分析:快表未满,更新快表的第四条
3000
编辑
分析:快表已满,随机更新快表
1000
编辑
2000
编辑
分析:快表已满,随机更新快表
五、代码
#include <stdio.h> #include <stdlib.h> #define PSIZE 1024 //页大小 #define DLEN 10 //逻辑地址和物理地址中页内偏移所占的位数 #define PNUM 8 //逻辑页面数 #define PLEN 3 //逻辑地址中页号所占的位数,页号为0~7,故TLB中需要3位二进制来保存页号 #define FNUM 16 //物理帧数 #define FLEN 4 //页表条目中物理帧数占4位,可表示16帧 #define TNUM 4 //TLB中缓存的条目数 //初始化慢表和快表 int PageTable[PNUM]={0x000C,0x0008,0x0005,0x000A,0x000F,0x0000,0x000E,0x000F}; int TLB[TNUM]={0x0000,0x0000,0x0000,0x0000}; //展示页表具体信息 void showtable() { //TLB会先转换为2进制 //>>n位相当于把后面的n个位都去掉, 然后与0x1位与操作相当于就把最后一位给掏出来 printf("\n快表TLB:\n"); printf("页号 保护位 修改位 访问位 有效位 帧号 十六进制\n"); for(int i=0;i<TNUM;i++) { printf(" %d\t",TLB[i]>>8); //8号位为页号 printf(" %d\t",(TLB[i]>>7)&0x1); //7号位为保护位 printf(" %d\t",(TLB[i]>>6)&0x1); //6号位为修改位 printf(" %d\t",(TLB[i]>>5)&0x1); //5号位为访问位 printf(" %d\t",(TLB[i]>>4)&0x1); //4号位为有效位 printf(" %d\t",TLB[i]&0xF); //0~3号位为帧号 printf(" 0x%04X\n",TLB[i]); //直接输出为4位16进制,不足的补上0 } printf("\n慢表PageTable:\n"); printf("页号 保护位 修改位 访问位 有效位 帧号 十六进制\n"); for(int i=0;i<PNUM;i++) { printf("第%d页\t",i); //与快表取法相同 printf(" %d\t",(PageTable[i]>>7)&0x1); printf(" %d\t",(PageTable[i]>>6)&0x1); printf(" %d\t",(PageTable[i]>>5)&0x1); printf(" %d\t",(PageTable[i]>>4)&0x1); printf(" %d\t",PageTable[i]&0xF); printf(" 0x%04X\n",PageTable[i]); } } int UpdateTLB(int ppp) { printf("更新TLB\n"); int i; int sum=0; for( i=0;i<TNUM;i++) //遍历TLB { if(TLB[i]>>8&0x7==ppp){ printf("快表找到了!!!\n");return 0;} //找到条目就溜了 if(TLB[i]==0x0000){ sum++;} } if(sum!=0) { for(int j=0;j<TNUM;j++) {if(TLB[j]==0x0000){ printf("替换TLB中第%d个条目!\n",j+1); TLB[j]=(ppp<<8)+(PageTable[ppp]&0x00FF); return 0;} } } else { int k= rand()%TNUM; printf("替换TLB中第%d个条目!\n",k+1); TLB[k]=(ppp<<8)+(PageTable[ppp]&0x00FF); } return 0; } int SearchTLB(int m) // 1.找页号 2.查看有效位 { int p,valid; //p页号,valid有效无效位 int i=0; for( i;i<TNUM;i++) { p=TLB[i]>>8&0x7;//找页号 if(p==m) { valid=(TLB[i]>>4)&0x1;//查看有效位 if(valid==1)return TLB[i]&0xF;// 若该页有效,取帧号 else return -1; } } if(i==TNUM)return -1;//没找到帧号 } int MMU( int logica,int size,int fangwen) //modify表示访问方式 { int physicsa,d,p,valid,f; //p页号,d页内偏移,f帧号,valid有效位 int i=0; d=logica&0x03FF;//逻辑地址的低10位 p=l_addr%1024 p=logica>>DLEN;//逻辑地址右移10位,取页号 p=l_addr/1024 printf("进程长度:%dB,页内偏移%d,页号%d\n",size,d,p); f = SearchTLB(p); if (f == -1) printf("快表无数据,访问慢表\n"); else { printf("快表有数据,直接输出\n"); physicsa = (f << DLEN) + d; return physicsa; } valid=(PageTable[p]>>FLEN)&0x1; if(valid==0) { printf("无效页面\n");return -1;}//无效页 else { printf("有效页面\n"); if(logica>=size) //内部碎片地址 { printf("内部碎片区域\n"); return -1; } if(fangwen==1){ PageTable[p]|=0x0040;}//更新 6号修改位 PageTable[p]|=0x0020;//更新 5号访问位 f=PageTable[p]&0xF;//取低4位, 得到帧号 physicsa=(f<<DLEN)|d;//计算物理地址 UpdateTLB(p); return physicsa; } } int main() { //初始化进程,修改慢表,然后show一下 int P,logica,physicsa; //logica测试的逻辑地址,physicala转化的物理地址。 printf("请输入进程大小\n"); do{ scanf("%d",&P); if(P>=PNUM*PSIZE||P<0) {printf("小老弟你真皮,越界输入\n"); } }while(P>=PNUM*PSIZE||P<0); for(int i=0;i<P/PSIZE+1;i++) { PageTable[i] +=0x0010; } showtable(); //测试逻辑地址 while(true){ printf("请输入一个测试逻辑地址\n"); scanf("%d",&logica); if(logica<0) {printf("小老弟你真皮,地址越界\n");continue;} if(logica>=PNUM*PSIZE) { printf("小老弟你真皮,地址越界\n");continue;} int m=rand()%2; //随机确定是否写页面 if(m==1) //写界面 { printf("\n************************************************写页面**************************************************\n"); printf("\n********************************************************************************************************\n"); physicsa=MMU(logica,P,1); } else //读界面 { printf("\n************************************************读页面*************************************************\n"); printf("\n*******************************************************************************************************\n"); physicsa=MMU(logica,P,0); } if(physicsa<0) printf("内存管理失败\n"); else{ printf("恭喜您,地址转化成功"); printf("由逻辑地址:0x%04X转化物理地址为:0x%04X\n",logica,physicsa); showtable(); } } return 0; }