哈工大操作系统实验---lab6:地址映射与共享


实验目的:
  • 深入理解操作系统的段、页式内存管理,深入理解段表、页表、逻辑地址、线性地址、物理地址等概念。
  • 实验段、页式内存管理的地址映射过程
  • 编程实现段、页式内存管理上的内存共享,从而深入理解操作系统的内存管理。

实验内容:
  • Bochs调试工具跟踪Linux 0.11的地址翻译(地址映射)过程,了解IA-32(Intel Architecture 32-bit)的CPU架构下的地址翻译和Linux 0.11的内存管理机制。
  • Ubuntu上编写多进程的生产者-消费者程序,用共享内存做缓冲区(上一个实验是用文件做缓冲区)
  • 在上一个实验(信号量的实现和在pc.c程序上的应用)的基础上,为Linux 0.11增加共享内存功能,并将生产者-消费者程序移植到Linux 0.11

实验过程:
1.跟踪地址翻译过程:

首先以汇编级调试的方式启动Bochs,引导Linux 0.11,在0.11下编译和运行程序test.c,可以看到程序会一直空转,也就是陷入了死循环,永远不会主动退出程序,

#include <stdio.h>

int i = 0x12345678;

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

    while (i)
        ;

    return 0;
}

然后在调试器中通过查看程序(进程)的各项系数参数,从GDT表、逻辑地址、LDT表、线性地址、页表、最后计算出变量i的物理地址,最后通过直接修改物理内存的方式来让test.c进程退出。


步骤:
1.编写test.c程序(如上),并放置oslab/oslab

指令:
sudo ./mount-hdc    //安装hdc目录里面的内容
cp test.c hdc/usr/root //将oslab中的test.c文件移动到hdc/usr/root下

2.启动汇编级调试方式(以前是./run直接启动os)

指令:
./dbg-asm

会显示如下所示的信息:
在这里插入图片描述
其中Next at = 0表示下面的指令是Bochs启动后要执行的第一条软件指令,
单步跟踪进去就能看到BIOS的代码了。现在输入命令c,即继续运行程序,Bochs会和以前输入./run一样启动os


3.在Linux 0.11上编译test.c得到可执行文件,然后运行test.c:

指令:
gcc -o test test.c //编译test.c
./test  //运行test程序

在这里插入图片描述
后面的0x00003004就是这个程序中变量i的逻辑地址,这个值在任何机器上都是一样的,在同一个机器上运行多次当然也是一样的。
可以看到程序一直在运行,没有退出,这也就是上面所说的陷入了死循环(正是因为进程不会退出,才可以在调试器中查看进程的各种资源: 逻辑地址、LDT表、GDT表和页表等信息)。


4.这个实验第一部分的目的就是一步步的找到这个变量i的物理地址,然后将它改成0,使得可以退出循环。

4.1 在命令行窗口输入ctrl+cBochs就会暂停运行,进入调试状态,命令行窗口会出现如下信息:
在这里插入图片描述

注:
1.如果000f处位置显示的是0008,表示按下Ctrl+c键中断发生在内核中,
这时需要输入c继续执行,然后在按下Ctrl+c键,直到变成000f为止。
2.如果cmp处显示的不是这个,就用’n’命令单步运行几次,直到停在cmp处,实际上就是要停留在test.c的while(i)语句处,方便下面的继续调试。

4.2 使用反汇编指令u/7,显示从当前位置(while(1))开始的7条指令的反汇编指令:
在这里插入图片描述
很容易分析出来变量i存放的位置就是ds:0x3004(就是上面输出的逻辑地址),
然后将变量i和0x00000000位置的值相比较:

if 相等 (i==0)
    就退出循环(jz ...)
else 不相等 (i!=0)
    就继续往下执行(jmp ...),也就是继续执行循环体的内容

4.3 开始寻找和逻辑地址ds:0x3004对应的物理地址,也就是去跟踪地址转换过程

思路: 要从逻辑地址找到物理地址,首先要找到虚拟地址(因为这是段页式内存机制),所以需要找段表,段表其实就是进程的LDT表,要找LDT表,需要通过LDTR寄存器,
LDT在哪里呢?LDTR寄存器是线索的起点,通过它可以在GDT中找到LDT的物理地址(实际上LDTR不是直接指向对应的LDT表的,而是存储GDT表对于这个LDT表的偏移)。

  • 找段表(LDT表)

