|
本文详细讲解Linux内核的加载过程,参考Linux-insiders,并结合Linux-5.6.6代码对原文的部分老旧内容做修改。
+ {9 @; g& d9 v5 v: k' y2 @7 B# @引导
5 O! P& ], F; c* g% J+ d
& g; x1 W) s" b
1. 按下电源开关后, CPU设置寄存器为预定值,程序在实模式下运行,程序首先执行0xfffffff0(映射至ROM)处内容,此处为复位向量,直接跳转至BIOS。8 }; j1 S* f9 U6 J( n, D5 L9 O
+ X0 \+ ?" l/ b( m 2. BIOS初始化,检查硬件,寻找可引导设备,跳转至引导扇区代码(boot.img)。
3 G0 x9 U/ W5 y5 u/ k. [0 J
+ L: z8 {) T% o7 b, S
3. core image的初始化代码将剩余的core image(包含GRUB 2的内核代码和文件系统驱动)加载到内存中,运行grub_main。 " ^5 {! s) P5 B2 h& V% z( ^
- 内核自带bootloader,但是新版本内核已经弃用。
- kernel boot protocol规定,bootloader必须具备协议中规定的头信息。( w1 _7 s" F( O3 x3 T0 ~: u
6 p5 o0 J# W0 [$ K$ ]7 t; K" i
% x* ?) {! e. p% L4 p 实模式运行内核* \% v6 B: z, R5 S3 K; G
- j4 D3 a7 ^+ b% L+ V6 u( y
1. kernel地址(header.S _start)位于X + sizeof(KernelBootSector) + 1
: a/ Y, U; \1 Y& v- t& S2 X / T; f# x) q* W4 Q! x
内核加载进入内存后,空间排布: - ( v7 e" p5 q9 O: N- Q! `. l4 c
- | Protected-mode kernel |
( {6 l6 ?* E2 J' { - 100000 +------------------------+
: c1 v* N5 k% c' @8 \0 q, c! g3 s - | I/O memory hole |
2 T! i1 Q e4 a/ a - 0A0000 +------------------------+ q9 u8 C5 V0 ?4 [
- | Reserved for BIOS | Leave as much as possible unused. ?$ k1 N0 d, n: y" H z
- ~ ~2 T$ _" G/ ^7 F* {$ W4 |/ O# A
- | Command line | (Can also be below the X+10000 mark)
/ [( d2 F' G7 ^* c) a5 O" p8 Z; }1 \ - X+10000 +------------------------+
0 Z1 V8 h x! `5 |0 z7 { - | Stack/heap | For use by the kernel real-mode code.
+ Y `: y$ W3 S. m+ T4 g% u - X+08000 +------------------------+1 |4 g* A' h8 t& d4 P
- | Kernel setup | The kernel real-mode code.
2 z* `6 o! u# F# `- Z, s - | Kernel boot sector | The kernel legacy boot sector.
' |, d2 p4 k) d5 ^ p1 Z" C3 {6 a - X +------------------------+" q3 j9 v+ f7 ^! g' K* d
- | Boot loader | <- Boot sector entry point 0x7C00: }: f8 A; p, y8 U4 s3 k" N0 ?
- 001000 +------------------------+& G0 l% t/ E: i
- | Reserved for MBR/BIOS | Z6 m9 _% M" r! _
- 000800 +------------------------+" g, k% u6 g% E8 `) @8 w* K2 m
- | Typically used by MBR |
" N( \+ s9 b$ {6 d' [! n& C - 000600 +------------------------+
* r4 T% e" |7 U7 T$ n - | BIOS use only | X; Q6 Y) G) `
- 000000 +------------------------+
复制代码
8 `( R5 e, Y9 D1 Y' \kernel初始代码功能:设置段寄存器,堆栈,BSS段,跳转进入main。
& u; R0 {, z3 s4 P$ G4 K$ n, d# K
2. main函数(主要用来填充boot_params参数) 7 f N H q) c q8 c# V* F
main函数解析:
# a6 b. k" F6 W8 }- copy_boot_params();
$ Y( J6 \" J4 p2 v2 U - /*7 H. }, A7 D" ]3 I
- 1. 将header.S中定义的hdr拷贝到boot_params结构体的" X7 J) t3 I! Q
- struct setup_header hdr中。
, R* k' f% ^' a& K/ p- _. d( d* X8 S - 2. 如果内核是通过老的命令行协议运行起来的,那么就更新$ J( ?. b" i7 H
- 内核的命令行指针(boot_params.hdr.cmd_line_ptr)。
8 S& \/ ~( P6 ^) d3 L3 H - */9 A5 i) G6 }& X
- /* Initialize the early-boot console */$ H9 C' d/ D& b G. I, C/ n
- console_init();6 L N7 ?- y/ V, x0 j1 P" r
- /*5 H& {5 l+ x o/ Q
- 根据命令行参数设置串口,例如ttyS0
% B2 S( t. m" g' x3 r - */
4 o3 _3 K5 W) g8 @9 e+ B: } - if (cmdline_find_option_bool("debug")): y, z5 m0 t# Q# l
- puts("early console in setup code\n");8 d$ ~! Y9 J9 ?9 T! t
- 5 a) W& P7 o" |* F6 T5 q
- /* End of heap check */
# K" W2 q [: Q$ T - init_heap();
' J& s0 }# H; I0 S5 `$ {$ I: h9 h - /*" Q4 g9 f5 B* R% w
- 1. stack_end = esp - STACK_SIZE
. u& \, \$ G1 ~+ s p( U8 d; G - 2. 如果heap_end大于stack_end,令stack_end=heap_end
: n- ]9 C! ?5 p7 U R' P' w" G, E - */
' V1 U9 M* Z9 j* Z5 O" K0 } - /* Make sure we have all the proper CPU support */ v; ]* N" E" M* r- k; b! {
- /*
0 H3 t- H2 }6 w" u - 查看当前CPU level,如果低于系统预设的最低CPU level,
$ V; w( i! F+ ]" \3 w3 s; r - 则系统停止运行
$ ^% A: S7 w g - */
+ c' |" `% ]6 y8 {! J; C - if (validate_cpu()) {1 P# n. y, P6 M! Y& H* ?
- puts("Unable to boot - please use a kernel appropriate "
0 b+ r6 X/ h( S' T% R - "for your CPU.\n");& v: S* P! |; {' y
- die();; b; e* l9 m2 X3 @7 o/ W
- }
) a4 m; S7 h4 N7 W - /* Tell the BIOS what CPU mode we intend to run in. */
1 z* b" g' W7 q6 N8 @4 D - set_bios_mode();
8 d: F, l! \6 ~$ |" U2 j3 d1 t9 b% K -
3 @: }5 _! B r - /* Detect memory layout */
4 s" N7 g: d0 K! h - /*& Z$ e( V7 Z! u5 D$ P" v6 b
- 循环执行调用号为0xe820的0x15中断调用,将每次的返回值, j% {, _6 P' [ F% F5 s) e$ X
- 保存在e820entry数组中,每项的成员如下/ _: D5 x4 H# t, S! G# c
- * 内存段的起始地址
# C, f9 `, I/ Z: W% ] - * 内存段的大小
3 U/ Q9 k) t8 }" o+ X8 k5 d - * 内存段的类型(类型可以是reserved, usable等等)。
$ @2 y+ j$ C: i; `! q5 o# j - */
& j7 l P/ u( | Y, g - detect_memory();& b2 N0 _* d+ v+ L( _7 B. @; Y
- /* Set keyboard repeat rate (why?) and query the lock flags */
3 ^, d3 [. c+ T. Y5 W - /*. S) ~; R4 o3 F, C+ t% S3 B
- 1. 通过中断获得键盘状态
6 `8 N& [- w+ `8 `, w& C# q) o - 2. 设置键盘的按键检测频率, r, H& X6 H8 f: f" g, [
- */
2 ^8 R N4 Z' L - keyboard_init();6 Z1 d C7 D4 U; e5 @8 q G9 O2 B
- 7 T f! F8 l: O4 m6 |3 ~
- /* Query Intel SpeedStep (IST) information */& `; a6 B6 o+ a a
- query_ist(); w" S- E8 L( e# y4 I
- 8 @. U8 J; p/ N# ?+ o
- /* Query APM information */- v. f' h9 |4 w3 k
- #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
" ~, j( w* b8 ^+ l - query_apm_bios();5 `9 k4 `2 B! q% A3 `2 [; E
- #endif' y9 p1 \. _) h$ d2 f( o6 S: X
-
0 ]+ ^' `7 O% s3 Y: N) k - /* Query EDD information */
' {# c1 E1 Y. `" x& | G0 X - #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
2 U0 H# c! K! M8 K+ ~+ t5 D - query_edd();7 a; I% N6 J( J' G K. i9 f
- #endif
3 U1 C0 a& e( P0 R7 F -
# S; Q5 M0 o1 O y( X - /* Set the video mode */" g J1 c5 g) `. B; y8 r! Z# b( S
- /*
3 x0 t' M$ n- E* D" ?/ | - 设置屏幕,暂时用不到=。=& | Z! H0 V: I O
- */
6 k a+ R$ ~0 Y' s( ~! L8 p - set_video();
8 l/ U3 u3 s# d: C0 v - 2 J' E4 ~8 J/ J
- /* Do the last things and invoke protected mode */
$ Y { U& C0 U$ N. }9 k( a- j - go_to_protected_mode();
复制代码 2 x$ |9 \8 `& n/ e" e
' y4 j1 Y" a% T. L+ Y
内核切换至保护模式
! _: c# ^ P) b. F3 C3 W
$ O3 T i' O1 X3 X
1. mian调用go_to_protected_mode()函数,由实模式切换至保护模式 # ~8 [* E; b x2 y. u
realmode_switch_hook: 如果boot_params.hdr.realmode_swtch存在,则跳转执行boot_params.hdr.realmode_swtch(禁用NMI中断),否则直接禁用NMI中断(写数据需要时间,所以后面紧跟io_delay实现短暂延迟)。
5 `/ l4 i" |1 c3 S
enable_a20:检测a20是否被激活,如果没有则尝试多种方法激活a20,激活失败,则系统停止运行。
( ~: H& C' v: H+ e8 P5 s& ^
set_idt(null_idt为空,使用lidt将null_idt加载入idt寄存器)。 - 9 h8 f; r- E' ]( K
- static void setup_idt(void)' v0 W( u" [) {4 o5 w9 X
- {
% V' B* ]+ V V' F - static const struct gdt_ptr null_idt = {0, 0};
a7 A3 J& {8 ]$ ?4 [ - asm volatile("lidtl %0" : : "m" (null_idt));2 v7 d3 s W0 v( N" F# a, C
- }
复制代码
! y3 O# E' l# C. p+ }* N5 J. O2 A: H
9 C4 K. E- b0 y4 S5 i$ [ W2 @ set_gdt- 使用boot_gdt[]数组存储gdt全局表,初始化CS,DS,TSS表项$ D9 Q5 k3 l2 e4 J
7 e/ L( P9 U6 L) V, u$ Y- /* CS: code, read/execute, 4 GB, base 0 */
4 t5 J9 r) H. o9 @- N4 e1 P! G - [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
' C; Q& W4 P: r0 a - /* DS: data, read/write, 4 GB, base 0 */
2 X( y" d: M5 M1 y$ j - [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),7 i5 y& G7 N8 G. g6 s; A) ?
- /* TSS: 32-bit tss, 104 bytes, base 4096 */
- a/ e( _8 O0 a/ b( Y2 \- ~" J - /* We only have a TSS here to keep Intel VT happy;5 J b- [8 a0 P; u7 ^! v
- we don't actually use it for anything. */$ G% D8 u8 v) e6 p; D
- [GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
复制代码 ; S) {' ~5 W9 S) p9 S* ^- }$ e8 X) m" \
- 使用static struct gdt_ptr gdt存储gdt全局表大小与地址
使用lgdt将gdt_ptr加载如gdt寄存器 * ]% n+ Z y( [$ T- @3 J0 F
" P% t" b E3 J" d) e2 uprotected_mode_jump(boot_params.hdr.code32_start,(u32&boot_params + (ds() << 4));- 该函数使用gcc noreturn特性描述
- 传递code32入口地址(0x100000),与boot_params内容
- x86_linux内核引导协议规定使用bzImage时,保护模式的内核被重定位至0x100000
5 ~7 Y/ Z. C* ?. j: x9 E+ _( @$ w; c
2. 保护模式总结
1 N* r+ ` {* ~5 J Q
gdtr寄存器(48位)存储全局描述符表的基址(32位)与大小(16位)。 1 v% b2 ]; G7 V n/ U
段寄存器存储段选择子(16位),包含段描述符在段描述表中的索引,GDT/LDT标志位,RPL请求者优先级(与段描述符中的优先级协同工作)。 $ Q/ s) w' u4 m
段描述符(64位):
3 p5 _$ C3 ^) X+ v& \+ T- 31 24 19 16 7 0# U7 H9 W- c3 y
- ------------------------------------------------------------
( C; o. I- k& s" v - | | |B| |A| | | | |0|E|W|A| |# t! M+ p+ }; E" {
- | BASE 31:24 |G|/|L|V| LIMIT |P|DPL|S| TYPE | BASE 23:16 | 4: K/ Q7 j G7 V( ~" x/ F6 [
- | | |D| |L| 19:16 | | | |1|C|R|A| |
7 P& v7 n( W9 r* c |0 O1 D - ------------------------------------------------------------; ~: k% C) f* L1 d
- | | |
: O- F' [' t! N0 |0 \ - | BASE 15:0 | LIMIT 15:0 | 06 Q" I$ v; j" e1 k
- | | |+ r* { u9 X) h9 i; X
- ------------------------------------------------------------
复制代码& B: N$ R+ [0 d/ m) P
- Limit(20位)表示内存段长度
- G = 0, 内存段的长度按照1 byte进行增长(Limit每增加1,段长度增加1 byte),最大的内存段长度将是1M bytes;
- G = 1, 内存段的长度按照4K bytes进行增长(Limit每增加1,段长度增加4K bytes),最大的内存段长度是4G bytes;
- Base(32位)表示段基址
- 40-47位定义内存段类型以及支持的操作
S标志(第44位)定义了段类型,S = 0说明这个内存段是一个系统段;S = 1说明这个内存段是一个代码段或者是数据段(堆栈段是一种特殊类型的数据段,堆栈段必须是可以进行读写的段)。 S = 1的情况下,第43位决定了内存段是数据段还是代码段。如果43位 = 0,说明是一个数据段,否则就是一个代码段。 数据段,第42,41,40位表示的是(E扩展,W可写,A可访问) 代码段,第42,41,40位表示的是(C一致,R可读,A可访问) - 4 g8 |( d! e) ?" |7 t% C
- | Type Field | Descriptor Type | Description
% B0 @$ V% k/ H: y0 u' | - |-----------------------------|-----------------|------------------+ V/ ~% ^5 j' K7 S# M
- | Decimal | |) r. J& G0 K/ \: p1 H7 N3 t
- | 0 E W A | |; c' l S; C/ i- G
- | 0 0 0 0 0 | Data | Read-Only
' r! c% [. A+ _! u$ h( V& q - | 1 0 0 0 1 | Data | Read-Only, accessed# \( p4 s& T4 Z9 q/ \
- | 2 0 0 1 0 | Data | Read/Write
* I/ j+ q0 S8 F - | 3 0 0 1 1 | Data | Read/Write, accessed& G2 ` C) ~6 s) _$ l+ G
- | 4 0 1 0 0 | Data | Read-Only, expand-down
# `! L& H& O$ j' e6 \, c7 l - | 5 0 1 0 1 | Data | Read-Only, expand-down, accessed
5 p3 N: P# G4 T/ Z" m - | 6 0 1 1 0 | Data | Read/Write, expand-down
; H, Q, K+ L* ^! }& F - | 7 0 1 1 1 | Data | Read/Write, expand-down, accessed
" Y4 x6 A4 [/ Z; _* a2 J4 k - | C R A | |
* l/ t( y& M5 J+ \$ @+ v# L - | 8 1 0 0 0 | Code | Execute-Only
2 }3 M5 c6 ]1 d( Y+ w - | 9 1 0 0 1 | Code | Execute-Only, accessed
" r. x! ]" U$ y" O# R - | 10 1 0 1 0 | Code | Execute/Read' U8 B/ }' L6 u/ z! t' x: y
- | 11 1 0 1 1 | Code | Execute/Read, accessed* g' l2 J0 B$ X& ]6 y+ \$ M, J, h3 ]
- | 12 1 1 0 0 | Code | Execute-Only, conforming
2 W. Y0 s7 N C, y - | 14 1 1 0 1 | Code | Execute-Only, conforming, accessed' O5 p% A& K; Y/ i1 b( U+ s- D
- | 13 1 1 1 0 | Code | Execute/Read, conforming
' S# h9 b, T& t( h1 d/ F$ v# z+ s - | 15 1 1 1 1 | Code | Execute/Read, conforming, accessed
复制代码
# F: F* j+ h* i; V) G8 M
) d" g3 Y( k6 @8 ?6 |+ M5 n
P 标志(bit 47) 说明该内存段是否已经存在于内存中。如果P = 0,那么在访问这个内存段的时候将报错。 AVL 标志(bit 52) 在Linux内核中没有被使用。 L 标志(bit 53) 只对代码段有意义,如果L = 1,说明该代码段需要运行在64位模式下。 D/B flag(bit 54) 根据段描述符描述的是一个可执行代码段、下扩数据段还是一个堆栈段,这个标志具有不同的功能。(对于32位代码和数据段,这个标志应该总是设置为1;对于16位代码和数据段,这个标志被设置为0。)。 可执行代码段。此时这个标志称为D标志并用于指出该段中的指令引用有效地址和操作数的默认长度。如果该标志置位,则默认值是32位地址和32位或8位的操作数;如果该标志为0,则默认值是16位地址和16位或8位的操作数。指令前缀0x66可以用来选择非默认值的操作数大小;前缀0x67可用来选择非默认值的地址大小。 栈段(由SS寄存器指向的数据段)。此时该标志称为B(Big)标志,用于指明隐含堆栈操作(如PUSH、POP或CALL)时的栈指针大小。如果该标志置位,则使用32位栈指针并存放在ESP寄存器中;如果该标志为0,则使用16位栈指针并存放在SP寄存器中。如果堆栈段被设置成一个下扩数据段,这个B标志也同时指定了堆栈段的上界限。 5 G9 s$ [. e% R
- 下扩数据段。此时该标志称为B标志,用于指明堆栈段的上界限。如果设置了该标志,则堆栈段的上界限是0xFFFFFFFF(4GB);如果没有设置该标志,则堆栈段的上界限是0xFFFF(64KB)。2 G' O- F6 W$ [% P9 A
- P+ h6 i! |5 z2 A0 a) J$ u4 W6 m3 z! u7 f! v: P1 Z
7 \5 v- f6 f# L+ `
内核切换至长模式4 a( ]9 I) W$ q; j+ M* _
. D! |9 C% G- Z
1. x86_64架构下code32_start(内核启动时在0x100000处加载,但是如果内核崩溃,需要重新加载内核时,此处会进行重定位) 在head_64.S(使用-fPIC编译,用于适配内核加载地址重定位)中定义。 9 i4 z2 F* i& t9 D7 @0 s5 C# U
2. head_64.S ; u( c1 G7 {/ ]* d7 x* f$ I- l
head_64.S(starup_32) 解析: - & o. r( X$ D% M
- __HEAD //宏定义,声名代码段(#define __HEAD .section ".head.text","ax")
9 v; Y8 M& Z, F - .code32( [4 d9 h4 x, v0 J# g# a* W
- SYM_FUNC_START(startup_32)# c1 Z2 a; V& i; P
- cld) x1 N: O ]& }# Q7 z
- /*7 Z. c4 d& ^& S5 R! E h
- * Test KEEP_SEGMENTS flag to see if the bootloader is asking+ @ J1 q1 J- i9 |' W
- * us to not reload segments: @+ Z5 k7 w: X# r3 r
- */9 {! \6 V" S3 _$ @1 ^1 n
- /*
- Q; Y$ e9 Q4 Y) H - 判断loadflags是否设置KEEP_SEGMENTS标志位
$ s0 O6 @$ x8 I, p* I. d - */
! Q g9 G4 Z3 j% g8 [3 @ - testb $KEEP_SEGMENTS, BP_loadflags(%esi)
4 ^+ ~. {4 w3 l- @) O% e - jnz 1f
9 b) @; o0 M; r; h6 r - /*
. c/ l; @5 P$ n* J m+ D4 M - 如果没有设置KEEP_SEGMENTS标志位,则使用DS段描述符初始化数据段寄存器
& \8 P% ]6 Y; u# |- _( y: @ - */3 T* `, p( z2 r. z# r( p
- cli
( w/ `$ r1 n; y, H - movl $(__BOOT_DS), %eax& k+ C" @) R/ J, g3 f
- movl %eax, %ds6 t* ?1 N' d! E3 @: N/ H
- movl %eax, %es6 L8 k* r1 I% \) f5 v! q# m% t6 c0 H
- movl %eax, %ss
1 p- O, I/ w, `; r& G - 1:
: N% m2 g8 @5 }& t! b9 b# N - 2 H5 [1 Y) @5 j+ J( _
- /*
* l. t: W: W }7 Y - * Calculate the delta between where we were compiled to run: p, d% _) Z5 v; e' I# U6 }
- * at and where we were actually loaded at. This can only be done) Y6 v. r/ r6 {; P( p
- * with a short local call on x86. Nothing else will tell us what
9 h; f t2 u L& i - * address we are running at. The reserved chunk of the real-mode
. l1 N& E8 g" F& d6 P - * data at 0x1e4 (defined as a scratch field) are used as the stack3 c) m! M9 M8 {; I; I/ b& @
- * for this calculation. Only 4 bytes are needed.
5 Y' c2 ]4 t- A. O. ?: Q - */
7 K1 J4 s+ p5 f; I - /*
* i- [4 h d- ]; T* u - 使用bootparams结构中的scratch作为临时栈顶,call 1f, popl %ebp(将当前物
( e) P! q; ?! ]5 L4 Q - 理位置置于ebp),通过subl $1b, %ebp 定位startup_32真实地址# V; J: P; T9 l3 G; u V1 C, x
- */" B0 x. Z1 }5 _0 v& D1 v
- leal (BP_scratch+4)(%esi), %esp [" J* {! d1 u8 M) X! I
- call 1f
" x0 \+ g- h8 e8 {" [ - 1: popl %ebp( D8 C. D4 \1 h) B
- subl $1b, %ebp
- z: D/ h; e* @* R; l* N' W6 p6 G4 ] -
& i# y; `) u3 W' n7 y4 F: B - /* setup a stack and make sure cpu supports long mode. */# C5 K8 W/ t5 B
- /* startup_32基地址结合boot_stack_end 重新设置栈顶
3 K z: V; j: B% W5 D' \: K) } - movl $boot_stack_end, %eax W Y- x6 N- d: F6 K% f
- addl %ebp, %eax
! H% O+ X# D4 T k" @ - movl %eax, %esp5 p! Y z( e7 t! G0 u; t3 J
- /*
! m" D2 c! p; M# M6 ` - 调用verify_cpu 判断CPU 是否支持长模式和SSE,如果不支持则不再向长模式跳转
8 [ o9 p% f& C+ s7 ] - */
" y* Q, [2 |- H) H, ` - call verify_cpu- }% i7 p+ Q8 A1 ]& P& R
- testl %eax, %eax
/ Z. ~- g2 V; J - jnz .Lno_longmode. w2 X2 p6 i7 E0 C4 I5 E' t
- /*, f% y/ D2 t: W& l0 V1 V
- * Compute the delta between where we were compiled to run at
+ V. `/ G, z- A3 ` _8 @& L - * and where the code will actually run at.4 P$ Z# S/ t$ ^8 G1 S
- *
( h1 v* s' F0 L% p+ k0 D - * %ebp contains the address we are loaded at by the boot loader and %ebx
: X- l; j% o% Q - * contains the address where we should move the kernel image temporarily2 O2 w4 e% I: P& M' F/ p
- * for safe in-place decompression.
! p% m. K- @ C8 U2 T - */0 L/ B; h/ b8 l- `. X
- 3 [. _ F7 J. T' I9 B
- #ifdef CONFIG_RELOCATABLE; l) n: s. k4 m
- movl %ebp, %ebx- o& N6 M4 x+ e% l. F. G# q5 P7 n
- movl BP_kernel_alignment(%esi), %eax" F0 W. e1 g% y' k! _! B r
- decl %eax
# ~' b( v% x/ g6 \8 n* W2 }8 g - addl %eax, %ebx
/ O* K% B% M: U - notl %eax1 H! S& \5 t0 r( ^5 X2 v
- andl %eax, %ebx3 v& t" U& j! G
- cmpl $LOAD_PHYSICAL_ADDR, %ebx
0 f- g% L- x( r. o" F6 d% u* o6 @ - jae 1f+ t- ~7 g f5 `2 k
- #endif, l& E( t, U( p0 j8 R% o1 G
- movl $LOAD_PHYSICAL_ADDR, %ebx3 i+ t, c# N7 I& Z! W4 {. b8 f
- 1:
/ k; `9 [" Q! o5 _) ~9 O8 J -
; X0 t4 H! f- ^) ? - /* Target address to relocate to for decompression */0 ~9 C. W L' h( h2 v- R0 [
- movl BP_init_size(%esi), %eax7 O: Z+ |+ ~# R5 }( L
- subl $_end, %eax
9 K9 g8 a# e8 Q9 m" ~- Z; q4 Q; g3 M - addl %eax, %ebx" ?0 ~) H2 ^* z" g I4 N
- & L, b# K- Z+ h( }; \, Y5 W ?
- /*
1 y: q/ H8 C& K8 S) a - * Prepare for entering 64 bit mode
+ f8 f* l o7 R; V/ `& I3 E - */9 a6 z( u1 l$ d8 x A) O' P1 Q
- ! A+ N, b' o- R: B, R
- /* Load new GDT with the 64bit segments using 32bit descriptor */5 ^4 }) y' o" Z: ]9 a
- /*' c. g; s; ~; M0 e+ c" Q( E \
- 重新加载全局描述表,64位代码段描述项添加 CS.L(长模式标志为) = 1 CS.D = 0
3 |% K: ^$ o% [) I$ m. Z) J; R; j - SYM_DATA_START_LOCAL(gdt)
) H! e6 g h0 D1 K; T. O - .word gdt_end - gdt9 b4 @" A; r. z* d: K2 r
- .long gdt
8 N. @# V5 K% j5 P& x" F - .word 03 V. O5 _9 ^! l0 B9 ^
- .quad 0x00cf9a000000ffff /* __KERNEL32_CS */
t6 i, x8 }- E* K2 f - .quad 0x00af9a000000ffff /* __KERNEL_CS */5 R9 d$ `, ^; _0 N( c9 O
- .quad 0x00cf92000000ffff /* __KERNEL_DS */, ]! V$ }8 d; ^6 F$ H! C0 H& E5 u* ^
- .quad 0x0080890000000000 /* TS descriptor */
/ |9 M/ _1 Q, \5 O2 B0 X2 T/ v' U2 q - .quad 0x0000000000000000 /* TS continued */
% v1 n, m& Q0 o( X7 L - SYM_DATA_END_LABEL(gdt, SYM_L_LOCAL, gdt_end), h% v& P Z X, F7 B
- */
; {' ^7 ~# D2 g5 C - 7 W$ ]: H1 i$ m( i! O' O3 z
- addl %ebp, gdt+2(%ebp)
+ G: B* A" U* K' K - lgdt gdt(%ebp)
, D+ d8 [" \0 F2 K0 `' v9 N. V' m -
2 d+ p9 p o8 `+ |) }: t - /* Enable PAE mode */
! c T y4 q; H0 v9 e8 l4 |" j - /* cr4寄存器第5位置1,开启PAE模式7 w7 _7 K$ g U- w, j4 e7 l* z
- movl %cr4, %eax
, B9 T/ J1 G M! Z7 \# Q9 f - orl $X86_CR4_PAE, %eax
# w8 x2 u" T9 u - movl %eax, %cr4 m2 g- g+ D! c5 b
- . K! D& X; y# b; [$ v) q' P
- ... (创建页表) ...
复制代码 6 R/ u- d, p% T7 P9 {: ~
页表(IA-32e 分页模式) 1 \/ N% i! S1 k9 x- C8 k6 p
1)cr3寄存器
4 r: b' q) m; y- 63 52 51 321 [* @. O' t; N( S
- --------------------------------------------------------------------------------; d! `! p9 [# l0 X0 M
- | | |% i% q3 r" _$ p
- | Reserved MBZ | Address of the top level structure |
8 W$ {- l" J7 Z4 I - | | |$ b+ M" d, M" c4 f
- --------------------------------------------------------------------------------
+ d+ N2 X/ P% R - 31 12 11 5 4 3 2 0
1 A' _3 i4 t1 c9 T% f7 D/ M6 e% s - --------------------------------------------------------------------------------( l+ f# l" k; m- |* j$ a
- | | | P | P | |
7 m% Q4 m/ M# M* B- e - | Address of the top level structure | Reserved | C | W | Reserved |2 |3 T9 d$ y, a) a/ o% j
- | | | D | T | |7 D$ V' _( f9 `5 ?3 _5 ~2 e; X
- --------------------------------------------------------------------------------
复制代码 7 t) N q( s: ^$ T* j6 C: @
Bits 63:52 - reserved must be 0. Bits 51:12 - stores the address of the top level paging structure; Bits 11:5 - reserved must be 0; Bits 4:3 - PWT or Page-Level Writethrough and PCD or Page-level cache disable indicate. These bits control the way the page or Page Table is handled by the hardware cache; Bits 2:0 - ignored; 9 _1 T5 l% A( P& v
' p6 m' a2 t1 ]3 p5 V; t7 C
2)页表项
/ L0 C9 ~& ?# @9 K: x% U$ Z- 63 62 52 51 32
' d& f- a& @# N0 i4 l: r - --------------------------------------------------------------------------------
8 _# j! V" e7 l! W2 k, `3 `# x - | N | | |
! A& f* ^ _2 g$ [9 C - | | Available | Address of the paging structure on lower level |
2 o9 u- O( V! i# M: i4 o# |' C - | X | | |
4 b; h L4 N9 |! H - --------------------------------------------------------------------------------1 t6 R2 p: t7 d. C
- 31 12 11 9 8 7 6 5 4 3 2 1 0
" T8 s) u$ L+ o - --------------------------------------------------------------------------------
6 C' _& F# W2 S6 R - | | | M |I| | P | P |U|W| |
) U% x: }4 p w6 g5 A3 x - | Address of the paging structure on lower level | AVL | B |G|A| C | W | | | P |# R) ^0 G* X0 S. @4 f1 S8 P# R
- | | | Z |N| | D | T |S|R| |+ G" I2 y+ n1 G7 h9 [' m- E5 c/ R
- --------------------------------------------------------------------------------
复制代码
0 z( w) b$ B8 w2 u9 z) r2 c& C
Bits 63 - N/X位(不可执行位)表示被这个页表项映射的所有物理页执行代码的能力;
9 [ `, F/ R9 _- a Bits 62:52 - CPU忽略,被系统软件使用; Bits 51:12 - 存储低级分页结构的物理地址; Bits 11:9 - 被 CPU 忽略; MBZ - 必须为 0; 忽略位; A - 访问标志位暗示物理页或者页结构被访问; PWT 和 PCD 用于缓存; U/S - 普通用户/超级管理员访问标志位 控制被这个页表项映射的所有物理页的访问权限; R/W - 读写位 控制被这个页表项映射的所有物理页的读写权限; P - 存在位 表示页表或物理页是否被加载进内存;
3 M/ i3 }5 @2 j% t. C
+ ]& Q7 Q8 P6 B# F; K3 Q: \
3)线性地址转换为物理地址 ( |' E3 Q: D- R2 ^! s+ p) v& l. Z
- 64位线性地址只有低48位有意义
- cr3寄存器存储4级页表地址
- 线性地址中的第39位到第47位存储4级页表项索引,第30位到第38位存储3级页表项索引,第29位到第21位存储2级页表项索引,第12位到第20位存储1级页表项索引,第0位到第11位提供物理页的字节偏移;* ^' I! T. @! |9 g' E5 r) P4 y
$ x& R" m- ^$ @5 ]9 { p, g7 [
继续解析 head_64.S(starup_32):
5 c% W% z+ _ n- /* Enable Long mode in EFER (Extended Feature Enable Register) */
3 v, v: Q" r3 I - /*9 F Q k6 J+ M# j* |8 i
- 启用拓展寄存器3 S9 M9 u3 G" g1 r; t! b( d4 T
- */
, _0 c$ ` B0 T. `: F/ q/ ~! ~ - movl $MSR_EFER, %ecx) Y3 z% ^+ @: A% F+ g0 p( f4 e# Q! X$ V
- rdmsr+ c7 V) x. X; a# K0 W, b
- btsl $_EFER_LME, %eax, }8 c# g& r3 X
- wrmsr
% L( H* \( M6 D! |& u, ^ - ; }1 F" Z5 D" J' ^7 ~
- /* After gdt is loaded */
# D9 t1 n! g' i, ^+ ? - /*
! |4 X5 y% D1 a* r: [ - 初始化LDT寄存器
6 B6 m+ \9 P" Z0 \, A5 Q1 _ - */7 m1 }% C& s& f8 T+ v/ q. Y+ c; f
- xorl %eax, %eax
; b- R5 v- B' g4 ` - lldt %ax. C4 h' v7 Y* W, T- ]
- movl $__BOOT_TSS, %eax
% ?- e: d8 l' a7 y! M4 B9 X4 _( p - ltr %ax! M7 m4 `- M: o+ @
-
3 r# G: p9 R4 [+ c4 k# O( ] - /* S5 N& L0 x" S! @5 w" [
- * Setup for the jump to 64bit mode' i& @* p: U/ V
- *
1 P6 r2 a5 O! O, k1 Q! [- f - * When the jump is performend we will be in long mode but8 v# V6 Z0 g4 Y1 R7 j& u) _
- * in 32bit compatibility mode with EFER.LME = 1, CS.L = 0, CS.D = 15 t; }( E! @- f/ H: f
- * (and in turn EFER.LMA = 1). To jump into 64bit mode we use; ?; w x+ t: n$ o, x+ T+ s l
- * the new gdt/idt that has __KERNEL_CS with CS.L = 1.
- ~* }9 a- P R6 k+ N6 L - * We place all of the values on our mini stack so lret can
6 K/ x2 n7 }# Y/ C0 V - * used to perform that far jump.; [& n8 Q3 V! S ^* {. o) |
- */
, L- X F5 y7 |8 b; E( A - pushl $__KERNEL_CS% i) x; z% H7 d+ z. b
- leal startup_64(%ebp), %eax
! C; _* \; q6 t3 q5 {* n! N - pushl %eax6 u2 C0 P& S2 J4 I8 d
- 0 D6 `' E4 V0 S: R
- /* Enter paged protected Mode, activating Long Mode */
! B$ @8 _. @5 q7 l0 k' z; ~0 @ - /*
" Y; }, n" {4 ^- w' g- ]% V+ x$ ~/ v - 启用分页机制
9 N, }; g+ q& F1 V, Q' _, | I - */7 I- j5 e2 r4 c
- movl $(X86_CR0_PG | X86_CR0_PE), %eax /* Enable Paging and Protected mode */& m; r M" ?0 n' Q% ]2 }
- movl %eax, %cr0. k% q; z7 R+ g3 b0 E
-
! S2 f: N, F0 l" L0 b! E - /* Jump from 32bit compatibility mode into 64bit mode. */( M1 t8 |' p: e$ A: [
- /*
1 W" [/ K& K( w* h7 \/ y2 y* F - cs段选择子(指向cs_kernel_64段描述符),rip(startup_64物理地址),已经压入栈中
! k) g; f& Q& b4 d - */
$ b, \7 E" ?! k) c6 f" _ - lret
# @6 V/ ]" ]- r; Z+ S% x - /*
4 L+ y) L6 O9 C$ L% W5 U - 跳转进入startup_64
F; S' f# n3 U7 P" \! w8 p - */
; a# E |) v: y( R9 `1 ?0 D - SYM_FUNC_END(startup_32)
复制代码 5 z, Q3 ]4 V6 M5 n8 B
长模式下内核解压缩& U' I. {# F8 ]
/ r/ B8 z7 K2 ? {( N# h; `. {) c
1. 进入64位长模式后,将数据段寄存器设置为空描述符,以实现寻址平坦化(长模式下段寄存器,段描述符显得有些鸡肋,只保留部分功能)。
8 X' J3 C ^! j- q5 b
2. 如果设置了内核重定位,则首先通过rip相对寻址获得当前代码段加载的基地址,2MB字节对齐后,与LOAD_PHYSICAL_ADDR比较,如果不同,则使用该基地址替换LOAD_PHYSICAL_ADDR(这种操作在startup32中实现过,但是在这里又实现一遍是因为64位引导可以直接跳到startup_64而忽略startup_32),紧接着将rbx设置为用以解压内核的代码的地址。 , A5 O. C& k$ \& U% c9 X' A: @( I0 o
3. 按照64位引导协议,重置rsp(以rbx为基地址),flag寄存器,GDT。 : ~: O9 m6 Z, e% z. X
4. 将压缩内核(位于当前代码与解压缩代码之间)复制到栈上(rbx为基地址)后,跳转到rbx处(用于解压内核的代码段)。 5 y: r9 ?# m1 O( n
5. 因为接下来会执行c语言程序,所以提前清空bss段。
6 _1 r! c5 d0 e O- l) R. M 6. 调用extract_kernel函数。- 初始化video/console(程序不知道系统引导类型,所以再次初始化)
- 初始化堆,堆长度为0x10000
- 调用choose_random_location(用来适配KASLR安全机制)选择可以用来写入已解压内核的物理空间
- 原地解压内核
- parse_elf函数将内核可加载段加载入choose_random_location的返回地址
- handle_relocations函数完成到64位内核代码段的跳转。" O& B; ?2 G6 Y, ~( b/ E1 d; n4 {
! |6 g9 ]3 v- p- _) ^至此,x86_64架构下64位linux内核成功运行。4 u* V* F0 y v w; f0 M% Y
|