I386 KERNEL HEAD

1.1 核心起頭程序

核心起頭程式位於 linux/arch/x86/kernel/head_32.S,從標籤 startup_32 開始執行,結束時跳往 I386 核心起始函式。 核心起頭程式是 VMLINUX 核心映像的入口點,是 LINUX 核心映像開始執行的地方。

核心起頭程式將 GRUB2 或 setup.bin 準備好的起動參數 boot_params,透過 esi 暫存器,複製到 LINUX 核心的啟動參數結構 boot_params。 在映像尾部的標籤 __brk_base 的記憶體位址建立分頁表,此分頁表只涵概核心範圍,用來保護核心(??)。


1.2 一般進入點

一般進入點標籤 startup_32 是核心映像的進入點,也是從核心解壓縮程式跳過來的地方。

因為是在保護模式中運作,所以要先設定全域描述子表,再設定程式運作的節區暫存器值。

linux/arch/x86/kernel/head_32.S
01 ENTRY(startup_32)
02     movl pa(stack_start),%ecx
03     testb $(1<<6), BP_loadflags(%esi)
04     jnz 2f
05     lgdt pa(boot_gdt_descr)
06     movl $(__BOOT_DS),%eax
07     movl %eax,%ds
08     movl %eax,%es
09     movl %eax,%fs
10     movl %eax,%gs
11     movl %eax,%ss
12 2:  leal -__PAGE_OFFSET(%ecx),%esp

行號說明
02取得標籤 stack_start 在映像內的偏移值。
03測試 loadflags 的位元六。BP_loadflags(%esi) 指向核心參數結構的 loadflags,即 linux/arch/x86/boot/header.S 的 loadflags,值應該是 0x01。
04當位元六的值為一,跳往下一個標籤 "2:"。這個位元值應該是 0,所以不會跳躍。
05載入全域描述子表,boot_gdt_descr。
06取得常數 __BOOT_DS,值為 24。即全域描述子表的第四個全域描述子的位置值。
07~11設定節區暫存器 ds、es、fs、gs、ss,全部指向第四個全域描述子。
12設定堆疊指標,好像是以 stack_start-0xC0000000 的位址值做為堆疊頂端,不過這樣說也不大對勁。

清除未初始化的變數記憶體,即 bss 區段內容。

linux/arch/x86/kernel/head_32.S
01     cld
02     xorl %eax,%eax
03     movl $pa(__bss_start),%edi
04     movl $pa(__bss_stop),%ecx
05     subl %edi,%ecx
06     shrl $2,%ecx
07     rep ; stosl

行號說明
01清除 CPU 的 direction 旗號位元,表示位址遞增,。
02清除 eax 暫存器值,。
03取得 bss 區段起始位址值,。
04取得 bss 區段終點位址值,。
05計算 bss 區段的體積,單位是位元組。
06轉換 bss 區段的體積值,單位為長整數組(即四位元組)。
07記憶體設定指令,rep 表示反覆執行,stosl 表示以 eax 暫存器值設定記憶體。

將核心起動參數結構從真實模式位址複製到核心資料結構 boot_params。 核心啟動參數結構是宣告在 linux/arch/x86/kernel/setup.c。

linux/arch/x86/kernel/head_32.S
01     movl $pa(boot_params),%edi
02     movl $(PARAM_SIZE/4),%ecx
03     cld
04     rep
05     movsl

行號說明
01取得啟動參數結構在映像中的位址,。
02將啟動參數體積轉換成長整數為單位的體積值,。
03清除 CPU 的 direction 旗號位元,表示位址遞增,。
04設定反覆模式,。
05執行資料複製,每次複製四位元組的資料。

將命令列資料從真實模式位址複製到核心命令列資料結構,。 核心命令列資料結構是宣告在 linux/init/main.c。

linux/arch/x86/kernel/head_32.S
01     movl pa(boot_params) + NEW_CL_POINTER,%esi
02     andl %esi,%esi
03     jz 1f
04     movl $pa(boot_command_line),%edi
05     movl $(COMMAND_LINE_SIZE/4),%ecx
06     rep
07     movsl
08 1:

行號說明
01取得命令列資料指標值,存入 esi 暫存器。
02~03當 esi 值為 0,表示沒有核心命令列資料,跳過資料複製程序。
04取得核心命令列資料結構位址值,存入 edi 暫存器。
05將命令列資料體積轉換成長整數為單位的體積值,。
06設定反覆模式,。
07執行資料複製,每次複製四位元組的資料。

建立分頁表和初始分頁目錄表。 分頁表位於 __brk_base 有 64KB 的空間,每四個位元組會對映一個 4KB 的頁記憶體,所以最大可以對映到 64MB。 初始分頁目錄表位於 initial_page_table,有 1024 個項目,用來指向分頁表的第 1000*N 的分頁項目位址,即每個目錄項目的距離是 4MB。 最大範圍只有 64 MB,因為核心不會超過這個體積,以筆者所編譯的核心體積來說,只有 9MB。

linux/arch/x86/kernel/head_32.S
01     movl $pa(__brk_base), %edi 
02     movl $pa(initial_page_table), %edx
03     movl $PTE_IDENT_ATTR, %eax 
04 10: leal PDE_IDENT_ATTR(%edi),%ecx 
05     movl %ecx,(%edx)            
06     movl %ecx,page_pde_offset(%edx)       
07     addl $4,%edx
08     movl $1024, %ecx
09 11: stosl
10     addl $0x1000,%eax
11     loop 11b
12     movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp
13     cmpl %ebp,%eax
14     jb 10b
15     addl $__PAGE_OFFSET, %edi
16     movl %edi, pa(_brk_end)
17     shrl $12, %eax
18     movl %eax, pa(max_pfn_mapped)
19     movl $pa(initial_pg_fixmap)+PDE_IDENT_ATTR,%eax
20     movl %eax,pa(initial_page_table+0xffc)
21     jmp default_entry

