操作系统页面管理机制的启动流程-xv6

本文探讨了xv6操作系统在启动过程中页面管理机制的启动流程。从实模式转换到保护模式,再到内核代码加载和分页机制的开启。在开启分页后,确保虚拟地址与物理地址的正确映射,保证代码执行的连续性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对在启动启动时,操作系统的页面管理机制的启动一直存在一些疑惑:

假设页面机制启动的指令(类似movl %eax,%cr0)的指令寄存器的值为ip=0x0001,则这条指令的下一条应该执行的指令对应的ip应该是ip=0x0002。但是问题是,对于下一条指令来说,此时虚拟内存的机制已经启动,ip=0x0002所对应的应该是虚拟地址,但是按道理,这里我们的下一条指令应该是存储在了物理地址**0x0002中。难道是说虚拟内存地址ip=0x0002也是映射到物理地址0x0002中,但是我们记得在内核空间中,虚拟内存地址**x应该是映射到物理地址x-KERNBASE中才对。又或者说,难道对于物理地址x,映射到它的虚拟地址有两个,分别是xx+KERNBASE

我们下面结合xv6的源码看一下(这里就不贴源码了)。BIOS初始化工作完成之后,会将boot代码加载进入内存,boot包括xv6中的bootasm.S和Bootmain.c两个文件。boot的入口在bootasm.S中,在bootasm中并没有开启分页机制,只是从实模式转化到了保护模式,然后跳到Bootmain.c中执行bootmain函数

 call    bootmain

bootmain函数的代码如下:

void
bootmain(void)
{
  struct elfhdr *elf;
  struct proghdr *ph, *eph;
  void (*entry)(void);
  uchar* pa;

  elf = (struct elfhdr*)0x10000;  // scratch space

  // Read 1st page off disk
  readseg((uchar*)elf, 4096, 0);

  // Is this an ELF executable?
  if(elf->magic != ELF_MAGIC)
    return;  // let bootasm.S handle error

  // Load each program segment (ignores ph flags).
  ph = (struct proghdr*)((uchar*)elf + elf->phoff);
  eph = ph + elf->phnum;
  for(; ph < eph; ph++){
    pa = (uchar*)ph->paddr;
    readseg(pa, ph->filesz, ph->off);
    if(ph->memsz > ph->filesz)
      stosb(pa + ph->filesz, 0, ph->memsz - ph->filesz);
  }

  // Call the entry point from the ELF header.
  // Does not return!
  entry = (void(*)(void))(elf->entry);
  entry();
}

显然这里的主要工作是将内核代码加载进内存。内核代码会编译到一个ELF文件中,内核代码的启动点是ELF符号表中的entry对应的地址。也可以看到知道bootmain,分页内存管理机制也没有启动。

下面我们看一下内核代码的启动部分,在entry.S中:

#include "asm.h"
#include "memlayout.h"
#include "mmu.h"
#include "param.h"

# Multiboot header.  Data to direct multiboot loader.
.p2align 2
.text
.globl multiboot_header
multiboot_header:
  #define magic 0x1badb002
  #define flags 0
  .long magic
  .long flags
  .long (-magic-flags)

# By convention, the _start symbol specifies the ELF entry point.
# Since we haven't set up virtual memory yet, our entry point is
# the physical address of 'entry'.
.globl _start
#查看kernel.ld文件就可以明白了,连接器把kernel的elf文件连接到了0X80100000地址处,这里的start_将会是物理地址
#外面文件调用这里的entry时会直接从物理地址调用
_start = V2P_WO(entry)   
# Entering xv6 on boot processor, with paging off.
.globl entry
entry:
  # Turn on page size extension for 4Mbyte pages
  movl    %cr4, %eax
  orl     $(CR4_PSE), %eax
  movl    %eax, %cr4
  # Set page directory
  movl    $(V2P_WO(entrypgdir)), %eax   # entrypgdir 在main.c里面定位
  movl    %eax, %cr3      
  # Turn on paging.
  movl    %cr0, %eax
  orl     $(CR0_PG|CR0_WP), %eax
  movl    %eax, %cr0 #这里开启分页,开启了分页之后,

  # Set up the stack pointer.
  movl $(stack + KSTACKSIZE), %esp

  # Jump to main(), and switch to executing at
  # high addresses. The indirect call is needed because
  # the assembler produces a PC-relative instruction
  # for a direct jump.
  mov $main, %eax
  jmp *%eax

.comm stack, KSTACKSIZE

我们知道ELF中的地址对应的是虚拟地址,我们通过链接器脚本可以设置ELF的虚拟地址。从链接器脚本中我们可以知道内核代码的虚拟地址的起始地址为0X80100000,并且对应的物理地址是0x100000。也就是把bootmain中把内核代码加载到以0x100000起始的连续物理内存空间中。但是内核代码对应的虚拟地址是以0X80100000的起始的。

现在来看一下内核入口的代码,根据注释:

By convention, the _start symbol specifies the ELF entry point.
Since we haven’t set up virtual memory yet, our entry point is
the physical address of ‘entry’.

我们知道

_start = V2P_WO(entry)

是为了更改ELF中的_start符号表项,这个符号表项主要是表示内核代码的入口地址,也是bootmain中调用的entery()入口。正如前面所说,ELF中所有地址都是基于虚拟地址的,但是此时页面管理机制还没有开启,所有虚拟地址不能用。这么一改之后,就相当与直接从物理地址进入内核代码了。

开启分页机制的代码主要是下面这部分:

  movl    $(V2P_WO(entrypgdir)), %eax   
  movl    %eax, %cr3      
  # Turn on paging.
  movl    %cr0, %eax
  orl     $(CR0_PG|CR0_WP), %eax
  movl    %eax, %cr0 

先在cr3寄存器中设置好页目录,然后开启分页机制,从此就进入了虚拟内存时代。但是正如我们前面所说,在

movl    %eax, %cr0 

时系统用的是物理地址,而在它的下一条语句系统用的是虚拟地址。假设这条语句的ip为0x00001,则它的下一条语句的ip=0x00002。我们要保证,虚拟地址0x00002对应的是物理地址中的0x00002,否则代码执行就乱掉了。所以我们看一下开启内存分页管理时的页目录设置:

movl    $(V2P_WO(entrypgdir)), %eax   

entrypgdir在main.c中:

pde_t entrypgdir[NPDENTRIES] = {
  // Map VA's [0, 4MB) to PA's [0, 4MB)
  [0] = (0) | PTE_P | PTE_W | PTE_PS,
  // Map VA's [KERNBASE, KERNBASE+4MB) to PA's [0, 4MB)
  [KERNBASE>>PDXSHIFT] = (0) | PTE_P | PTE_W | PTE_PS,
};

好了,答案很清晰。内核虚拟地址空间中,[0-4M)和[KERNBASE, KERNBASE+4MB)都映射到物理地址[0, 4MB)。这样就实现了从没有开启虚拟内存管理到开启虚拟内存管理的平滑过度。很妙!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值