操作系统实验6:地址映射与共享

news2025/1/31 22:39:37

本次实践项目有两个基本内容:
(1)用Bochs调试工具跟踪Linux-0.11的地址转换过程;
(2)实现基于共享物理页框的进程间内存共享。

知识点补充

GDT和GDTR

和一个段有关的信息需要 8 个字节来描述,所以称为段描述符(Segment Descriptor),每个段都需要一个描述符。为了存放这些描述符,需要在内存中开辟出一段空间。在这段空间里,所有的描述符都是挨在一起,集中存放的,这就构成一个描述符表。最主要的描述符表是全局描述符表(Global Descriptor Table, GDT)。为了跟踪全局描述符表,处理器内部有一个 48 位的寄存器,称为全局描述符表寄存器(GDTR),该寄存器分为两部分,分别是 32 位的线性地址和 16 位的边界。
在这里插入图片描述

存储器的段描述符格式

在这里插入图片描述

段选择子的组成

在这里插入图片描述
在保护模式下访问一个段时,传送到段选择器的是段选择子。它由三部分组成,第一部分是描述符的索引号,用来在描述符表中选择一个段描述符。 TI 是描述符表指示器(Table Indicator), TI=0 时,表示描述符在 GDT 中; TI=1 时,描述符在 LDT 中。 RPL 是请求特权级,表示给出当前选择子的那个程序的特权级别,正是该程序要求访问这个内存段。

地址转换过程跟踪

要跟踪地址转换过程,首先需要以汇编级调试的方式启动Bochs,即在编译好Linux-0.11后,通过运行命令./dbg-asm来启动调试器,此时Bochs模拟器会处于黑屏状态,执行命令的宿主主机窗口中的显示如下图所示:
在这里插入图片描述
"Next at t=0"表示下面执行的指令是Bochs启动后要执行的第一条指令,单步跟踪进去就能看到BIOS代码。现在直接输入命令“c”,即继续运行程序,Bochs和以前一样启动Linux-0.11。
现在需要在Linux-0.11上编写一个测试程序test.c,要跟踪的地址就是这个程序中的地址。test.c代码如下:

#include <stdio.h>

int i = 0x12345678;

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

将test.c拷贝到Linux-0.11上编译、运行,运行输出如下:

The logical address of i is 0x00003004

由于打印的是逻辑地址,即离开程序段首的偏移地址,所以只要程序test.c不发生变化,0x00003004这个值也是不会变化的,即在同一机器上多次运行test.c,这个逻辑地址也是一样的。
由于test.c中有一个死循环,所以这个程序不会主动退出,正是这样,其各种资源,如逻辑地址、LDT表、GDT表、页表等信息才能在调试器中用调试命令查看。
现在在Bochs命令行窗口按下Ctrl + c键,Bochs会暂停运行,进入调试状态。此时的Bochs会有很大的可能是在test.c中运行,因为此时Linux-0.11中进程很少。宿主机调试器窗口会显示类似如下信息:
在这里插入图片描述
若其中的"000f"显示为"0008",则说明按下Ctrl+c中断发生在内核中,这时需要输入c继续执行,然后再按下Ctrl+c直到变为"000f"为止。如果显示的指令不是cmp,就用"n"命令单步运行几步,直到停在cmp指令上,实际上就是停在while(i)语句处。然后用“u/8命令”,显示从当前位置开始的8条指令的反汇编代码,如下图所示:
在这里插入图片描述
这正是从while(i)开始到return语句的汇编代码。不难分析出,变量i就保存在地址DS:0x3004处。cmp指令要将DS:3004处存放的内容和0就行比较,只有等于0才跳出循环,即执行"jz .+0x00000004"。
现在要开始寻找逻辑地址DS:0x3004对应的物理地址,即开始跟踪地址转换过程。由于是段页式内存结构,所以要先用段表找到虚拟地址。DS:0x3004是逻辑地址,DS表明这个地址属于DS段,只有找到进程对应的段表以后,才能通过DS寄存器的值在段表中找到DS段的具体信息,得到虚拟地址。这个段表就是进程的LDT表,接下来就要找到这个LDT表,LDTR就是起点。LDTR寄存器中存放的是当前进程LDT表地址在GDT表中的偏移值。
用"sreg"命令可以看到各个寄存器的信息:
在这里插入图片描述
可以看到ldtr的值是0x0068 = 0000000001101000,根据段选择子的结构,当前进程的LDT存放在GDT表中的13(1101)号位置。

