xv6学习笔记内核初始化

内核初始化

根据博客的内容,我大致画了计算机启动时内核初始化的流程图

计算机启动时存放在ROM中的BIOS程序从磁盘中的第一个扇区(引导扇区)读取程序,加载到内存地址为0x7c00处,然后设置程序计数器%ip,跳转至该地址,执行BootLoader(引导加载器)。BootLoader负责从实模式切换到保护模式并且将存在存储设备的操作系统二进制文件读入内存,最后将控制权交给操作系统。

XV6文档提到,xv6的BootLoader包含一个16位的汇编代码文件bootasm.s和C程序文件bootmain.c

设置A20地址线使得CPU完全使用所有地址总线。通过源码可以看出,xv6是通过0x64和0x60端口的,看端口的第二位是否为1,如果为1则该位就可以继续使用。

;写端口数据设置A20地址线
seta20.1:
  inb     $0x64,%al               
  testb   $0x2,%al
  jnz     seta20.1
​
  movb    $0xd1,%al             
  outb    %al,$0x64
​
seta20.2:
  inb     $0x64,%al              
  testb   $0x2,%al
  jnz     seta20.2
​
  movb    $0xdf,%al              
​
  outb    %al,$0x60

从实模式切换到保护模式。

在实模式下,段寄存器 保存段描述表的索引。段描述表内,每一条记录段的基物理地址,界限,权限位。

上面是段描述表,从代码可以看出,有两个段,一个代码段一个数据段,并且他们都是从物理地址值的0开始,段大小都是4GB,线性地址=段地址+偏移,所以此时线性地址等于虚拟地址。也就是说xv6实际上并没有使用段

;载入GDT描述符到寄存器,开启保护模式
lgdt    gdtdesc
movl    %cr0, %eax
orl     $CR0_PE, %eax
movl    %eax, %cr0
​
ljmp    $(SEG_KCODE<<3), $start32

值得说明的一点是,这时候并没有允许分页硬件工作,所以线性地址就是物理地址。

然后初始化栈,因为调用C语言的函数需要用栈。xv6的BootLoader将0x7c00处设为临时栈。可以从代码看出这个start就是0x7c00,BootyLoader是从0x7c00开始向上增长,而栈是向下增长,所以并不会重合。

设置好临时栈后,调用bootmain函数,他的任务就是加载并运行内核。

  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();

bootmain函数的过程流程图说的很清楚,唯一要提的一点是最后,通过入口地址将控制权交给内核,调用entry();进入内核,而那个入口地址,就是entry的地址。通过文档知道,查看kernel发现开始地址是0x0010000c

进入内核后,此时内核现在存在于内存的低地址处,而内核的虚拟地址是在高地址(0x80000000),所以之后需要就分页。

先设置页表,并开始分页。

  movl    %cr4, %eax
  orl     $(CR4_PSE), %eax
  movl    %eax, %cr4
​
#Set page directory
​
  movl    $(V2P_WO(entrypgdir)), %eax
  movl    %eax, %cr3
​
###### Turn on paging.
​
  movl    %cr0, %eax
  orl     $(CR0_PG|CR0_WP), %eax
​
  movl    %eax, %cr0
​

页表的物理地址entrypgdir就存在%CR3寄存器中

这是entrypgdir的内容

_attribute__((__aligned__(PGSIZE)))
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,
​
};

可以看出虚拟地址(线性地址)-KERNBASE(0x80000000) = 物理地址 ,页表将内核虚拟地址的4Mb内存映射到物理地址的低内存。

开启页表后,就调用main函数,进行一系列的初始化。

int main(void)
{
  kinit1(end, P2V(4*1024*1024)); // phys page allocator
  kvmalloc();      // kernel page table
  mpinit();        // detect other processors
  lapicinit();     // interrupt controller
  seginit();       // segment descriptors
  cprintf("\ncpu%d: starting xv6\n\n", cpunum());
  picinit();       // another interrupt controller
  ioapicinit();    // another interrupt controller
  consoleinit();   // console hardware
  uartinit();      // serial port
  pinit();         // process table
  tvinit();        // trap vectors
  binit();         // buffer cache
  fileinit();      // file table
  ideinit();       // disk
  if(!ismp)
  timerinit();   // uniprocessor timer
  startothers();   // start other processors
  kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
  userinit();      // first user process
  mpmain();        // finish this processor's setup
​
}

猜你喜欢

转载自blog.csdn.net/qq_37702890/article/details/84571904
今日推荐