sreg命令可以显示所有寄存器的信息:
在这里插入图片描述

可以看到LDTR寄存器的值是0x0068=0000000001101000(二进制),表示LDT表存放在GDT表的1101(二进制)号位置,而且GDT表的位置也已经由GDTR寄存器给出,在物理地址0x00005cb8下(不同的实验者可能会不一样,比如书上的就是0x00005cc8,所以后面的数值也会有点不同,但是计算方法和思路是一样的)。

使用命令xp /32w 0x00005cb8查看GDT表:
在这里插入图片描述
GDT表中的每一项占64位(8个字节),所以要查找的项的地址是0x00005cb8 + 13*8,使用命令:xp /2w 0x00005cb8 + 13*8
在这里插入图片描述
这里只要保证后面的两个数字和sreg输出中,LDTR后面的dldh值是不是一致。
0x52d00068 0x000082fd 将加粗的数字组合成:
=> 0x00fd52d0,这就是LDT表的物理地址(GDT表存放了所有进程的LDT表)。
使用命令:xp /8w 0x00fd52d0
在这里插入图片描述
这就是LDT表前四项的内容了,也就是已经找到了段表了

  • 段描述符
    在保护模式(32位)下段寄存器有另外一个名字,叫做段选择子,因为它保存的信息主要是该段在段表里的索引值,用这个索引值可以从段表中选择出相应的段描述符

先看看ds选择子里面的内容,还是使用命令sreg:
在这里插入图片描述
可以看到ds的值是0x0017,段选择子是一个16位寄存器,它的各位的函数如下:
在这里插入图片描述
其中RPL是请求特权级,当访问一个段时,处理器要检查RPLCPL(放在cs的位0和位1中,用来表示当前代码的特权级),即使程序有足够的特权级(CPL)来访问一个段,但如果RPL(如放在ds中,表示请求数据段)的特权级不足,则仍然不能访问,即如果RPL的数值大于CPL(数值越大,权限越小),则用RPL的值覆盖CPL的值。而段选择子中的TI是表指示标记,如果TI=0,则表示段描述符(段的详细信息)在GDT(全局描述符表)中,即去GDT中去查;而TI=1,则去LDT(局部描述符表)中去查。
看看上面的ds0x0017=0000000000010111(二进制),所以RPL=11,可见是在最低的特权级(因为在应用程序中执行),TI=1,表示查找LDT表,索引值为10(二进制)= 2(十进制),表示找LDT表中的第3个段描述符(从0开始编号)。
LDTGDT的结构一样,每项占8个字节。所以第3“0x00003fff 0x10c0f300”就是搜寻好久的ds的段描述符了。用sreg输出中ds所在行的dldh值可以验证找到的描述符是否正确。
接下来看看段描述符里面放置的是什么内容:
在这里插入图片描述
可以看到,段描述符是一个64位二进制的数,存放了段基址和段限长等重要的数据。其中位P(Present)是段是否存在的标记;位S用来表示是系统段描述符(S=0)还是代码或数据段描述符(S=1);四位TYPE用来表示段的类型,如数据段、代码段、可读、可写等;DPL是段的权限,和CPLRPL对应使用;位G是粒度,G=0表示段限长以位为单位,G=1表示段限长以4KB为单位。
(上面这段话我其实看不太懂)

  • 段基址和线性地址(线性地址也就是虚拟内存中地址)
    上面说了那么一大堆东西,实际上需要的只有段基址这一项数据,即段描述符0x00003fff 0x10c0f300 中加粗部分组合而成的 0x10000000
    这就是ds段在线性地址空间的起始地址(也就是该进程对应的段在虚拟内存中的起始地址),
    这里的段是代码段(因为while(1)执行在代码段),要找其他的段也是类似的方法。
    段基址 + 段内偏移 = 线性地址
    0x10000000 + 0x3004 = 0x10003004

用命令验证线性地址是不是对的:calc ds:0x3004
在这里插入图片描述
发现是对的,当前已经找到了该进程在虚拟内存中的线性地址了,然后需要找物理地址

  • 页表
    从线性地址计算物理地址,需要查找页表,线性地址翻译成物理地址的过程如下:
    在这里插入图片描述
    可以看到线性地址变成物理地址,首先需要找出页目录号 + 页表号 + 页内偏移,它们分别对应了32位线性地址10位 + 10位 + 12位,所以0x10003004的页目录号=64,页号=3,页内偏移=4。