GDT表的位置由GDTR寄存器明确给出,即在物理地址0x00005cb8位置处,GDT表中每一项占8个字节,所以我们要查找的LDT表的物理地址是0x00005cb8 + 13 * 8。用命令“xp /2w 0x00005cb8+13*8”可以查看这个位置的内容:
在这里插入图片描述
这两步在不同的机器上执行时得到的数值可能不一样,这是正常的。如果向确认是否正确,就看执行sreg命令后的输出信息中,ldtr所在行里的dl和dh的值,它们是Bachs自动计算出来的,从GDT表中找到的LDT地址应该和Bochs计算出来的一致。
将得到的数字“0xa2d00068 0x000082f9”进行组合,组合方式如下,得到LDT表的物理地址0x00f9a2d0,这就是LDT表的物理地址,组合方式是由段描述符的格式决定的。

执行命令“xp /8w 0x00f9a2d0”可以得到:
在这里插入图片描述
这就是当前进程LDT表的前四项内容了。
现在可以根据DS寄存器来查找LDT表了,由上面"sreg"命令获得的寄存器信息“ds:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=3”,可以直到,DS寄存器的值是0x0017,按照段选择子的格式,0x0017 = 0x0000000000010111,去掉最低三位,剩余的位组合起来得到的数值是2,即偏移为,因此是第三项,即“0x00003fff 0x10c0f300”,这个就是DS段的信息。用同样的组合方式组合“0x00003fff 0x10c0f300”得到DS段的基址为0x10000000,这就是当前进程DS段在虚拟内存空间中的起始地址。因此DS:0x3004对应的虚拟地址为:

0x10000000+3004 = 0x10003004

现在已经得到了虚拟地址,接下来就要将其转换为物理地址,核心就是查找页表。首先要计算出虚拟地址中的页目录号、页表号和页内偏移,它们分别对应了虚拟地址的前10位、中间10位和末尾的12位。不难计算出,虚拟地址0x10003004对应的页目录号是64,页号是3,页内偏移是4。页目录表的位置由CR3寄存器给出,用“creg”命令可以看到:
在这里插入图片描述
说明页目录表的基址为0。页目录表和表中内容都很简单,就是1024个32位二进制树,这32位中的前20位表示物理页框号,后面是一些属性信息(其中最重要的是最后一位P,表示是否有效)。第65页目录项就是要找的内容,用命令“xp /w 0+644”查看:
在这里插入图片描述
其中的027是属性,显然P=1,因此这个页目录项是有效的。因此页表所在物理页框号位0x00fa9,即该页目录对应的1024个页的所有页表项信息存放在物理地址0x00fa9000处,从该位置开始查找第3个页表项,即“xp /w 0x00fa9000+3
4”:
在这里插入图片描述
其中的067是属性,显然P=1,说明页表项也是有效的。
现在已知虚拟地址0x10003004对应的物理页框号为0x00fa7000,将它和页内偏移0x0004连接到一起,得到物理地址为0x00fa7004,这个就是变量i的物理地址,用命令“xp /w 0x00fa7004”查看:
在这里插入图片描述
得到的数值就是变量i的值,说明这个过程是正确的。
现在直接修改内存来改变i的值,使用命令“setpmem 0x00fa7004 4 0”实现,表示从0x00fa7004地址开始的4个字节都设置为0,然后使用“c”命令继续Bochs的运行,可以看到test进程退出了,说明i变量修改成功了。

基于共享物理页框的进程间内存共享的实现

在Linux下,可以通过shmget()和shmat()两个系统调用来使用共享内存。因此本部分的具体实现内容就是在Linux-0.11下添加shmget()和shmat()两个系统调用(Linux-0.11上没有这两个系统调用)。添加系统调用的具体过程可以参照操作系统实验2:系统调用。