行號說明
01取得標籤 __brk_base 的偏移位址,存入 edi 暫存器,這裡由連結檔保留 64KB 的記憶體空間。
02取得初始分頁表目錄指標陣列的起始位址,存入 edx 暫存器。此時 edx 初始分頁表目錄指標陣列的第一個陣列單元,初始分頁表目錄指標陣列有 1024 個陣列單元。
03取得分頁表屬性,值為 0x003,表示分頁表為存在,並且可讀可寫。
04核心分頁表建立迴圈。取得分頁表項目 PDE 位址?? 筆者有點看不懂。從 __brk_base+3 開始??
05將一個分頁表項目位址(__brk_base+3+4*N)存入初始分頁表目錄指標陣列的一個項目,initial_page_table[N]。
06將一個分頁表項目位址存入核心分頁表目錄指標陣列的一個項目,此項目在在 initial_page_table+4K+4*N 的位址上。page_pde_offset 是 0xC0000000 >>20,等於 0xC00,即 4K。
07edx 指向初始分頁表目錄的下一個陣列項目,這是為著下次回到標籤 "10:" 使用。
08設定迴圈數為 1024。
09~11初始分頁表設定迴圈,迴圈數 1024 次。範圍是 __brk_base+4KB*N ~ __brk_base+4KB*(N+1)。
10將 eax 的值存入初始分頁表項目,值為 0x1000*M+0x003。M=0~1023。
11將 eax 的值以 0x1000 遞增,意思是一次加 4KB,值為 0x1000*N+0x003。N 為 0~1023。這和分頁表項目的表格有關,pageoffset[31~16]+pageattribute[15~0]。
12~14當 eax 的值已經到達 ebp 所指定的記憶體位址,停止設定分頁表。計算映像結束位址+16K+0x03,存入 ebp,再和目前的分頁偏移位址比較。 其中,PTE_IDENT_ATTR 只是為了和 eax 內的 PTE_IDENT_ATTR 做對映而加入。 筆者猜想,這應該只是為了保護核心而建立的分頁表,所以分頁表的範圍只到映像結束位址+16K。
15~16有點像是 movl $__brk_base+,pa(_brk_end),這是錯誤的組合語言,只是為了示意。addl $__PAGE_OFFSET, %edi 會讓 edi=$pa(__brk_base)+__PAGE_OFFSET=$__brk_base。
17將 eax 的值轉換成頁數值,計算 eax 所指向的頁框數。eax 的值是分頁表所指向的頁框位址加上分頁屬性 0x003,而頁位址的單位是 4KB,即 0x1000,所以要往右移 12 位元。
18將頁框數設定到最大已對映的頁框變數。
19~20計算初始分頁修正對映圖位址設定到初始分頁表目錄指標陣列的最後一個陣列單元,即 initial_page_table[1023]。initial_pg_fixmap 的值是 .fill 1024,4,0,這是一個分頁目錄的內容??。
21跳到 default_entry 進行接下來的程序。

定義頁偏移量,此值是 CONFIG_PAGE_OFFSET 加上型態 UL,變成一個無號長整數。

linux/arch/x86/include/asm/page_32_types.h
01 #define __PAGE_OFFSET _AC(CONFIG_PAGE_OFFSET, UL)

_AC 的定義是 __AC,將 (X,Y) 轉換成 X##Y,即 XY。例如 _AC(5,UL) 會轉換成 5UL。

linux/include/linux/const.h
01 #define __AC(X,Y) (X##Y)
02 #define _AC(X,Y)  __AC(X,Y)

_AC(CONFIG_PAGE_OFFSET, UL) 會轉譯成 __AC(0xC0000000,UL),再轉譯成 0xC0000000UL。 位址偏移值 0xC0000000 是 LINUX映像編譯的起始位址,表示映像最大體積為 0x40000000,即 1GB。

linux/include/generated/autoconf.h
01 #define CONFIG_PAGE_OFFSET 0xC0000000

pa(X) 是符號位址值減去 0xC0000000 的偏移值。

linux/arch/x86/kernel/head_32.S
01 #define pa(X) ((X) - __PAGE_OFFSET)

堆疊標籤 stack_start 位於堆疊頂端,堆疊體積為 init_thread_union+THREAD_SIZE,等於 THREAD_SIZE/4+THREAD_SIZE,即 2K+8K,單位是 long。

linux/arch/x86/kernel/head_32.S
01 .data
02 .balign 4
03 ENTRY(stack_start)
04 .long init_thread_union+THREAD_SIZE

啟動參數結構 boot_params 宣告在 linux/arch/x86/kernel/setup.c。

linux/arch/x86/kernel/setup.c
01 struct boot_params __initdata boot_params;

linux/arch/x86/include/asm/setup.h
01 #define PARAM_SIZE 4096

linux/arch/x86/kernel/vmlinux.lds
01 .brk : AT(ADDR(.brk) - 0xC0000000) {
02   __brk_base = .;
03   . += 64 * 1024; 
04   *(.brk_reservation)
05   __brk_limit = .;
06  }


1.3 多處理器進入點

沒有看到多處理器的進入點的程式碼,這可能是作者預備給多處理器用的起頭程序,目前不需要用到。

linux/arch/x86/kernel/head_32.S
01 __CPUINIT
02 ENTRY(startup_32_smp)
03     cld
04     movl $(__BOOT_DS),%eax
05     movl %eax,%ds
06     movl %eax,%es
07     movl %eax,%fs
08     movl %eax,%gs
09     movl pa(stack_start),%ecx
10     movl %eax,%ss
11     leal -__PAGE_OFFSET(%ecx),%esp

行號說明
02宣告入口點 startup_32_smp,這個入口點可能是作者預留給 SMP,但其實沒有用到。
03清除 CPU 的 direction 旗號位元,表示位址遞增,。
04~08設定節區暫存器 ds、es、fs、gs,全部指向第四個全域描述子。
09取得標籤 stack_start 在映像內的偏移值。
10設定堆疊節區暫存器 ss,全部指向第四個全域描述子。
11設定堆疊指標,好像是以 stack_start-0xC0000000 的位址值做為堆疊頂端,不過這樣說也不大對勁。

1.4 內定進入點

內定進入點 default_entry 是一般進入點和多處理器進入點會合的地方。 內定進入點會設定 CPU cr4 的 MMU 功能,之後啟動分頁功能。

linux/arch/x86/kernel/head_32.S
01 default_entry:
02 #define cr4_bits pa(mmu_cr4_features)
03     movl cr4_bits,%edx
04     andl %edx,%edx
05     jz 6f
06     movl %cr4,%eax        
07     orl %edx,%eax
08     movl %eax,%cr4
09     testb $X86_CR4_PAE, %al        
10     jz 6f
11     movl $0x80000000, %eax
12     cpuid
13     subl $0x80000001, %eax
14     cmpl $(0x8000ffff-0x80000001), %eax
15     ja 6f
16     call verify_cpu
17     mov $0x80000001, %eax
18     cpuid
19     btl $(X86_FEATURE_NX & 31), %edx
20     jnc 6f
21     movl $MSR_EFER, %ecx
22     rdmsr
23     btsl $_EFER_NX, %eax
24     wrmsr
25 6:  movl $pa(initial_page_table), %eax
26     movl %eax,%cr3        
27     movl %cr0,%eax
28     orl  $X86_CR0_PG,%eax
29     movl %eax,%cr0        
30     ljmp $__BOOT_CS,$1f
31 1:  addl $__PAGE_OFFSET, %esp
32     pushl $0
33     popfl
34     cmpb $0, ready
35     jnz checkCPUtype
36     call setup_idt

