linux kernel 加载与初始化

《Linux内核深度解析》读书笔记。

1. 读取引导程序的地址

以U-Boot为例,启动代码路径为arch/arm/armv8/start.S

ARM64处理器到虚拟地址0取指令,刚上电时的MMU还未启用,也就是到物理地址0取指令。

2. 保存启动参数

U-boot启动从_start开始,直接跳转至标号reset处,之后做的第一件事就是保存启动时的参数。


reset:

    /* Allow the board to save important registers */

    b save_boot_params

    ...

 WEAK(save_boot_params)

    b save_boot_params_ret /* back to my caller */

ENDPROC(save_boot_params)

这里需要注意的:使用WEAK弱符号类型的函数修饰,可使用强符号以覆盖旧符号。另外,在初始状态下,为小端字节序,禁止MMU,禁止指令/数据cache的状态。

3. 根据处理器当前的异常级别设置寄存器,启用部分硬件功能


switch_el x1, 3f, 2f, 1f

3: set_vbar vbar_el3, x0

mrs x0, scr_el3

orr x0, x0, #0xf /* SCR_EL3.NS|IRQ|FIQ|EA */

msr scr_el3, x0

msr cptr_el3, xzr /* Enable FP/SIMD */

#ifdef COUNTER_FREQUENCY

ldr x0, =COUNTER_FREQUENCY

msr cntfrq_el0, x0 /* Initialize CNTFRQ */

#endif

b 0f

2: set_vbar vbar_el2, x0

mov x0, #0x33ff

msr cptr_el2, x0 /* Enable FP/SIMD */

b 0f

1: set_vbar vbar_el1, x0

mov x0, #3 << 20

msr cpacr_el1, x0 /* Enable FP/SIMD */

根据异常级别,设置异常向量的起始地址,通过CPTR寄存器启用FP/SIMD。在EL3级别时,设置SCR_EL3的NS,IRQ,FIQ和EA四个bit位,将中断,快速中断,同步外部终止和系统错误转发到异常级别3。

4. 执行特定的勘误表和硬件初试化


/* Apply ARM core specific erratas */

bl apply_core_errata

/* Processor specific initialization */

bl lowlevel_init

5.执行_main函数

路径arch/arm/lib/ctr0_64.S

U-Boot分为SPL和正常的U-Boot程序两个部分;SPL即第二程序加载器(可选),处理器内部集成的静态RAM比较小,无法装载一个完整的U-Boot镜像,此时需要SPL,主要负责初始化内存和存储设备驱动,然后把正常的U-Boot镜像从存储设备中读到内存中执行。

5.1 为调用board_init_f函数做环境准备


bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */

mov x0, sp

bl board_init_f_alloc_reserve

mov sp, x0

/* set up gd here, outside any C code */

mov x18, x0

bl board_init_f_init_reserve

设置临时的栈空间,通过调用board_init_f_alloc_reserve在栈的顶部为结构体global_data分配空间,通过 board_init_f_init_reserve初始化global_data结构体。

5.2 调用board_init_f函数


mov x0, #0

bl board_init_f

调用board_init_f,执行前期初始化,包括讲U-Boot程序复制到内存中以及初始化硬件,依次执行common/board_f.c数组init_sequence_f中的每个函数。


void board_init_f(ulong boot_flags)

{

...

if (initcall_run_list(init_sequence_f))

    hand();

...

}

5.3 重定位&调用c_runtime_cpu_setup

对复制到内存中的U-boot镜像重新定位,然后调用c_runtime_cpu_setup,设置最终的完整环境,设置异常向量表的起始地址。


#if defined(CONFIG_ARMV8_SPL_EXCEPTION_VECTORS) || !defined(CONFIG_SPL_BUILD)

/* Relocate vBAR */

adr x0, vectors

switch_el x1, 3f, 2f, 1f

3: msr vbar_el3, x0

b 0f

2: msr vbar_el2, x0

