NOVA微内核实验二之物理地址与虚拟地址间的相互映射

一 实验准备

   下载实验代码源文件

      $ git clone

      https://os.inf.tu-dresden.de/repo/git/mkc.git

      $ make后产生如下源文件

     

二  实验分析

     打开user/src/start.S

      汇编源码文件内容如下:

      .text                      //编译器关键词       指定后续编译出来内容放在代码段(可执行)
     .global __start        //编译器关键词        告诉编译器后续跟的为全局变量名
     __start:             //编译链接后的程序起始地址
         mov $stack_top, %esp         
         call main_func      //1. 将函数返回地址入桟  2. 让指针%eip指向函数开始处

         ud2       // 让CPU产生invaild opcode exception的软件指令,内核一旦发现该指令,立即停止运行

     打开user/src/user.cc

       C++源码文件:

       #define NORETURN __attribute__((noreturn))     //表示此函数无返回值

       #define EXTERN_C extern "C"    //限定extern类型,   修饰的变量或函数按C语言方式编译与链接

       EXTERN_C NORETURN
       void main_func ()
       {
          while (1) ;
       }

     打开user/src/linker.ld

      链接(C程序与汇编)脚本文件:  描述输入文件中的节如何被映射到输出文件中,并控制输出文件的内存分布

      OUTPUT_FORMAT("elf32-i386")   //输出目标文件格式
      OUTPUT_ARCH("i386")           //运行平台
      ENTRY(__start)                     //设置程序入口点
      PHDRS  //此命令仅在生成elf文件时有效,elf目标格式用program headers 程序头来描述如何被载入内存
      {
   data    PT_LOAD FLAGS(6);//PT_LOAD为属性,表示程序运行时应被加载入内存
 text    PT_LOAD FLAGS(5);    //描述定义了两个段
       }

      SECTIONS     // 描述输出文件的内存布局,告诉ld如何把输入文件的sections映射到输出文件的sections

      {
           . =  0x1000 + SIZEOF_HEADERS;        //定义当前地址
           .data  :
           {
              BYTE(42);                    // 分配一个字节,存放值42
              . = ALIGN(0x1000);      //以一页4KB为单位对齐桟指针
              stack_top = .;
              *(.data);
              *(.bss);         //将输入文件中的所有.data.bss section都链接到输出文件的.data section 部分
           } : data             //将输出文件的.data section分配到PHDRS命令定义的data的section中
         .text :                               
          {
              . = ALIGN(0x1000);
              *(.text);                          //将输入文件中的所有.text section都链接到输出文件的.text section 部分
           } : text                             //将输出文件的.text section分配到PHDRS命令定义的text的section中
      }

    Goto user/build and make user binary(ELF 目标文件)

     打开user.nova.debug.map 可以看到对应地址

     00002000 T __start
     0000200c T main_func
     00002000 D stack_top

     再将此ELF binary file 作为启动模块通过boot loader加载到kernel之后

     $ ls boot 

     $ cat boot/menu.lst

   Multiboot Information 格式与内容

    由 Multiboot info pointer(物理地址) 指向这张表,其中需要flags设置标志位,其余变量可选,

     当flags[3]置位时,变量mods_count和mods_addr值有效。

     mod_addr是一个length为mods_count阵列模块结构的物理地址。

     

      物理地址需要映射到内核重映射区域的虚拟地址,通过函数void * Ptab::remap(phys_addr)

      每次映射会覆盖掉之前的映射,因此每次调用remap函数时,旧的指针会无效。           remap area

      

    

    Executable and Linkable Format(ELF) 

         

                

                 ELF Header 包含查询到 Program header table 的偏移量(ph_offset)

                 Program header table描叙程序运行时使用的段(告诉系统如何建立一个进程的内存映像)

                 ELF Header Format:

                 

                 magic,data,type(2):magic魔数(四个字节)用于确认文件是否是ELF格式的目标文件,data指定elf文件编码方式,type=2表示为可执行文件。

                    entry: user EIP (指令指针寄存器)系统的第一个传输控制的虚拟地址,在此启动进程

                    ph_offset:find PH table

                    ph_count:PH table number

                   PH Table Format:

                   

                   type:PT_LOAD(load the segment)

                   flags:writable?

                   offset:该节相当于elf文件起始位置的偏移

                   virtual address:该节的映射地址

                   file/mem size:在文件和内存中的segment size

                   alignment:地址对齐的约束