在IA-32(英特尔的32位CPU架构)下,页目录表的位置由CR3寄存器指引,用creg命令可以看到:
在这里插入图片描述
说明页目录表的基址为0,看看其内容用命令: xp /68w 0
在这里插入图片描述
页目录表和页表中的内容很简单,是1024个32位数,这32位中前20位是物理页框号,后面是一些属性信息(最重要的是最后一位P),其中第65个页目录项就是要找的内容,
用命令查看: xp /w 0 + 64*4
在这里插入图片描述
其中的027是属性,显然P=1,这里还也可以分析出其它的属性。
页表所在的物理页框号为0x00fa6,即页表在物理内存为0x00fa6000处,从该位置开始查找3号页表项(每个页表项4个字节),用命令: xp /w 0x00fa6000 + 3*4
在这里插入图片描述
其中的067是属性,显然P=1。

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

1.用命令: page 0x10003004,可以得到信息:“linear page 0x10003004 maps to physical page 0x00fa3004”
2.用命令: xp /w 0x00fa3004:
在这里插入图片描述
然后已经找到了物理地址,但是还有最后一步: 直接修改物理地址的值,使得变量i为0
命令: setpmem 0x00fa3004 4 0(表示从0x00fa3004地址开始设置4个字节都为0)
在这里插入图片描述
然后输入命令c(如上),继续从调试状态的程序运行下去,就会看到在Bochs中程序顺利退出:
在这里插入图片描述
到这里第一个部分也正式完成了


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

说明: 第二个部分的实验博主并没有自己运行出结果,只是在最后标注的两份实验报告的基础上进行理解,而且下面的代码都是基于真实的Ubuntu环境下实现的。

本实验是在Ubuntu下完成的,与上一个信号量实验中的pc.c的功能要求基本一致,
仅有两点不一样:

  • 不用文件做缓冲区,而是使用共享内存
  • 不将生产者和消费者放置同一个文件pc.c,而是生产者是producer.c,消费者是consumer.c,两个程序都是单进程的,通过信号量共享缓冲区进行进程间的通信。
    Linux下,可以通过shmget()shmat()两个系统调用使用共享内存,当然Linux 0.11本身是没有这两个系统调用的,要自己来实现。

Linux支持两种方式的共享内存。一种方式是shm_open()mmap()shm_unlink()的组合;另一种方式是shmget()shmat()shmdt()的组合。本实验建议使用后一种方式。

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

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

该系统调用会新建或者打开一页物理内存作为共享内存,返回该共享内存的shmid,即该页共享内存在操作系统中的标识,如果多个进程使用相同的key调用shmget(),则这些进程就会获得相同的shmid即得到了同一块共享内存的标识
shmget()实现时,
如果key所对应的内存已经建立,直接返回shmid,否则新建然后再返回。
如果size超过一页内存大小,返回-1,并置errnoEINVAL
如果系统没有空闲内存了,返回-1,并置errnoENOMEM
参数shmflg在本次实验中可以直接忽略。

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

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

该系统调用会将shmid对应的共享内存页面映射到当前进程的虚拟地址空间中,并返回一个逻辑地址p,调用进程可以读写逻辑地址p来读写这一页共享内存。两个进程都调用shmat可以关联到同一页物理内存上,此时两个进程读写p指针就是在读写同一页内存,从而实现了基于共享内存的进程间通信
如果shmid不正确,返回-1,并置errnoEINVAL
参数shmaddrshmflg在本次实验中可以直接忽略。

本实验的这个部分就是要实现如下所示的图,也就是不同的进程要共享同一段内存空间,实现基于共享内存的进程间通信。
在这里插入图片描述
观察一下上面的图,不难发现要从后往前来一步步建立:

第一步:

要获得一个空闲的物理内存空间,即调用函数get_free_page() (这是shmget()系统调用要完成的主要工作)。

第二步:

shmget()创建好的物理共享内存页面进程对应的虚拟内存 以及逻辑地址关联起来,让进程对某个逻辑地址的读写就是对这个共享物理地址进行读写

