補充知識
補充1. AT&T匯編
由于內核代碼采用的gcc編譯器使用AT&T的匯編格式,首先補充下關于AT&T匯編的知識。
- 匯編器命令 (assembler directives)
- 匯編程序中以
.
開頭的不會被翻譯成機器指令,而是給編譯器一些特殊的指示。 - 操作數賦值方向
- 從左到右。
- 前綴
- 寄存器前綴為
%
,立即數前綴為$
。
- 寄存器前綴為
- 后綴
- 指令最后一個字符用于表示操作數的大小,
b
表示byte(1個字節),w
表示word(2個字節),l
表示long(4個字節)。
- 指令最后一個字符用于表示操作數的大小,
補充2. A20門和PS/2 Controller
在 8086 中有 20 根地址總線,通過 CS:IP 對的方式尋址,最大訪問地址為 1MB,然而,FFFFH:FFFFH = 10FFEFH,也就是說從 100000H 到 10FFEFH 無法訪問,當訪問這段地址時,會產生 wrap-around,也就是實際訪問地址會對 1MB 求模。
到了 80286 中有 24 根地址總線,最大訪問地址為 16MB。這個時候,不會產生 wrap-around,為了向下兼容 8086,需要使用第 21 根地址總線。
所以 IBM 的工程師使用 PS/2 Controller 輸出端口中多余的端口來管理 A20 gate,也就是第 21 根地址總線(從 0 開始)。
注意下表,0x60
用于讀寫數據,0x64
用于讀寫狀態。
PS/2 Controller IO Ports
IO Port | Access Type | Purpose |
---|---|---|
0x60 | Read/Write | Data Port |
0x64 | Read | State Register |
0x64 | Write | Command Register |
Status Register
Bit | Meaning |
---|---|
1 | Input buffer status (0 = empty, 1 = full) (must be clear before attempting to write data to IO port 0x60 or IO port 0x64) |
Command Register
Command Byte | Meaning | Response |
---|---|---|
0xD1 | Write next byte to Controller Output Port Note: Check if output buffer is empty first |
None |
PS/2 Controller Output Port
Bit | Meaning |
---|---|
1 | A20 gate (output) |
boot.S
boot/boot.S
可以分為兩部分,第一部分是在實模式下運行的。
#include <inc/mmu.h>
# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.
# .set 相當于 #define,用于設置常量
.set PROT_MODE_CSEG, 0x8 # kernel code segment selector
.set PROT_MODE_DSEG, 0x10 # kernel data segment selector
.set CR0_PE_ON, 0x1 # protected mode enable flag
# .globl使得連接程序(ld)能夠看到start。
# 作用是使得同一文件夾的其他文件能引用start。
.globl start
start:
.code16 # Assemble for 16-bit mode
cli # Disable interrupts
cld # String operations increment
# Set up the important data segment registers (DS, ES, SS).
# AX, DS, ES, SS 寄存器全部置0
xorw %ax,%ax # Segment number zero
movw %ax,%ds # -> Data Segment
movw %ax,%es # -> Extra Segment
movw %ax,%ss # -> Stack Segment
# Enable 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.
# 從PS/2 Controller的I/O Port讀取一個byte
# **** **1* 表示忙, 所以用0x2作test運算
# 若test結果不為0, jnz跳轉回函數起點
seta20.1:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.1
# 通知PS/2 Controller,將下一個寫入0x60的字節寫出到 Output Port
movb $0xd1,%al # 0xd1 -> port 0x64
outb %al,$0x64
# 與seta20.1作用相同,等待端口空閑
seta20.2:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.2
# 將0xdf寫出到0x60, 再寫出到 Output Port, 打開了A20 gate
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.
lgdt gdtdesc
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 $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
# If bootmain returns (it shouldn't), loop.
spin:
jmp spin
# 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