行號說明
03取得 mmu_cr4_features 變數資料值,存入 edx。
04當 mmu_cr4_features 的值為 0,程式跳往後面的標籤 "6:"。筆者認為此時的 mmu_cr4_features 值為 0。
05讀取 cr4 暫存器值,存入 eax。
07~08cr4 將 mmu_cr4_features 的值加入 cr4 值,存入 cr4 暫存器。也就是將 mmu_cr4_features 設定到 cr4 暫存器。
09~10當 cr4 的 PAE 位元值為 0,表示沒有啟用 PAE 功能,程式跳往標籤 "6:"。PAE 是 physical address extension,用於支援 64G 位址空間。X86_CR4_PAE 值為 0x20。
11設定 eax 為 0x80000000,準備用來讀取 cpu id。
12讀取 cpu id,結果會存入 eax。
13將 eax 的位元 31 和 位元 0 清除,筆者比較傾向使用 andl,如 andl $~0x80000001,%eax。
14~15當 eax 大於 0x0000fffe,表示 cpu 不支援 PAE,程式跳往標籤 "6:"。
16檢驗 cpu 的 long mode,用於支援 64GB 的虛擬記憶體。
17設定 eax 為 0x80000001,準備用來讀取 cpu id。
18讀取 cpu id,結果會存入 edx。
19~20當 %edx 和 0x0000000F 的和邏輯為 0,表示 CPU 不支援執行停止(execute disable)功能,程式跳往標籤 "6:"。
21設定 CPU 特別模式暫存器群(MSR)的延伸特性功能暫存器位址值 0xC0000080 到 ecx。
22讀取特性延伸功能暫存器,值為在 eax。
23致能執行停止功能,將 0x0000000B 的值設定到 eax。
24寫入特性延伸功能暫存器,。
25~26將初始分頁表目錄指標陣列位址設定到 CPU 的 cr3 暫存器,。
27~29設定 cr0 的 pg 位元,表示啟動分頁功能。
30使用跳躍指令,設定 cs 節區暫存器。
31設定新的堆疊指標暫存器,即 esp=esp+0xC0000000,這是虛擬記憶體的堆疊暫存器。
32~33初始化 elfags 暫存器為 0,。
34~35這是作者的防錯程式,原設定是當 SMP 模式才有用,用來跳過 idt 的設定。 第一次執行時 ready 值為 0,第二次執行時 ready 值為一,會跳過 idt 的設定。應該不會用到。
36設定保護模式的中斷描述子表,有別於真實模式的中斷表。

1.5 檢驗 CPU 型態

檢驗 CPU 為 386 或 486 以上,之後載入早期全域描述子表、中斷描述子表、區域描述子表。 最後,跳往 LINUX 初始化程序,即 I386 核心起始程式,執行核心初始化程序。

linux/arch/x86/kernel/head_32.S
01 checkCPUtype:
02     movl $-1,X86_CPUID
03     movb $3,X86        
04     pushfl            
05     popl %eax        
06     movl %eax,%ecx        
07     xorl $0x240000,%eax    
08     pushl %eax        
09     popfl            
10     pushfl            
11     popl %eax        
12     xorl %ecx,%eax        
13     pushl %ecx        
14     popfl
15     testl $0x40000,%eax    
16     je is386
17     movb $4,X86        
18     testl $0x200000,%eax    
19     je is486

行號說明
02將 0xFFFFFFFF 寫入 X86_CPUID ??,-1 表示沒有 CPU。
03至少要 80386 以上,。
04~05取得 eflags,。
06將 elfags 的值保存在 ecx,後面會用到。
07設定 eflasg 的 ac 和 id 位元值,。
08~09存入新的 eflags,。
10~11取得 eflags,。
12取得新的 eflags 和舊的 eflags 的互斥或邏輯的值,即不同的部分的值,應該只有 ac 和 id 會不一樣。
13~14回存就的 eflags,。
15~17當 id 位元值為 1,表示 cpu 是 386,跳到 386 處理程序。否則 cpu 就是 486 以上,設定 X86 為四。
18~19當 ac 位元值為 1,表示 cpu 是 486,跳到 486 處理程序。否則 cpu 就是 pentium 以上等級。

linux/arch/x86/kernel/head_32.S
01     xorl %eax,%eax            
02     cpuid
03     movl %eax,X86_CPUID        
04     movl %ebx,X86_VENDOR_ID        
05     movl %edx,X86_VENDOR_ID+4    
06     movl %ecx,X86_VENDOR_ID+8    
07     orl %eax,%eax        
08     je is486
09     movl $1,%eax        
10     cpuid
11     movb %al,%cl        
12     andb $0x0f,%ah        
13     movb %ah,X86
14     andb $0xf0,%al        
15     shrb $4,%al
16     movb %al,X86_MODEL
17     andb $0x0f,%cl        
18     movb %cl,X86_MASK
19     movl %edx,X86_CAPABILITY

行號說明
01清除 eax,。
02讀取 cpuid,。
03~06將 cpu 資訊存入 X86_CPUID 和 X86_VENDOR_ID 等資料結構。
07~08當 eax 為 0,表示 cpu 是 486,跳到 486 處理程序。
09~19當 cpu 是 pentium 以上等級,取得其他的 cpu 資訊。
10cpuid,產生 cpu 資訊。
10al 有 cpu 的遮罩資訊,暫時存入 cl。
12~13ah 有 cpu 的 等級資訊,存入變數 X86。。
14~16al 的高四位元有 cpu 模型資訊,存入變數 X86_MODEL。
17~18cl(即al) 的低四位元有 cpu 的遮罩資訊,存入 X86_MASK。
19edx 有 cpu 的能力資訊,存入 X86_CAPABILITY。

linux/arch/x86/kernel/head_32.S
01 is486:    
02     movl $0x50022,%ecx    
03     jmp 2f
04 is386:    
05     movl $2,%ecx        
06 2:  movl %cr0,%eax
07     andl $0x80000011,%eax    
08     orl %ecx,%eax
09     movl %eax,%cr0
10     call check_x87
11     lgdt early_gdt_descr
12     lidt idt_descr
13     ljmp $(__KERNEL_CS),$1f
14 1:  movl $(__KERNEL_DS),%eax    
15     movl %eax,%ss            
16     movl $(__USER_DS),%eax        
17     movl %eax,%ds
18     movl %eax,%es
19     movl $(__KERNEL_PERCPU), %eax
20     movl %eax,%fs            
21     movl $(__KERNEL_STACK_CANARY),%eax
22     movl %eax,%gs
23     xorl %eax,%eax            
24     lldt %ax
25     cld            
26     pushl $0        
27     movb $1, ready
28     jmp *(initial_code)

