知識點
- 作業系統的啟動知識和中斷的建立與初始化
- 涉及到Intel 806386暫存器,AT&T匯編,gcc行內匯編,C函式堆疊,Makefile等知識
筆記主要按照作業系統的啟動和中斷的建立兩個部分來記錄
理論課的介紹
系統啟動
當CPU剛加電初始化時,CS:IP暫存器根據設定的初始值跳轉到BIOS韌體處執行第一條指令,根據指令跳轉到BIOS資料區執行BIOS代碼,BIOS在完成硬體的自檢后,會將作業系統的啟動代碼加載到記憶體,此時CPU還處于實模式,只能尋址20位,也就是1MB的記憶體空間(通常處于記憶體空間的低位地址),所以作業系統的啟動代碼需要加載在這1MB的尋址空間內,實驗環境下,啟動代碼需加載到記憶體地址0x7C00處,啟動代碼再將CPU從實模式轉成保護模式,得以獲得32位的尋址空間(4GB),去加載代碼量龐大的作業系統,理論課視頻截圖較為系統地展示了這個程序,如下圖所示
為什么不利用BIOS直接加載作業系統?原因是不同作業系統可能擁有不同的檔案系統,BIOS無法撰寫所有檔案系統的決議代碼,所以將加載程式作為作業系統的一部分,讓作業系統可以“定制化”實作自己的加載程式,主引導記錄和活動磁區的存在主要是硬碟磁區的原因,
中斷建立
中斷源的型別
- 系統呼叫(system call):應用程式主動向作業系統發出的服務請求
- 例外(exception):非法指令或者其它原因導致指令執行失敗(如:記憶體出錯)后的處理請求
- 中斷(hardware interrupt):來自硬體設備的處理請求

