正点原子 I.MX6ULL MINI 开发板学习笔记:Linux 系统移植——U-Boot、Kernel、rootfs 编译与启动

分享
I.MX6ULL Linux 启动全流程

这篇笔记开始进入 Linux 部分:在正点原子 I.MX6ULL MINI 开发板上完成系统移植——从源码编译 U-Boot、Linux 内核和 busybox 根文件系统,让开发板从裸机状态跑起完整的 Linux。内容整理自我的学习笔记(note.pdf),每一步都实际跑通过。

1. 完成什么实验?

从源代码出发,分别编译出三个系统组件:U-Boot(引导程序)、zImage+dtb(内核与设备树)、busybox rootfs(根文件系统),并通过 SD 卡烧录 + tftp 下载内核 + NFS 挂载根文件系统的方式,把开发板启动到 Linux 命令行。

2. U-Boot、Kernel、rootfs 各是什么?

  • U-Boot:bootloader 的一种。内核加载之前先初始化硬件(时钟、DDR、启动设备),再把内核镜像读进内存并跳转过去,为内核运行提供引导。
  • Kernel(Linux 内核):操作系统的核心代码。我们用的是 NXP 维护的 Linux 4.1.15,需要针对开发板裁剪、编译。
  • rootfs(根文件系统):内核启动后挂载的第一个文件系统,/bin、/etc、/lib 等目录都在里面,shell、库文件、配置文件都由它提供。用 busybox 手工构建,最容易理解移植过程。

3. U-Boot 编译与烧录

U-Boot 图形化配置基于 ncurses 库,先装依赖;然后清配置、打默认配置、编译:

sudo apt-get install libncurses5-dev
tar -zxvf uboot.tar.gz
cd uboot

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

编译产物是顶层目录的 u-boot.imx。它不是纯程序,前面还有一段给 BootROM 看的头部:

u-boot.imx 组成

烧录用正点原子提供的 imxdownload 工具,本质是把 u-boot.imx 用 dd 写到 SD 卡 1KB 偏移处(BootROM 固定从 1KB 偏移读镜像;分区表在 0~512 字节,第一个分区从 1MB 开始,两者错开互不干扰):

./imxdownload u-boot.imx /dev/sdd   # 写整张卡,不是某个分区

4. U-Boot 环境变量与常用命令

bootcmd 是倒计时结束后自动执行的命令,bootargs 是传给内核的启动参数。开发阶段最常用的是“tftp 下载内核 + NFS 挂载 rootfs”组合:

# 从 EMMC 启动(烧录完成后用)
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
saveenv

# 网络启动(开发调试用:zImage 从 tftp 下载,rootfs 走 NFS)
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/home/linux/nfs/rootfs,v3,proto=tcp rw ip=192.168.1.50:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off'
saveenv
命令作用
printenv / setenv / saveenv查看、修改、保存环境变量
tftp 80800000 zImage通过 tftp 把文件下载到 DDR 指定地址
nfs 80800000 192.168.1.100:/home/linux/nfs/zImage通过 NFS 下载文件
fatload mmc 1:1 80800000 zImage从 EMMC 第 1 分区读文件到内存
mmc info / mmc dev / mmc part查看/切换 MMC 设备、列出分区(0=SD 卡,1=EMMC)
bootz 80800000 - 83000000启动 zImage,- 表示无 ramdisk,83000000 是 dtb 地址

5. 内核编译与裁剪

sudo apt-get install lzop
tar -zxvf linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek.tar.gz
cd linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/

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   # 图形化裁剪驱动
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

cp arch/arm/boot/zImage ~/tftpboot/
cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb ~/tftpboot/

产物有三个:内核镜像 arch/arm/boot/zImage、设备树 arch/arm/boot/dts/imx6ull-alientek-emmc.dtb、以及各驱动模块 *.ko。配置体系的关系是:menuconfig 的每个选项来自各级目录的 Kconfig,选择结果写进顶层 .config,Makefile 再根据 .config 里的 CONFIG_xxx 变量决定编译哪些 .c。

6. busybox 手工制作 rootfs

rootfs 常见做法有 busybox、Buildroot、Yocto 三种。busybox 最手工、最能看清根文件系统的构成,步骤如下:

tar -vxjf busybox-1.29.0.tar.bz2
cd busybox-1.29.0/
# 顶层 Makefile 改两处:ARCH ?= arm 、CROSS_COMPILE ?= arm-linux-gnueabihf-