行號說明
01~05當 cpu 是 486,將 ecx 設定為 0x50022。當 cpu 是 386,將 ecx 設定為 0x2。
06~09設定 cr0 的 bit32,bit1,bit 0。
10檢查 cpu 內建的浮點運算器。
11載入早期全域描述子表,early_gdt_descr。
12載入中斷描述子表,idt_descr,。
13使用跳躍指令,將 cs 變更為核心節區描述子。
14~15設定 ss 堆疊節區暫存器值,同核心資料節區描述子。
16~18設定資料與額外節區暫存器值,同使用者資料節區描述子。
19~20設定 fs 旗號節區暫存器值,同核心 PERCPU 節區描述子。
21~22設定圖形節區暫存器值,同核心小堆疊節區描述子。
23清除 eax,。
24載入區域描述子表,為空的區域描述子表。
25清除 cpu direction 旗號,這是 cpu 初始值。
26將 0 堆入堆疊,表示沒有參數傳遞。
27設定 ready 旗號為一,表示已經執行過核心起頭程式,不可以再執行。
28執行 I386 核心起始程式,。

2 CPU 驗證函式

CPU 驗證函式的檔案是 linux/arch/x86/kernel/verify_cpu.S。 CPU 驗證函式的功能是確定 cpu 支援 long mode。支緣 long mode 的同時也要支援分頁,所以也會檢查分頁特性 PSE 和 PGE。

linux/arch/x86/kernel/verify_cpu.S
01 verify_cpu:
02     pushfl                
03     pushl    $0            
04     popfl
05     pushfl                
06     popl    %eax
07     movl    %eax,%ebx
08     xorl    $0x200000,%eax
09     pushl    %eax
10     popfl
11     pushfl
12     popl    %eax
13     cmpl    %eax,%ebx
14     jz    verify_cpu_no_longmode    

行號說明
02預存 eflags 暫存器值,。
02~14測試 eflags 的 id 位元,id 位元可以設定和清除,表示 cpu 支援 cpuid 指令。
03將 eglags 暫存器設定為 0,做法是先將值堆入堆疊,再從堆疊將值取得存入 eflags。
05讀取 eflags 暫存值,存入 eax。
07將 eax 存入 ebx,即 eflags 存入 ebx。
08~10將 eflags 的第 21 位元值反向,回存 elfags。0x200000 是什麼?? id, identification flag。
11~12再讀一次 eflags,存入 eax。
13~14比較新舊 eflags,相同的時,表示沒有支援 cpuid 指令,也不會 long mode,程式跳往 verify_cpu_no_longmode。long mode 是指 64 位元模式,相對於 protection mode(32 位元)和 real mode(16 位元)。

檢驗 cpu 是否為 AuthenticAMD,。

linux/arch/x86/kernel/verify_cpu.S
01     movl    $0x0,%eax        
02     cpuid
03     cmpl    $0x1,%eax
04     jb    verify_cpu_no_longmode    
05     xor    %di,%di
06 verify_cpu_amd:  cmpl    $0x68747541,%ebx    
07     jnz    verify_cpu_noamd
08     cmpl    $0x69746e65,%edx
09     jnz    verify_cpu_noamd
10     cmpl    $0x444d4163,%ecx
11     jnz    verify_cpu_noamd
12     mov    $1,%di            
13     jmp    verify_cpu_check

行號說明
01清除 eax,準備讀取 cpuid。
02讀取 cpuid,結果會存入 eax。
03~04當 eax 小於 1,表示不支援 cpuid 1,程式跳往 verify_cpu_no_longmode。
05清除 di 暫存器值,。
06~11比較 AMD CPU 的 ID,內容為字串 AuthenticAMD。標籤 verify_cpu_amd 是筆者另外加上去的。
06~07比較 ebx 與 0x68747541 (Auth),不相同時,表示不是 AMD CPU,程序跳往 verify_cpu_noamd。
08~09比較 ebx 與 0x69746e65 (enti),不相同時,表示不是 AMD CPU,程序跳往 verify_cpu_noamd。
10~11比較 ebx 與 0x444d4163 (cAMD),不相同時,表示不是 AMD CPU,程序跳往 verify_cpu_noamd。
12設定 di 為一,表示 cpu 為 amd。
13程序跳往 verify_cpu_check,檢驗 cpu。

檢驗 cpu 是否為 GenuineIntel,。

linux/arch/x86/kernel/verify_cpu.S
01 verify_cpu_noamd:
02 verify_cpu_intel:
03     cmpl    $0x756e6547,%ebx
04     jnz    verify_cpu_check
05     cmpl    $0x49656e69,%edx
06     jnz    verify_cpu_check
07     cmpl    $0x6c65746e,%ecx
08     jnz    verify_cpu_check
09     movl    $0x1, %eax        
10     cpuid
11     movl    %eax, %ecx
12     andl    $0x0ff00f00, %eax
13     shrl    $8, %eax
14     cmpl    $6, %eax
15     ja    verify_cpu_clear_xd
16     jb    verify_cpu_check
17     andl    $0x000f00f0, %ecx
18     shrl    $4, %ecx
19     cmpl    $0xd, %ecx
20     jb    verify_cpu_check    
21 verify_cpu_clear_xd:
22     movl    $MSR_IA32_MISC_ENABLE, %ecx
23     rdmsr
24     btrl    $2, %edx        
25     jnc    verify_cpu_check    
26     wrmsr

行號說明
02筆者想把這個標籤命令為 verify_cpu_intel。
03~08比較 AMD CPU 的 ID,內容為字串 GenuineIntel。標籤 verify_cpu_amd 是筆者另外加上去的。
03~04比較 ebx 與 0x68747541 (Genu),不相同時,表示不是 INTEL CPU,程序跳往 verify_cpu_check。
05~06比較 ebx 與 0x69746e65 (ineI),不相同時,表示不是 INTEL CPU,程序跳往 verify_cpu_check。
07~18比較 ebx 與 0x444d4163 (ntel),不相同時,表示不是 INTEL CPU,程序跳往 verify_cpu_check。
09設定 eax 為 1,這是 cpuid 的指令參數,表示要取得 cpu 的版本資訊和特性資訊。
10執行 cpuid,。
11將結果 eax 存入 ecx。eax 有 cpu 版本資訊,包括類型、家族、模型等。
12~13取得版本資訊,在位元 8~11,所以要往右位移八位元。
14~16比較家族號碼與 6,家族號碼大於六表示 cpu 很新,家族號碼大於六表示 cpu 太舊,家族號碼等於六表示 cpu 是目前內定 cpu。
17當家族號碼等於六,檢驗 cpu 模型值與延伸模型值,位於原 eax 的 bit 4~7 和 16~19。
18~20當模型值小於 0x0d,程式跳往 verify_cpu_check。
21將 xd 除能,xd 是 execute disable 的功能,可將記憶體分成指令記憶體和資料記憶體,預防緩衝區攻擊的硬體技術。LINUX 並沒有使用此技術。
22~23讀取 MSR_IA32_MISC_ENABLE 暫存器值,設定 ecx 值為 0x1a0,這是 rdmsr 的參數。
24測試 MSR_IA32_MISC_ENABLE 的高位元組的位元 1,並清除之。該位元為 0,程式跳往 verify_cpu_check,即略過寫回 MSR_IA32_MISC_ENABLE。
25~26將清除了 MSR_IA32_MISC_ENABLE 的高位元組的位元 1 的值寫回 MSR_IA32_MISC_ENABLE,。

