哈工大-操作系统-HitOSlab-李治军-实验6-内存映射和共享

实验6-内存映射和共享

实验内容请查看实验指导手册

绪论

老规矩,我们来梳理一下,这个实验做了什么事情:

  1. 地址翻译:

    1. 在Bochs中运行一个test.c文件,先是通过相应的命令语句,查看到了程序中i的逻辑地址为ds:0x3004
    2. 通过寄存器ldtrgdtr我们获取到了LDT表的内容,通过ds段寄存器存放的段选择子0x0017,我们在LDT表的对应位置找到了ds段的段描述符为“0x00003fff 0x10c0f300”
    3. 有了ds段描述符,我们通过组合得到段ds的线性地址为0x10003004。通过查页表,最终我们成功找到了变量i的物理地址:0x00fa6004

    地址翻译的过程如下图所示:
    在这里插入图片描述

  2. 共享内存的生产者/消费者程序:

    1. 编写一个内核程序shm.c,自己实现两个函数shmget(), shmat()。在shmget()函数中,调用get_free_page()找到一块空闲的物理内存;在shmat()函数中,调用put_page()函数将物理内存的物理地址转换为线性地址。

一、实验内容

1.跟踪地址翻译过程

(1)准备
在Ubuntu的终端中通过sudo ./mount-hdc命令挂载虚拟机,在(hdc/usr/root)目录下新建test.c,代码如下:

#include <stdio.h>

int i = 0x12345678;

int main(void)
{
    
    
    printf("The logical/virtual address of i is 0x%08x", &i);
    fflush(stdout);

    while (i)
        ;

    return 0;
}

使用命令sudo umount hdc退出挂载,再在终端中依次输入./dbg-asm, c启动Bochs
在这里插入图片描述
在Bochs中编译并运行test.c,得到以下结果:
在这里插入图片描述
只要test.c不变,0x00003004这个值在任何人的机器上都是一样的。即使在同一个机器上多次运行test.c,也是一样的。

test.c是一个死循环,只会不停占用CPU,不会退出。

(2)暂停
test.c运行的时候,在命令行窗口按“ctrl+c”,Bochs会暂停运行,进入调试状态。绝大多数情况下都会停在test内,显示类似如下信息:

(0) [0x00fc8031] 000f:00000031 (unk. ctxt): cmp dword ptr ds:0x3004, 0x00000000 ; 833d0430000000

其中加粗的“000f”如果是“0008”,则说明中断在了内核里。那么就要c,然后再ctrl+c,直到变为“000f”为止。

如果显示的下一条指令不是“cmp …”,就用“n”命令单步运行几步,直到停在“cmp …”,如下图所示:
在这里插入图片描述
使用命令“u /7”,显示从当前位置开始7条指令的反汇编代码,如下:
在这里插入图片描述
这就是test.c中从while开始一直到return的汇编代码。变量i保存在ds:0x3004这个地址(这个地址就是上课时学到的虚拟地址)并不停地和0进行比较,直到它为0,才会跳出循环。

现在,开始寻找ds:0x3004对应的物理地址。

(3)段表
ds:0x3004是虚拟地址,ds这两个字母表明这个虚拟地址属于ds段。

要将虚拟地址翻译成物理地址,首先要找到段表,然后通过ds的值在段表中找到ds段的具体信息,才能继续进行地址翻译。每个在IA-32上运行的应用程序都有一个段表,叫LDT,段的信息叫段描述符。

LDT在哪里呢?ldtr寄存器是线索的起点。为了能让CPU定位LDT表,设置了ldtr寄存器。通过它可以在GDT(全局描述符表)中找到LDT的物理地址。

用“sreg”命令:
在这里插入图片描述
可以看到ldtr的值是0x0068=0000000001101000(二进制),表示LDT表存放在GDT表的1101(二进制)=13(十进制)号位置(每位数据的意义参考后文叙述的段选择子)。而GDT的位置已经由gdtr明确给出:在物理地址的0x00005cb8处。

用“xp /32w 0x00005cb8”查看GDT表的前16项,如下:
在这里插入图片描述
GDT表中的每一项占64位(8个字节),要查找的是GDT表的13号位置,该处的地址应该是0x00005cb8 + 13*8

