你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

Linux内核的加载过程

[复制链接]
gaosmile 发布时间:2020-9-13 14:51
本文详细讲解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
  • 寻找可引导设备方式: 定位MBR分区, 引导扇区存储在第一个扇区(512字节)的头446字节处。引导扇区以0x55和0xaa(magic bytes)结束。
  • MBR分区代码只占用一个扇区, 空间较小,只执行了一些初始化工作, 然后跳转至GRUB2的core image(以diskboot.img为起始)继续执行。

    % U- {3 b4 [) P% F% n' G
+ L: z8 {) T% o7 b, S
3. core image的初始化代码将剩余的core image(包含GRUB 2的内核代码和文件系统驱动)加载到内存中,运行grub_main。
" ^5 {! s) P5 B2 h& V% z( ^
  • grub_main 初始化控制台,计算模块基地址,设置root设备,读取 grub 配置文件,加载模块等,最后将grub切换为normal模式
  • normal模式调用grub_normal_execute完成最后的准备工作,显示一个菜单列出可用的操作系统。
  • 选择操作系统后grub_menu_execute_entry被调用,用以运行boot命令,引导操作系统, 运行kernel代码。

    7 n  z9 ]8 @3 m
  • 内核自带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
内核加载进入内存后,空间排布:
  1. ( v7 e" p5 q9 O: N- Q! `. l4 c
  2.       | Protected-mode kernel  |
    ( {6 l6 ?* E2 J' {
  3. 100000   +------------------------+
    : c1 v* N5 k% c' @8 \0 q, c! g3 s
  4.       | I/O memory hole        |
    2 T! i1 Q  e4 a/ a
  5. 0A0000   +------------------------+  q9 u8 C5 V0 ?4 [
  6.       | Reserved for BIOS      | Leave as much as possible unused. ?$ k1 N0 d, n: y" H  z
  7.       ~                        ~2 T$ _" G/ ^7 F* {$ W4 |/ O# A
  8.       | Command line           | (Can also be below the X+10000 mark)
    / [( d2 F' G7 ^* c) a5 O" p8 Z; }1 \
  9. X+10000  +------------------------+
    0 Z1 V8 h  x! `5 |0 z7 {
  10.       | Stack/heap             | For use by the kernel real-mode code.
    + Y  `: y$ W3 S. m+ T4 g% u
  11. X+08000  +------------------------+1 |4 g* A' h8 t& d4 P
  12.       | Kernel setup           | The kernel real-mode code.
    2 z* `6 o! u# F# `- Z, s
  13.       | Kernel boot sector     | The kernel legacy boot sector.
    ' |, d2 p4 k) d5 ^  p1 Z" C3 {6 a
  14.   X +------------------------+" q3 j9 v+ f7 ^! g' K* d
  15.       | Boot loader            | <- Boot sector entry point 0x7C00: }: f8 A; p, y8 U4 s3 k" N0 ?
  16. 001000   +------------------------+& G0 l% t/ E: i
  17.       | Reserved for MBR/BIOS  |  Z6 m9 _% M" r! _
  18. 000800   +------------------------+" g, k% u6 g% E8 `) @8 w* K2 m
  19.       | Typically used by MBR  |
    " N( \+ s9 b$ {6 d' [! n& C
  20. 000600   +------------------------+
    * r4 T% e" |7 U7 T$ n
  21.       | BIOS use only          |  X; Q6 Y) G) `
  22. 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函数解析:

  1. # a6 b. k" F6 W8 }
  2. copy_boot_params();
    $ Y( J6 \" J4 p2 v2 U
  3. /*7 H. }, A7 D" ]3 I
  4. 1. 将header.S中定义的hdr拷贝到boot_params结构体的" X7 J) t3 I! Q
  5. struct setup_header hdr中。
    , R* k' f% ^' a& K/ p- _. d( d* X8 S
  6. 2. 如果内核是通过老的命令行协议运行起来的,那么就更新$ J( ?. b" i7 H
  7. 内核的命令行指针(boot_params.hdr.cmd_line_ptr)。
    8 S& \/ ~( P6 ^) d3 L3 H
  8. */9 A5 i) G6 }& X
  9. /* Initialize the early-boot console */$ H9 C' d/ D& b  G. I, C/ n
  10. console_init();6 L  N7 ?- y/ V, x0 j1 P" r
  11. /*5 H& {5 l+ x  o/ Q
  12. 根据命令行参数设置串口,例如ttyS0
    % B2 S( t. m" g' x3 r
  13. */
    4 o3 _3 K5 W) g8 @9 e+ B: }
  14. if (cmdline_find_option_bool("debug")): y, z5 m0 t# Q# l
  15.         puts("early console in setup code\n");8 d$ ~! Y9 J9 ?9 T! t
  16. 5 a) W& P7 o" |* F6 T5 q
  17. /* End of heap check */
    # K" W2 q  [: Q$ T
  18. init_heap();
    ' J& s0 }# H; I0 S5 `$ {$ I: h9 h
  19. /*" Q4 g9 f5 B* R% w
  20. 1. stack_end = esp - STACK_SIZE
    . u& \, \$ G1 ~+ s  p( U8 d; G
  21. 2. 如果heap_end大于stack_end,令stack_end=heap_end
    : n- ]9 C! ?5 p7 U  R' P' w" G, E
  22. */
    ' V1 U9 M* Z9 j* Z5 O" K0 }
  23. /* Make sure we have all the proper CPU support */  v; ]* N" E" M* r- k; b! {
  24. /*
    0 H3 t- H2 }6 w" u
  25. 查看当前CPU level,如果低于系统预设的最低CPU level,
    $ V; w( i! F+ ]" \3 w3 s; r
  26. 则系统停止运行
    $ ^% A: S7 w  g
  27. */
    + c' |" `% ]6 y8 {! J; C
  28. if (validate_cpu()) {1 P# n. y, P6 M! Y& H* ?
  29.         puts("Unable to boot - please use a kernel appropriate "
    0 b+ r6 X/ h( S' T% R
  30.                 "for your CPU.\n");& v: S* P! |; {' y
  31.         die();; b; e* l9 m2 X3 @7 o/ W
  32. }
    ) a4 m; S7 h4 N7 W
  33. /* Tell the BIOS what CPU mode we intend to run in. */
    1 z* b" g' W7 q6 N8 @4 D
  34. set_bios_mode();
    8 d: F, l! \6 ~$ |" U2 j3 d1 t9 b% K

  35. 3 @: }5 _! B  r
  36. /* Detect memory layout */
    4 s" N7 g: d0 K! h
  37. /*& Z$ e( V7 Z! u5 D$ P" v6 b
  38. 循环执行调用号为0xe820的0x15中断调用,将每次的返回值, j% {, _6 P' [  F% F5 s) e$ X
  39. 保存在e820entry数组中,每项的成员如下/ _: D5 x4 H# t, S! G# c
  40. * 内存段的起始地址
    # C, f9 `, I/ Z: W% ]
  41. * 内存段的大小
    3 U/ Q9 k) t8 }" o+ X8 k5 d
  42. * 内存段的类型(类型可以是reserved, usable等等)。
    $ @2 y+ j$ C: i; `! q5 o# j
  43. */
    & j7 l  P/ u( |  Y, g
  44. detect_memory();& b2 N0 _* d+ v+ L( _7 B. @; Y
  45. /* Set keyboard repeat rate (why?) and query the lock flags */
    3 ^, d3 [. c+ T. Y5 W
  46. /*. S) ~; R4 o3 F, C+ t% S3 B
  47. 1. 通过中断获得键盘状态
    6 `8 N& [- w+ `8 `, w& C# q) o
  48. 2. 设置键盘的按键检测频率, r, H& X6 H8 f: f" g, [
  49. */
    2 ^8 R  N4 Z' L
  50. keyboard_init();6 Z1 d  C7 D4 U; e5 @8 q  G9 O2 B
  51. 7 T  f! F8 l: O4 m6 |3 ~
  52. /* Query Intel SpeedStep (IST) information */& `; a6 B6 o+ a  a
  53. query_ist();  w" S- E8 L( e# y4 I
  54. 8 @. U8 J; p/ N# ?+ o
  55. /* Query APM information */- v. f' h9 |4 w3 k
  56. #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
    " ~, j( w* b8 ^+ l
  57.         query_apm_bios();5 `9 k4 `2 B! q% A3 `2 [; E
  58. #endif' y9 p1 \. _) h$ d2 f( o6 S: X

  59. 0 ]+ ^' `7 O% s3 Y: N) k
  60.         /* Query EDD information */
    ' {# c1 E1 Y. `" x& |  G0 X
  61. #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
    2 U0 H# c! K! M8 K+ ~+ t5 D
  62.         query_edd();7 a; I% N6 J( J' G  K. i9 f
  63. #endif
    3 U1 C0 a& e( P0 R7 F

  64. # S; Q5 M0 o1 O  y( X
  65. /* Set the video mode */" g  J1 c5 g) `. B; y8 r! Z# b( S
  66. /*
    3 x0 t' M$ n- E* D" ?/ |
  67. 设置屏幕,暂时用不到=。=& |  Z! H0 V: I  O
  68. */
    6 k  a+ R$ ~0 Y' s( ~! L8 p
  69. set_video();
    8 l/ U3 u3 s# d: C0 v
  70. 2 J' E4 ~8 J/ J
  71. /* Do the last things and invoke protected mode */
    $ Y  {  U& C0 U$ N. }9 k( a- j
  72. 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寄存器)。
  1. 9 h8 f; r- E' ]( K
  2. static void setup_idt(void)' v0 W( u" [) {4 o5 w9 X
  3. {
    % V' B* ]+ V  V' F
  4.         static const struct gdt_ptr null_idt = {0, 0};
      a7 A3 J& {8 ]$ ?4 [
  5.         asm volatile("lidtl %0" : : "m" (null_idt));2 v7 d3 s  W0 v( N" F# a, C
  6. }
复制代码

    ! 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

  1. 7 e/ L( P9 U6 L) V, u$ Y
  2. /* CS: code, read/execute, 4 GB, base 0 */
    4 t5 J9 r) H. o9 @- N4 e1 P! G
  3. [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
    ' C; Q& W4 P: r0 a
  4. /* DS: data, read/write, 4 GB, base 0 */
    2 X( y" d: M5 M1 y$ j
  5. [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),7 i5 y& G7 N8 G. g6 s; A) ?
  6. /* TSS: 32-bit tss, 104 bytes, base 4096 */
    - a/ e( _8 O0 a/ b( Y2 \- ~" J
  7. /* We only have a TSS here to keep Intel VT happy;5 J  b- [8 a0 P; u7 ^! v
  8. we don't actually use it for anything. */$ G% D8 u8 v) e6 p; D
  9. [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 u
protected_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位):

  1. 3 p5 _$ C3 ^) X+ v& \+ T
  2. 31          24        19      16              7            0# U7 H9 W- c3 y
  3. ------------------------------------------------------------
    ( C; o. I- k& s" v
  4. |             | |B| |A|       | |   | |0|E|W|A|            |# t! M+ p+ }; E" {
  5. | BASE 31:24  |G|/|L|V| LIMIT |P|DPL|S|  TYPE | BASE 23:16 | 4: K/ Q7 j  G7 V( ~" x/ F6 [
  6. |             | |D| |L| 19:16 | |   | |1|C|R|A|            |
    7 P& v7 n( W9 r* c  |0 O1 D
  7. ------------------------------------------------------------; ~: k% C) f* L1 d
  8. |                             |                            |
    : O- F' [' t! N0 |0 \
  9. |        BASE 15:0            |       LIMIT 15:0           | 06 Q" I$ v; j" e1 k
  10. |                             |                            |+ r* {  u9 X) h9 i; X
  11. ------------------------------------------------------------
复制代码
& 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可访问)
    1. 4 g8 |( d! e) ?" |7 t% C
    2.               |           Type Field        | Descriptor Type | Description
      % B0 @$ V% k/ H: y0 u' |
    3. |-----------------------------|-----------------|------------------+ V/ ~% ^5 j' K7 S# M
    4. | Decimal                     |                 |) r. J& G0 K/ \: p1 H7 N3 t
    5. |             0    E    W   A |                 |; c' l  S; C/ i- G
    6. | 0           0    0    0   0 | Data            | Read-Only
      ' r! c% [. A+ _! u$ h( V& q
    7. | 1           0    0    0   1 | Data            | Read-Only, accessed# \( p4 s& T4 Z9 q/ \
    8. | 2           0    0    1   0 | Data            | Read/Write
      * I/ j+ q0 S8 F
    9. | 3           0    0    1   1 | Data            | Read/Write, accessed& G2 `  C) ~6 s) _$ l+ G
    10. | 4           0    1    0   0 | Data            | Read-Only, expand-down
      # `! L& H& O$ j' e6 \, c7 l
    11. | 5           0    1    0   1 | Data            | Read-Only, expand-down, accessed
      5 p3 N: P# G4 T/ Z" m
    12. | 6           0    1    1   0 | Data            | Read/Write, expand-down
      ; H, Q, K+ L* ^! }& F
    13. | 7           0    1    1   1 | Data            | Read/Write, expand-down, accessed
      " Y4 x6 A4 [/ Z; _* a2 J4 k
    14. |                  C    R   A |                 |
      * l/ t( y& M5 J+ \$ @+ v# L
    15. | 8           1    0    0   0 | Code            | Execute-Only
      2 }3 M5 c6 ]1 d( Y+ w
    16. | 9           1    0    0   1 | Code            | Execute-Only, accessed
      " r. x! ]" U$ y" O# R
    17. | 10          1    0    1   0 | Code            | Execute/Read' U8 B/ }' L6 u/ z! t' x: y
    18. | 11          1    0    1   1 | Code            | Execute/Read, accessed* g' l2 J0 B$ X& ]6 y+ \$ M, J, h3 ]
    19. | 12          1    1    0   0 | Code            | Execute-Only, conforming
      2 W. Y0 s7 N  C, y
    20. | 14          1    1    0   1 | Code            | Execute-Only, conforming, accessed' O5 p% A& K; Y/ i1 b( U+ s- D
    21. | 13          1    1    1   0 | Code            | Execute/Read, conforming
      ' S# h9 b, T& t( h1 d/ F$ v# z+ s
    22. | 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) 解析:
  1. & o. r( X$ D% M
  2.         __HEAD  //宏定义,声名代码段(#define __HEAD  .section  ".head.text","ax")
    9 v; Y8 M& Z, F
  3.         .code32( [4 d9 h4 x, v0 J# g# a* W
  4. SYM_FUNC_START(startup_32)# c1 Z2 a; V& i; P
  5.         cld) x1 N: O  ]& }# Q7 z
  6.         /*7 Z. c4 d& ^& S5 R! E  h
  7.         * Test KEEP_SEGMENTS flag to see if the bootloader is asking+ @  J1 q1 J- i9 |' W
  8.         * us to not reload segments: @+ Z5 k7 w: X# r3 r
  9.         */9 {! \6 V" S3 _$ @1 ^1 n
  10.         /*
    - Q; Y$ e9 Q4 Y) H
  11.         判断loadflags是否设置KEEP_SEGMENTS标志位
    $ s0 O6 @$ x8 I, p* I. d
  12.         */
    ! Q  g9 G4 Z3 j% g8 [3 @
  13.         testb $KEEP_SEGMENTS, BP_loadflags(%esi)
    4 ^+ ~. {4 w3 l- @) O% e
  14.         jnz 1f
    9 b) @; o0 M; r; h6 r
  15.         /*
    . c/ l; @5 P$ n* J  m+ D4 M
  16.         如果没有设置KEEP_SEGMENTS标志位,则使用DS段描述符初始化数据段寄存器
    & \8 P% ]6 Y; u# |- _( y: @
  17.         */3 T* `, p( z2 r. z# r( p
  18.         cli
    ( w/ `$ r1 n; y, H
  19.         movl    $(__BOOT_DS), %eax& k+ C" @) R/ J, g3 f
  20.         movl    %eax, %ds6 t* ?1 N' d! E3 @: N/ H
  21.         movl    %eax, %es6 L8 k* r1 I% \) f5 v! q# m% t6 c0 H
  22.         movl    %eax, %ss
    1 p- O, I/ w, `; r& G
  23. 1:
    : N% m2 g8 @5 }& t! b9 b# N
  24. 2 H5 [1 Y) @5 j+ J( _
  25. /*
    * l. t: W: W  }7 Y
  26. * Calculate the delta between where we were compiled to run: p, d% _) Z5 v; e' I# U6 }
  27. * at and where we were actually loaded at.  This can only be done) Y6 v. r/ r6 {; P( p
  28. * with a short local call on x86.  Nothing  else will tell us what
    9 h; f  t2 u  L& i
  29. * address we are running at.  The reserved chunk of the real-mode
    . l1 N& E8 g" F& d6 P
  30. * data at 0x1e4 (defined as a scratch field) are used as the stack3 c) m! M9 M8 {; I; I/ b& @
  31. * for this calculation. Only 4 bytes are needed.
    5 Y' c2 ]4 t- A. O. ?: Q
  32. */
    7 K1 J4 s+ p5 f; I
  33. /*
    * i- [4 h  d- ]; T* u
  34. 使用bootparams结构中的scratch作为临时栈顶,call 1f, popl %ebp(将当前物
    ( e) P! q; ?! ]5 L4 Q
  35. 理位置置于ebp),通过subl $1b, %ebp 定位startup_32真实地址# V; J: P; T9 l3 G; u  V1 C, x
  36. */" B0 x. Z1 }5 _0 v& D1 v
  37.         leal    (BP_scratch+4)(%esi), %esp  [" J* {! d1 u8 M) X! I
  38.         call    1f
    " x0 \+ g- h8 e8 {" [
  39. 1:    popl    %ebp( D8 C. D4 \1 h) B
  40.         subl    $1b, %ebp
    - z: D/ h; e* @* R; l* N' W6 p6 G4 ]

  41. & i# y; `) u3 W' n7 y4 F: B
  42. /* setup a stack and make sure cpu supports long mode. */# C5 K8 W/ t5 B
  43. /* startup_32基地址结合boot_stack_end 重新设置栈顶
    3 K  z: V; j: B% W5 D' \: K) }
  44.         movl    $boot_stack_end, %eax  W  Y- x6 N- d: F6 K% f
  45.         addl    %ebp, %eax
    ! H% O+ X# D4 T  k" @
  46.         movl    %eax, %esp5 p! Y  z( e7 t! G0 u; t3 J
  47. /*
    ! m" D2 c! p; M# M6 `
  48. 调用verify_cpu 判断CPU 是否支持长模式和SSE,如果不支持则不再向长模式跳转
    8 [  o9 p% f& C+ s7 ]
  49. */
    " y* Q, [2 |- H) H, `
  50.         call    verify_cpu- }% i7 p+ Q8 A1 ]& P& R
  51.         testl    %eax, %eax
    / Z. ~- g2 V; J
  52.         jnz    .Lno_longmode. w2 X2 p6 i7 E0 C4 I5 E' t
  53. /*, f% y/ D2 t: W& l0 V1 V
  54. * Compute the delta between where we were compiled to run at
    + V. `/ G, z- A3 `  _8 @& L
  55. * and where the code will actually run at.4 P$ Z# S/ t$ ^8 G1 S
  56. *
    ( h1 v* s' F0 L% p+ k0 D
  57. * %ebp contains the address we are loaded at by the boot loader and %ebx
    : X- l; j% o% Q
  58. * contains the address where we should move the kernel image temporarily2 O2 w4 e% I: P& M' F/ p
  59. * for safe in-place decompression.
    ! p% m. K- @  C8 U2 T
  60. */0 L/ B; h/ b8 l- `. X
  61. 3 [. _  F7 J. T' I9 B
  62. #ifdef CONFIG_RELOCATABLE; l) n: s. k4 m
  63.         movl    %ebp, %ebx- o& N6 M4 x+ e% l. F. G# q5 P7 n
  64.         movl    BP_kernel_alignment(%esi), %eax" F0 W. e1 g% y' k! _! B  r
  65.         decl    %eax
    # ~' b( v% x/ g6 \8 n* W2 }8 g
  66.         addl    %eax, %ebx
    / O* K% B% M: U
  67.         notl    %eax1 H! S& \5 t0 r( ^5 X2 v
  68.         andl    %eax, %ebx3 v& t" U& j! G
  69.         cmpl    $LOAD_PHYSICAL_ADDR, %ebx
    0 f- g% L- x( r. o" F6 d% u* o6 @
  70.         jae    1f+ t- ~7 g  f5 `2 k
  71. #endif, l& E( t, U( p0 j8 R% o1 G
  72.         movl    $LOAD_PHYSICAL_ADDR, %ebx3 i+ t, c# N7 I& Z! W4 {. b8 f
  73. 1:
    / k; `9 [" Q! o5 _) ~9 O8 J

  74. ; X0 t4 H! f- ^) ?
  75.         /* Target address to relocate to for decompression */0 ~9 C. W  L' h( h2 v- R0 [
  76.         movl    BP_init_size(%esi), %eax7 O: Z+ |+ ~# R5 }( L
  77.         subl    $_end, %eax
    9 K9 g8 a# e8 Q9 m" ~- Z; q4 Q; g3 M
  78.         addl    %eax, %ebx" ?0 ~) H2 ^* z" g  I4 N
  79. & L, b# K- Z+ h( }; \, Y5 W  ?
  80. /*
    1 y: q/ H8 C& K8 S) a
  81. * Prepare for entering 64 bit mode
    + f8 f* l  o7 R; V/ `& I3 E
  82. */9 a6 z( u1 l$ d8 x  A) O' P1 Q
  83. ! A+ N, b' o- R: B, R
  84.         /* Load new GDT with the 64bit segments using 32bit descriptor */5 ^4 }) y' o" Z: ]9 a
  85.         /*' c. g; s; ~; M0 e+ c" Q( E  \
  86.         重新加载全局描述表,64位代码段描述项添加 CS.L(长模式标志为) = 1 CS.D = 0
    3 |% K: ^$ o% [) I$ m. Z) J; R; j
  87.         SYM_DATA_START_LOCAL(gdt)
    ) H! e6 g  h0 D1 K; T. O
  88.                 .word    gdt_end - gdt9 b4 @" A; r. z* d: K2 r
  89.                 .long    gdt
    8 N. @# V5 K% j5 P& x" F
  90.                 .word    03 V. O5 _9 ^! l0 B9 ^
  91.                 .quad    0x00cf9a000000ffff    /* __KERNEL32_CS */
      t6 i, x8 }- E* K2 f
  92.                 .quad    0x00af9a000000ffff    /* __KERNEL_CS */5 R9 d$ `, ^; _0 N( c9 O
  93.                 .quad    0x00cf92000000ffff    /* __KERNEL_DS */, ]! V$ }8 d; ^6 F$ H! C0 H& E5 u* ^
  94.                 .quad    0x0080890000000000    /* TS descriptor */
    / |9 M/ _1 Q, \5 O2 B0 X2 T/ v' U2 q
  95.                 .quad   0x0000000000000000    /* TS continued */
    % v1 n, m& Q0 o( X7 L
  96.         SYM_DATA_END_LABEL(gdt, SYM_L_LOCAL, gdt_end), h% v& P  Z  X, F7 B
  97.         */
    ; {' ^7 ~# D2 g5 C
  98. 7 W$ ]: H1 i$ m( i! O' O3 z
  99.         addl    %ebp, gdt+2(%ebp)
    + G: B* A" U* K' K
  100.         lgdt    gdt(%ebp)
    , D+ d8 [" \0 F2 K0 `' v9 N. V' m

  101. 2 d+ p9 p  o8 `+ |) }: t
  102.         /* Enable PAE mode */
    ! c  T  y4 q; H0 v9 e8 l4 |" j
  103.         /* cr4寄存器第5位置1,开启PAE模式7 w7 _7 K$ g  U- w, j4 e7 l* z
  104.         movl    %cr4, %eax
    , B9 T/ J1 G  M! Z7 \# Q9 f
  105.         orl    $X86_CR4_PAE, %eax
    # w8 x2 u" T9 u
  106.         movl    %eax, %cr4  m2 g- g+ D! c5 b
  107. . K! D& X; y# b; [$ v) q' P
  108.         ... (创建页表) ...
复制代码
6 R/ u- d, p% T7 P9 {: ~
页表(IA-32e 分页模式)
1 \/ N% i! S1 k9 x- C8 k6 p
1)cr3寄存器

  1. 4 r: b' q) m; y
  2. 63                  52 51                                                       321 [* @. O' t; N( S
  3. --------------------------------------------------------------------------------; d! `! p9 [# l0 X0 M
  4. |                     |                                                          |% i% q3 r" _$ p
  5. |    Reserved MBZ     |            Address of the top level structure            |
    8 W$ {- l" J7 Z4 I
  6. |                     |                                                          |$ b+ M" d, M" c4 f
  7. --------------------------------------------------------------------------------
    + d+ N2 X/ P% R
  8. 31                                  12 11            5     4     3 2             0
    1 A' _3 i4 t1 c9 T% f7 D/ M6 e% s
  9. --------------------------------------------------------------------------------( l+ f# l" k; m- |* j$ a
  10. |                                     |               |  P  |  P  |              |
    7 m% Q4 m/ M# M* B- e
  11. |  Address of the top level structure |   Reserved    |  C  |  W  |    Reserved  |2 |3 T9 d$ y, a) a/ o% j
  12. |                                     |               |  D  |  T  |              |7 D$ V' _( f9 `5 ?3 _5 ~2 e; X
  13. --------------------------------------------------------------------------------
复制代码
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)页表项

  1. / L0 C9 ~& ?# @9 K: x% U$ Z
  2. 63  62                  52 51                                                  32
    ' d& f- a& @# N0 i4 l: r
  3. --------------------------------------------------------------------------------
    8 _# j! V" e7 l! W2 k, `3 `# x
  4. | N |                     |                                                     |
    ! A& f* ^  _2 g$ [9 C
  5. |   |     Available       |     Address of the paging structure on lower level  |
    2 o9 u- O( V! i# M: i4 o# |' C
  6. | X |                     |                                                     |
    4 b; h  L4 N9 |! H
  7. --------------------------------------------------------------------------------1 t6 R2 p: t7 d. C
  8. 31                                              12 11  9 8 7 6 5   4   3 2 1     0
    " T8 s) u$ L+ o
  9. --------------------------------------------------------------------------------
    6 C' _& F# W2 S6 R
  10. |                                                |     | M |I| | P | P |U|W|    |
    ) U% x: }4 p  w6 g5 A3 x
  11. | 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
  12. |                                                |     | Z |N| | D | T |S|R|    |+ G" I2 y+ n1 G7 h9 [' m- E5 c/ R
  13. --------------------------------------------------------------------------------
复制代码

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):

  1. 5 c% W% z+ _  n
  2.   /* Enable Long mode in EFER (Extended Feature Enable Register) */
    3 v, v: Q" r3 I
  3.         /*9 F  Q  k6 J+ M# j* |8 i
  4.         启用拓展寄存器3 S9 M9 u3 G" g1 r; t! b( d4 T
  5.         */
    , _0 c$ `  B0 T. `: F/ q/ ~! ~
  6.         movl    $MSR_EFER, %ecx) Y3 z% ^+ @: A% F+ g0 p( f4 e# Q! X$ V
  7.         rdmsr+ c7 V) x. X; a# K0 W, b
  8.         btsl    $_EFER_LME, %eax, }8 c# g& r3 X
  9.         wrmsr
    % L( H* \( M6 D! |& u, ^
  10. ; }1 F" Z5 D" J' ^7 ~
  11.         /* After gdt is loaded */
    # D9 t1 n! g' i, ^+ ?
  12.         /*
    ! |4 X5 y% D1 a* r: [
  13.         初始化LDT寄存器
    6 B6 m+ \9 P" Z0 \, A5 Q1 _
  14.         */7 m1 }% C& s& f8 T+ v/ q. Y+ c; f
  15.         xorl    %eax, %eax
    ; b- R5 v- B' g4 `
  16.         lldt    %ax. C4 h' v7 Y* W, T- ]
  17.         movl    $__BOOT_TSS, %eax
    % ?- e: d8 l' a7 y! M4 B9 X4 _( p
  18.         ltr    %ax! M7 m4 `- M: o+ @

  19. 3 r# G: p9 R4 [+ c4 k# O( ]
  20.         /*  S5 N& L0 x" S! @5 w" [
  21.         * Setup for the jump to 64bit mode' i& @* p: U/ V
  22.         *
    1 P6 r2 a5 O! O, k1 Q! [- f
  23.         * When the jump is performend we will be in long mode but8 v# V6 Z0 g4 Y1 R7 j& u) _
  24.         * in 32bit compatibility mode with EFER.LME = 1, CS.L = 0, CS.D = 15 t; }( E! @- f/ H: f
  25.         * (and in turn EFER.LMA = 1).    To jump into 64bit mode we use; ?; w  x+ t: n$ o, x+ T+ s  l
  26.         * the new gdt/idt that has __KERNEL_CS with CS.L = 1.
    - ~* }9 a- P  R6 k+ N6 L
  27.         * We place all of the values on our mini stack so lret can
    6 K/ x2 n7 }# Y/ C0 V
  28.         * used to perform that far jump.; [& n8 Q3 V! S  ^* {. o) |
  29.         */
    , L- X  F5 y7 |8 b; E( A
  30.         pushl    $__KERNEL_CS% i) x; z% H7 d+ z. b
  31.         leal    startup_64(%ebp), %eax
    ! C; _* \; q6 t3 q5 {* n! N
  32.         pushl    %eax6 u2 C0 P& S2 J4 I8 d
  33. 0 D6 `' E4 V0 S: R
  34.         /* Enter paged protected Mode, activating Long Mode */
    ! B$ @8 _. @5 q7 l0 k' z; ~0 @
  35.         /*
    " Y; }, n" {4 ^- w' g- ]% V+ x$ ~/ v
  36.         启用分页机制
    9 N, }; g+ q& F1 V, Q' _, |  I
  37.         */7 I- j5 e2 r4 c
  38.         movl    $(X86_CR0_PG | X86_CR0_PE), %eax /* Enable Paging and Protected mode */& m; r  M" ?0 n' Q% ]2 }
  39.         movl    %eax, %cr0. k% q; z7 R+ g3 b0 E

  40. ! S2 f: N, F0 l" L0 b! E
  41.         /* Jump from 32bit compatibility mode into 64bit mode. */( M1 t8 |' p: e$ A: [
  42.         /*
    1 W" [/ K& K( w* h7 \/ y2 y* F
  43.         cs段选择子(指向cs_kernel_64段描述符),rip(startup_64物理地址),已经压入栈中
    ! k) g; f& Q& b4 d
  44.         */
    $ b, \7 E" ?! k) c6 f" _
  45.         lret
    # @6 V/ ]" ]- r; Z+ S% x
  46.         /*
    4 L+ y) L6 O9 C$ L% W5 U
  47.         跳转进入startup_64
      F; S' f# n3 U7 P" \! w8 p
  48.         */
    ; a# E  |) v: y( R9 `1 ?0 D
  49. 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
赞 收藏 评论0 发布时间:2020-9-13 14:51

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版