补一个知识点:
父子进程共享同一块物理页框,这个页框需要设置成只读,因为如果父子进程都可以写,那么两个进程在并发执行时就会相互影响使得内存空间的信息出现错误
但是进程的读写是最基本的操作,那当要写内存的时候该怎么做呢?
一旦写内存的时候就会出现读写异常中断(有写好的中断处理函数),中断处理的结果就是给要写的进程新分配一个内存空间,这个进程对应的页表也要改值指向新的物理内存空间。
这就是著名的写时复制机制。

  • 虚拟地址->物理地址
    在第二步中,首先需要完成虚拟地址和物理内存页面的映射关系,核心就是创建页表,然后填写页表,使之映射起来。
    LInux0.11中提供了这样一个函数:
    put_page(tmp,address);//tmp为虚拟地址,address为物理地址
    物理地址address就是第一步用shmget()创建的共享内存对应的地址,所以这里建立起映射关系的核心操作就是得到虚拟地址tmp,即是要从进程虚拟内存空间划分出一段空间

要在进程的虚拟内存区域划分出一段空间, 首先需要了解进程虚拟空间的分布,阅读源码(主要是exec.c)可以发现每个进程在虚拟内存空间会分配64MB的大小,分别有代码段、数据段、堆段(用来存放程序中未初始化的全局变量的一个段)、栈段,从图(待制作)看出,堆段到栈段的虚拟子内存空间是没有被使用的,我们可以在这里分割出一个页表来建立起到物理内存的映射关系。 问题: 虚拟内存不是实际存在的,为什么在这里可以建立一个页表

这些段的信息都是存储在进程PCB中的,所以可以用current->brk找到从进程开始处到brk位置的长度,当然这还不是brk所在的虚拟地址,只是离分配给该进程的64MB虚拟内存开始处的偏移地址current->brk再加上该进程开始处的虚拟地址才是我们想要的虚拟地址,当前进程开始处的虚拟地址存放在current->ldt[1]中,可以用get_base(current->ldt[1])获取,所以经过:

tmp = get_base(current->ldt[1]) + current->brk;//当前进程的虚拟地址 + brk在虚拟内存中的偏移 = brk的虚拟地址

计算出虚拟地址tmp以后,就可以用put_page(tmp,address)函数来创建页表了,也就可以建立起虚拟地址到物理地址的映射关系了。

  • 逻辑地址->虚拟地址
    现在到了完成内存映射的最后一步,即建立起逻辑地址和虚拟地址的映射,建立这个映射关系就是要建立一个段表,实际上这个段表已经建立好了(在用户看来,程序就是分段存储在内存中的,只不过实际上这个内存是虚拟内存),就是进程PCB中存放的IDT表,现在需要做的就是根据段表将虚拟地址反推得到逻辑地址。

因为,逻辑地址 + 段基址 = 虚拟地址,所以,逻辑地址 = 虚拟地址 - 段基址,也就是:

logicalAddress = tmp - get_base(current->ldt[1])

现在算出了逻辑地址,只需要通过shmat()返回给用户程序,用户程序操作这个逻辑地址实际上就是在操作那一页用shmget()建立起来的共享物理内存区域了

代码实现:shm.c

#include <sys/shm.h>
#include <linux/kernel.h>
#include <error.h>
#include <linux/mm.h>
#include <linux/sched.h>

shm_t shms[SHM_SIZE] = {{0, 0, 0}};

/**
 * 该文件用于实现对共享物理内存空间的操作
 * 1.创建 
 * 2.返回逻辑地址,以便于生产者-消费者读写 
*/

/**
 * 该系统调用会新建或者打开一页物理内存作为共享内存,返回该共享内存的shmid,
 * 即该页共享内存在操作系统中的标识,如果多个进程使用相同的key调用shmget(),
 * 则这些进程就会获得相同的shmid,即得到了同一块共享内存的标识。
 * 在shmget()实现时,
 * 如果key所对应的内存已经建立,直接返回shmid,否则新建然后再返回。
 * 如果size超过一页内存大小,返回-1,并置errno为EINVAL。
 * 如果系统没有空闲内存了,返回-1,并置errno为ENOMEM。
 * 参数shmflg在本次实验中可以直接忽略。
*/
int shmget(key_t key, size_t size/*, int shmflg*/)
{
    int i;

    //得到原来已经创建好的共享内存(在生产者已经创建好了的共享内存,消费者拿着相同的key对应的是同一块内存)
    for(i = 0; i < SHMS_SIZE; i++)
    {
        if(shms[i].key == key)
        {
            return i;
        }
    }

    //创建新的共享内存
    for(i = 0; i < SHMS_SIZE; i++);
    if(i == SHMS_SIZE)
    {
        printk("no shm place");
        errno = ENOMEM;
        return -1;
    }
    if(size > PAGE_SIZE) 
    {
        errno = EINVAL;
        return -1;
    }
    shms[i].size = size;
    shms[i].key = key;
    if(!(shms[i].addr = get_free_page()))
    {
        return ENOMEM;
    }
    return i;
}