使用命令:xp /2w 0x00005cb8 + 13*8
在这里插入图片描述
这里我们得到了两个地址:0x52d00068, 0x000082fd。将这两个地址,同之前“sreg”命令得到的ldtr行中的dldh相比较,如果对得上,那么说明我们的操作没有问题,可以继续后面的步骤

“0x52d00068 0x000082fd” 将其中的加粗数字组合为“0x00fd52d0”,这就是 LDT 表的物理地址(为什么这么组合,参考后文介绍的段描述符)。

输入命令 xp /8w 0x00fd52d0,得到:
在这里插入图片描述
这就是LDT表的前4项内容了。后面我们将会用到LDT表中的内容。

(4)段描述符
在保护模式下,段寄存器有另一个名字,叫段选择子,因为它保存的信息是一个段描述符表(Segment Descriptor Table)中某一描述符项在表中的索引值。用这个索引值可以从段描述符表中“选择”出相应的段描述符。

先看看ds选择子的内容,还是用“sreg”命令:
在这里插入图片描述
可以看到,ds的值是0x0017。段选择子是一个16位寄存器,它各位的含义如下图:
在这里插入图片描述

RPL是请求特权级,当访问一个段时,处理器要检查RPL和CPL(放在cs的位0和位1中,用来表示当前代码的特权级),即使程序有足够的特权级(CPL)来访问一个段,但如果RPL(如放在ds中,表示请求数据段)的特权级不足,则仍然不能访问,即如果RPL的数值大于CPL(数值越大,权限越小),则用RPL的值覆盖CPL的值。

.
TI是表指示标记,如果TI=0,则表示段描述符(段的详细信息)在GDT(全局描述符表)中,即去GDT中去查;而TI=1,则去LDT(局部描述符表)中去查。

而我们查到的ds=0x0017=0000000000010111(二进制)。也即RPL=11(二进制),可见是在最低的特权级(因为在应用程序中执行)。

TI=1,表示查找LDT表,索引值为10(二进制)= 2(十进制),表示找LDT表中的第3个段描述符(从0开始编号)。

LDTGDT的结构一样,每项占8个字节(64位)。所以在上面查到的LDT表中第3项“0x00003fff 0x10c0f300”就是搜寻好久的ds的段描述符了。

用“sreg”输出中ds所在行的dldh值可以验证找到的描述符是否正确。
在这里插入图片描述

接下来看看段描述符里面放置的是什么内容:
在这里插入图片描述

可以看到,段描述符是一个64位二进制的数,存放了3块基地址和段限长等重要的数据。
位P(Present)是段是否存在的标记;
位S用来表示是系统段描述符(S=0)还是代码或数据段描述符(S=1);
四位TYPE用来表示段的类型,如数据段、代码段、可读、可写等;
DPL是段的权限,和CPL、RPL对应使用;
位G是粒度,G=0表示段限长以位为单位,G=1表示段限长以4KB为单位;
其他内容就不详细解释了。
段描述符组合成线性地址,就是将段描述符中的基地址由低到高组合在一起。

(5)线性地址

费了很大的劲,我们终于是找到了虚拟地址ds:0x3004所对应的段描述符。实际上我们需要的只有段基址一项数据,即段描述符“0x00003fff 0x10c0f300” 中加粗部分组合成的 0x10000000。这就是ds 段在线性地址空间中的起始地址。用同样的方法也可以算算其它段的基址,都是这个数。

段基址+段内偏移,就是线性地址了。所以ds:0x3004 的线性地址就是:

0x10000000 + 0x3004 = 0x10003004

用“calc ds:0x3004”命令可以验证这个结果。
在这里插入图片描述
发现是对的,当前已经找到了该进程在虚拟内存中的线性地址了,下一步就是找物理地址。

从线性地址计算物理地址,需要查找页表。线性地址变成物理地址的过程如下:
在这里插入图片描述
首先需要算出线性地址中的页目录号、页表号和页内偏移,它们分别对应了32位线性地址的10位+10位+12位,所以0x10003004的页目录号是64,页号3,页内偏移是4。(不信你们可以自己转换成2进制算一下)

IA-32下,页目录表的位置由CR3寄存器指引。“creg”命令可以看到:
在这里插入图片描述
CR3 = 0x00000000说明页目录表的基址为0。看看其内容,“xp /68w 0”:
在这里插入图片描述
页目录表和页表中的内容很简单,是1024个4字节。这4字节(32位)中前20位是物理页框号,后面是一些属性信息(其中最重要的是最后一位P)。

