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
}