shmget()系统调用的函数原型为:

int shmget(key_t key, size_t size, int shmflg);

该系统调用会新建/打开一页物理内存作为共享内存,并返回该页共享内存的shmid,即该页共享内存在操作系统中的标识。如果多个进程使用相同的key调用shmget,则这些进程就会获得相同的shmid,即得到同一块内存的标识。在shmget实现时,如果key所对应的共享内存已经建立,则直接返回shmid,否则新建。如果size超过一页内存的大小,返回-1,并置errno为EINVAL。如果系统无空=空闲内存,返回-1,并置errno为ENOMEM。对于本实验,shmflg参数忽略。

shmat()系统调用的函数原型为:

void* shmat(int shmid,const void *shmaddr, int shmflg);

该系统调用会将shmid指定的共享页面映射到当前进程的虚拟地址空间中,并返回一个逻辑地址p,调用进程可以通过读写逻辑地址p来读写这一页共享内存。如果shmid非法,返回-1,并置errno为EINVAL。对于本实验,参数shmaddr和shmflg都忽略。

因此,两个进程都调用shmat可以关联到同一页内存上,此时两个进程读写p指针就是在读写同一页内存,从而实现了基于共享内存的进程间通信。

下面参照操作系统实验2:系统调用添加系统调用。

  1. 添加系统调用的编号
    系统调用编号在 include/unistd.h中定义,打开该文中找到系统调用编号的定义:
    在这里插入图片描述

  2. 添加IDT(中断描述符表)
    打开include/linux/sys.h文件,在文件中的sys_call_table[]的数组中添加sys_shmget和sys_shmat,注意这里的前后顺序要和之前的系统调用编号的前后关系对应起来。同时,将sys_shmget()和sys_shmat()声明全局函数。
    在这里插入图片描述

  3. 修改系统调用数
    修改kernel/system_call.s中的系统调用数,由原来的72改为74.在这里插入图片描述

  4. 实现sys_shmget()和sys_shmat()

    在kernel/目录下新建一个shm.c文件,用于保存这个两个函数。
    在include/目录下新建一个shm.h文件,用于相关数据类型声明和函数声明。

    sys_shmget()函数的主要作用是获得一个空闲的物理页面,可以通过调用已有的get_free_page()函数来实现。

在这里插入图片描述
sys_shmat()的主要作用是将这个页面和进程的虚拟地址以及逻辑地址关联起来,让进程对某个逻辑地址的读写就是在读写该内存页。该函数首先要完成虚拟地址和物理页面的映射,核心就是填写页表,在Linux-0.11中的函数:

unsigned long put_page(unsigned long page,unsigned long address)

该函数的作用就是完成这样的映射,直接调用即可。函数中的page就是内存页的物理地址,即shm_list[shmid].page,address是虚拟地址。Linux-0.11给每个进程分配了64M的虚拟内存,其分布如下图所示。
在这里插入图片描述
可以看出,brk和start_stack之间的虚拟内存并没有使用,因此可以在这里分割一个虚拟内存页和那个物理内存页建立映射。brk和start_stack都是存储在进程的PCB中,可以用current->brk找到当前进程的brk,当前进程开始的虚拟地址存放在current->ldt[1]中,可以用get_base(current->ldt[1])获得,因此该虚拟内存页的虚拟地址为get_base(current->ldt[1]) + current->brk.
这样调用put_page函数就可以建立映射关系了。最后需要更新brk指针的指向,并返回虚拟内存页的逻辑地址,即原来的brk。

shm.h文件的内容如下:

#include <stddef.h>     


typedef unsigned int key_t;

struct struct_shmem
{
    unsigned int size;
    unsigned int key;
    unsigned long page;
};

int shmget(key_t key, size_t size);
void* shmat(int shmid);

#define SHM_NUM  16 

shm.c文件内容如下:

#include <shm.h>
#include <linux/mm.h>        
#include <unistd.h>     
#include <errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>

struct struct_shmem shm_list[SHM_NUM] = {{0,0,0}};

