Linux的启动过程概述
Linux内核的启动过程是一个复杂而又有序的流程,涉及到硬件初始化、引导加载、内核初始化等多个步骤。以下是Linux内核的典型启动流程:
BIOS/UEFI阶段:
电源启动:计算机通电后,BIOS(基本输入/输出系统)或UEFI(统一扩展固件接口)开始执行。
POST(电源自检):进行硬件自检,检查系统中的硬件设备是否正常。
引导加载程序阶段:
MBR/UEFI固件:BIOS通过Master Boot Record(MBR)或UEFI固件加载引导加载程序(Boot Loader)。
Boot Loader:常见的引导加载程序包括GRUB(GRand Unified Bootloader)或LILO(LInux LOader)等。引导加载程序的作用是加载内核映像。
内核加载阶段:
加载内核映像:引导加载程序加载内核映像(通常是vmlinuz)到内存中。
初始化RAM磁盘:如果有RAM磁盘(ramdisk),则初始化RAM磁盘。
内核启动阶段:
启动内核:引导加载程序将控制权交给内核,开始执行内核代码。
初始化内核数据结构:内核初始化页表、中断描述符表(IDT)等数据结构。
启动第一个进程:内核启动第一个用户空间进程(通常是init)。
初始化阶段:
设备初始化:内核开始初始化各种硬件设备,包括CPU、内存控制器、输入输出控制器等。
文件系统初始化:挂载根文件系统,初始化文件系统模块。
初始化进程:内核初始化其他重要的内核线程和进程。
用户空间阶段:
启动用户空间:用户空间初始化完成后,控制权交给init或systemd等用户空间的第一个进程。
启动系统服务:用户空间进程负责启动各种系统服务和用户应用程序。
内核的初次编译
编译内核之前需要先在 ubuntu 上安装 lzop 库
bash
sudo apt-get install lzop
编译脚本的内容如下
bash
#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
以上命令分别是清理工程,配置工程,打开图形配置页面,执行make编译linux源码
Linux 工程目录分析
编译之后,会有很多生成的文件,具体的文件夹和主要文件作用如下;
在 arch/arm/configs 中就包含有 I.MX6U-ALPHA 开发板的默认配置文件:imx_v7_defconfig,执行"make imx_v7_defconfig"即可完成配置。arch/arm/boot/dts 目录里面是对应开发平台的设备数文件。arch/arm/boot 目录下会保存编译出来的 Image 和 zImage 镜像文件,而 zImage 就是我们要用的 linux 镜像文件。arch/arm/mach-xxx 目录分别为相应平台的驱动和初始化文件,比如 mach-imx 目录里面就是 I.MX 系列 CPU 的驱动和初始化文件。
linux内核的启动流程
在Linux 内核的连接脚本文件 arch/arm/kernel/vmlinux.lds文件中,ENTRY指明了Linux的内核入口,入口为 stext,stext 定义在文件arch/arm/kernel/head.S。
stext 函数
Linux 内核启动之前要求如下:
- 关闭 MMU。
- 关闭 D-cache。
- I-Cache 无所谓。
- r0=0。
- r1=machine nr(也就是机器 ID)。
- r2=atags 或者设备树(dtb)首地址。
检查当前系统
在stext函数中,它做的主要操作是,确保CPU在SVC模式,关闭中断,读处理器的ID,检查当前的系统是否支持当前的CPU,这个就是通过比对proc_info_list 信息。
Linux 内核将每种处理器都抽象为一个 proc_info_list 结构体,每种处理器都对应一个procinfo。因此可以通过处理器 ID 来找到对应的 procinfo 结构,__lookup_processor_type 函数找到对应处理器的 procinfo 以后会将其保存到 r5 寄存器中。
同时该函数会验证设备数的合法性,创建页表,最终会调用__mmap_switched 函数。
__mmap_switched 函数
该函数定义在文件 arch/arm/kernel/head-common.S中。最终调用 start_kernel 来启动 Linux 内核,start_kernel 函数定义在文件 init/main.c中。
start_kernel 函数
这个函数里面有很多的子函数,实现的功能很复杂,主要是进行系统各个异常的检测和各个设备模块的初始化。start_kernel 函数最后调用了 rest_init。
rest_init 函数
该函数定义在文件 init/main.c 中
该函数主要进行的是启动RCU锁调度器,kernel_thread 创建 kernel_init 进程,也就是的init 内核进程,进程的PID是1,在这个进程会实现由内核态带用户态的转变。接下来会调用函数 kernel_thread 创建 kthreadd 内核进程,此内核进程的 PID 为 2。kthreadd,进程负责所有内核进程的调度和管理。也会创建空闲进程,空闲进程的PID是0。
linux内核的移植过程
以I.MX6ULL EVK 开发板为参考,然后将 Linux 内核移植到正点原子的 I.MX6U-ALPHA 开发板上。
先对NXP官方开发板的linux内核进行修改和编译
修改顶层Makefile
bash
ARCH ?=ARM
CROSS_COMPILE ?=arm-linux-gnueabihf-
配置并编译 Linux 内核
bash
make clean
make imx_v7_mfg_defconfig
make -j16
Linux 内核编译完成以后会在 arch/arm/boot 目录下生成 zImage 镜像文件,如果使用设备树的话还会在 arch/arm/boot/dts 目录下开发板对应的.dtb(设备树)文件。
在测试之前,需要修改些环境变量,烧录到 I.MX6U-ALPHA 开发板会提示,提示内核崩溃,因为 VFS(虚拟文件系统)不能挂载根文件系统。
在linux内核添加自己的开发板
添加开发板默认配置文件
将 arch/arm/configs 目 录 下 的 imx_v7_mfg_defconfig 重 新 复 制 一 份 , 命 名 为
imx_alientek_emmc_defconfig
bash
cd arch/arm/configs
cp imx_v7_mfg_defconfig imx_alientek_emmc_defconfig
后续可以使用以下命令配置内核
bash
make imx_alientek_emmc_defconfig
添加开发板对应的设备树文件
进入目录 arch/arm/boot/dts 中,复制一份 imx6ull-14x14-evk.dts,然后将其重命名为 imx6ull-alientek-emmc.dts
bash
cd arch/arm/boot/dts
cp imx6ull-14x14-evk.dts imx6ull-alientek-emmc.dts
.dts 是设备树源码文件,编译 Linux 的时候会将其编译为.dtb 文件, 需 要 修 改 文 件 arch/arm/boot/dts/Makefile , 找 到 " dtb-$(CONFIG_SOC_IMX6ULL)"配置项,在此配置项中加入"imx6ull-alientek-emmc.dtb
编译脚本如下
bash
#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihfimx_alientek_emmc_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
后续就是进行主频的修改,是能8线EMMC驱动,修改网络设备,保存修改后的图形化配置文件。