3.2 内核初始化(盘古开天地)

Interrupt Pipeline系列文章大纲-CSDN博客

3.2.1 内核初始化的神话

        Linux内核的初始化流程,很多文章都有分析,自己也学习了很多次,总是感觉记忆不深刻。突发奇想,觉得整个初始化流程与中国神话传说盘古开天地特别的契合,所以尝试着更生动形象的分析一下内核初始化化流程。

        天和地还没有分开的时候,宇宙混沌一片,从外表看像一个大鸡蛋。这个蛋形混沌宇宙可以用“0”来非常形象的表示。Linux内核的初始化是从0开始的,Documentation/arm64/booting.txt里面写明了,此时中断是完全关闭的,一切似乎都是静止的。

        宇宙混沌中,开始孕育代号为0的巨人盘古,孕育的顺序是从头(head.S)开始,然后是身体(stext)。什么是宇宙?宇者,上下四方,空间为宇;宙者,古往今来,时间为宙。盘古为了开天辟地,必须分开空间和时间。在空间维度,要对内存空间进行切分,即基于MMU的空分复用。在时间维度,要对CPU的运行时间进行切分,即基于进程调度的时分复用。盘古作为0号进程,在head.S汇编代码中,沿着_head(头)->stext(身)->__primary_switch(MMU)->__primary_switched(进程)完成了上述4个步骤,之后就可以跳转到C语言函数start_kernel开始开天辟地。

        盘古开天辟地不仅仅是简单的分开天地,还要创造世间万物,就像start_kernel要进行各个子系统的初始化一样。当所有的子系统都初始化完毕,开天辟地的最后一步,就是分出天和地,start_kernel的最后一步是rest_init,也有异曲同工之妙。在rest_init中,创建”地”进程即1号进程init,它将孕育所有用户进程,是所有的用户进程的祖先;创建”天”进程为2号线程kthreadd,它将孕育所有内核线程,是所有的内核线程的祖先。

        盘古(0号进程)在完成开天辟地之后,就事了拂衣去,闲云野鹤地idle了,也被称为idle进程。只有在天地之间没有任何进程运行的时候,才被调度运行。

3.2.2 从头 (Head)开始

        Linux内核代码的执行真的是从头(Head)开始!为啥这么说?

        内核入口所在的文件名是head.S,启动代码链接时的代码段名字叫做__HEAD(即.head.text头代码),启动代码的标签是_head,全部都是用单词Head(头),司马昭之心路人皆知哈。接下来分析具体是怎么做到的。

        根据Linux内核源码目录下的Makefile,vmlinux的链接文件是vmlinux.lds。

export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds

        链接文件vmlinux.lds是由vmlinux.lds.S文件生成的。以ARM64为例,vmlinux.lds.S位于目录arch/arm64/kernel/。vmlinux.lds生成的过程记录在.vmlinux.lds.cmd文件中。

        在vmlinux.lds.S文件中,与Linux内核运行入口相关的主体内容如下:

1

ENTRY(_text)

2

3

SECTIONS

4

{

5

    ……

6

. = KIMAGE_VADDR + TEXT_OFFSET;

7

8

.head.text : {

9

_text = .;

10

HEAD_TEXT

11

}

12

    ……

13

.text : {

/* Real text segment*/

14

_stext = .;

/* Text and read-only data*/

15

    ……

16

}

17

}

        第1行,使用ENTRY命令将符号_text的值设置为Linux运行时入口地址。

        _text在哪里定义?

        看第9行,此处定义了_text符号。咬文嚼字一下哈,为啥叫这个名字呢?追溯到commit e2f81844efa2 ARM: vmlinux.lds: use _text and _stext the same way as x86。x86使用_text来标记.head.text段的起始地址,同时也是内核镜像的运行起始地址;用_stext来标记.text段的起始地址。_text之后第10行就是HEAD_TEXT。HEAD_TEXT定义在include/asm-generic/vmlinux.lds.h:

        #define HEAD_TEXT  KEEP(*(.head.text))

        所以,只要在源码中定义了.head.text段,就会链接到_text。 这就必须说到定义在include/linux/init.h中的宏定义__HEAD。通过宏定义__HEAD就可以在源码中定义.head.text段。其中“ax”后缀的定义参考Section (Using as) (sourceware.org):a代表allocatable,x代表executable。

#define __HEAD          .section  ".head.text","ax"

       __HEAD宏在哪里使用?

        在head.S(arch/arm64/kernel)的起始位置,使用了__HEAD宏。所以,Linux内核运行的入口位置就是head.S中的标号_head。

       __HEAD

_head:

       /*

        * DO NOT MODIFY. Image header expected by Linux boot-loaders.

        */

#ifdef CONFIG_EFI

       /*

        * This add instruction has no meaningful effect except that

        * its opcode forms the magic "MZ" signature required by UEFI.

        */

       add x13, x18, #0x16

       b     stext

#else

       b     stext                            // branch to kernel start, magic

       .long      0                          // reserved

#endif

        回到vmlinux.lds.S文件,_text的值是多少?结合第6行和第9行,可知:

        _text = KIMAGE_VADDR + TEXT_OFFSET。

        根据生成的vmlinux.lds,可以直接看到宏定义自动展开的结果,算出来是0xFFFF 0000 0800 0000。

. = ((((((0xffffffffffffffff)) - (((1)) << (48)) + 1) + (0)) + (0x08000000))) + 0x00080000;

        也可以追一下宏定义的位置,手动展开并计算,具体过程如下:

        KIMAGE_VADDR定义在arch/arm64/include/asm/memory.h,

#define VA_BITS         (CONFIG_ARM64_VA_BITS)     //配置为48

#define VA_START        (UL(0xffffffffffffffff) - \

    (UL(1) << VA_BITS) + 1)

#define KIMAGE_VADDR        (MODULES_END)

#define MODULES_END         (MODULES_VADDR + MODULES_VSIZE)

#define MODULES_VADDR       (VA_START + KASAN_SHADOW_SIZE)

#define MODULES_VSIZE       (SZ_128M)

#define KASAN_SHADOW_SIZE   (0)

        TEXT_OFFSET定义arch/arm64/Makefile:

TEXT_OFFSET := 0x00080000

你可能感兴趣的:(Interrupt,Pipeline,linux,服务器,运维)