【IMX6ULL Linux系统移植】内核编译裁剪 + rootfs 构建 + 完整启动流程梳理

文章目录

    • 前言
    • 一、内核的编译与剪裁
      • [1.1 相关概念](#1.1 相关概念)
      • [1.2 软件安装(lzop)](#1.2 软件安装(lzop))
      • [1.3 编译流程](#1.3 编译流程)
    • 二、uboot之后的Linux内核启动流程
      • [2.1 Linux内核启动的前置条件](#2.1 Linux内核启动的前置条件)
      • [2.2 Linux内核启动流程](#2.2 Linux内核启动流程)
    • 三、根文件系统
      • [3.1 正点原子根文件系统代码阅读](#3.1 正点原子根文件系统代码阅读)
      • [3.2 制作自己的根文件系统](#3.2 制作自己的根文件系统)
    • [四、Linux 完整启动流程](#四、Linux 完整启动流程)

前言

在上篇文章IMX6ULL Linux系统移植】SD卡分区 | uboot详解中,在使用SD卡进行系统移植情况下,我们完成了对SD卡分区的了解,并且对uboot的启动流程有了一定的掌握。在这篇文章中,我们将学习内核的编译与剪裁,根文件系统rootfs,以及对Linux系统启动流程的完整梳理。

一、内核的编译与剪裁

1.1 相关概念

内核是操作系统的核心功能代码,是一个大型的开源项目,在嵌入式设备中,不需要这么多的功能,并且也装不下这么大的系统,因此,我们需要对这个源码进行裁剪、编译。我们使用的内核版本为Linux4.1.15。

1.2 软件安装(lzop)

lzop 是基于 LZO 算法的高速无损压缩程序。

用途:

✅ Linux 内核编译:生成 zImage 内核镜像,很多嵌入式内核配置依赖 lzop,缺少它编译会报错。

✅ 嵌入式开发、bootloader 加载 lzo 压缩的内核。

1.3 编译流程

  1. 把linux内核复制到虚拟机,进行解压操作:
    tar -zxvf linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek.tar.gz

  2. 进入内核文件夹:
    cd linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/

  3. 使用编译命令进行编译

    c 复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_emmc_defconfig
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig  #内核地图,驱动,模块和c文件的关系
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
    1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean:
      大清理,清除编译产生的中间文件,以及执行文件等;
    2. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_emmc_defconfig:
      生成 .config,内核编译的核心配置文件;
    3. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig:
      图形化配置界面(终端窗口),用来修改内核配置 ,如图所示,

      在这里面进行参与编译文件的选择就称之为内核的裁剪。
    4. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16:
      编译全套产物:zImage 内核镜像 + 设备树 dtb + 内核模块 ko
    • -j16:多线程编译,16 个任务并行,数字一般填 CPU 核心数
  4. 生成结果
    结果文件主要为2个必备文件以及一些.ko文件:

    • 内核镜像文件: arch/arm/boot/zImage
    • 设备树文件: arch/arm/boot/dts/imx6ull-alientek-emmc.dtb文件
    • 相关的驱动文件 *.ko
  5. 将编译好的两个系统相关的文件拷贝到tftp管理目录下

  6. 重新启动开发板,每次从Ubuntu管理目录下载最新版本的内核和设备树文件

二、uboot之后的Linux内核启动流程

U-Boot 是引导加载程序(Bootloader),负责硬件初始化,找到并把 Linux 内核加载到内存,最后启动内核;内核接管之后,U-Boot 就退出,不再运行。

2.1 Linux内核启动的前置条件

  1. MMU 必须关闭
    U-Boot 跳转内核瞬间,不能开启 MMU 。

    • MMU 一开就是虚拟地址,而内核刚入口stext汇编是按照物理地址运行的;
    • 内核head.S里面会自己去初始化页表、由内核自己打开 MMU;
    • 如果 U-Boot 带着 MMU 直接跳过去,地址翻译错乱,内核直接崩溃。
  2. D-cache(数据缓存)必须关闭
    D-cache 是读写内存用的缓存。

    • 如果 D-cache 开启,缓存中的脏数据和真实 DDR 内存不一致;
    • 内核初始化内存、BSS 清零、操作设备树的时候,会读到脏缓存,引发随机 bug。
  3. I-Cache(指令缓存)无所谓
    I-cache 只缓存代码指令。

    • 打开 I-cache 只是加速取指令,不会影响数据一致性;
    • 内核stext汇编里会自行处理 I-cache,所以 U-Boot 开或者关都可以。
  4. r0 = 0
    ARM Linux 启动 ABI 强制约定,r0 必须置 0,是历史 ABI 保留项。

  5. r1 = machine nr(机器 ID)
    现在设备树 dtb 方式下,这个 machine nr可以填 - 1,内核会忽略 r1,直接解析 r2 里的 dtb。IMX6ULL 就是用 dtb 方式。

  6. r2 = atags /dtb 首地址

    • ATAGS:老式,把板载信息(内存大小、串口、根分区)打包成标签列表,放在内存,r2 指向这个 atags 起始地址;
    • DTB 设备树:现代方案(IMX6ULL 用这个),r2 存放 dtb 在内存中的物理起始地址。

    内核 stext 汇编拿到 r2,后续 C 代码start_kernel就会解析这个 dtb,获取硬件信息。

2.2 Linux内核启动流程

接下来就是Linux启动流程了。与uboot的流程类似,我们可以通过lds链接脚本来对linux启动的流程进行梳理:

打开arch/arm/kernel/vmlinux.lds:

  1. [_stext, _etext] .text 代码段:内核可执行指令(所有内核函数代码),只读,运行期间永久保留,不会释放。
  2. [_sdata, _edata] .data 数据段:已经初始化的全局变量,可读可写,内核全程使用。

我们跟进stext去看Linux内核相关的代码,刚从 U-Boot 跳过来的时候,CPU 还处在裸机环境,C 语言根本跑不起来。必须先用汇编stext做最底层的硬件 / 寄存器环境搭建,环境就绪后,才能进入 C 代码环境。ENTRY(stext)就是标记这一段汇编是内核的入口,因此我们使用grep -Rin 'ENTRY(stext)'指令来找到这个入口:

  • head.S:带 MMU 的内核启动汇编(IMX6ULL 用这个)
  • head-nommu.S:无 MMU 版本,不使用

我们进入head.S:

往下翻,我们可以看到带有注释的一段代码:

这段代码作用是:先保存 MMU 开启后的目标入口__mmap_switched,设置返回地址,把临时页表基址存入 r8;然后从proc_info结构体取出当前 CPU 对应的__cpu_flush函数地址并跳转。__cpu_flush负责配置 TTBR 页表寄存器、开启 MMU 和 Cache;MMU 打开完成后返回,继续执行__mmap_switched,完成 BSS 清零、准备 C 栈,最终调用start_kernel:

此时,我们要再跟入会发现start_kernel不再此文件夹了,因为start_kernel就是大量的初始化,所以,它位于linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/init目录下,去此目录下查找:

翻到start_kernel最好发现执行的函数为rest_init(),跟进去:

rest_init()创建了 PID=1 的 kernel_init(用户态 init 进程前身)和内核线程管家 kthreadd(PID=2),然后主线程变成 idle 空闲线程,进入 cpu idle 循环。

kernel_init 是 PID1 的入口,负责做完内核最后的初始化,释放 init 段内存。到此,Linux内核就启动起来了。最后,还需去挂载根文件系统、启动用户态 init 程序。

三、根文件系统

根文件系统是Linux操作系统组成的基础,是内核启动时所要挂载的第一个文件系统,我们的一些配置文件、库函数、系统命令都保存在文件系统中,同时也可以通过文件系统实现用户与操作系统功能的交互。

由于我们在开发阶段使用第三种系统烧录的方法(uboot烧写到SD卡 + zImage通过tftp下载 +rootfs通过nfs挂载)比较便捷,因此,需要将rootfs复制到nfs目录下:

c 复制代码
cd nfs
mkdir rootfs
sudo tar -xvf rootfs.tar.bz2 #解压的时候, 加上sudo, 不然可能报错

因为需要从上位机中通过nfs下载根文件系统,因此,在交互界面中一定要确认下载地址以及ip等信息:

除此以外,还要确认ubuntu中nfs设置正确:

在完成上述操作后,开发板就可以成功从ubuntu中下载rootfs了。

3.1 正点原子根文件系统代码阅读

在Linux内核启动后,就是加载rootfs根系统文件了:

  • 蓝色(内核侧):内核启动 → 加载 rootfs → 执行 linuxrc。linuxrc 本质上就是 init 进程,是系统里第一个用户态进程(PID=1),是内核与用户空间的分界点。
  • 青色(用户空间初始化):
    • /etc/inittab 是 init 的"配置清单",决定要跑哪些启动脚本、重启脚本、以及按下 ctrl+alt+del 时执行什么;
    • /etc/init.d/rcS 是其中最核心的启动脚本,负责设置环境变量和系统参数;
    • mount -a 依据 /etc/fstab 把 sd、flash 等剩余文件系统全部挂载上;
    • 最后执行 /etc/profile 配置好用户环境。
  • 终点:运行 bash shell,进入交互模式,用户可以输入命令。

3.2 制作自己的根文件系统

根文件系统的制作常见有如下几种: busybox构建根文件系统、 Buildroot构建根文件系统、Yocto构建根文件系统。

这里采用busybox构建根文件系统,其特点是易于构建简单的根文件系统,而且文件系统占用空间小,但是需要自己配置库文件,易于理解移植过程。

操作步骤:

  1. 将源码拷贝到Ubuntu主机下。(从busybox官网下载源码)
  2. 然后将其解压
    tar -vxjf busybox-1.29.0.tar.bz2
  3. 修改busybox顶层的Makfile
    • 先进入Makefile
      cd busybox-1.29.0/
      vim Makefile
    • 修改内容:
      CROSS_COMPILE ?=------>CROSS_COMPILE ?= arm-linux-gnueabihf-
      ARCH ?= $(SUBARCH)------>ARCH ?= arm
  1. 配置busybox,进入menuconfig图形界面

    make defconfig //使用默认配置

    make menuconfig //打开图形化配置

  2. 通过图形界面配置busybox工具

    配置路径如下:

    c 复制代码
    Location:
    -> Settings
    -> Build static binary (no shared libs)
    c 复制代码
    Location:
    -> Settings
    -> vi-style line editing commands//选中
    c 复制代码
    Location:
    -> Linux Module Utilities
    -> Simplified modutils
    c 复制代码
    Location:
    -> Linux System Utilities
    -> mdev (16 kb) //确保下面的全部选中, 默认都是选中的
  3. 创建一个用来存放生成根文件系统的目录,并编译busybox

    c 复制代码
    mkdir /home/linux/nfs/rootfs //创建存放根文件系统的目录
    make //编译
    make install CONFIG_PREFIX=/home/linux/nfs/rootfs //安装
    ls -l /home/linux/nfs/rootfs //查看根文件系统是否创建成功
  4. 此时的文件系统还缺少了一些运行的库文件,需要将工具链中的libc库拷贝过去。

    • cd /home/linux/nfs/rootfs
    • mkdir lib
    • cp ~/tools/opt/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinuxgnueabihf/libc/lib/*so* /home/linux/nfs/rootfs/lib/ -d
    • cp ~/tools/opt/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinuxgnueabihf/libc/lib/*.a /home/linux/nfs/rootfs/lib/ -d
  5. 将符号链接库删除,并拷贝真实的库文件

    • rm /home/linux/nfs/rootfs/lib/ld-linux-armhf.so.3
    • cp ~/tools/opt/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinuxgnueabihf/libc/lib/ld-linux-armhf.so.3 /home/linux/nfs/rootfs/lib
  6. 将工具链下的lib库拷贝过去

    • cp ~/tools/opt/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinuxgnueabihf/lib/*so* /home/linux/nfs/rootfs/lib/ -d
    • cp ~/tools/opt/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinuxgnueabihf/lib/*.a /home/linux/nfs/rootfs/lib/ -d
  7. 将工具链的usr/lib库拷贝过去

    • mkdir /home/linux/nfs/rootfs/usr/lib -p
    • cp ~/tools/opt/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinuxgnueabihf/libc/usr/lib/*so* /home/linux/nfs/rootfs/usr/lib -d
    • cp ~/tools/opt/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinuxgnueabihf/libc/usr/lib/*.a /home/linux/nfs/rootfs/usr/lib -d
  8. 进入根目录文件系统查看这两个目录的大小

    c 复制代码
    cd /home/linux/nfs/rootfs
    du ./lib ./usr/lib -sh

    与上图所示大小一样即为正确。

  9. 创建其余文件夹

    c 复制代码
    mkdir dev
    mkdir proc
    mkdir mnt
    mkdir sys
    mkdir tmp
    mkdir root
    mkdir lib/modules
  10. 我们还需要完善下根我们系统中的配置文件,这些配置文件可以进一步完善Linux系统中的功能

    • 创建/etc/init.d/rcS文件,并将文件修改为如下内容,该文件是一个shell脚本, Linux内核启动后需要启动的一些服务都可以通过该脚本进行配置。

      c 复制代码
      mkdir ~/nfs/rootfs/etc/init.d -p
      touch ~/nfs/rootfs/etc/init.d/rcS
      vim ~/nfs/rootfs/etc/init.d/rcS

      修改文件内容如下:

      c 复制代码
      #!/bin/sh
      PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
      LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
      export PATH LD_LIBRARY_PATH
      mount -a
      mkdir /dev/pts
      mount -t devpts devpts /dev/pts
      echo /sbin/mdev > /proc/sys/kernel/hotplug
      mdev -s
    • 修改文件权限

      chmod 0777 ~/nfs/rootfs/etc/init.d/rcS

    • 创建/etc/fstab文件,并将文件修改为如下内容,在rcS脚本中执行mount -a,挂载所有文件系统,就会找到fstab文件,并进行文件挂载。

      c 复制代码
      touch ~/nfs/rootfs/etc/fstab
      vim ~/nfs/rootfs/etc/fstab

      修改内容如下:

      c 复制代码
      #<file system> <mount point> <type> <options> <dump> <pass>
      proc /proc proc defaults 0 0
      tmpfs /tmp tmpfs defaults 0 0
      sysfs /sys sysfs defaults 0 0
      字段 作用
      file system 待挂载设备 / 伪文件系统名称,可以是块设备/dev/mmcblk0p2,也可以是内核虚拟文件标识proc、sysfs
      mount point 挂载点目录,目录必须提前创建好,否则mount -a会报错
      type 文件系统类型:proc/sysfs/tmpfs/ext4等
      options 挂载选项,defaults等价于 rw,suid,dev,exec,auto,nouser,async
      dump dump 备份工具标记;嵌入式统一写0,代表不做备份
      pass fsck 开机磁盘检查;嵌入式统一写0,代表开机不做磁盘自检
    • 创建/etc/inittab文件,该文件指定启动后一些代码的运行方式。

      c 复制代码
      touch ~/nfs/rootfs/etc/inittab
      vim ~/nfs/rootfs/etc/inittab

      修改文件内容如下:

      c 复制代码
      #etc/inittab
      ::sysinit:/etc/init.d/rcS
      console::askfirst:-/bin/sh
      ::restart:/sbin/init
      ::ctrlaltdel:/sbin/reboot
      ::shutdown:/bin/umount -a -r
      ::shutdown:/sbin/swapoff -a

      内容的格式为:

      c 复制代码
      <id>:<runlevels>:<action>:<process>
      <id>:每个指令的标识符, 不能重复。 对于busybox而言<id>用来指定启动进程的控制tty, 一般
      我们将串口或者LCD屏幕设置为控制tty。
      <runlevels>:对busybox来说此项说明完成没用
      <action>: 动作, 用于指定<process>可能使用到的动作。 如下图所示
      <process>:具体的动作, 如程序、 脚本、 命令等。

      action动作可配置为如下参数:

  11. 接下来就可以通过nfs挂载测试了。

四、Linux 完整启动流程

  1. bootROM 阶段

IMX6ULL 平台在芯片 0x0 地址接了 96K bootROM,里面是厂商固化的启动代码,上电启动后触发复位异常

根据用户设置的启动方式,将 EMMC 或 SD 卡存储器的前 4K 代码拷贝到 SRAM 中执行,其中包括:IVT 镜像入口地址(0X877FF400,0x8780 0000 -3K)、DCD(DDR 基础初始化代码)、BootData(镜像加载地址及大小)

根据 DCD 中的内容完成对 DDR 的基础初始化

根据 BootData 将 SPL 加载到 OCRAM 中(on-chip ram)

  1. SPL 阶段

在 OCRAM 中运行 SPL 代码

对 DDR 做深度初始化

将完整的 uboot 拷贝到 0x87800000 地址

重新定位链接地址(board_init_f → relocate_code, relocate_vectors)

  1. uboot 阶段

重新设置异常向量表,执行 reset 复位异常

运行 uboot 完成对硬件设备初始化

与用户交互,执行 bootcmd 加载内核

  1. 内核阶段

运行内核代码

  1. 文件系统阶段

执行 init 进程 → 启动配置文件 /etc/inittab → 运行 /etc/init.d/rcS 脚本 → 挂载 /etc/fstab 文件 → 启动终端

相关推荐
gwf2162 天前
RDMA事件与异步通知机制:CMA事件与设备事件深度解析
驱动开发·linux内核·rdma·rdma_cm·异构计算·异步事件·cve分析
susplus2 天前
【IMX6ULL Linux系统移植】SD卡分区 | uboot详解
linux·arm·u-boot
剑指offer.6 天前
ARM裸机开发-SPI
arm开发·嵌入式硬件·嵌入式·arm
gwf2167 天前
内核RDMA子系统(ib_core)架构与核心数据结构:从驱动视角解构数据通路
linux内核·rdma·网卡驱动·mlx5·gpudirect·ib_core·内核架构
susplus9 天前
【ARM 裸机开发 (IMX6ULL-mini)】I2C协议详解
嵌入式·arm·i2c
gwf2169 天前
企业级SSD双端口与高可用架构深度解析(Dual Port / Multipath / ALUA / Active-Active)
linux内核·高可用·存储技术·多路径·企业级ssd·双端口·alua
今年下半年10 天前
【运维】windows虚拟机安装arm(aarch64)服务器
运维·windows·arm·虚拟机·aarch64
额额额对了11 天前
SPI通信
linux·c语言·汇编·单片机·嵌入式硬件·arm
一直C11 天前
【ARM嵌入式开发】LCD液晶屏显示原理、色彩模式与显存地址映射详解
lcd·嵌入式·arm·寄存器·cortex-a7