b 0f

1: msr vbar_el1, x0

0:

5.5 调用函数board_init_r

调用函数board_init_r执行后期初始化,依次执行init_sequence_r中的每个函数,最后执行run_main_loop。


void board_init_r(gd_t *new_gd, ulong dest_addr)

{

...

if (initcall_run_list(init_sequence_r))

    hang();

...

}

5.6 run_main_loop函数

(1) 通过bootdelay_process读取环境变量:bootdelay和bootcmd,分别定义了延迟时间和要执行的命令。

(2) 调用autoboot_command。首先调用abortboot,等待用户按键;在等待时间内用户无按键,就调用函数run_command_list,自动执行bootcmd定义的命令。假如bootcmd定义的命令是"bootm",函数run_command_list查找命令表,发现命令"bootm"的处理函数是do_bootm。

(3) do_bootm先后初始化全局变量"bootm_header_t images",把内核镜像从存储设备读到内存,读取其他信息传递硬件信息的扁平设备树(FDT),将内核加载到正确的位置,之后根据操作系统类型在数组boot_os中查抄引导linux内核的引导函数do_bootm_linux(这是第一次执行do_bootm_linux,实际是为执行内核做准备,如拷贝FDT,根据bootargs设定多处理器启动方式等),第二次调用do_bootm_linux是实际执行内核,跳转到内核入口,第一个参数为FDT二进制文件起始地址(后面三个参数保留),同时根据配置宏CONFIG_GICV2或CONFIG_GICV3发送中断请求唤醒所有从处理器,禁止处理器缓存和内存管理单元,根据配置宏设定内核执行的异常等级。

6. 内核初试化

6.1 preserve_boot_args

把引导程序传递的4个参数保存在全局数组boot_args中。

6.2 el2_setup

设定内核运行的异常等级

(1)当进入内核时,异常等级为1,那么在异常级别1执行内核。

(2)当进入内核时,异常等级为2,支持VHE那么内核继续在2执行,如果不支持那么降到1执行内核。

在虚拟化中,运行虚拟机的操作系统称为host OS,在虚拟机里面的操作系统称为guest OS,guest OS的用户进程在异常0运行,内核在异常级别1运行;kvm的特点就是直接在处理器上执行guest OS。

以虚拟机KVM为例,普通的虚拟化异常等级切换,kvm模块需要穿越异常等级1和2;Arm64引入虚拟化宿主扩展后,在异常级别2执行host OS操作系统内核,kvm就不再需要从异常级别1切换到异常级别2。

6.3 __create_page_tables

(1)创建恒等映射,恒等映射的特点就是虚拟地址和物理地址相同,为了在开启处理器的内存管理单元的一瞬间能够平滑过渡。由__enable_mmu负责开启内存管理单元,内核把函数__enable_mmu附近的代码放在恒等映射代码节(.idmap.text)中,恒等映射代码节的起始地址存放在全局变量__idmap_text_start中,结束地址存放在全局变量__idmap_text_end中。idmap_pg_dir为恒等映射的页全局目录的地址地址。

(2)在内核的页表中为内核镜像创建映射,内核镜像起始地址为_text,结束地址为_end。

6.4 __primary_switch

(1)调用函数__enable_mmu以开启内存管理单元。

(2)调用__primary_switched,设置不同异常级别的栈指针,VBAR_EL1设置为异常向量表的起始地址,计算内核镜像起始虚拟地址和物理地址的差值,保存到全局变量kimage_voffset中,调用start_kernel。

6.5 start_kernel

首先初始化基础设施,即初始化内核的各个子系统,然后调用rest_init创建init和kthreadd线程。

之后,init进程继续执行初始化,主要为smp作初始化和准备,启动所有从处理器,执行脚本0~7的初始化,挂载根文件系统,释放初始化代码和数据占用的内存,最后从文件系统中装载init程序,并转换成用户空间的init进程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容