目录
概述
1 简述PC Linux 启动过程
2 嵌入式Linux内核启动流程
2.1 嵌入式Linux内核启动分析
2.2 启动Linux内核前的准备
2.3 启动内核第一阶段流程图
3 嵌入式Linux内核启动分析
3.1 链接文件 vmlinux.lds
3.2 启动文件head.S
3.2 启动文件head-common.S
概述
本文主要介绍嵌入式Linux 内核启动(内核版本: linux4.1.15)过程从链接文件进 和运行汇编部分代码的全部过程。笔者对汇编部分代码中重要函数的功能做了详细的介绍。通过流程图的方式,呈现内核启动过程中,在这一部分主要做了哪些工作。
源代码下载地址:
【免费】NXP官方原版Uboot和Linux资源-CSDN文库
1 简述PC Linux 启动过程
在了解嵌入式Linux的启动过程之前,先了解一下PC Linux 启动过程,其总结如下:
第一阶段:从BOIS----->linux内核
用户打开 PC 的电源时,CPU 将自动进入实模式,并从地址 0xFFFF0 开始自动执行程序代码,这个地址通常是 ROM-BIOS 中的地址。此时 BIOS 进行开机自检,并按 BIOS 中设置的启动设备(通常是硬盘)进行启动。
接着启动设备上安装的引导程序lilo 或 grub 开始引导 Linux(也就是启动设备的第一个扇区),这时,Linux 才获了启动权 。
第二阶段:
Linux boot进行内核的引导,在这个阶段必须完成磁盘引导、读取机器系统数据、实模式和保护模式的切换、加载数据段寄存器以及重置中断描述符表等。
第三阶段:
执行 init 程序(也就是系统初始化工作),init 程序调用了 rc.sysinit 和 rc 等程序,而 rc.sysinit 和 rc 在完成系统初始化和运行服务的任务后,返回 init。
第四阶段:
init启动 mingetty,打开终端供用户登录系统,用户登录成功后进入了Shell,这样就完成了从开机到登录的整个启动过程。
图1:PC linux启动流程
2 嵌入式Linux内核启动流程
2.1 嵌入式Linux内核启动分析
嵌入式系统由于其复杂和多样性,不可能像PC 一样有一个标准的BIOS。整个系统的启动和初始化过程,必须要由用户来掌控。启动代码部分被称为Bootloader,其主要完成初始化硬件,分配内存映射空间,为linux内核的启动创造运行条件。
Bootloader严重依赖硬件,用户的板卡硬件是多样的,不可能有一个通用的bootloader以支持所有的设备。但对于内核而言,其启动的流程大致相同。嵌入式Linux系统启动的流程如下:
图2:嵌入式 linux启动流程
2.2 启动Linux内核前的准备
根据图2可知,启动Linux内核前,其实MCU已经做了许多工作,现在对启动内核的准备工作做如下总结:
Step - 1: 初始化MCU内部硬件
设置中断和向量表,屏蔽中断,防止硬件中断打断程序执行配置相关的寄存器,初始化看门狗和堆栈等,为程序运行准备环境。
Step - 2: 运行用户bootloader
这个阶段主要提供面向用户操作功能,包括配置Linux内核启动参数,查看和修改内存,下载根文件系统或者内核文件等
Step - 3: 加载内核
这个工作主要有用户bootloader完成,其将Linux内核复制到内存中进行解压缩,设置指令寄存器的值,指向内核代码的入口,为启动内核做准备。
2.3 启动内核第一阶段流程图
第一阶段也可以被称为内核引导阶段,其主要为内核的启动创造条件。该阶段的执行流程如下:
图3:嵌入式 linux内核启动流程
3 嵌入式Linux内核启动分析
笔者使用基于ARM的板卡,芯片型号为i.MX6ULL,Linux内核版本为4.1.15。查看内核源码之前注意,必须编译一次源码,否则,许多链接文件是找不到的。这些文件是在编译代码之后才会产生。
3.1 链接文件 vmlinux.lds
文件路径: kernel/arch/arm/kernel/vmlinux.lds
通过这个链接文件,可以知道ARM系统中的Linux汇编代码入口位置在/arch/arm/kernel/head.S文件中的stext,图中几行代码的功能介绍如下“
代码492行:定义系统的硬件架构为arm内核
代码493行:内核启动的入口为stext
3.2 启动文件head.S
此时CPU的状态
类型 | 状态 |
---|---|
MMU | 关闭 |
D-cache | 关闭 |
I-cache | 关闭或者带卡 |
r0 | 等于0 |
r1 | 保存硬件平台的编号 |
在内核根目录下:/arch/arm/kernel/head.S, 打开head.S,可以找到stext的原型:
代码81行:确定内核运行BE8模式
代码83行:确定运行的CPU内核是ARM
代码92 行:调用函数 safe_svcmode_maskall 确保 CPU 处于 SVC 模式,关闭所有的中断。 safe_svcmode_maskall 定义在文件 arch/arm/include/asm/assembler.h 中
代码 94 行:读处理器 ID, ID 值保存在 r9 寄存器中。 __lookup_processor_type定义在arch/arm/kernel/head-common.S 中。
代码 95 行:调用函数__lookup_processor_type 检查当前系统是否支持此 CPU,如果支持就获取 procinfo 信 息 。 procinfo 是 proc_info_list 类 型 的 结 构 体 , proc_info_list 在 文 件arch/arm/include/asm/procinfo.h
代码 121 行:调用函数vet_atags 验证 atags 或设备树(dtb)的合法性。函数vet_atags 定义在文件 arch/arm/kernel/head-common.S 中。
代码 137 行:将函数mmap_switched 的地址保存到 r13 寄存器中。 mmap_switched 定义在 文件 arch/arm/kernel/head-common.S, __mmap_switched 最终会调用 start_kernel 函数。
代码 144 行: 调 用 enable_mmu 函 数 使 能 MMU , enable_mmu 定 义 在 文 件arch/arm/kernel/head.S 中。 enable_mmu 最终会通过调用turn_mmu_on 来打开 MMU,turn_mmu_on 最后会执行 r13 里面保存的mmap_switched 函数。
3.2 启动文件head-common.S
在内核根目录下:arch/arm/kernel/head-common.S
代码 82 行:调用函数mmap_switched_data 。函数mmap_switched_data定义在文件 arch/arm/kernel/head-common.S 中。
代码 104 行:调用函数start_kernel,start_kernel 函数定义在文件 init/main.c中。
该函数主要获取MCU的ID,__lookup_processor_type被/arch/arm/kernel/head.S中的ENTRY(stext)调用