天天看點

清華大學教學核心ucore學習系列(1) bootloader

ucore是清華大學作業系統課程的實驗核心,也是一個開源項目,是不可多得的非常好的作業系統學習資源

https://github.com/chyyuu/ucore_lab.git, 各位同學可以使用git下載下傳源碼和文檔。

本文我會對項目中的code/lab1/boot/bootasm.S檔案進行完全注釋。

1 #include <asm.h>
asm.h頭檔案中包含了一些宏定義,用于定義gdt,gdt是保護模式使用的全局段描述符表,其中存儲着段描述符。
 2
 3 # Start the CPU: switch to 32-bit protected mode, jump into C.
 4 # The BIOS loads this code from the first sector of the hard disk into
 5 # memory at physical address 0x7c00 and starts executing in real mode
 6 # with %cs=0 %ip=7c00.
此段注釋說明了要完成的目的:啟動保護模式,轉入C函數。
這裡正好說了一下bootasm.S檔案的作用。計算機加電後,由BIOS将bootasm.S生成的可執行代碼從硬碟的第一個扇區複制到記憶體中的實體位址0x7c00處,并開始執行。
此時系統處于實模式。可用記憶體不多于1M。
 7 
 8 .set PROT_MODE_CSEG,        0x8                     # kernel code segment selector
 9 .set PROT_MODE_DSEG,        0x10                    # kernel data segment selector
這兩個段選擇子的作用其實是提供了gdt中代碼段和資料段的索引,具體怎麼用的下面用到的時候我們詳細解釋

10 .set CR0_PE_ON,             0x1                     # protected mode enable flag
這個變量也在下面用到的時候解釋

11 
12 # start address should be 0:7c00, in real mode, the beginning address of the running bootloader
13 .globl start
14 start:
這兩行代碼相當于定義了C語言中的main函數,start就相當于main,BIOS調用程式時,從這裡開始執行

15 .code16                                             # Assemble for 16-bit mode
因為以下代碼是在實模式下執行,是以要告訴編譯器使用16位模式編譯。

16     cli                                             # Disable interrupts
17     cld                                             # String operations increment
關中斷,設定字元串操作是遞增方向。cld的作用是将direct flag标志位清零,it means that instructions that autoincrement the source index and destination index (like MOVS) will increase both of them。

18 
19     # Set up the important data segment registers (DS, ES, SS).
20     xorw %ax, %ax                                   # Segment number zero
ax寄存器就是eax寄存器的低十六位,使用xorw清零ax,效果相當于movw $0, %ax。 但是好像xorw性能好一些,google了一下沒有得到好答案

21     movw %ax, %ds                                   # -> Data Segment
22     movw %ax, %es                                   # -> Extra Segment
23     movw %ax, %ss                                   # -> Stack Segment
24
将段選擇子清零
 
25     # Enable A20:
26     #  For backwards compatibility with the earliest PCs, physical
27     #  address line 20 is tied low, so that addresses higher than
28     #  1MB wrap around to zero by default. This code undoes this.
準備工作就緒,下面開始動真格的了,激活A20位址位。先翻譯注釋:由于需要相容早期pc,實體位址的第20位綁定為0,是以高于1MB的位址又回到了0x00000.
好了,激活A20後,就可以通路所有4G記憶體了,就可以使用保護模式了。

怎麼激活呢,由于曆史原因A20位址位由鍵盤控制器晶片8042管理。是以要給8042發指令激活A20
8042有兩個IO端口:0x60和0x64, 激活流程位: 發送0xd1指令到0x64端口 --> 發送0xdf到0x60,done!

29 seta20.1:
30     inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
31     testb $0x2, %al
32     jnz seta20.1
發送指令之前,要等待鍵盤輸入緩沖區為空,這通過8042的狀态寄存器的第2bit來觀察,而狀态寄存器的值可以讀0x64端口得到。
上面的指令的意思就是,如果狀态寄存器的第2位為1,就跳到seta20.1符号處執行,知道第2位為0,代表緩沖區為空

33 
34     movb $0xd1, %al                                 # 0xd1 -> port 0x64
35     outb %al, $0x64                                 # 0xd1 means: write data to 8042's P2 port
發送0xd1到0x64端口
36 
37 seta20.2:
38     inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
39     testb $0x2, %al
40     jnz seta20.2
41 
42     movb $0xdf, %al                                 # 0xdf -> port 0x60
43     outb %al, $0x60                                 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
44 
到此,A20激活完成。

45     # Switch from real to protected mode, using a bootstrap GDT
46     # and segment translation that makes virtual addresses
47     # identical to physical addresses, so that the
48     # effective memory map does not change during the switch.
轉入保護模式,這裡需要指定一個臨時的GDT,來翻譯邏輯位址。這裡使用的GDT通過gdtdesc段定義,它翻譯得到的實體位址和虛拟位址相同,是以轉換過程中記憶體映射不會改變

49     lgdt gdtdesc
載入gdt

50     movl %cr0, %eax
51     orl $CR0_PE_ON, %eax
52     movl %eax, %cr0
打開保護模式标志位,相當于按下了保護模式的開關。cr0寄存器的第0位就是這個開關,通過CR0_PE_ON或cr0寄存器,将第0位置1

53 
54     # Jump to next instruction, but in 32-bit code segment.
55     # Switches processor into 32-bit mode.
56     ljmp $PROT_MODE_CSEG, $protcseg
57
由于上面的代碼已經打開了保護模式了,是以這裡要使用邏輯位址,而不是之前實模式的位址了。
這裡用到了PROT_MODE_CSEG, 他的值是0x8。根據段選擇子的格式定義,0x8就翻譯成:
        INDEX         TI     CPL
        0000 0000 0000 1      00      0
INDEX代表GDT中的索引,TI代表使用GDTR中的GDT, CPL代表處于特權級。
      
PROT_MODE_CSEG選擇子選擇了GDT中的第1個段描述符。這裡使用的gdt就是變量gdt,下面可以看到gdt的第1個段描述符的基位址是0x0000,是以經過映射後和轉換前的記憶體映射的實體位址一樣。
      
58 .code32                                             # Assemble for 32-bit mode
59 protcseg:
60     # Set up the protected-mode data segment registers
61     movw $PROT_MODE_DSEG, %ax                       # Our data segment selector
62     movw %ax, %ds                                   # -> DS: Data Segment
63     movw %ax, %es                                   # -> ES: Extra Segment
64     movw %ax, %fs                                   # -> FS
65     movw %ax, %gs                                   # -> GS
66     movw %ax, %ss                                   # -> SS: Stack Segment
67 
重新初始化各個段寄存器。
68     # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
69     movl $0x0, %ebp
70     movl $start, %esp
71     call bootmain
棧頂設定在start處,也就是位址0x7c00處,call函數将傳回位址入棧,将控制權交給bootmain
72 
73     # If bootmain returns (it shouldn't), loop.
74 spin:
75     jmp spin
76 
77 # Bootstrap GDT
78 .p2align 2                                          # force 4 byte alignment
79 gdt:
80     SEG_NULLASM                                     # null seg
81     SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)           # code seg for bootloader and kernel
82     SEG_ASM(STA_W, 0x0, 0xffffffff)                 # data seg for bootloader and kernel
83 
84 gdtdesc:
85     .word 0x17                                      # sizeof(gdt) - 1
86     .long gdt                                       # address gdt      

繼續閱讀