banner
NEWS LETTER

6-虚拟存储器

Scroll down

虚拟存储器

  • 分别实现实模式、分段式、段页式三种内存的地址转换与数据加载功能。
  1. 地址转换
    • 在MMU类中,实现三个地址转换的方法,将逻辑地址转换为线性地址再转换为物理地址。
    • private String toRealLinearAddr(String logicAddr)
    • private String toSegLinearAddr(String logicAddr)
    • private String toPagePhysicalAddr(String linearAddr)
  2. 数据加载
    • 在Memory类中,实现三个数据加载方法。
    • public void real_load(String pAddr, int len)
    • public void seg_load(int segIndex)
    • public void page_load(int vPageNo)
  3. 融合cache与TLB
    • 将cache与TLB融合到MMU中。
  • 逻辑地址:指令中给出的地址, 48 位(16位段寄存器 + 32位段内偏移量)。
    • CPU在运行指令时,如果想要访问内存,它并不是用直接使用内存的地址访问,而是给出一个由 16 位段寄存器和 32 位段内偏移量拼起来的 48 位的逻辑地址。
      • 比如,如果CPU想知道当前指令的地址,他给出的逻辑地址应该是(CS:EIP)。
      • 其中,CS是 16 位代码段寄存器,EIP是 32 位指令指针寄存器(也就是程序计数器PC)。
  • 线性地址:逻辑地址到物理地址的中间层, 32 位。
    • 如果没有启用分页机制,那么线性地址就等于物理地址
    • 如果启用了分页机制,那么线性地址需要通过再一次变换才能得到物理地址。
  • 物理地址:内存中的地址, 32 位。

实模式

Memory的real_load方法

  • SEGMENT和PAGE均为false时,为实模式
  • 变量:pAddrlen
  1. 直接用地址和长度从磁盘disk.read加载data
  2. 把data再写进内存(注意实模式下,内存地址对应磁盘地址,即pAddr)
  • System.arraycopy(data, 0, memory, start, len);
    • 原数组data从起始位置0,复制到目标数组memory的start位置开始,复制的元素数量为len
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 实模式下从磁盘中加载数据
*
* @param pAddr 实模式下,内存地址对应磁盘地址
* @param len 数据段长度
*/
public void real_load(String pAddr, int len) {
byte[] data = disk.read(pAddr, len);//从磁盘中读取数据(方法在Disk.java中)
//更新数据
int start = Integer.parseInt(Transformer.binaryToInt(pAddr));//start,为内存地址
System.arraycopy(data, 0, memory, start, len);
}

