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

linux在启动过程中的初始化

[复制链接]
gaosmile 发布时间:2020-6-7 18:29
看了很多关于linux内存管理的文章还是云里雾里,听了很多关于linux内存管理的课程还是一头雾水。其实很多时候造成不懂的原因不是资料太少,恰恰是资料太多,而且各个内核版本的差异,32位64位的不同,文章的胡编乱造等都给读者带来疑惑。本着对内存深度剖析的态度,希望以版本kernel-4.14,架构AARCH64为专题做个内存管理的架构性整理。
这篇文章我们先来看下linux在启动过程中的初始化。

% x7 P& i+ K7 x! P
创建启动页表:
在汇编代码阶段的head.S文件中,负责创建映射关系的函数是create_page_tables。create_page_tables函数负责identity mapping和kernel image mapping。
  • identity map:是指把idmap_text区域的物理地址映射到相等的虚拟地址上,这种映射完成后,其虚拟地址等于物理地址。idmap_text区域都是一些打开MMU相关的代码。
  • kernel image map:将kernel运行需要的地址(kernel txt、rodata、data、bss等等)进行映射。
    1. 1 J9 o" a; W0 o( Q# z$ o. B! M
    2. arch/arm64/kernel/head.S:6 c+ [* B& n  |/ E/ y: t/ h# A! v
    3. ENTRY(stext)
      5 F0 m* D5 D9 L7 J
    4.         bl      preserve_boot_args0 Y/ L, p( P2 d9 y: {% l5 {( n
    5.         bl      el2_setup                       // Drop to EL1, w0=cpu_boot_mode' r* L% x5 P! ^7 }
    6.         adrp    x23, __PHYS_OFFSET
      - D; w: A5 \& n
    7.         and     x23, x23, MIN_KIMG_ALIGN - 1    // KASLR offset, defaults to 0
      , x% _7 y+ g2 P+ W  d
    8.         bl      set_cpu_boot_mode_flag
      , p; O6 A, z+ w5 ~% m: s( r' j2 `
    9.         bl      __create_page_tables
      2 e/ ~' i  q- d* g0 }2 j  a( j: k
    10.         /*& N7 X% c* t5 w+ S7 P0 Z3 ~1 P
    11.          * The following calls CPU setup code, see arch/arm64/mm/proc.S for
      : k0 d2 U2 O/ a6 a- p
    12.          * details.
      9 L7 H5 `2 K/ u! Y/ b$ ^
    13.          * On return, the CPU will be ready for the MMU to be turned on and
      ! `1 R# z$ W  G
    14.          * the TCR will have been set.
      ! ^% D. ^8 B8 q) g; K
    15.          */
      3 H! l7 I9 J7 v+ a2 E
    16.         bl      __cpu_setup                     // initialise processor" S. _( q0 C: x1 G  J
    17.         b       __primary_switch
      . [# E& L2 |8 X6 @
    18. ENDPROC(stext)
    复制代码
    ! a8 B+ ~% C. S4 L0 @! n, \
__create_page_tables主要执行的就是identity map和kernel image map:
  1. $ D: z' g  G' w+ l5 E8 \& k
  2. __create_page_tables:- o, z2 ~# M  @- u1 e
  3. ......4 M0 h% H: v' x# V* A( E
  4.         create_pgd_entry x0, x3, x5, x6
    0 }, e# K0 Y/ Y6 Z
  5.         mov     x5, x3                          // __pa(__idmap_text_start)
    6 _' S9 j, \% f4 Q
  6.         adr_l   x6, __idmap_text_end            // __pa(__idmap_text_end)# g. K9 `) ?9 k/ Y) e. q# j
  7.         create_block_map x0, x7, x3, x5, x6
    $ c6 \' B1 o: \/ e

  8. * {# ^9 |5 ^' g/ a5 n
  9.         /*0 E+ |4 U' v5 G4 q
  10.          * Map the kernel image (starting with PHYS_OFFSET).
    * ^* e0 d5 l) A' V5 B- ^$ f' ]
  11.          */  s( U8 w  i7 g% o  ~5 p2 p" b, j
  12.         adrp    x0, swapper_pg_dir) r) C$ Z+ Q! g5 `
  13.         mov_q   x5, KIMAGE_VADDR + TEXT_OFFSET  // compile time __va(_text)
    ( S9 V$ ], v8 ]
  14.         add     x5, x5, x23                     // add KASLR displacement
    7 p# m1 b  Z. h5 {9 U
  15.         create_pgd_entry x0, x5, x3, x6' f; B6 P2 p7 b) i# z$ ~
  16.         adrp    x6, _end                        // runtime __pa(_end)
    " {% F% g7 m' W0 G1 ?  Z
  17.         adrp    x3, _text                       // runtime __pa(_text)
    1 j1 `* U7 U4 j! \7 d0 q$ z0 i! z
  18.         sub     x6, x6, x3                      // _end - _text  |! I1 x) T7 l/ ^6 Y
  19.         add     x6, x6, x5                      // runtime __va(_end)4 _& G5 G5 L8 O; D% h0 N" U
  20.         create_block_map x0, x7, x3, x5, x60 X) U0 |' p& `! a
  21. ......
复制代码

. Z/ c  O* l1 B! ~
其中调用create_pgd_entry进行PGD及所有中间level(PUD, PMD)页表的创建,调用create_block_map进行PTE页表的映射。关于四级页表的关系如下图所示,这里就不进一步解释了。
微信图片_20200607182745.jpg

; x) i- A8 f& u! R' p) o7 K: K( p
汇编结束后的内存映射关系如下图所示:
微信图片_20200607182749.jpg
; t) U! K/ X  {6 I. l
当执行完上面的map之后,MMU就已经打开了并且开始进入C代码运行阶段,那么下一步就要对dtb进行映射了。
  L/ P7 K- z% d# }6 ?6 a9 w
fixmap区之dtb map:
微信图片_20200607182752.jpg
在执行setup_arch中,会最先进行early_fixmap_init(),这个函数就是用来map dtb的,但是它只会建立dtb对应的这段物理地址中间level的页表entry,而最后一个level的页表映射则通过setup_machine_fdt函数里的fixmap_remap_fdt来创建。
  1. <font color="#001000"><font style="background-color:rgb(255, 255, 255)"><font face="Tahoma">! t) K3 Z6 f- L; Q/ x0 Z/ H/ j
  2. void *__init fixmap_remap_fdt(phys_addr_t dt_phys)% X8 g' E/ F* V4 R( j( E
  3. {; Q5 K9 |# ~! }3 w: K
  4.      void *dt_virt;, z  x/ V8 G/ f( e% s/ N9 E9 D
  5.      int size;: U+ I3 k* ~; Z2 i
  6. 5 V8 v. R5 w- p# E
  7.      dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);. }9 i+ |6 L* U: b# ?
  8.      if (!dt_virt)
    : T; J4 M# k% t  x3 w2 @% o- E
  9.          return NULL;  f0 j$ g: w. D+ O) r1 d$ F+ ]
  10.   ?! q0 O6 l: H
  11.      memblock_reserve(dt_phys, size);9 }0 ~* t! x5 \6 ^/ N
  12.      return dt_virt;
    ' v( t7 n; f+ n, R; ~0 g$ b2 O- R
  13. }</font></font></font><font color="#001000"><font style="background-color:rgb(255, 255, 255)"><font face="Tahoma">
    4 t  i5 T: x" C7 b5 Z9 ^3 j
  14. </font></font></font>
