1. 程式人生 > >linux2.4 啟動程式碼head.S分析

linux2.4 啟動程式碼head.S分析

32位啟動程式碼,暫時不考慮SMP的情況。關鍵程式碼分析

頁目錄表的起始地址在0x101000,由於目前仍然處於真實模式,地址都是
實體地址

開始啟動核心
startup_32:
 清方向標誌位
 cld
 用核心資料段的地址來初始化ds,es,fs,gs暫存器
 巨集__KERNEL_DS在segment.h中有定義,對於i386體系結構來說__KERNEL_DS=0x18
 
 movl $(__KERNEL_DS),%eax
 movl %eax,%ds
 movl %eax,%es
 movl %eax,%fs
 movl %eax,%gs

 初始化頁表,由於程式中實用的符號的地址都是虛擬地址,所以$pg0 - __PAGE_OFFSET就是pg0的實體地址
 movl $pg0-__PAGE_OFFSET,%edi
 頁表項的值:該頁在記憶體中,使用者可寫 
 movl $007,%eax  
 進行初始化頁表
2: stosl
 索引值加1
 add $0x1000,%eax
 初始化pg0和pg1兩張表
 cmp $empty_zero_page-__PAGE_OFFSET,%edi
 jne 2b

3:
 頁目錄表的實體地址:$swapper_pg_dir-__PAGE_OFFSET
 起始於0x101000
 movl $swapper_pg_dir-__PAGE_OFFSET,%eax
 頁目錄表的實體地址存入cr3暫存器中
 movl %eax,%cr3  
 開啟分頁機制,重置cr0控制暫存器
 movl %cr0,%eax
 orl $0x80000000,%eax
 movl %eax,%cr0
 
 這樣做只是為了重新整理指令流水線,486之後採用了2條流水線,保證運算元是虛擬地址

這樣才能平穩過渡到保護模式  
 jmp 1f
1:
 movl $1f,%eax
 jmp *%eax 
1:
 初始化堆疊指標暫存器,核心堆疊結構:
 task_union+核心資料段。task_union佔用8k
 lss stack_start,%esp
 
 初始化eax暫存器為0
 xorl %eax,%eax
 將為初始化資料段的其實地址存入edi暫存器中
 movl $ SYMBOL_NAME(__bss_start),%edi
 將核心映像的結束地址存入ecx中。
 movl $ SYMBOL_NAME(_end),%ecx
 將offset存入ecx中。
 subl %edi,%ecx
 對這段記憶體區域進行初始化操作,初始化為0
 rep
 stosb

 設定中斷描述符表
 call setup_idt

 利用push/pop指令初始化eflags暫存器
 pushl $0
 popfl
 
 將第三張頁表的首地址存入edi暫存器中
 movl $ SYMBOL_NAME(empty_zero_page),%edi
 一共需要初始化4k的記憶體。
 前2k記憶體存放引導引數,後2kb記憶體存放命令列引數
 movl $512,%ecx
 cld
 rep
 movsl
 將後2kb初始化為0
 xorl %eax,%eax
 movl $512,%ecx
 rep
 stosl

設定中斷描述符表子程式
setup_idt:
 將預設中斷處理函式的有效地址放入edx暫存器中
 lea ignore_int,%edx
 初始化中斷門描述符
 中斷處理程式入口地址放在0-15位
 movl $(__KERNEL_CS << 16),%eax
 核心程式碼段選擇符存放在16-31位
 movw %dx,%ax  
 movw $0x8E00,%dx
 將中斷描述符表的地址存入edi中
 lea SYMBOL_NAME(idt_table),%edi
 設定256項中斷描述符表項
 mov $256,%ecx
rp_sidt:
 設定表項,一個表項佔8位元組
 movl %eax,(%edi)
 movl %edx,4(%edi)
 addl $8,%edi
 dec %ecx
 jne rp_sidt
 ret

定義核心棧
ENTRY(stack_start)
 .long SYMBOL_NAME(init_task_union)+8192
 .long __KERNEL_DS

int_msg:
 .asciz "Unknown interrupt, stack: %p %p %p %p/n"
 ALIGN
定義預設中斷處理過程,僅僅列印"Unknown interrupt, stack: %p %p %p %p/n"
ignore_int:
 cld
 movl $(__KERNEL_DS),%eax
 movl %eax,%ds
 movl %eax,%es
 pushl 12(%esp)
 pushl 12(%esp)
 pushl 12(%esp)
 pushl 12(%esp)
 pushl $int_msg
 call SYMBOL_NAME(printk)
1: hlt
 jmp 1b

定義中斷描述符表表項數量
#define IDT_ENTRIES 256

定義頁目錄表,首先定一個了2張頁表,用來對映核心記憶體空間

分別用於核心和使用者區使用,並且對映到相同的實體地址空間(0-8M),

但是不能通過使用者地址空間的虛擬地址來訪問核心空間,這樣做的原因是保證真實模式到保護模式的平穩過渡。
.org 0x1000
ENTRY(swapper_pg_dir)
 .long 0x00102007
 .long 0x00103007
 .fill BOOT_USER_PGD_PTRS-2,4,0
 .long 0x00102007
 .long 0x00103007
 .fill BOOT_KERNEL_PGD_PTRS-2,4,0

第一張頁表

由於在進入starup_32之前,reamponline.S已經將核心程式碼段設定成從1M開始了。真實模式 flush_instr:
 ljmpl $__KERNEL_CS, $0x00100000
符號地址在真實模式下:cs:offset
.org 0x2000
ENTRY(pg0)

第二張頁表
.org 0x3000
ENTRY(pg1)

兩張頁表對映8M空間
.org 0x4000
ENTRY(empty_zero_page)

定義全域性表述符表,因為linux採用的是分頁機制,所以在全域性描述符表中設定4個表項,

簡化分段到分頁的地址轉換,這時虛擬地址空間和線性地址空間是一樣的,都能表示4G

空間。在虛擬空間中,核心起始地址和使用者地址空間起始位置相同。
ENTRY(gdt_table)
 .quad 0x0000000000000000 空描述表項,一般不用
 .quad 0x0000000000000000 同上
 .quad 0x00cf9a000000ffff 核心程式碼段
 .quad 0x00cf92000000ffff 核心資料段
 .quad 0x00cffa000000ffff 使用者程式碼段
 .quad 0x00cff2000000ffff 使用者資料段