如上圖所示,中斷向量表可以建立起中斷源和服務例程之間的聯系,在實操課程中中斷向量表的建立也是一項重要的內容,
實驗課的一些知識
在Lab1中,代碼主要分為bootblock和kernel兩個部分,bootblock完成了主引導記錄、活動磁區檔案系統識別以及加載程式的功能,kernel則是完成系統內核的功能,
bootblock
從make和makefile來看,bootblock主要涉及到的源檔案有bootmain.c,bootasm.S,sign.c,其中,bootmain.c,bootasm.S實作了bootblock的大部分功能,而sign.c從代碼上看來,只是將第二個命令列引數(argv[1])的檔案指標指向內容拷貝到argv[2]指向的檔案,并在檔案結尾加入主引導記錄標志0x55, 0xAA,控制拷貝完的檔案大小為512位元組,在make編譯鏈接的程序中,傳入bin/sign(sign.c生成)的檔案為obj/bootblock.out,輸出的檔案為bin/bootblock,bootblock.out由bootmain.c和bootasm.S生成,
bootasm.S
在實模式下,段暫存器和ip暫存器只提供16位的操作空間,為了尋址1MB的記憶體空間,此時段暫存器存盤的基值(16位,base)會左移四位,加上偏移量(IP暫存器,offset)作為邏輯地址,
一開始CPU還處于實模式,段機制還未啟動,但是將CPU轉成保護模式之前需要將重要的段暫存器設定好,轉換前段暫存器DS,ES,SS需置0
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
在進入保護模式之前,需要開啟A20地址線https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_appendix_a20.html,開啟步驟為
- 等待8042 Input buffer為空;
- 發送Write 8042 Output Port (P2)命令到8042 Input buffer;
- 等待8042 Input buffer為空;
- 將8042 Output Port(P2)得到位元組的第2位置1,然后寫入8042 Input buffer;
對應代碼為
seta20.1:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.1
movb $0xd1, %al # 0xd1 -> port 0x64
outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
seta20.2:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al # 0xdf -> port 0x60
outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
接下來載入GDT,
lgdt gdtdesc
...
# Bootstrap GDT
.p2align 2 # force 4 byte alignment
gdt:
SEG_NULLASM # null seg
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
gdtdesc:
.word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt
從代碼上看,CPU會先讀取GDT的描述資訊,確定GDT的大小(24位元組),再跳轉到gdt的所在地址,執行記憶體分段,實驗環境中,記憶體被分為代碼段和資料段,大小都為4G(0x0~0xffffffff).設定完成之后,進入32位的保護模式,進行相應的準備作業,
.code32 # Assemble for 32-bit mode
protcseg:
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # Our data segment selector, $PROT_MODE_DSEG=0x10,ax最高位為1,使ds等段暫存器相應位置1,以支持保護模式
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. The stack region is from 0--start(0x7c00)
movl $0x0, %ebp
movl $start, %esp # start為bootasm.S入口地址
一切準備作業就緒后,呼叫C函式bootmain.c繼續執行以至可以加載作業系統內核call bootmain,bootmain負責將內核加載到記憶體0x10000處,并檢查是否為合法的ELF檔案,
kernel
在Lab1中,提及最多的知識是關于C函式堆疊的內容,見https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_3_1_function_stack.html,除錯的編程參考于此
Lab1關于中斷的內容主要是實作LDT的初始化,線索從kern/trap/vector.S開始,其中以匯編的形式記錄了__vectors[].從功能上看,__vectors像是一個函式指標陣列,每個陣列成員對應某個函式操作的入口,不同型別的中斷向量被觸發之后都會跳轉到kern/trap/trapentry.S的__alltraps處,目前來講,這些功能使用C語言都能實作類似的效果,但是作業系統在這里使用了匯編的原因據推斷是這里需要獲取段暫存器的值,使用匯編會直接很多,__alltraps的目的是輔助C函式trap()的實作,trap的代碼如下:
void
trap(struct trapframe *tf) {
trap_dispatch(tf);
}
# trapframe的結構如下
struct trapframe {
struct pushregs tf_regs;
uint16_t tf_gs;
uint16_t tf_padding0;
uint16_t tf_fs;
uint16_t tf_padding1;
uint16_t tf_es;
uint16_t tf_padding2;
uint16_t tf_ds;
uint16_t tf_padding3;
uint32_t tf_trapno;
/* below here defined by x86 hardware */
uint32_t tf_err;
uintptr_t tf_eip;
uint16_t tf_cs;
uint16_t tf_padding4;
uint32_t tf_eflags;
/* below here only when crossing rings, such as from user to kernel */
uintptr_t tf_esp;
uint16_t tf_ss;
uint16_t tf_padding5;
} __attribute__((packed));
匯編代碼
.text
.globl __alltraps
__alltraps:
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushal
# load GD_KDATA into %ds and %es to set up data segments for kernel
movl $GD_KDATA, %eax
movw %ax, %ds
movw %ax, %es
pushl %esp
call trap
push陳述句主要是為了模擬C函式的堆疊形式,最先入堆疊的是C函式的實際引數,該引數為指向trapframe的指標,但在此之前需要將指標指向內容壓堆疊,可以看到匯編壓堆疊順序和結構成員宣告相反(說明:結構成員tf_paddingx是作為填充消除編譯器的記憶體對齊問題),之后呼叫trap_dispatch,根據中斷號執行不同的中斷例程,
回到LDT的初始化,現在已經大概清楚從中斷向量到觸發中斷例程的程序,但是怎么聯系起中斷向量__vectors[]和中斷源呢?這是idt_init需要實作的東西,中斷源用結構體gatedesc描述,再將陣列idt[256]對應不同的中斷源,idt_init做的很重要的一件事是怎么將__vectors[]和idt[]相同下標的gatedesc成員賦值好,最后利用匯編命令lidt載入LDT.
結尾
筆記寫的不是非常嚴謹,此筆記主要功能是提供自己能夠快速回憶作業系統知識,有其他重要知識點就等以后意識到再補充吧
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/5001.html
標籤:C
上一篇:歸并排序
下一篇:C 實戰練習題目72