/**
 * 该系统调用会将shmid对应的共享内存页面映射到当前进程的虚拟地址空间中,并返回一个逻辑地址p,
 * 调用进程可以读写逻辑地址p来读写这一页共享内存。
 * 两个进程都调用shmat可以关联到同一页物理内存上,此时两个进程读写p指针就是在读写同一页内存,
 * 从而实现了基于共享内存的进程间通信。
 * 如果shmid不正确,返回-1,并置errno为EINVAL。
 * 参数shmaddr和shmflg在本次实验中可以直接忽略。
*/
void * shmat(int shmid/*, const void * shmaddr, int shmflg*/);
{
    unsigned long logical_addr;

    if(shmid < 0 || shmid >= SHMS_SIZE)
    {
        //shmid对应的物理内存不存在
        errno = EINVAL;
        return -1;
    }
    logival_addr = current->brk;
    current->brk += PAGE_SIZE;
    if(!put_page(shms[shmid].addr,current->start_code+logival_addr))
    {
        //分页分不出来
        errno = EINVAL;
        return -1;
    }

    //返回共享物理空间对应的逻辑地址
    return (void)*logival_addr;
}

上述的过程只是1.建立了一个共享内存空间 2.得到了这个共享内存空间的逻辑地址,得到了地址然后要做什么呢?
当然就是要操作这个内存空间,所以就引出了用生产者—消费者进程来应用这个创建好的共享物理内存作为共享缓冲区,上个实验可以看到是用文件作为共享缓冲区,所以这是和上个实验(信号量的实现与应用)第一个不同点
第二个不同点这次是把生产者和消费者都作为不同的程序(produce.c consumer.c)。

producer.c

#include <unistd.h>
#include <syscall.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>

#define BUF_SIZE 10
#define COUNT 500
#define KEY 183
#define SHM_SIZE (BUF_SIZE+1)*sizeof(short)

int main(int argc,char ** argv)
{
    int pid;//该进程的id
    unsigned short count = 0;//生产资源的个数
    int shm_id;//共享物理内存空间的id
    short *shmp;//操作共享内存的逻辑地址
    sem_t *empty;//三个实现进程间的同步
    sem_t *full;
    sem_t *mutex;

    //关闭原来的信号量
    sem_unlink("empty");
    sum_unlink("full");
    sum_unlink("mutex");

    //新打开三个信号量
    empty = sem_open("empty",0_CREAT|O_EXCL,0666,10);
    full = sem_open("full",0_CREAT|O_EXCL,0666,0);
    mutex = sem_open("mutex",0_CREAT|O_EXCL,0666,1);
    if(empty == SEM_FAILED || full == SEM_FAILED || mutex == SEM_FAILED)
    {
        //申请信号量失败
        printf("sem_open error!\n");
        return -1;
    }

    //使用KEY值申请一块共享物理内存
    shm_id = shmget(KEY,SHM_SIZE,IPC_CREAT|0666);
    if(shm_id == -1)
    {
        //申请共享内存失败
        printf("shmget error!\n");
        return -1;
    }
    shmp = (short*)shmat(shm_id,NULL,0);//返回共享物理内存的逻辑地址


    pid = syscall(SYS_getpid);//得到进程的pid
    
    //生产者生产出资源
    while(count <= COUNT)
    {
        sem_wait(empty);//P(empty)
        sem_wait(mutex);//P(mutex)

        printf("Producer 1 process %d : %d\n",pid,count);
        fflush(stdout);
        *(shmp++) = count++;
        if(!(count % BUF_SIZE))
        {
            shmp -= 10;
        }          
        
        sem_post(mutex);//V(mutex)
        sem_post(full);//V(full)
    }
    return 0;
}

consumer.c

#include <unistd.h>
#include <syscall.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>

#define BUF_SIZE 10
#define KEY 183