int sys_shmget(key_t key, size_t size)
{
    int i;
    unsigned long page;
    
    if(size > PAGE_SIZE){
        errno = EINVAL;
        printk("shmget:The size connot be greater than the PAGE_SIZE!\r\n");
        return -1;
    }
    if(key == 0){
        printk("shmget:key connot be 0!\r\n");
        return -1;
    }
    //判斷是否已经创建
    for(i = 0; i < SHM_NUM; i++){
        if(shm_list[i].key == key)
            return i;
    }
    page = get_free_page();  //申请内存页
    if(!page){
        errno = ENOMEM;
        printk("shmget:connot get free page!\r\n");
        return -1;
    }
    for(i = 0; i < SHM_NUM; i++){
        if(shm_list[i].key == 0){
            shm_list[i].size = size;
            shm_list[i].key = key;
            shm_list[i].page = page;
            break;
        }
    }
    return i;
}


void* sys_shmat(int shmid)
{
    unsigned long tmp;  //虚拟地址
    unsigned long logicalAddr;
    if(shmid < 0 || shmid >= SHM_NUM || shm_list[shmid].page == 0 || shm_list[shmid].key <= 0){
        errno = EINVAL;
        printk("shmat:The shmid id invalid!\r\n");
        return NULL;
    }
    tmp = get_base(current->ldt[1]) + current->brk;  //计算虚拟地址
    put_page(shm_list[shmid].page,tmp);
    logicalAddr = current->brk;  //记录逻辑地址
    current->brk += PAGE_SIZE;  //更新brk指针
    return (void *)logicalAddr;
}
  1. 修改Makefile文件
    文件位置:kernel/Makefile
    在这里插入图片描述
    在这里插入图片描述
    修改完成之后重新编译整个工程。

  2. 编写测试代码
    这里编写两个测试进程,一个进程向共享内存页中写入数据,另一进程从中读取数据,比对写入和读出的数据就能验证实验过程是否正确。
    test1.c每间隔5秒向共享内存页中写入递增的数据,代码如下:

#define   __LIBRARY__
#include <shm.h>
#include <unistd.h>


static inline _syscall1(void*,shmat,int,shmid);
static inline _syscall2(int,shmget,key_t,key,size_t,size);

int main()
{
    key_t key = 666;
    size_t size = sizeof(int);
    int shmid = shmget(key,size);
    int* p = (int*)shmat(shmid);
    *p = 0;
    while(1){
        (*p)++;
        printf("process1:write  %d\r\n",*p);
        sleep(5);
    }
    return 0;
}

test2.c每间隔5妙从共享内存页中读取数据,代码如下:

#define   __LIBRARY__
#include <shm.h>
#include <unistd.h>


static inline _syscall1(void*,shmat,int,shmid);
static inline _syscall2(int,shmget,key_t,key,size_t,size);

int main()
{
    key_t key = 666;
    size_t size = sizeof(int);
    int shmid = shmget(key,size);
    int* p = (int*)shmat(shmid);
    while(1){
        printf("process2:read   %d\r\n",*p);
        sleep(5);
    }
    return 0;
}

  1. 文件拷贝
    先在lab6目录下,执行下列命令就行挂载:
sudo ./mount-hdc

然后将test1.c和test2.c拷贝到linux-0.11中,命令如下:

cp test1.c test2.c ./hdc/usr/root/

另外,在test1.c和test2.c中用到shm.h和修改过的unistd.h,因此也需要将这两个文件拷贝到Linux-0.11中,命令如下(在lab6目录下):

 cp ./linux-0.11/include/unistd.h ./hdc/usr/include/
 cp ./linux-0.11/include/shm.h ./hdc/usr/include/
  1. 编译并运行测试代码
    运行Linux-0.11,然后编译test1.c和test2.c,命令如下:
gcc -o test1 test1.c 
gcc -o test2 test2.c
sync 

注意,Linux-0.11只有一个终端,而现在需要在一个终端上同时运行两个程序,方法是在命令末尾输入&,命令就会进入后台运行。命令如下:

./test1 &
./test2 &