我们需要翻译的线性地址是0x10003004的页目录号是64,用(“xp /w 0+64*4”)查看:
在这里插入图片描述
页目录项的物理页框号为0x00fa9,即页表在物理内存0x00fa9000位置为起点的一块内存中。

0x00fa9000位置开始查找3号页表项,得到(xp /w 0x00fa9000+3*4):
在这里插入图片描述
得到0x00fa6067,其中0x00fa6便是页表项的物理页框号。

(7)物理地址

最终结果马上就要出现了!

线性地址0x10003004对应页表项的物理页框号为0x00fa6,和页内偏移0x004接到一起,得到0x00fa6004,这就是变量i的物理地址。可以通过两种方法验证。

第一种方法是用命令“page 0x10003004”,可以得到信息:“linear page 0x10003000 maps to physical page 0x00fa6000”。
在这里插入图片描述
第二种方法是用命令“xp /w 0x00fa7004”,可以看到:
在这里插入图片描述
这个数值确实是test.ci的初值。

现在,通过直接修改内存来改变i的值为0,命令是:setpmem 0x00fa7004 4 0,表示从0x00fa7004地址开始的4个字节都设为0。然后再用“c”命令继续Bochs的运行,可以看到test.c退出了,说明i的修改成功了,此项实验结束。
在这里插入图片描述

这一部分的实验,u1s1不算很难,刚开始做或者看肯定是有很多不明所以的地方。个人建议做实验的同时,二刷一次李老师的视频,并结合《Linux0.11完全注释》这本书的5.3节,相信你会有很大的收获。

2.基于共享内存的生产者-消费者程序

这个实验要求我们在Linux0.11上,让生产者和消费者共用一块物理内存(实验5是使用文件读写)。这里就涉及到所学的内存管理相关知识。

Linux0.11上是没有shmget(), shmat()函数的,需要自己编写系统调用实现。

不将生产者和消费者放置同一个文件pc.c,而是生产者是producer.c,消费者是consumer.c,两个程序都是单进程的。

通过地址跟踪实验,我们一步步的从逻辑地址——》得到虚拟地址——》找到线性地址——》最后找到物理地址。

现在呢,我们要让两个进程共享一个物理内存,完成实验的思路也即:先开辟一块物理内存——》然后完成物理地址和线性地址之间的映射——》再找到对应的虚拟地址。

1.获得空闲物理页面

实验报告当中也给出了方法:通过调用get_free_page()函数,该函数会帮我们找到内存当中空闲区域并返回该区域起始物理地址。这一步我们在shmget函数中实现:

shmget()函数代码

2.地址映射
(1)
有了空闲的物理页面,接下来需要完成线性地址和物理页面的映射。使用put_page(addr1, addr2)建立物理地址addr1和线性地址addr2的映射。

这部分的代码,我们放在函数shmat()中执行:

shmat()代码

3.寻找空闲的虚拟地址空间
有了空闲物理页面,也有了建立线性地址和物理页面的映射,但要完成本实验还需要能获得一段空闲的虚拟地址空闲。

实验结果如下图所示,不知道为什么做不对。生产者生产完10个资源后,消费者不能去消费?
在这里插入图片描述在这里插入图片描述

二、回答问题

  1. 对于地址映射实验部分,列出你认为最重要的那几步(不超过 4 步),并给出你获得的实验数据。

    (1)获得程序中变量i的逻辑地址:ds:3004
    (2)查LDT表得到段描述符:“0x00003fff 0x10c0f300”
    (3)通过段描述符得到线性地址:0x10003004
    (4)查页表得到物理地址:0x00fa6004

  2. test.c 退出后,如果马上再运行一次,并再进行地址跟踪,你发现有哪些异同?为什么?

    逻辑地址和虚拟地址不变,页目录地址是操作系统放置的, 物理分页变了,所以物理地址变了。原因是每次进程加载后都有64M的虚拟地址空间,导致段基址不同。
    而数据段偏移量不变,这是编译时就设置完毕的。

参考资料:
1.哈工大操作系统实验—lab6:地址映射与共享
2.蓝桥云课-操作系统原理与实践
3.虚拟地址、逻辑地址、线性地址、物理地址
4.Linux-0.11操作系统实验6-地址映射与共享

Guess you like

Origin blog.csdn.net/qq_42518941/article/details/120119828