嘮叨一下
沒想到第一課的信息量就這麼大,如果是沒有接觸過彙編的同學可能會有些難度,這裏不僅要求對系統有一定的瞭解,還得會通過調試的方法來查看整個程序的運行過程。
我的環境還沒有搭建好,看了一些大佬的在環境搭建的時所到的坑,多有點耐心一定可以的。
不知道你們有沒有看PPT,第一課的教案內容也十分豐富,人家的緒論一點都不水反而是整體的總結了一下系統的各個層次和設計難點。作業可以看成是實踐教學了,美國的這種教育體系很和我的胃口不會覺得無聊,有挑戰性!
作業筆記
第一步很人性化的教你如何建立一個git上的倉庫,這一步有興趣可以看看
第二步搭建環境,老師寫了一個小的內核程序,JOS,我們需要完善裏面的一些代碼。程序運行在qemu上面,屬於模擬器畢竟操作系統可能被玩壞。
Part 1: PC BootstrapGetting
Started with x86 assembly:熟悉一下指令集,老師也知道單獨看彙編那些指令也記不住,所以只是告訴我們在之後可能會用得上這些資料。彙編有好幾種不同的指令集,這次所用的是NASM Intel風格的彙編指令。
Simulating the x86:配置搭建運行環境,這裏參加一些大佬的環境部署,會有很多坑
The PC’s Physical Address Space:這張圖信息量爆炸!不過也不難理解,講的很詳細
個人理解:
- 最早的時候PC電腦只有16位(Intel 8088),因此可以尋址空間很少,因爲總線只有16位。所以最早只能尋址640K (0x00000000 ~ 0x0000FFFF),所以這一段被稱爲是low memory,如果在16bit下只能隨機的訪問這麼多內存。
- 屏幕的緩存和固件驅動都需要佔用一定的存儲空間,這個部分就是空出來的,一般來說我們的視頻緩存內容都會用voliate修飾並且放在這個區域裏面。
- 最早的BIOS程序都是放在ROM上面的,近幾年都放在了flash上面,程序也不大,就是一個初始化硬件引導系統的一個裝置,有了它無論是在u盤裏面還是硬盤,系統都能被加載到內存裏面運行起來。
- JOS使用的是前256M因此就只學習32位系統的設計
- 學習GDB調試方法,由於已經提供了.gdbinit文件,
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
這一個是GDB第一個執行的指令的位置,其中可以分析得到幾點
PC開始執行的位置需要兩個寄存器,在括號的裏面,前面的f000是CS(code segment)存儲的地址,再加上IP 存儲的位置是0xfff0。physical address = 16 * segment + offset。CS存儲的是段的地址,IP是offset。
ljmp是一個長跳轉指令,可以不改變系統狀態寄存器的值。這條指令的意思是jumps to the segmented address CS = 0xf000 and IP = 0xe05b。在下面的boot.S文件裏面也用到了這個指令。
Boot.S文件內容分析
其實boot的整個的過程需要兩個文件,其中一個是.S結尾的彙編文件,另一個是.C結尾的c文件,因爲在最初的時候需要經歷實模式到特權模式的轉換,16bit到32bit的轉換,所以最開始的步驟是在彙編文件內部完成初始化的,之後切換到32bit的模式下,最後進入main函數內部開啓bootloader程序的過程(系統引導等)
#include <inc/mmu.h>
#用於給一些寄存器做配置
.set PROT_MODE_CSEG, 0x8
.set PROT_MODE_DSEG, 0x10
.set CR0_PE_ON, 0x1
#.globl代表代碼start可以被其他的文件所調用
.globl start
start:
.code16 # Assemble for 16-bit mode
cli # Disable interrupts
cld # String operations increment 主要是爲了main函數複製內核程序使用
# Set up the important data segment registers (DS, ES, SS). 清零所有寄存器
xorw %ax,%ax # Segment number zero 異或操作指令
movw %ax,%ds # -> Data Segment
movw %ax,%es # -> Extra Segment
movw %ax,%ss # -> Stack Segment
# Enable A20: 使能A20總線,可以提升訪問空間的容量
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
seta20.1:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.1
movb $0xd1,%al # 0xd1 -> port 0x64
outb %al,$0x64
seta20.2:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.2
movb $0xdf,%al # 0xdf -> port 0x60
outb %al,$0x60
# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to their physical addresses, so that the
# effective memory map does not change during the switch.
# 加載全局描述符(GDT),標號gdtsec處存放了6字節有關GDT位置的信息
lgdt gdtdesc
# 將cr0最後一位置爲1,正式切換到保護模式
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
# ljmp長跳轉,在加載GDT以後,由於內部硬件設計的原因,必須要重設所有段寄存器的值,但沒有直接改變指令寄存器cs的指令,因此使用一個長跳轉指令改變cs的值。
ljmp $PROT_MODE_CSEG, $protcseg
.code32 # Assemble for 32-bit mode
protcseg:
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
# Set up the stack pointer and call into C.
movl $start, %esp
call bootmain #進入bootmain c文件內
# If bootmain returns (it shouldn't), loop.
#這裏是一個死循環,如果bootmain函數發生錯誤返回,計算機就會卡在這兒,就操作系統而言,最好在這裏設置一些提示信息
spin:
jmp spin
介紹一下gdt
# Bootstrap GDT
.p2align 2 # force 4 byte alignment
gdt:
SEG_NULL # null seg
SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg 代碼段
SEG(STA_W, 0x0, 0xffffffff) # data seg 數據段
gdtdesc:
.word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt
標號gdt處設計了有3個表項的GDT表,每個表項長度爲8字節,第一個表項必須設置爲空的
標號gdtdesc是要往GDTR寄存器里加載的6字節信息,GDTR寄存器的低2字節儲存GDT表的長度,高4字節儲存GDT表在內存中的首地址。
在保護模式下需要gdt來完成尋址等操作,下面來分析一下GDT的作用
參考GDT詳解
實模式下的內存尋址
內存絕對地址 Address = Segament:Offset
16-bit段寄存器CS(Code Segment),DS(Data Segment),SS(Stack Segment)來指定Segment,CPU將段積存器中的數值向左偏移4-bit,放到20-bit的地址線上就成爲20-bit的Base Address。
- Segment是一個段的Base Address。最大長度是64 KB,這是16-bit系統所能表示的最大長度
- Offset是相對於此Segment Base Address的偏移量
保護模式下的內存尋址
段模式 && 頁模式
頁模式基於段模式,因此實際上是分爲純段模式和段頁模式兩種
段模式下的尋址
基本思想仍舊是
Address = segment + offset
但是由於運行在32位系統上面,所以兩個指標都是32位,因此需要記錄段的最大長度limit段的base address 以及在保護模式下對於某一段所設定的保護權限。這三個指標就構成了64位的數據,這個數據就叫做段描述符。
段描述符格式 [Base Address ,Limit, Access]
- IA-32允許將一個段的Base Address設爲32-bit所能表示的任何值
- Limit則可以被設爲32-bit所能表示的,以2^12爲倍數的任何指
- Real Mode下,一個段的Base Address只能是16的倍數(因爲其低4-bit是通過左移運算得來的,只能爲0,從而達到使用16-bit段寄存器表示20-bit Base Address的目的
- 而一個段的Limit只能爲固定值64 KB。
GDT的出場
雖然需要64bit的數據結構去存儲一個段的描述符,但是由於intel需要向後兼容所以,將段積存器仍然規定爲16-bit。
16bit是不夠用的,所以需要把這些長度爲64-bit的段描述符放入一個數組中,而將段寄存器中的值作爲下標索引來間接引用。(事實上,是將段寄存器中的高13 -bit的內容作爲索引)
GDT可以被放在內存的任何位置,那麼當程序員通過段寄存器來引用一個段描述符時,CPU必須知道GDT的入口,也就是基地址放在哪裏。所以 Intel的設計者門提供了一個寄存器GDTR用來存放GDT的入口地址,程序員將GDT設定在內存中某個位置之後,可以通過LGDT指令將GDT的入口地址裝入此積存器。
除了GDT之外,IA-32還允許程序員構建與GDT類似的數據結構,它們被稱作LDT(Local Descriptor Table),但與GDT不同的是,LDT在系統中可以存在多個,並且從LDT的名字可以得知,LDT不是全局可見的,它們只對引用它們的任務可見,每個任務最多可以擁有一個LDT。另外,每一個LDT自身作爲一個段存在,它們的段描述符被放在GDT中。