然后就可看到打印出来的信息了,如下图所示:
在这里插入图片描述
可以看出,写入和读出的数据是一样的。
注意,在实验过程发现,如果一个进程结束会将共享的物理内存页释放,此时如果另一个进程再去读写该物理内存页就会导致错误!

至此,整个实验结束!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/104615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【3D目标检测】Orthographic Feature Transform for Monocular 3D Object Detection

目录概述细节网络结构正交特征变换模块其余部分概述 本文是基于单目图像的3D目标检测方法。 【2018】【OFT-Net】 研究的问题&#xff1a; 在图像表示中&#xff0c;物体的比例会随着深度的变化而变化&#xff1b;物体的外观随着视点的不同而不同&#xff1b;物体的距离&…

看漫画也能学Python?小学生都能学会,《看漫画学python 2》PDF中文超清版,可分享

学习Python的小伙伴大部分应该都知道《看漫画学Python&#xff1a;有趣、有料、好玩、好用&#xff08;全彩版&#xff09;》这本书&#xff01; 《看漫画学Python》进阶版&#xff0c;在第1版的基础上讲解Python进阶知识&#xff0c;帮助读者完善Python知识体系&#xff0c;提…

点云Delaunay三角剖分(三维)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 四面体网格是一种三维非结构化网格,它划分了一个三维域。这种类型的分区有许多有利的性质。例如,它很适合具有任意复杂几何结构的领域,它可以很容易地在局部细化和粗化(没有悬挂节点),并且它可以完全自动地创建…

GitHub上超强Python零基础入门学习资源,让你的学习事半功倍

根据2020年StackOverflow开发者调查报告&#xff0c;Python是世界上最受欢迎的语言之一&#xff0c;排名仅次于Rust和TypeScript。更令人惊讶的是&#xff0c;Python是开发人员最想尝试的语言。 如果你是一位使用Python的开发人员&#xff0c;而且希望提高自己的技术水平&…

【Numpy基础知识】使用genfromtxt导入数据

使用Numpy进行I/O操作 来源&#xff1a;Numpy官网&#xff1a;https://numpy.org/doc/stable/user/basics.html 文章目录使用Numpy进行I/O操作导包【1】定义输入【2】将行拆分为列【3】跳过行和选择列【4】选择数据类型【5】设置名称【6】调整转换【7】快捷键功能NumPy 提供了几…

JAVA面试(不同类型的公司特点)

工作那么多年面试过很多公司&#xff0c;同时也面试过很多求职者。作为一个JAVA程序员&#xff0c;10年风雨路&#xff0c;10年技术路&#xff0c;一路走来&#xff0c;实属不易&#xff0c;JAVA语言从诞生&#xff0c;到发展起来&#xff0c;到风靡全球&#xff0c;到如今的混…

java回顾:私服搭建

目录 一、搭建私服 1.1、搭建私服原因 1.2、nexus软件的安装 1.3、私服的使用 1.4、仓库介绍 1.5、从私服下载资源 1.6、下载失败 1.7、将资源上传到私服 1.7.1 问题 一、搭建私服 1.1、搭建私服原因 公司开发了jar包&#xff0c;可供其他项目组使用、无网络使用…

次世代培训——我们从游戏美术发展历程讲起

自90后们记忆最初的小霸王游戏算起&#xff0c;中国游戏大致经历了从像素时代-预渲染时代-初世代-中世代-次世代这样几个阶段的变革。有趣的是&#xff0c;当主机、PC游戏早已跨过了像素时代&#xff0c;手游的兴起和发展却依然要遵从这一发展规律从头开始&#xff0c;当人们已…

FineReport智能报表工具- CSS修改控件样式

1. 概述 1.1 使用场景 在实际项目中&#xff0c;为了让 FineReport 报表中的控件和页面风格一致&#xff0c; 可以通过 CSS 修改控件的显示样式。 如下图所示&#xff0c;希望参数面板上文本框控件的边框变为红色&#xff0c;字体颜色变为蓝色。 除更换控件字体、修改边框颜色…

从风控中那些被标准化定义的问题谈起