檢查 CPU 的分頁功能,PSE 和 PGE。二者都必須支援,否則 CPU 無法執行 LONG MODE。

linux/arch/x86/kernel/verify_cpu.S
01 verify_cpu_check:
02     movl    $0x1,%eax        
03     cpuid
04     andl    $REQUIRED_MASK0,%edx
05     xorl    $REQUIRED_MASK0,%edx
06     jnz    verify_cpu_no_longmode
07     movl    $0x80000000,%eax    
08     cpuid
09     cmpl    $0x80000001,%eax
10     jb      verify_cpu_no_longmode    
11     movl    $0x80000001,%eax    
12     cpuid
13     andl    $REQUIRED_MASK1,%edx
14     xorl    $REQUIRED_MASK1,%edx
15     jnz     verify_cpu_no_longmode

行號說明
01~02讀取 cpu 版本資訊,eax 為 1 是 cpuid 的參數。
04~06確定支援分頁功能。取 pse 和 pge 兩個位元值,再將此二位元值反向。若結果不為 0,表示 pse 和 pge 不都是一,不支援分頁功能,程式跳往 verify_cpu_no_longmode。
07~08讀取 cpu 版本資訊,eax 為 0x80000000 是 cpuid 的參數。
09~10比較 eax 與 0x80000001,eax 小於 0x80000001,程式跳往 verify_cpu_no_longmode。
11~12讀取 cpu 版本資訊,eax 為 0x80000001 是 cpuid 的參數。
13~15REQUIRED_MASK1 的值為 0,所以程式往下走,不會跳往 verify_cpu_no_longmode。

SSE 是 streaming simd extention 的簡寫。SEE 包含 MMX 指令集的支援。 測試 SSE 就是檢查 MMX 指令集的支援,LINUX 並沒有支援 MMX,所以這部份會跳過。

linux/arch/x86/kernel/verify_cpu.S
01 verify_cpu_sse_test:
02     movl    $1,%eax
03     cpuid
04     andl    $SSE_MASK,%edx
05     cmpl    $SSE_MASK,%edx
06     je    verify_cpu_sse_ok
07     test    %di,%di
08     jz    verify_cpu_no_longmode    
09     movl    $MSR_K7_HWCR,%ecx
10     rdmsr
11     btr    $15,%eax        
12     wrmsr
13     xor    %di,%di            
14     jmp    verify_cpu_sse_test    

行號說明
01~02讀取 cpu 版本資訊,eax 為 1 是 cpuid 的參數。
03~06SSE_MASK 的值為 0,所以等式成立,程式跳往 verify_cpu_sse_ok。
07當 di 為 0,程式跳往 verify_cpu_no_longmode。
09~12清除 MSR_K7_HWCR 的位元 0~3。
13清除 di 的值,表示不知道 cpu 的種類。
14再檢查 SSE 一次。

不支援 long mode 的出口。

linux/arch/x86/kernel/verify_cpu.S
01 verify_cpu_no_longmode:
02     popfl                
03     movl $1,%eax
04     ret

行號說明
01回復 eflags 的值,。
02設定 eax 的值為一,表示不支援 long mode??。

支援 long mode 的出口。

linux/arch/x86/kernel/verify_cpu.S
01 verify_cpu_sse_ok:
02     popfl                
03     xorl %eax, %eax
04     ret

行號說明
01回復 eflags 的值,。
02清除 eax 的值為 0,表示支援 long mode??。

3 相關主題

核心起頭程式處理了四個部分的內容,包括未初始化記憶體、啟動參數結構、啟動命令列、分頁表。


3 關於未初始化記憶體

linux/arch/x86/kernel/vmlinux.lds
01  . = ALIGN((1 << 12));
02  .bss : AT(ADDR(.bss) - 0xC0000000) {
03   __bss_start = .; *(.bss..page_aligned) *(.bss) . = ALIGN((1 << 12)); __bss_stop = .;
04  }

行號說明
01對齊 4KB 的記憶體邊界。
02~04宣告程式區域 .bss。放入 PROGRAM .bss..page_aligned、.bss。結尾對齊 4KB 的記憶體邊界。

2 關於啟動參數

啟動參數結構宣告在 linux/arch/x86/kernel/setup.c,而其內容則是在 vmlinux 核心正式啟動前就已經準備好了。 如果是以 GRUB2 啟動 LINUX,則啟動參數由 GRUB2 填寫。 如果是由其他載入器啟動 LINUX,並執行了 LINUX 的設置程式 SETUP.BIN,則啟動參數由 SETUP.BIN 填寫。

從啟動參數的宣告可以看出關於啟動參數的一些資訊。 __initdata 將啟動參數安置在區段 .init.data,這是在整個 vmlinux 核心映像很前面的地方,定義在 linux/arch/x86/kernel/vmlinux.lds。

linux/arch/x86/kernel/setup.c
01 struct boot_params __initdata boot_params;

程式區段 .init.data 的連結描述。
從開頭就先對齊 1<<12,接著就是放置程式區段 .init.text,再放程式區段 .init.data,而程式區段 .init.data 的第一個程式內容就是 .init.data。 所有 .init 的程式區段和資料結構都會放到這段記憶體,對齊分頁邊界 4KB,並且在記憶體初始化的過程中被保護,而得以維持住資料的內容。

linux/arch/x86/kernel/vmlinux.lds
01 . = ALIGN((1 << 12));
02 .init.text : AT(ADDR(.init.text) - 0xC0000000) { 
03 _sinittext = .; *(.init.text) *(.meminit.text) _einittext = .; }
04 .init.data : AT(ADDR(.init.data) - 0xC0000000) { 
05 *(.init.data) *(.meminit.data) *(.init.rodata) 
06 ....
07 }

行號說明
01記憶體對齊頁邊界 4KB。
02~03宣告連結段落 .init.text,並於段落內放置 .init.text、.meminit.text。
03~04宣告連結段落 .init.data,並於段落內放置 .init.data、.meminit.data、.init.rodata 以及其他程式。