int main()
{
    int pid;
    int shm_id;
    short *shmp;
    short *index;
    sem_t *empty;
    sem_t *full;
    sem_t *mutex;

    shm_id = shmget(KEY,0,0);//使用和生产者同一个KEY值,会返回同一个shm_id(指向同一个内存空间)
    if(shm_id == -1)
    {
        //申请共享内存失败
        printf("shmget error!\n");
        return -1;
    }
    shmp = (short*)shmat(shm_id,NULL,0);//返回共享物理内存的逻辑地址
    index = shmp + BUF_SIZE;
    *index = 0;

    //打开生产者那里创建的三个信号量
    empty = sem_open("empty",0);
    full = sem_open("full",0);
    mutex = sem_open("mutex",0);
    if(empty == SEM_FAILED || full == SEM_FAILED || mutex == SEM_FAILED)
    {
        //申请信号量失败
        printf("sem_open error!\n");
        return -1;
    }

    if(!sysvall(SYS_fork))
    {
        pid = syscall(SYS_getpid);//得到进程的pid

        //消费者1开始消费资源
        while(1)
        {
            sem_wait(full);//P(full)
            sem_wait(mutex);//P(mutex)

            printf("Consumer 1 process %d : %d\n",pid,shem[*index]);
            fflush(stdout);
            if(*index == 9)
            {
                *index = 0;
            }
            else
            {
                (*index)++;
            }

            sem_post(mutex);//V(mutex)
            sem_post(empty);//V(empry)
        }
        return 0;
    }

    if(!sysvall(SYS_fork))
    {
        pid = syscall(SYS_getpid);//得到进程的pid

        //消费者2开始消费资源
        while(1)
        {
            sem_wait(full);
            sem_wait(mutex);

            printf("Consumer 2 process %d : %d\n",pid,shem[*index]);
            fflush(stdout);
            if(*index == 9)
            {
                *index = 0;
            }
            else
            {
                (*index)++;
            }

            sem_post(mutex);
            sem_post(empty);
        }
        return 0;
    }

    if(!sysvall(SYS_fork))
    {
        pid = syscall(SYS_getpid);//得到进程的pid

        //消费者3开始消费资源
        while(1)
        {
            sem_wait(full);
            sem_wait(mutex);

            printf("Consumer 3 process %d : %d\n",pid,shem[*index]);
            fflush(stdout);
            if(*index == 9)
            {
                *index = 0;
            }
            else
            {
                (*index)++;
            }

            sem_post(mutex);
            sem_post(empty);
        }
        return 0;
    }
    return 0;
}

由于Linux 0.11只有一个终端,而现在需要在同一个终端下同时运行两个程序(上述的生产者和消费者程序)。
Linuxshell有后台运行程序的功能,只要在命令的末尾输入一个&,命令就会自动进入到后台执行,前台会马上回到提示符状态,然后运行下一个命令,例如:

# ./producer &
# ./consumer

生产者进程会在后台运行,产生资源到共享内存空间,然后消费者会在前台运行,消费共享内存空间的资源,从而可以通过这个共享内存来实现进程间的相互通信了

实验问题:

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

  • 输入命令u/7反汇编,查看变量i对应的逻辑地址
  • 逻辑地址找虚拟地址要通过段表,也就是IDT表,然后IDT表要根据LDTR寄存器和GDT表,对应的命令就是sreg
  • 根据ds(代码段)寄存器查找IDT表,得到基址,然后通过基址 + 逻辑地址 = 虚拟地址
  • 根据虚拟地址找到物理地址,核心就是查找页表。
  • 然后就是一堆我看不懂的操作
  • 最后找到了物理地址就需要使用命令setpmem (物理地址) 4 0来修改变量i的值,然后命令c继续运行就可以退出程序了。

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

  • 首先再运行一次程序,相当于一切都是重来的,变量i重新被赋非0值,所以仍然还是会死循环。
  • 继续进行地址跟踪,根据虚拟地址找到物理地址,发现物理地址和第一次运行时的不一样了,因为在这个进程没有被运行时,内存是会被释放的,所以其他进程是可以利用这个内存的,虽然还是给它这个虚拟地址,但是重新分配内存的时候,物理地址并不一定还是那个地址。

HIT-OS-LAB参考资料:
1.《操作系统原理、实现与实践》-李治军、刘宏伟 编著
2.《Linux内核完全注释》
3.两个哈工大同学的实验源码
4.Linux-0.11源代码
(上述资料,如果有需要的话,请主动联系我))

该实验的参考资料
网课
官方文档
参考实验报告_1
参考实验报告_2
返回顶部

  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值