说到风控业务中被标准化的定义问题&#xff0c;作为风控人最应该来谈谈这个内容&#xff0c;尤其是在数字化的风控内容上。数字化风控的标准化程度较高&#xff0c;在互金经过了一番喷薄的发展后&#xff0c;对于SOP守则&#xff0c;业务问题的定义及留给后续从业者可借鉴的资料…

足球人生:青春不过几届世界杯

&#x1f389;花有重开日&#xff0c;人无再少年。将近一个月的卡塔尔世界杯正式落帷幕&#xff0c;见证了最伟大的世界杯决赛&#xff0c;新老两代球王的巅峰对决&#xff0c;节奏拉满。诸神黄昏&#xff0c;球王加冕&#xff0c;最完美的结局。究竟怎样的颠沛流离才配得上这一…

web--拉灯泡切换黑天与白夜的精美动画

功能&#xff1a; 进入界面会出现一个灯泡&#xff08;下面有可以自由飘动也可以自由拉动的绳子&#xff09;&#xff0c;鼠标左键按住不松开可以拉动绳子&#xff0c;松开变化亮起&#xff0c;同时有拉响的清脆声响&#xff0c;把它放在web作业的设计里面绝对是非常非常不错的…

SQL注入总结复习

SQL注入总结复习 一、前提 1、web三层架构 学习SQL注入&#xff0c;前提必须要了解web程序的三层架构。 数据访问层&#xff08;DAL&#xff09;&#xff1a;主要负责对数据库进行增删改查&#xff0c;将存储在数据库中的数据提交给业务层&#xff0c;同时将业务层处理的数据…

第七章 集合相关知识

什么是集合 为了存储不同类型的多个对象, Java提供了一系列特殊的类, 这些类可以存储任意类型的对象&#xff0c;并且存储的长度可变,被统称为集合。集合可以简单理解为一个长度可变可以存储不同数据类型的动态数组。集合都位于java.uti包中,使用集合时必须导入java.util包。 …

前端二面必会手写面试题汇总

实现Vue reactive响应式 // Dep module class Dep {static stack []static target nulldeps nullconstructor() {this.deps new Set()}depend() {if (Dep.target) {this.deps.add(Dep.target)}}notify() {this.deps.forEach(w > w.update())}static pushTarget(t) {if …

LeetCode刷题复盘笔记—一文搞懂动态规划之300. 最长递增子序列问题(动态规划系列第二十七篇)

今日主要总结一下动态规划的一道题目&#xff0c;300. 最长递增子序列 题目&#xff1a;300. 最长递增子序列 Leetcode题目地址 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除…

想要月入过万?不防尝试一下Python这个职业!

自从20世纪90年代初发布以来&#xff0c;Python一直相当火爆&#xff0c;在这二十多年里&#xff0c;它的流行程度远远超过了C、C#、Java甚至Javascript。 Python为什么受欢迎&#xff1f; Python迅猛发展背后的一个主要驱动力是它学习起来相当容易&#xff0c;使用起来功能强…

程序员需要了解的硬核知识CPU

大家都是程序员&#xff0c;大家都是和计算机打交道的程序员&#xff0c;大家都是和计算机中软件硬件打交道的程序员&#xff0c;大家都是和CPU打交道的程序员&#xff0c;所以&#xff0c;不管你是玩儿硬件的还是做软件的&#xff0c;你的世界都少不了计算机最核心的 - CPU C…

【C语言进阶】通讯录不好用?进来,零基础带你写出自己的通讯录

目录 &#x1f929;前言&#x1f929;&#xff1a; &#x1f60e;正文&#xff1a;编写通讯录&#x1f60e;&#xff1a; 1.文件建立&#xff1a; ①.头文件Contact.h&#xff1a; ②.函数定义文件Contact.c&#xff1a; ③.工程测试文件test.c&#xff1a; 2.通讯录整体执行…

A White Paper on Neural Network Quantization--阅读笔记1

A White Paper on Neural Network Quantization--阅读笔记1一、模型量化的意义二、量化主要做什么三、目前量化主要分类四、量化基本知识介绍0、基本知识1、误差来源2、量化范围的设定五、量化方法介绍1、均匀仿射量化(Uniform affine quantization)2、对称均匀量化(Symmetric …