啟動參數結構 boot_params 存放的內容很雜,主要是硬體相關的資訊,包括 BIOS、顯示器、磁碟機、系統記憶體等等。 因為 LINUX 是運作在保護模式,而且不會再回到真實模式,所以啟動參數結構必須收集存放於 BIOS 的硬體資訊。 在真實模式的啟動參數結構是一群變數,核心將啟動參數結構群組成一群結構,這樣的做法方便於管理。

linux/arch/x86/include/asm/bootparam.h
01 struct boot_params {
02     struct screen_info screen_info;
03     struct apm_bios_info apm_bios_info;
04     __u8  _pad2[4];
05     __u64  tboot_addr;
06     struct ist_info ist_info;
07     __u8  _pad3[16]; 
08     __u8  hd0_info[16];  
09     __u8  hd1_info[16];  
10     struct sys_desc_table sys_desc_table;  
11     struct olpc_ofw_header olpc_ofw_header;       
12     __u8  _pad4[128];             
13     struct edid_info edid_info;         
14     struct efi_info efi_info;            
15     __u32 alt_mem_k;             
16     __u32 scratch;        
17     __u8  e820_entries;                
18     __u8  eddbuf_entries;                
19     __u8  edd_mbr_sig_buf_entries;       
20     __u8  _pad6[6];                  
21     struct setup_header hdr;    
22     __u8  _pad7[0x290-0x1f1-sizeof(struct setup_header)];
23     __u32 edd_mbr_sig_buffer[EDD_MBR_SIG_MAX]; 
24     struct e820entry e820_map[E820MAX];      
25     __u8  _pad8[48];               
26     struct edd_info eddbuf[EDDMAXNR];      
27     __u8  _pad9[276];                
28 } __attribute__((packed));

行號說明
02顯示資訊結構,。
03APM BIOD 資訊結構,。
05可信任啟動位址,INTEL TRUSTED EXECUTION TECHNOLOGY。
06IST 資訊結構。IST 是 INTEL SPEEDSTEP 的簡稱。
08第一個硬碟資訊,。
09第二個硬碟資訊,。
10MCA 系統描述表結構。MCA 是 IBM MICRO CHANNEL ARCHITECTURE 的簡稱。
11OLPC 檔頭結構。 OLPC 是 ONE LAPTOP PER CHILD 的簡稱。
13EDID 結構,顯示器描述結構。
14EFI 資訊結構。EFI 是 EXTENDED FIRMWARE INTERFACE。
15可變記憶體體積,這是指位於 1MB~16MB 之間的記憶體。
16??,。
17E820 記憶體對映單元數目,最高是 128。
18EDD 緩衝區數,。
19EDD 主要啟動表的特性緩衝區數目,??。
21設置程式檔頭結構,。
23EDD 主要啟動表的特性緩衝區陣列,最大 16 個特性紀錄。
24E820 記憶體對映資訊,可儲存 128 個記憶體對映單元,用於紀錄 BIOS 記憶體資訊。
26EDD 資訊結構,可存放六個磁碟資訊。

顯示資訊結構。

linux/include/linux/screen_info.h
01 struct screen_info {
02     __u8  orig_x;        
03     __u8  orig_y;        
04     __u16 ext_mem_k;    
05     __u16 orig_video_page;    
06     __u8  orig_video_mode;    
07     __u8  orig_video_cols;    
08     __u8  flags;        
09     __u8  unused2;        
10     __u16 orig_video_ega_bx;
11     __u16 unused3;        
12     __u8  orig_video_lines;    
13     __u8  orig_video_isVGA;    
14     __u16 orig_video_points;
15     __u16 lfb_width;    
16     __u16 lfb_height;    
17     __u16 lfb_depth;    
18     __u32 lfb_base;        
19     __u32 lfb_size;        
20     __u16 cl_magic, cl_offset; 
21     __u16 lfb_linelength;    
22     __u8  red_size;        
23     __u8  red_pos;        
24     __u8  green_size;    
25     __u8  green_pos;    
26     __u8  blue_size;    
27     __u8  blue_pos;        
28     __u8  rsvd_size;    
29     __u8  rsvd_pos;        
30     __u16 vesapm_seg;    
31     __u16 vesapm_off;    
32     __u16 pages;        
33     __u16 vesa_attributes;    
34     __u32 capabilities;     
35     __u8  _reserved[6];    
36 } __attribute__((packed));

行號說明
02X 軸螢幕游標位置,。
03Y 軸螢幕游標位置,。
04延伸記憶體,。
05視訊頁數,。
06視訊模式,。
07視訊寬度,。
08視訊旗號,。
10EGA 視訊 BX??,。
12視訊高度,。
13有 VGA 旗號,。
14字型體積,。
15LINUX FRAME BUFFER 的寬度,FRAME BUFFER 是指視窗緩衝區??。
16LINUX FRAME BUFFER 的高度,。
17LINUX FRAME BUFFER 的深度,。
18LINUX FRAME BUFFER 的基底,。
19LINUX FRAME BUFFER 的體積,。
20命令列特徵值,。
21命令列偏移植,。
22LINUX FRAME BUFFER 的線長度,??。
23紅色遮罩體積,8 位元。
24紅色參數位置,第 16 位元。
25綠色遮罩體積,8 位元。
26綠色參數位置,第 8 位元。
27藍色遮罩體積,8 位元。
28藍色參數位置,第 0 位元。
29保留遮罩體積,8 位元。
30保留參數位置,第 24 位元。
31VESAPM 的節區值,。
32VESAPM 的偏移值,。
33LINUX FRAME BUFFER 的分頁數,。
34VESA 屬性值,。
35能力值,。

先進電源管理 BIOS 資訊結構。

linux/include/linux/apm_bios.h
01 struct apm_bios_info {
01     __u16    version;
01     __u16    cseg;
01     __u32    offset;
01     __u16    cseg_16;
01     __u16    dseg;
01     __u16    flags;
01     __u16    cseg_len;
01     __u16    cseg_16_len;
01     __u16    dseg_len;
01 };

行號說明
01,。
01APM 版本,APM 是 ADVANCE POWER MANAGEMENT。
01APM 程式碼節區,。
01APM 程式入口點,。
01APM 16 位元程式節區,。
01APM 資料節區,。
01APM 旗號群,。
01APM 程式體積,。
01APM 資料體積,。

IST 資訊結構。
IST 是 INTEL SPEED STEP 技術的簡稱。

linux/arch/x86/include/asm/ist.h
01 struct ist_info {
01     __u32 signature;
01     __u32 command;
01     __u32 event;
01     __u32 perf_level;
01 };

