【操作系统--MMU内存管理单元】Linux环境下C语言详解(附代码)

本文涉及的产品
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时计算 Flink 版,5000CU*H 3个月
实时数仓Hologres,5000CU*H 100GB 3个月
简介: 【操作系统--MMU内存管理单元】Linux环境下C语言详解(附代码)

 一、内容与要求

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。

image.gif编辑

b.物理地址构成分析:

物理地址空间16KB(0 ~16383B),故用14个二进制表示一个物理地址。帧大小与页大小相同,物理地址空间分成16帧,故物理地址的高4位为帧号f,低10位表示页内偏移d。

image.gif编辑

c.页表条目分析:

页表共8个条目,定义一个长度为8的整型数组来存放页表,故每个条目占32位。物理地址空间共分16帧,故每个条目需要4位存储帧号,其它位用于控制位和扩展位,本实验设计的模拟程序仅需维护低8位。页表的每个条目构成如下:image.gif编辑

有效无效位:小于进程所需的实际页面数的条目的有效无效位设置为1,表示有效页,反之,设置为0,表示无效页,MMU不解析无效页的地址。

修改位:某次访问地址是否修改所在页,本实验中对某地址随机决定是否修改页面,若地址访问将写页面,设置为1,若地址访问仅读页面设置为0。

访问位:访问地址时设置其所在页面的访问位为1,与修改位一起用于TLB的替换算法。

保护位:表示访问权限,1表示只读,0表示读/写,本实验全部页面设置为0。

d.TLB条目分析:

TLB共4个条目,可缓存刚才访问过的4个页面,存储其页面号和相应帧号。定义一个长度为4的整型数组存放TLB,每个条目32位,其中低8位与页表每个条目的低8位相同。逻辑地址空间共8页,故TLB中需要有3位存放刚刚访问过的页面号。TLB的每个条目构成如下:

image.gif编辑

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

image.gif编辑

分析:[7000/1024]=7(向上取整),慢表中确实有7条有效位为1的条目

8000

image.gif编辑

分析:8000超过了7000B进程的范围,内容正确

image.gif编辑

分析:由于存储在内存中的数据地址是0位开始的,而我存入的进程的大小为7000B,而现在我查询的数据逻辑地址为7000,就刚刚好超过了进程的逻辑地址的下表,所以虽然是在有效页面中,但是是子啊内部碎片区域

6000

image.gif编辑

分析:快表未满,更新快表的第一条

5000

image.gif编辑

分析:快表未满,更新快表的第二条

4500

image.gif编辑

0

image.gif编辑

分析:快表未满,更新快表的第三条

4000

image.gif编辑 分析:快表未满,更新快表的第四条

3000

image.gif编辑

分析:快表已满,随机更新快表

1000

image.gif编辑

2000

image.gif编辑

分析:快表已满,随机更新快表

五、代码

#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;
}

image.gif


相关实践学习
CentOS 8迁移Anolis OS 8
Anolis OS 8在做出差异性开发同时,在生态上和依赖管理上保持跟CentOS 8.x兼容,本文为您介绍如何通过AOMS迁移工具实现CentOS 8.x到Anolis OS 8的迁移。
目录
打赏
0
0
0
0
25
分享
相关文章
使用阿里云操作系统控制台排查内存溢出
操作系统控制台是阿里云最新推出的一款智能运维工具,专为提升运维效率、优化服务器管理而设计。它集成了多种运维管理功能,包括操作系统助手、插件管理器以及其他实用工具,为用户提供一站式的运维解决方案。无论是个人开发者还是企业运维团队,都可以通过这一平台轻松管理服务器和操作系统。
65 18
 使用阿里云操作系统控制台排查内存溢出
Linux 手动安装快速部署 LNMP 环境实战
本文详细记录了在阿里云ECS上手动搭建LNMP环境的过程,系统选用Ubuntu 24.04。主要内容包括:1) 使用`apt`安装Nginx和MySQL,并更新软件源;2) 编译安装PHP 8.4.5,配置PHP-FPM及环境路径;3) 配置MySQL root用户密码;4) 调整Nginx支持PHP解析并测试整体环境。通过此过程,重现手动配置服务器的细节,帮助熟悉各组件的安装与协同工作。
Linux云服务器如何搭建LNMP环境
LNMP环境是Linux系统中常用的Web服务架构,由Linux、Nginx、MySQL/MariaDB和PHP组成,适用于高效托管动态网站。本文以CentOS 7为例,详细介绍了LNMP环境的搭建步骤,包括Nginx、MariaDB和PHP的安装与配置,以及最终通过创建`index.php`文件验证环境是否成功部署。具体操作涵盖配置YUM仓库、安装服务、编辑配置文件、启动服务等关键步骤,确保用户能够顺利搭建并运行LNMP环境。
53 1
Linux云服务器如何搭建LNMP环境
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
(已成功解决)Linux环境报错—bash: wget: command not found;常见Linux发行版本,Linux中yum、rpm、apt-get、wget的区别;Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
1203 68
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
如何在阿里云的linux上搭建Node.js编程环境?
本指南介绍如何在阿里云Linux服务器(Ubuntu/CentOS)上搭建Node.js环境,包含两种安装方式:包管理器快速安装和NVM多版本管理。同时覆盖全局npm工具配置、应用部署示例(如Express服务)、PM2持久化运行、阿里云安全组设置及外部访问验证等步骤,助你完成开发与生产环境的搭建。
【手把手教你Linux环境下快速搭建Kafka集群】内含脚本分发教程,实现一键部署多个Kafka节点
本文介绍了Kafka集群的搭建过程,涵盖从虚拟机安装到集群测试的详细步骤。首先规划了集群架构,包括三台Kafka Broker节点,并说明了分布式环境下的服务进程配置。接着,通过VMware导入模板机并克隆出三台虚拟机(kafka-broker1、kafka-broker2、kafka-broker3),分别设置IP地址和主机名。随后,依次安装JDK、ZooKeeper和Kafka,并配置相应的环境变量与启动脚本,确保各组件能正常运行。最后,通过编写启停脚本简化集群的操作流程,并对集群进行测试,验证其功能完整性。整个过程强调了自动化脚本的应用,提高了部署效率。
【手把手教你Linux环境下快速搭建Kafka集群】内含脚本分发教程,实现一键部署多个Kafka节点
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
93 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
探索操作系统核心:从进程管理到内存分配
本文将深入探讨操作系统的两大核心功能——进程管理和内存分配。通过直观的代码示例,我们将了解如何在操作系统中实现这些基本功能,以及它们如何影响系统性能和稳定性。文章旨在为读者提供一个清晰的操作系统内部工作机制视角,同时强调理解和掌握这些概念对于任何软件开发人员的重要性。
深入理解操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅,从进程管理的基本概念出发,逐步探索到内存管理的高级技巧。我们将通过实际代码示例,揭示操作系统如何高效地调度和优化资源,确保系统稳定运行。无论你是初学者还是有一定基础的开发者,这篇文章都将为你打开一扇了解操作系统深层工作原理的大门。
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
114 6