make defconfig
make menuconfig
#   Settings -> [*] Build static binary (no shared libs)   静态链接,省去库依赖
#   Linux Module Utilities -> [*] Simplified modutils       insmod/rmmod 等
#   Linux System Utilities -> [*] mdev                       自动生成 /dev 节点

make
make install CONFIG_PREFIX=/home/linux/nfs/rootfs

busybox 装完后还缺 C 库和目录骨架,要从交叉工具链拷贝(注意 ld-linux-armhf.so.3 是符号链接,必须删掉换成真实文件):

cd /home/linux/nfs/rootfs
mkdir lib
cp <工具链>/arm-linux-gnueabihf/libc/lib/*so* lib/ -d
cp <工具链>/arm-linux-gnueabihf/libc/lib/*.a  lib/ -d
rm lib/ld-linux-armhf.so.3                              # 删符号链接
cp <工具链>/arm-linux-gnueabihf/libc/lib/ld-linux-armhf.so.3 lib/   # 拷真实文件
mkdir -p usr/lib
cp <工具链>/arm-linux-gnueabihf/libc/usr/lib/*so* usr/lib/ -d
cp <工具链>/arm-linux-gnueabihf/libc/usr/lib/*.a  usr/lib/ -d

mkdir dev proc mnt sys tmp root lib/modules

最后写三个配置文件,它们决定了系统启动后做什么:

# /etc/inittab —— init 的行为表:先跑 rcS,再在串口起 shell
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/umount -a -r
::shutdown:/sbin/swapoff -a
#!/bin/sh
# /etc/init.d/rcS —— 系统初始化脚本(inittab 的 sysinit 触发)
PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
export PATH LD_LIBRARY_PATH
mount -a                                   # 按 /etc/fstab 挂载
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug  # 热插拔交给 mdev
mdev -s                                     # 扫描生成 /dev 节点
# /etc/fstab —— mount -a 时挂载的虚拟文件系统
#<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

Ubuntu 端的 NFS 服务要在 /etc/exports 里导出 rootfs 目录:/home/linux/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)。之后开发板用网络启动的 bootargs 就能直接把这套 rootfs 挂起来。

7. SD 卡分区布局

如果选择完全离线启动(不依赖网络),SD 卡上的布局是理解启动过程的关键:U-Boot 以裸数据形式写在 1KB 偏移的保留区,zImage 和 dtb 放在 FAT32 分区(U-Boot 的 fatload 只能读 FAT),rootfs 解压到 EXT3 分区,内核模块放到 /lib/modules。

SD 卡分区布局

8. 启动全流程串联

  1. BootROM:芯片 0 地址处的 96KB 固化代码。按拨码开关选启动设备,初始化基础时钟(ARM PLL 396M、System PLL 528M),从 SD/EMMC 的 1KB 偏移读 IVT/BootData/DCD,按 DCD 初步初始化 DDR,把 SPL 加载到片内 RAM(OCRAM 0x00900000)并跳转。
  2. SPL:片内 RAM 只有 128KB,装不下完整 U-Boot,所以分两阶段。SPL 关看门狗、把 CPU 主频设为 792MHz、初始化 PLL 和 IOMUXC、深度初始化并校准 DDR,然后把完整 U-Boot 读到 DDR 0x87800000,跳转。
  3. U-Boot 主程序:初始化外设、读环境变量、执行 bootcmd——把 zImage 加载到 0x80800000、dtb 加载到 0x83000000,bootz 启动内核。跳转前内核要求:MMU 关闭、D-cache 关闭、r0=0、r1=机器 ID、r2=dtb 地址。
  4. Kernel:入口 stext(arch/arm/kernel/head.S)→ __mmap_switched → start_kernel(init/main.c)→ rest_init 创建 pid 1 的 kernel_init 线程和 pid 2 的 kthreadd → kernel_init 里 do_basic_setup 完成驱动模型初始化 → 按 bootargs 挂载 rootfs → 执行 /sbin/init。
  5. rootfs 用户态:/sbin/init(busybox)读 /etc/inittab → sysinit 执行 /etc/init.d/rcS(设环境变量、mount -a 挂 proc/sysfs/tmpfs、mdev -s 生成 /dev 节点)→ askfirst 在串口启动 /bin/sh → 回车进入命令行。

9. 常见坑

  • NFS 挂载失败 kernel panic(VFS: Unable to mount root fs):检查 bootargs 里 root=/dev/nfs、nfsroot 路径是否和 Ubuntu 端 exports 一致;新版本 NFS 要在 nfsroot 里加 ,v3,proto=tcp。
  • rootfs 解压报错:busybox 生成的压缩包解压要加 sudo,否则权限不对。
  • 库文件是符号链接:ld-linux-armhf.so.3 等直接拷符号链接会指向不存在的路径,必须替换成真实文件。
  • 千万别写 SD/EMMC 前两个块:分区表在那里,mmc write 前两个块会把卡写废。

10. 实验现象

minicom 连上串口后上电:先看到 U-Boot 的版本信息和倒计时,倒计时结束自动执行 bootcmd,tftp 下载 zImage 和 dtb 后内核启动日志刷屏,最后停在 Please press Enter to activate this console——回车,出现 / # 提示符,ls 能看到 bin、etc、lib、usr 等目录,系统移植完成。

总结

问题答案
U-Boot 是什么?bootloader,初始化硬件并把内核引导起来
u-boot.imx 由什么组成?IVT + BootData + DCD + U-Boot 程序本体
bootcmd 和 bootargs 区别?bootcmd 是 U-Boot 自动执行的命令;bootargs 是传给内核的参数
内核编译产物?zImage + dtb(+ 各驱动 *.ko)
rootfs 第一个用户态进程?/sbin/init(busybox 提供),pid=1
Linux 启动顺序?BootROM → SPL → U-Boot → Kernel → init → rcS → shell

系统跑起来只是第一步。接下来的笔记进入驱动开发:先写一个最简单的内核模块,再把它升级成能控制 LED 的字符设备驱动。

继续阅读

嵌入式开发面试知识手册

嵌入式开发工程师面试知识手册 适用对象:零基础起步、目标是通过嵌入式软件/驱动/BSP 方向面试的读者 编写依据:赵哲简历全部知识点 + 智能车 AGV 控制平台完整项目代码(i.MX6ULL Linux 端 + STM32F407 固件)+ Linux 驱动练习代码包 + embedded-agent 嵌入式开发智能体仓库 + 真实面试手撕代码与追问记录 使用方法:每篇按"通俗解释 → 原理细节 → 命令/API → 代码实例 → 项目结合 → 记忆口诀 → 面试问答"组织;口诀用于快速记忆,问答用于自测。第9篇是讲项目的演练场,第10篇是手撕代码实战题库。 总目录 * 第1篇 C语言与数据结构 * 1.1 编译链接全过程 * 1.2 数据类型与内存

作者:azhezhezhe

正点原子 I.MX6ULL MINI 开发板学习笔记:Linux 驱动架构详解——编译、树内树外与运行时效果

这是 Linux 驱动小系列的总结篇。前三篇把 LED 驱动从内核模块写到 misc 设备,属于“会用”;这篇把背后的架构讲透:驱动到底怎么编译、内核树内和树外两种方式的区别、以及驱动里每个操作在系统跑起来之后到底产生了什么效果。内容整理自我的学习笔记(note.pdf)和实际工程 code/2026/20260803。 1. 驱动代码到底怎么编译的? 不管哪种方式,最终都靠内核的 kbuild 系统。核心就三个文件的关系: * Kconfig:定义 menuconfig 里的选项(如 CONFIG_MYMODULE),三态 y/m/n——y 编进内核,m 编成模块,n 不编; * .config:menuconfig 的选择结果,保存在源码顶层; * Makefile:

作者:azhezhezhe

正点原子 I.MX6ULL MINI 开发板学习笔记:misc 设备——字符设备驱动的快捷方式

上两篇分别用手动 mknod 和 class_create+device_create 注册字符设备,调用链一长串。这篇用 misc(混杂)设备 一步搞定。代码来自实际工程 code/2026/20260803/05_misc_led,已验证可用。 1. 完成什么实验? 用 misc_register 重写 LED 驱动:一步完成设备号分配、字符设备注册、/dev/myled0 节点创建,同时保留上一篇的 sysfs 属性 led_status。 2. 什么是 misc 设备? misc(混杂)设备是 Linux 给“不好归类的小字符设备”

作者:azhezhezhe