行號說明
01,。
01IST 特徵值,IST 是 INTEL SPEED STEP。
01IST 命令,。
01IST 事件,。
01IST 效能層級,。

MCA 系統描述表結構。
MCA 是 IBM MICROCHANNEL ARCHITECTURE 的簡稱。

linux/arch/x86/include/asm/bootparam.h
01 struct sys_desc_table {
01     __u16 length;
01     __u8  table[14];
01 };

行號說明
01,。

OFW 的 OLPC 檔頭結構。
OFW 是 OPEN FIRMWARE 的簡寫。
OLPC 是 ONE LAPTOP PER CHILD 的簡寫。

linux/arch/x86/include/asm/bootparam.h
01 struct olpc_ofw_header {
01     __u32 ofw_magic;    
01     __u32 ofw_version;
01     __u32 cif_handler;    
01     __u32 irq_desc_table;
01 } __attribute__((packed));

行號說明
01,。
01OFW 特徵值, ofw 是 OPEN FIRMWARE ??。
01OFW 項目數,。
01OFW CIF 處理函式,。
01OFW 中斷描述表,。

EDID 表結構。
EDID 是 EXTENDED DISPLAY INFORMATION DESCRIPTION 的簡稱,通常由 DVI 或 HDMI 介面提供,用以描述顯示器的顯示規格。

linux/include/video/edid.h
01 struct edid_info {
02     unsigned char dummy[128];
03 };

行號說明
02EDID 表是顯示器的資料表。

EFI 資訊結構。
EFI 是 EXTENDED FIRMWARE INTERFACE 的簡稱。

linux/arch/x86/include/asm/bootparam.h
01 struct efi_info {
01     __u32 efi_loader_signature;
01     __u32 efi_systab;
01     __u32 efi_memdesc_size;
01     __u32 efi_memdesc_version;
01     __u32 efi_memmap;
01     __u32 efi_memmap_size;
01     __u32 efi_systab_hi;
01     __u32 efi_memmap_hi;
01 };

行號說明
01,。

設置程式檔頭結構。

linux/arch/x86/include/asm/bootparam.h
01 struct setup_header {
02     __u8    setup_sects;
03     __u16    root_flags;
04     __u32    syssize;
05     __u16    ram_size;
06     __u16    vid_mode;
07     __u16    root_dev;
08     __u16    boot_flag;
09     __u16    jump;
10     __u32    header;
11     __u16    version;
12     __u32    realmode_swtch;
13     __u16    start_sys;
14     __u16    kernel_version;
15     __u8    type_of_loader;
16     __u8    loadflags;
17     __u16    setup_move_size;
18     __u32    code32_start;
19     __u32    ramdisk_image;
20     __u32    ramdisk_size;
21     __u32    bootsect_kludge;
22     __u16    heap_end_ptr;
23     __u8    ext_loader_ver;
24     __u8    ext_loader_type;
25     __u32    cmd_line_ptr;
26     __u32    initrd_addr_max;
27     __u32    kernel_alignment;
28     __u8    relocatable_kernel;
29     __u8    _pad2[3];
30     __u32    cmdline_size;
31     __u32    hardware_subarch;
32     __u64    hardware_subarch_data;
33     __u32    payload_offset;
34     __u32    payload_length;
35     __u64    setup_data;
36 } __attribute__((packed));

行號說明
01 宣告設置程式檔頭結構。
02 設定設置程式磁區數,磁區體積為 512 位元組,由 build.c 填寫。
03 設定根旗號 root_flags,值應該是一。
04 設定系統映像體積,即 LINUX 自我解壓縮映像檔體積。
05 變數 ram_size 已經沒有用,。
06 顯示模式,值應該是 0xFFFD。linux/arch/x86/include/asm/boot.h。
07 根裝置特徵值,由 build.c 填寫。
08 啟動旗號,0x55AA。
09 跳躍指令,用來跳躍到設置程式的執行入口點 start_of_setup。
10 設置程式檔頭字串 HdrS。
11 設置程式版本號碼,如 0x020A。
12 真實模式切換變數 realmode_swtch,值為 0。
13 啟動節區值 0x1000。這是早期 LINUX 的啟動位址,現在已經沒有用了。
14 核心版本字串指標,用來指向核心版本字串。
15 載入器類型,值為 0,使用 ancient bootloader。
16 載入旗號,值為一表示可以使用 HEAP。
17 設置程式移動體積值,0x8000。當 setup 的載入位址不是 0x90000。
18 啟動位址 0x100000,LINUX 的啟動位址。
19 ram disk 的載入位址。
20 ram disk 的體積值,。
21 變數已經沒有用,。
22 指向 heap 的結束位址,。
23 Extended boot loader version,載入器版本。
24 Extended boot loader type,載入器型態。
25 cmd_line_ptr,命令列指標指向核心命令列。位於設置程式起始位址和 0xA0000 之間。
26 RAM DISK 的最大值。
27 核心對齊值為 0x1000000。
28 核心重定址旗號為一,表示核心可重定址。
29 最小對齊值 13,即 8KB。
30 命令列體積值 2047。
31 X86 的硬體子架構值為 0。
32 硬體子架構的資料值為 0。
33 payload_offset,值為 0x6c。
34 payload_length,值為 0x48c646。
35 setup_data,設定資料指標,64 位元才有用到。

BIOS 記憶體對映單元結構。
由BIOS INT?? E820 命令提供。

linux/arch/x86/include/asm/e820.h
01 struct e820entry {
02     __u64 addr;    
03     __u64 size;    
04     __u32 type;    
05 } __attribute__((packed));

行號說明
02記憶體起始位址,。
03記憶體體積,。
04記憶體類型,。

EDD 資訊結構。
EDD 是 EXTENDED DISK DRIVE 的簡稱。

linux/include/edd.h
01 struct edd_info {
02     __u8 device;
03     __u8 version;
04     __u16 interface_support;
05     __u16 legacy_max_cylinder;
06     __u8 legacy_max_head;
07     __u8 legacy_sectors_per_track;
08     struct edd_device_params params;
09 } __attribute__ ((packed));

行號說明
01裝置名稱,。
01裝置版本,。
01裝置介面,。
01裝置最大磁柱值,。
01裝置最大磁頭值,。
01磁軌最大磁區值,。
01裝置參數結構,。

EDD 裝置參數結構。

