1. 程式人生 > >xv6學習筆記核心初始化

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
​
}