复制代码

7 x; _% u8 C+ I: F# _/ ~
fixmap_remap_fdt主要是为fdt建立地址映射,在该函数的最后,顺便就调用memblock_reserve保留了该段内存。
可以看出dtb的映射采用的是fixmap,所谓fixmap就是固定映射,它需要我们明确的知道想要映射的物理地址,并把这段地址映射到想要映射的虚拟地址上。当然这里固定映射还有些片面,因为在fixmap机制实现上,也有支持动态分配虚拟地址的功能,这个功能主要用于临时fixmap映射(这个临时映射就是用来执行early ioremap使用的。),而dtb的映射属于永久映射。

1 _  ]$ n3 g* ?, C# M
fixmap区之early ioremap:
对于一些硬件需要在内存管理系统起来之前就要工作的,我们就可以使用这种机制来映射内存给这些硬件driver使用。各个模块在使用完early ioremap的地址后,需要尽快把这段映射的虚拟地址释放掉,这样才能反复被其他模块继续申请使用。
early_ioremap_init会调用early_ioremap_setup:
微信图片_20200607182755.png
可见它的实现是依赖fixmap的,所以它必须要在early_fixmap_init之后才能运行。
注意:如果想要在伙伴系统初始化之前进行设备寄存器的访问,那么可以考虑early IO remap机制。
至此我们已经知道dtb和early ioremap都是在fixmap区的,如下图:
微信图片_20200607182758.jpg
, g9 L2 O0 V2 R1 f7 d! F
系统内存的布局:
完成dtb的map之后,内核可以访问这一段的内存了,通过解析dtb中的内容,内核可以勾勒出整个内存布局的情况,为后续内存管理初始化奠定基础。这一步主要在setup_machine_fdt中完成。这里就不看代码了,其调用流程是:setup_machine_fdt->early_init_dt_scan->early_init_dt_scan_nodes
微信图片_20200607182802.png
就像注释中所示内核根据dtb的不同node勾勒出choosen node,root node,memory node相应内存区域。
除了这3个node,还有一个reserved-memory node,它是在上面讲到dtb map的时候fixmap_remap_fdt函数做的。下面我们看下这4个node的具体实现。
  • choosen node
    8 x: r. E! U5 x+ I! d' l* m