linux/include/edd.h
01 struct edd_device_params {
01     __u16 length;
01     __u16 info_flags;
01     __u32 num_default_cylinders;
01     __u32 num_default_heads;
01     __u32 sectors_per_track;
01     __u64 number_of_sectors;
01     __u16 bytes_per_sector;
01     __u32 dpte_ptr;
01     __u16 key;
01     __u8 device_path_info_length;
01     __u8 reserved2;
01     __u16 reserved3;
01     __u8 host_bus_type[4];
01     __u8 interface_type[8];
01     union {
01         struct {
01             __u16 base_address;
01             __u16 reserved1;
01             __u32 reserved2;
01         } __attribute__ ((packed)) isa;
01         struct {
01             __u8 bus;
01             __u8 slot;
01             __u8 function;
01             __u8 channel;
01             __u32 reserved;
01         } __attribute__ ((packed)) pci;
01         struct {
01             __u64 reserved;
01         } __attribute__ ((packed)) ibnd;
01         struct {
01             __u64 reserved;
01         } __attribute__ ((packed)) xprs;
01         struct {
01             __u64 reserved;
01         } __attribute__ ((packed)) htpt;
01         struct {
01             __u64 reserved;
01         } __attribute__ ((packed)) unknown;
01     } interface_path;
01     union {
01         struct {
01             __u8 device;
01             __u8 reserved1;
01             __u16 reserved2;
01             __u32 reserved3;
01             __u64 reserved4;
01         } __attribute__ ((packed)) ata;
01         struct {
01             __u8 device;
01             __u8 lun;
01             __u8 reserved1;
01             __u8 reserved2;
01             __u32 reserved3;
01             __u64 reserved4;
01         } __attribute__ ((packed)) atapi;
01         struct {
01             __u16 id;
01             __u64 lun;
01             __u16 reserved1;
01             __u32 reserved2;
01         } __attribute__ ((packed)) scsi;
01         struct {
01             __u64 serial_number;
01             __u64 reserved;
01         } __attribute__ ((packed)) usb;
01         struct {
01             __u64 eui;
01             __u64 reserved;
01         } __attribute__ ((packed)) i1394;
01         struct {
01             __u64 wwid;
01             __u64 lun;
01         } __attribute__ ((packed)) fibre;
01         struct {
01             __u64 identity_tag;
01             __u64 reserved;
01         } __attribute__ ((packed)) i2o;
01         struct {
01             __u32 array_number;
01             __u32 reserved1;
01             __u64 reserved2;
01         } __attribute__ ((packed)) raid;
01         struct {
01             __u8 device;
01             __u8 reserved1;
01             __u16 reserved2;
01             __u32 reserved3;
01             __u64 reserved4;
01         } __attribute__ ((packed)) sata;
01         struct {
01             __u64 reserved1;
01             __u64 reserved2;
01         } __attribute__ ((packed)) unknown;
01     } device_path;
01     __u8 reserved4;
01     __u8 checksum;
01 } __attribute__ ((packed));

行號說明
01,。

2 關於啟動命令列

啟動命令列(boot_command_line)是一個命令字串的集合,用於存放核心啟動時期的相關參數。 使用 GRUB2 啟動 LINUX 時,由 LINUX 載入器模組填寫命令列的內容,並放置在啟動參數結構的後面 4KB 的位址,體積是 4KB。

啟動命令列的內容是一連串的字串,其代表 linux 的啟動命令與命令的參數,指定了系統啟動時的相關設定,包括硬體與核心的參數設定。 通常是由使用者從 bootloader 填寫,或 bootloader 自行填寫,或空白。 LINUX 啟動過程中會讀取此命令列內容並執行之。

舉例來說,這是啟動 MMINI2440 的過程。
在 bootloader 的命令列上下達 param set linux_cmd_line "..."。
"..." 就是啟動命令列的內容。

01 Supervivi> param set linux_cmd_line "console=ttySAC0 root=/dev/nfs 
01 nfsroot=192.168.0.224:/opt/FriendlyARM/mini2440/rootfs_qtopia_qt4 
01 ip=192.168.0.10:192.168.0.224:192.168.0.224:255.255.255.0:sbc2440.arm9.net:eth0:off"
01 Change linux command line to "console=ttySAC0 root=/dev/nfs 
01 nfsroot=192.168.0.224:/opt/FriendlyARM/mini2440/rootfs_qtopia_qt4 
01 ip=192.168.0.10:192.168.0.224:192.168.0.224:255.255.255.0:sbc2440.arm9.net:eth0:off"

在 bootloader 命令列上鍵入 boot,啟動 LINUX。
啟動命令列的參數就會被 LINUX 執行。
下面的啟動訊息中,05.06.07 是在同一行。

01 Supervivi> boot
02 Copy linux kernel from 0x00060000 to 0x30008000, size = 0x00500000 ... done
03 zImage magic = 0x016f2818
04 Setup linux parameters at 0x30000100
05 linux command line is: "console=ttySAC0 root=/dev/nfs 
06 nfsroot=192.168.0.224:/opt/FriendlyARM/mini2440/rootfs_qtopia_qt4 
07 ip=192.168.0.10:192.168.0.224:192.168.0.224:255.255.255.0:sbc2440.arm9.net:eth0:off"
08 MACH_TYPE = 1999
09 NOW, Booting Linux......
10 ...
11 TCP cubic registered
12 NET: Registered protocol family 17
13 s3c2410-rtc s3c2410-rtc: setting system clock to 2138-05-22 16:47:26 UTC (1018865950)

LINUX 啟動過程中,開啟網路,嘗試連結 NFS。
連結成功時,出現 "NFS root ...Done" 訊息。
此時,LINUX 已經啟動完成。

01 eth0: link down
01 IP-Config: Complete:
01      device=eth0, addr=192.168.0.10, mask=255.255.255.0, gw=192.168.0.224,
01      host=sbc2440, domain=, nis-domain=arm9.net,
01      bootserver=192.168.0.224, rootserver=192.168.0.224, rootpath=
01 Looking up port of RPC 100003/2 on 192.168.0.224
01 eth0: link up, 100Mbps, full-duplex, lpa 0x45E1
01 Looking up port of RPC 100005/1 on 192.168.0.224
01 VFS: Mounted root (nfs filesystem) on device 0:11.
01 Freeing init memory: 156K
01 hwclock: settimeofday() failed: Invalid argument
01 [15/Apr/2002:10:19:18 +0000] boa: server version Boa/0.94.13
01 [15/Apr/2002:10:19:18 +0000] boa: server built Jul 26 2010 at 15:58:29.
01 [15/Apr/2002:10:19:18 +0000] boa: starting server pid=680, port 80
01                         
01 Try to bring eth0 interface up......NFS root ...Done
01 
01 Please press Enter to activate this console. 


2 關於分頁

因為核心是一塊重要記憶體,必須保護起來,如果可以的話以唯讀的特性加以保護。 使用分頁表將核心的記憶體範圍以分頁的形式管理,並保護。 核心的記憶體內容不可以被置換,被其他使用者更改,這些特性都可以使用分頁的方式達到。

這裡講 80x86 的分頁技術??