三   实验代码编写与分析

          打开  kern/src/ec.cc      

              完成 root_invoke()函数:
              void Ec::root_invoke()
             {
                 printf ("root_invoke\n");
                 // TODO
                // - current->regs.eax holds pointer to Multiboot info (see multiboot.h)
                // - get mbi remapped, find single Multiboot_module
                // - get module descriptor mapped, print physical addr and size, check
                // - if its size is correct (should be equal to filesize of user.nova)
                // - get module remapped (its an elf binary, see elf.h for details)
                // - sanity check and decode elf binary
                // - finally start user module via ret_user_iret()

               //  find multi boot info
                Multiboot * mbi = static_cast<Multiboot *>(Ptab::remap (current->regs.eax));//强制类型转换 ,内核中只能访问虚拟地址空间,要访问硬件寄存器则需要将其物理地址先映射到虚拟地址空间
                if (!(mbi->flags & 8) || (mbi->mods_count != 1))//check flafs[3](判断mod_count与mod_addr是否有效),mods_count(是否有模块)
                    panic ("exactly ONE multi boot module is required.\n");

               // load module desciptor
               Multiboot_module mod = *static_cast<Multiboot_module *>(Ptab::remap (mbi->mods_addr));
       printf ("load module from %x - %x (%u bytes) : ", mod.mod_start, mod.mod_end, mod.mod_end - mod.mod_start);
              char * cmd = static_cast<char *>(Ptab::remap (mod.cmdline));
               printf ("%s\n",cmd);

               // remap elf header
               Eh * e = static_cast<Eh *>(Ptab::remap (mod.mod_start));
               if (e->ei_magic != 0x464c457f || e->ei_data != 1 || e->type != 2)//check elf 文件
                         panic ("No ELF\n");

               unsigned count = e->ph_count;
               current->regs.eip = e->entry;

                // remap program headers
               Ph * p = static_cast<Ph *>(Ptab::remap (mod.mod_start + e->ph_offset));
               for (; count--; p++) {
                     if (p->type == Ph::PT_LOAD) {
                             unsigned attr = p->flags & Ph::PF_W ? 7 : 5;
                             if (p->f_size != p->m_size || p->v_addr % PAGE_SIZE != p->f_offs % PAGE_SIZE)//check 两个基本的equal要求
                                     panic ("Bad ELF\n");
                             mword phys = align_dn (p->f_offs + mod.mod_start, PAGE_SIZE);
                             mword virt = align_dn (p->v_addr, PAGE_SIZE);
                             mword size = align_up (p->f_size, PAGE_SIZE);
                             while (size) 
                                {
                                      Ptab::insert_mapping (virt, phys, attr);
                                      virt += PAGE_SIZE;
                                      phys += PAGE_SIZE;
                                      size -= PAGE_SIZE;
                                 }
                         }
                  }
              ret_user_iret();//返回用户态
              FAIL;//判断循环流是否可以结束
           }

               

       打开  kern/src/ptab.cc      

            // add 4k mappings only
           void Ptab::insert_mapping (mword virt, mword phys, mword attr)
         {
             mword* pdir = static_cast<mword*>(Kalloc::phys2virt(Cpu::cr3()));//    kern/include/cpu.h 得到全局寄存器cr3值(page directory的起始物理地址)转换成虚拟地址
               mword* ptab;      // page table

               if ((pdir[virt >> 22] & 1) == 0) {     //virt is linear address,1 means entry vaild             

                 // add ptab if necessary
                 ptab = static_cast<mword*>(Kalloc::allocator.alloc_page(1, Kalloc::FILL_0));//得到一个新的页表的起始虚拟地址
                 mword p = Kalloc::virt2phys (ptab);
                 pdir[virt >> 22] = p + 0x23 + (attr & 4);//将新的页表的物理地址放入相应页目录
              } else {

                 ptab = static_cast<mword*>(Kalloc::phys2virt (pdir[virt >> 22] & ~PAGE_MASK));  }//得到y页目录对应的页表的起始虚拟地址
                assert ((phys & PAGE_MASK) == 0);//为假则输出信息

               ptab[(virt >> PAGE_BITS) & 0x3ff] = (phys & ~PAGE_MASK) | (attr & PAGE_MASK);//(virt >> PAGE_BITS) & 0x3ff取虚拟地址的中间10位得到页表序号,相应页表中存入前20 位为物理页面起始地址,后12位为属性
               

         }


           void * Ptab::remap (mword addr)
        {
           mword* pdir = static_cast<mword*>(Kalloc::phys2virt(Cpu::cr3()));
           // flush TLB : old 4M mapping
           pdir[REMAP_SADDR >> 22] = 0;  //REMAP_SADDR映射到的虚拟线性地址(kern/include/memory.h)
           Cpu::flush(REMAP_SADDR);      //clean the TLB of the memory address(REMAP_SADDR) kern/include/cpu.h
           // insert new mapping
           pdir[REMAP_SADDR >> 22] = (addr & 0xfff00000) + 0xe3;
           return reinterpret_cast<void *>(REMAP_SADDR + (addr & 0xfffff));

        }

       打开  kern/src/kalloc.cc  

        void * Kalloc::alloc_page (unsigned size, Fill fill)
     {
            if (begin + size * PAGE_SIZE > end)
                   panic ("kernel : no mem\n");
            void * p = reinterpret_cast<void*>(begin);//强制无关类型间的转化,产生的值保持相同的比特位
            begin += size * PAGE_SIZE;
            if (fill)
              memset (p, fill == FILL_0 ? 0 : ~0, size * PAGE_SIZE);//分配size张页大小的内存(kern/include/string.h)
           return p;
     }

       void * Kalloc::phys2virt (mword phys)
    {
          mword virt = phys + reinterpret_cast<mword>(&OFFSET);// &:GET ADDRESS
          return reinterpret_cast<void*>(virt);
    }

      mword Kalloc::virt2phys (void * virt)
    {
         mword phys = reinterpret_cast<mword>(virt) - reinterpret_cast<mword>(&OFFSET);
         return phys;
    }

四  实验结果

    在ec.cc文件中添加编写程序所需头文件,multiboot.h,elf.h

    $make

    $make run

    得出如下实验结果:(实验是搭建在qemu虚拟平台上)

     

猜你喜欢

转载自blog.csdn.net/qq_40326481/article/details/79434934