MMU

  • ``toRealLinearAddr`方法: 逻辑地址转线性地址。
    • 段寄存器左移4位 + 段内偏移量的低16位 再补齐32位
  1. 截取前16位段寄存器;再截取offset低16位(实际截取32位),转成int。
    • 在实际计算中,我们把高 16 位看作基址,低 32 位看作偏移量(实际有用的只有低 16 位)
  2. 将段寄存器和偏移量化为整数
  3. 段寄存器左移4位
    • 可以直接使用Java中的<<运算符将整数左移4位
  4. 两个数字相加,转成二进制,高位补0到32位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 实模式下的逻辑地址转线性地址
*
* @param logicAddr 48位 = 16位段寄存器 + 32位offset,计算公式为:①(16-bits段寄存器左移4位 + offset的低16-bits) = 20-bits物理地址 ②高位补0到32-bits
* @return 32-bits实模式线性地址
*/
private String toRealLinearAddr(String logicAddr) {
String segReg = logicAddr.substring(0, 16);//取逻辑地址的前16位作为段寄存器
String offset = logicAddr.substring(16, 48);//取逻辑地址的后32位作为偏移量

//将段寄存器和偏移量化为整数
int segRegInt = Integer.parseInt(Transformer.binaryToInt(segReg));
int offsetLow16 = Integer.parseInt(Transformer.binaryToInt(offset));

//计算20位物理地址
int physicalAddr = (segRegInt << 4) + offsetLow16;

// 将20位物理地址转换为32位
String linearAddr = String.format("%32s", Integer.toBinaryString(physicalAddr)).replace(' ', '0');//转换为32位,并用0填充高位

return linearAddr;
}

分段式

Memory类:实现seg_load段加载方法

  1. 从磁盘上加载该段的数据到内存。
    • 如何从磁盘上读取一整段呢?你应该使用段基址作为访问磁盘的地址,用**段限长(即段大小)**作为读取的长度。
    • 至于段基址和段限长是多少,参考我们3.2.3的规定。
      • private char[] base = new char[32]; // 32位基地址
      • private char[] limit = new char[20]; // 20位限长
    • 那么加载过来之后写到内存的哪里呢?由于分段式下每个段大小只有1MB,不会超出内存大小,所以我们默认把数据放在物理地址为 0 的地方
  2. 除了加载数据,你还需要填好全局描述符表GDT,需要填入的内容还是按照3.2.3的规定进行填写。下面为该规定的原文。
    • 每个由MMU装载进入GDT的段,其段基址均为全 0 ,其限长均为全 1未开启分页时粒度为false,开启分页后粒度为true
  • 变量:segIndex段索引
  1. 获取段描述符:
    • (有方法)通过 segIndex 获取对应的段描述符 segDes
  2. 获取段基址和段限长:
    • 段基址:32位全0
    • 段限长:20位全1
  3. 判断是否开启分页
    • 如果未开启分页模式(PAGE 为 false),
    • 从磁盘中读取数据,并将数据写入内存物理地址为0的地方。
    • 注意,段限长是从0开始计数,因此读取长度需要加1
  4. 更新段描述符:
    • 基址
    • 限长
    • 有效位:validBit 设置为 true 表示段已在内存中
    • 粒度:granularity 设置为 PAGE(未开启分页时粒度为false,开启分页后粒度为true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 段式存储模式下,从磁盘中加载数据.段页式存储中,不用从磁盘中加载数据
*
* @param segIndex 段索引
*/
//在段页式存储管理下,从磁盘加载数据应该是以页为单位的,不再是以段为单位。
// 开启分页之后,一个段应该是4GB。因此开启分页之后,seg_load应该跳过加载数据这一步,它的作用在开启分页之后仅仅是填写GDT,加载数据的任务应该交给page_load来完成。

public void seg_load(int segIndex) {
//获取段描述符
SegDescriptor segDes = getSegDescriptor(segIndex);
//获取段基址(访问磁盘的地址)
String segBase = "00000000000000000000000000000000"; // 32位基址,全0
int baseInt = Integer.parseInt(Transformer.binaryToInt(segBase));
//获取段限长(读取的长度)
String limit = "11111111111111111111"; // 20位限长,全1
int limitInt = Integer.parseInt(Transformer.binaryToInt(limit));

if(!PAGE) {//未开启分页,段式存储,要从磁盘中加载数据,并且写到内存的物理地址为0的地方
//从磁盘中加载数据
byte[] data = disk.read(segBase, limitInt + 1);//注:段限长是一个从 0 开始的计数,因此需要+1才能表示实际的字节数
//更新数据,写到内存的物理地址(destPost)为0的地方
System.arraycopy(data, 0, memory, 0, limitInt);
}

//填好全局描述符表GDT
segDes.base = segBase.toCharArray();
segDes.limit = limit.toCharArray();
segDes.validBit = true;//有效位,为true表示被占用(段已在内存中)
segDes.granularity = PAGE;
}

MMU类:​​​​toSegLinearAddr​​​​ 逻辑地址转线性地址。

  • 在分段式下,逻辑地址转线性地址应该要查全局描述符表GDT,按照2.3.4的流程进行计算。
    • 48 位的逻辑地址包含 16 位的段选择符和 32 位的段内偏移量。
    • MMU首先通过段选择符内的 13 位索引值
    • 从段描述符表中找到对应的段描述符,从中取出 32 位的基地址,与逻辑地址中 32 位的段内偏移量相加,就得到 32 位线性地址。
  • 注意,不要以为可以偷懒直接把逻辑地址的前 16 位去掉
  1. 直接调用已有的函数获得段索引​​​​segIndex​​​​
  2. 根据段索引获得段基址,也是直接调用函数。将段基址转成int
  3. 截取32位段内偏移​​​​offset​​​​,转成int
  4. 段基址与段内偏移相加转成32位二进制,得到线性地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 分段模式下的逻辑地址转线性地址
*
* @param logicAddr 48位 = 16位段选择符(高13位index选择段表项) + 32位段内偏移
* @return 32-bits 线性地址
*/

private String toSegLinearAddr(String logicAddr) {
//获取段索引(高13位index选择段表项)
int segIndex = getSegIndex(logicAddr);
//从全局描述符表GDT中获取段描述符中的段基址base
String segBaseAddr = String.valueOf(Memory.getMemory().getBaseOfSegDes(segIndex));
//提取段内偏移(32位)
String offset = logicAddr.substring(16, 48);
//将段基址和段内偏移转换为整数
int segBaseAddrInt = Integer.parseInt(Transformer.binaryToInt(segBaseAddr));
int offsetInt = Integer.parseInt(Transformer.binaryToInt(offset));
//计算线性地址
int linearAddr = segBaseAddrInt + offsetInt;
//将线性地址转换为32位
String linearAddrStr = String.format("%32s", Integer.toBinaryString(linearAddr)).replace(' ', '0');
return linearAddrStr;
}

段页式

Memory类:page_load页加载方法。

  1. 从磁盘上加载该页数据到内存。
    • 如何在磁盘上读取该页数据呢?你应该使用该虚页的起始地址作为作为访问磁盘的地址,用页大小作为读取的长度。
    • 至于该虚页的起始地址是多少,可以直接根据虚页号得到。
    • 那么加载过来之后写到内存的哪里呢?这就需要你找出一个空闲的物理页框然后放下去啦。
  2. 除了加载数据,你还需要填好页表,如果你使用有效位数组的话还需要填好有效位数组
  • 变量:​​​​vPageNo​​​​ 虚拟页号

1. 加载页数据

  1. 使用该虚页的起始地址作为访问磁盘的地址,起始地址=虚页号×一页大小
  2. 由于一页的大小是4KB,需要把虚拟页号乘以2的12次方
    • 即转为二进制作为地址,二进制后面加12个0
  3. 磁盘读出一页数据data。(长度为​​​​PAGE_SIZE_B​​​​页长)

2. 写入内存

  1. 寻找空闲内存的物理页框——遍历valid找false。
  • 范围​​​​0 ~ pageValid.length​​​​,
    • 当​​​pageValid[i]​​​​为​false​​​​,说明不在内存中,即空闲,将其改为占用,并记录页框号​​​​frameNO​​​​。
  1. 接着将数据装入页框。将页框号×页大小,获得物理地址,将数据写入内存。(长度为​​​​PAGE_SIZE_B​​​​页长)

3. 填页表

  1. 由虚拟页号获得该页(​​​​getPageItem(vPageNo)​​​​),
  • 将​​​​pageFrame​​​​设为页框号二进制的低20位转成的数组(物理页框号)
  • 将​​​​isInMem​​​​设为true。(装入了内存)
  1. 填好有效位(刚刚占用的设为true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 段页式存储下,从磁盘中加载数据
* 不考虑16MB内存用满的情况
*
* @param vPageNo 虚拟页号
*/
public void page_load(int vPageNo) {
//虚拟页号的起始地址
String vPageStartAddr = Transformer.intToBinary(String.valueOf(vPageNo)).substring(12) + "000000000000";
//页大小作为读取的长度
int size = PAGE_SIZE_B;
//从磁盘中加载数据
byte[] data = disk.read(vPageStartAddr, size);

//找出一个空闲的物理页框
int freePageFrame = -1;
for (int i = 0; i<pageValid.length; i++){
if (!pageValid[i]){//如果该页不在内存中,即valid=false
freePageFrame = i;//找到空闲的物理页框i
break;
}
}
if (freePageFrame == -1){//无空闲
throw new RuntimeException("No free page frame");
}

//数据写到内存的物理地址为freePageFrame的地方
int phyAddr = freePageFrame * PAGE_SIZE_B;//物理地址,即物理页框号*页大小
System.arraycopy(data, 0, memory, phyAddr, size);

//填好页表
PageItem pageItem = getPageItem(vPageNo);
pageItem.pageFrame = Transformer.intToBinary(String.valueOf(freePageFrame)).substring(12).toCharArray();//物理页框号
pageItem.isInMem = true;//装入位

//填好有效位数组
pageValid[freePageFrame] = true;
}

修改seg_load方法。

  • 因此开启分页之后,seg_load应该跳过加载数据这一步,它的作用在开启分页之后仅仅是填写GDT,加载数据的任务应该交给page_load来完成。
  • if(!PAGE) {加载数据}

MMU类:toPagePhysicalAddr页级地址转换方法

  • 在段页式下,线性地址转物理地址需要查页表,然后进行虚拟页号到物理页号的替换,具体流程可以参考课件。
  1. 线性地址提取前 20 位作为虚拟页号后 12 位作为页内偏移
  2. 物理页号的获取(TLB是否可用):都是使用 getFrameOfPage 函数,区别在于类不同
    1. 如果开启了tlb,TLB
      • 调用tlb类的函数,由虚拟页号获取
    2. 未开启,内存
      • 调用Memory类的函数,由虚拟页号获取
  3. 最后,将物理页号与页内偏移拼接获得物理地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 段页式下的线性地址转物理地址
*
* @param linearAddr 32位
* @return 32-bits 物理地址
*/
private String toPagePhysicalAddr(String linearAddr) {
// TODO
//获取虚拟页号(线性地址的高20位)+获取页内偏移(线性地址的低12位)
String vPageNo = linearAddr.substring(0, 20);
String offset = linearAddr.substring(20);
int vPageNoInt = Integer.parseInt(Transformer.binaryToInt(vPageNo));
//查页表,获取物理页号
char[] pageFrame;
if(TLB.isAvailable){
//如果TLB可用,从TLB中获取物理页号
pageFrame = TLB.getTLB().getFrameOfPage(vPageNoInt);
}else {
//如果TLB不可用,从内存中获取物理页号
pageFrame = Memory.getMemory().getFrameOfPage(vPageNoInt);
}
String pageFrameStr = String.valueOf(pageFrame);
//将物理页号和页内偏移拼接为物理地址
String physicalAddr = pageFrameStr + offset;

return physicalAddr;
// TODO: add tlb here
}

4. cache与TLB的融合

a. 将cache融合进MMU中

  • 这一步相对简单。需要注意,由于cache是memory的缓存,所以任何涉及到访问主存数据的地方都要添加对cache的调用。
  • 只有两个标了todo的地方需要改,MMU类的read和write。直接加入当cache有效时,通过cache读/写即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public byte[] read(String logicAddr, int length) {
String physicalAddr = addressTranslation(logicAddr, length);
// TODO: add cache here
if (Cache.isAvailable) {//如果cache可用
return cache.read(physicalAddr, length);//从cache中读取数据
}
//如果cache不可用,直接从内存中读取数据
return memory.read(physicalAddr, length);
}

public void write(String logicAddr, int length, byte[] data) {
String physicalAddr = addressTranslation(logicAddr, length);
// TODO: add cache here
if (Cache.isAvailable) {//如果cache可用
cache.write(physicalAddr, length, data);//将数据写入cache
}
//无论cache是否可用,都要将数据写入内存
memory.write(physicalAddr, length, data);
}

b. 将TLB融合进MMU中

  1. MMU类addressTranslation函数。在标出的地方判断
    • 若TLB有效:若tlb中没有该页内存中没有该页,缺页中断
      • 内存从磁盘加载该页的数据。
      • 并且将该页写入tlb。tlb.write(i);
    • 若TLB无效:直接判断内存中有没有该页(应该是有代码的)
1
2
3
4
5
6
7
8
9
10
11
12
13
// TODO: add tlb here
//1. 启用TLB之后,判断是否缺页的工作应该首先交给TLB来完成。
//2. 如果发生缺页,page_load方法会进行填页表,填页表之后不要忘记填TLB。
if (TLB.isAvailable){
if(!tlb.isValidPage(i)){//如果TLB中不存在该页
// 缺页中断,该页不在内存中,内存从磁盘加载该页的数据
memory.page_load(i);//从磁盘中加载数据,并填写页表
tlb.write(i);//填写TLB
}
}else if (!memory.isValidPage(i)) {
// 缺页中断,该页不在内存中,内存从磁盘加载该页的数据
memory.page_load(i);
}
  1. toPagePhysicalAddr方法,上面讲过了。
1
2
3
4
5
6
7
if(TLB.isAvailable){
//如果TLB可用,从TLB中获取物理页号
pageFrame = TLB.getTLB().getFrameOfPage(vPageNoInt);
}else {
//如果TLB不可用,从内存中获取物理页号
pageFrame = Memory.getMemory().getFrameOfPage(vPageNoInt);
}

If you like my blog, you can approve me by scanning the QR code below.

Other Articles
Article table of contents TOP
  1. 1. 虚拟存储器
  2. 2. 实模式
    1. 2.1. Memory的real_load方法
    2. 2.2. MMU
  3. 3. 分段式
    1. 3.1. Memory类:实现seg_load段加载方法
    2. 3.2. MMU类:​​​​toSegLinearAddr​​​​ 逻辑地址转线性地址。
  4. 4. 段页式
    1. 4.1. Memory类:page_load页加载方法。
      1. 4.1.1. 1. 加载页数据
      2. 4.1.2. 2. 写入内存
      3. 4.1.3. 3. 填页表
    2. 4.2. 修改seg_load方法。
    3. 4.3. MMU类:toPagePhysicalAddr页级地址转换方法
  5. 5. 4. cache与TLB的融合
    1. 5.1. a. 将cache融合进MMU中
    2. 5.2. b. 将TLB融合进MMU中
Please enter keywords to search