微信图片_20200607182805.png
该节点有一个bootargs属性,该属性定义了内核的启动参数,比如mem= xx,此外,还处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中。
  • root node

    $ |, r: B  d! `& @! F
与内存无关,暂时不详述,以后有机会讲到device tree系列再详述。
  • memory node

    ( d) k& U, n& P  o" @
微信图片_20200607182809.png
通过memblock_add加入到memblock.memory对应的memblock_type链表中进行管理。

% I" I* L- d9 R$ G
接下来到arm64_memblock_init函数:

  1. . m  q9 A6 t" ]# i& v
  2. void __init arm64_memblock_init(void)  O1 A& L* D9 @. R" `/ r  u8 h
  3. {; \' N- ^) X" Y1 S
  4. ......
      @+ N* J% L7 W( [; K
  5.      memblock_reserve(__pa_symbol(_text), _end - _text); 1.kernel image保留区
    : y9 P' m/ T7 |$ i  l. Y
  6. #ifdef CONFIG_BLK_DEV_INITRD
    % H" X* M- B9 y! f$ {: z
  7.      if (initrd_start) {1 w3 I* ~3 H+ _1 O' Q
  8.          memblock_reserve(initrd_start, initrd_end - initrd_start); 2.initrd保留区. |& B& F& t+ Q4 |
  9.          /* the generic initrd code expects virtual addresses */
    # v$ [# i, T$ r3 v$ m* A% M" `" S
  10.          initrd_start = __phys_to_virt(initrd_start);- {1 \) U2 ?2 k8 [
  11.          initrd_end = __phys_to_virt(initrd_end);  h1 k" b; b- ~7 o$ {  H
  12.      }* k! I3 e2 J3 m- A9 x
  13. #endif
    8 R* v1 E8 C* \2 X) }3 B* x
  14.      early_init_fdt_scan_reserved_mem(); 3.dts中配置为保留的区域4 E5 s4 i9 h8 j: v
  15. ......
    : P3 Y. @3 x# Y
  16. }
复制代码
+ a, p3 I" W) {! k2 s$ @
  • reserve内核代码、数据区等(_text到_end那一段,具体的内容可以参考内核链接脚本)
  • 保留initital ramdisk image区域(从initrd_start到initrd_end区域)
  • reserved-memory node 如下所示:

    . p0 Z/ T) ?- v) _, [6 B# h
微信图片_20200607182812.jpg

, O  M! ~5 g1 q; s& w
完成:
通过上面的一系列操作,需要动态管理的内存已经被放到了memory type和reserved type这两个region中了,现在内存已经被memblock模块所管理了,这只是启动后的第一步,后续内存才会加入到伙伴系统去管理。

+ K$ A6 k4 V& o# `
赞 收藏 评论0 发布时间:2020-6-7 18:29

举报

0个回答

所属标签

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