Day60 ARM嵌入式Linux开发环境搭建与系统启动流程
day60 ARM嵌入式Linux开发环境搭建与系统启动流程
本文旨在系统性地整理ARM嵌入式Linux开发的核心知识,涵盖从交叉编译环境配置、网络文件系统(NFS)挂载、串口调试,到Bootloader引导、内核启动及根文件系统挂载的完整流程。
一、 开发环境基础:交叉编译与网络服务
在ARM嵌入式开发中,我们无法直接在目标板(如imx6ull)上编写和编译程序,必须在PC(通常是Ubuntu虚拟机)上完成开发,再将程序部署到目标板上运行。这种“在A平台开发,B平台运行”的模式称为交叉开发 (Cross Development)。
1.1 交叉编译器 (Cross Compiler)
由于PC的CPU架构(x86)与目标板的CPU架构(ARM)不同,它们执行的机器码(0/1组合)也完全不同。因此,不能使用PC上的gcc编译出能在ARM板上运行的程序。我们必须安装专门用于ARM架构的交叉编译工具链。
- 核心工具:
arm-linux-gnueabihf-gcc- 这是一个通称,具体版本可能为
arm-linux-gnueabihf-gcc-4.9.4。 - 它是整个编译工具链 (Toolchain) 的一部分,该工具链还包括链接器(
ld)、汇编器(as)等工具。 - 向下兼容: 较高版本的编译器通常能兼容低版本的功能,并可能支持更多新指令集。
- 安装方式: 通常通过解压预编译好的安装包,并将其路径添加到系统的环境变量
PATH中来完成。
- 这是一个通称,具体版本可能为
# 示例:在Ubuntu PC上使用交叉编译器编译一个简单的C程序
linux@ubuntu:~/nfs$ vim main.c # 编辑源代码
// main.c 文件内容
#include <stdio.h>
int main(int argc, const char *argv[])
{
printf("hello world\n"); // 打印 "hello world" 到控制台
return 0; // 程序正常退出
}
# 使用交叉编译器编译,生成名为 armhello 的可执行文件
linux@ubuntu:~/nfs$ arm-linux-gnueabihf-gcc main.c -o armhello
代码讲解:
vim main.c: 使用文本编辑器创建并编辑源文件main.c。arm-linux-gnueabihf-gcc main.c -o armhello: 调用ARM专用的交叉编译器arm-linux-gnueabihf-gcc来编译main.c源文件。-o armhello参数指定输出的可执行文件名为armhello。这个文件是为ARM架构编译的,只能在ARM设备上运行。
1.2 网络文件系统 (NFS) 配置
为了方便地将编译好的程序传输到开发板,我们不再依赖反复烧写SD卡,而是采用网络文件系统 (Network File System, NFS)。它允许开发板通过网络挂载PC上的一个目录,实现文件共享。
- 角色:
- NFS Server: 运行在Ubuntu PC上,负责共享指定目录。
- NFS Client: 运行在开发板上,负责挂载服务器共享的目录。
- 配置步骤:
- 配置NFS Server (Ubuntu PC):
- 编辑配置文件
/etc/exports,添加需要共享的目录及其权限。 - 重启NFS服务使配置生效。
- 编辑配置文件
# 查看 /etc/exports 配置文件
linux@ubuntu:~$ cat /etc/exports
/home/linux/nfs/rootfs (rw,sync,no_subtree_check,no_root_squash)
# 注释: 共享 /home/linux/nfs/rootfs 目录,权限为读写(rw),同步写入(sync),不检查子树(no_subtree_check),root用户不降权(no_root_squash)
# 创建并设置共享目录 (如果不存在)
linux@ubuntu:~$ mkdir -p /home/linux/nfs/rootfs
# 重启NFS服务 (根据实际情况选择命令)
linux@ubuntu:~$ sudo systemctl restart nfs-kernel-server
# 或者
linux@ubuntu:~$ sudo service nfs-kernel-server restart
- 配置NFS Client (开发板):
- 在开发板上执行挂载命令,将PC上的共享目录挂载到本地的一个目录下(如
/mnt)。
- 在开发板上执行挂载命令,将PC上的共享目录挂载到本地的一个目录下(如
# 在开发板终端执行挂载命令
root@ATK-IMX6U:~# mount -o nolock,nfsvers=3 192.168.1.3:/home/linux/nfs/rootfs /mnt
# 注释: 将IP为192.168.1.3的PC上的 /home/linux/nfs/rootfs 目录,以nolock和nfsvers=3选项挂载到开发板的/mnt目录下。
代码讲解:
mount: Linux系统用于挂载文件系统的命令。-o nolock,nfsvers=3: 挂载选项。nolock表示禁用文件锁,nfsvers=3指定使用NFS协议的第3版,以兼容较老的开发板。192.168.1.3:/home/linux/nfs/rootfs: 指定要挂载的远程目录。格式为服务器IP:服务器端路径。/mnt: 指定本地挂载点,即在开发板上访问共享文件的位置。
1.3 串口调试与网络连接
开发板没有键盘和显示器,我们需要通过串口 (Serial Port) 来与其通信和控制。同时,为了使用NFS和TFTP,PC与开发板之间必须建立网络连接。
- 串口调试助手:
- Windows: 使用
SecureCRTPortable.exe或minicom。 - Ubuntu: 使用
minicom。 - 配置参数: 波特率
115200,数据位8,无校验,停止位1。
- Windows: 使用
# 在Ubuntu PC上启动minicom (需先安装)
linux@ubuntu:~$ sudo minicom
# 注释: 启动minicom后,需要在设置中指定串口设备 (如 /dev/ttyUSB0) 和波特率 (115200)。
- 网络连接配置:
- Ubuntu PC: 需要配置静态IP地址,以便作为NFS/TFTP服务器。
- 开发板: 需要配置IP地址,以便与PC通信。
# Ubuntu PC 的网络配置文件 /etc/network/interfaces
auto lo
iface lo inet loopback
auto ens33
iface ens33 inet static
address 192.168.1.3
netmask 255.255.255.0
gateway 192.168.1.1
# 注释: 配置网卡ens33为静态IP 192.168.1.3,子网掩码255.255.255.0,网关192.168.1.1。
# 在开发板上配置IP地址
root@ATK-IMX6U:~# ifconfig eth0 192.168.1.100
# 注释: 设置开发板的eth0网卡IP为192.168.1.100。
# 在开发板上测试与PC的连通性
root@ATK-IMX6U:~# ping 192.168.1.3
PING 192.168.1.3 (192.168.1.3): 56 data bytes
64 bytes from 192.168.1.3: seq=0 ttl=64 time=0.782 ms
64 bytes from 192.168.1.3: seq=1 ttl=64 time=0.690 ms
--- 192.168.1.3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.690/0.736/0.782 ms
# 注释: 成功ping通PC,表明网络连接正常。
理想结果:
- 在Ubuntu PC上成功编译出
armhello。- 在开发板上成功挂载NFS目录,
/mnt下能看到PC上/home/linux/nfs/rootfs的所有文件。- 在开发板上执行
./armhello,控制台输出hello world。- PC与开发板之间可以互相ping通。
二、 系统启动流程详解
了解系统如何从上电开始一步步启动至最终运行用户程序,是进行底层开发的基础。整个过程主要分为三个阶段:Bootloader引导、内核启动、根文件系统挂载。
2.1 Bootloader (U-Boot) 引导阶段
当开发板上电时,CPU会从其内部ROM中执行一段固化的代码,这段代码会根据拨码开关 (boot0, boot1) 的状态决定从哪个设备(如SD卡、eMMC)加载Bootloader(通常是U-Boot)。
-
拨码开关作用: 决定启动设备。
- SD卡启动: D1=ON, D2=OFF, D3=OFF, D4=OFF, D5=OFF, D6=OFF, D7=ON, D8=OFF
-
Bootloader功能:
- 初始化部分内存和硬件。
- 根据启动模式,将自身前半部分代码拷贝到内存中运行。
- 在内存中初始化完整的内存空间,并将自身的后半部分代码拷贝到内存中继续执行。
- 最终,将Linux内核镜像 (
zImage) 和设备树文件 (*.dtb) 从存储设备(或网络)加载到内存的特定地址(通常是0x80800000和0x83000000),然后跳转到内核入口地址启动内核。
# U-Boot 启动日志示例
U-Boot 2016.03-g0ae7e33 (Aug 14 2022 - 19:42:45 +0800)
CPU: Freescale i.MX6ULL rev1.1 792 MHz (running at 396 MHz)
DRAM: 512 MiB
Net: FEC1
Hit any key to stop autoboot: 0
# 注释: 系统上电后,U-Boot打印硬件信息,并在倒计时结束后自动启动内核。
2.2 内核 (Kernel) 启动阶段
Bootloader将内核加载到内存后,会将控制权交给内核。内核开始执行其初始化过程:
- 初始化硬件: 初始化CPU、内存管理单元(MMU)、中断控制器等。
- 建立页表 (Page Table): MMU通过页表将程序使用的虚拟地址转换为物理地址。页表本身也需要占用一部分内存空间。
- 处理内核参数: 从Bootloader传递过来的启动参数(如根文件系统类型、位置、控制台设置等)会被解析。
- 挂载根文件系统 (Rootfs): 这是内核启动的关键一步。内核会根据参数去挂载指定的根文件系统。
- 启动init进程: 根文件系统挂载成功后,内核会启动第一个用户空间进程,通常是
/linuxrc或/sbin/init。
2.3 根文件系统 (Rootfs) 与应用层
根文件系统是按一定格式组织的一堆文件的集合,包含了操作系统运行所需的所有基本文件、库和应用程序。它是用户程序运行的基础环境。
- 挂载方式: 可以从本地存储(如eMMC分区)挂载,也可以通过网络(如NFS)挂载。
- 启动用户程序: init进程会根据配置文件(如
/etc/inittab)启动各种系统服务和最终的用户界面或应用程序。
三、 高级启动方式:TFTP + NFS
为了提高开发效率,避免每次修改内核或根文件系统后都重新烧写SD卡,我们可以采用更灵活的TFTP + NFS启动方式。
3.1 TFTP 服务配置
TFTP (Trivial File Transfer Protocol) 是一种简单的文件传输协议,常用于在网络中传输小文件,如内核镜像和设备树。
- 配置步骤:
- 在Ubuntu PC上安装并配置TFTP服务。
- 将内核镜像 (
zImage) 和设备树文件 (*.dtb) 放入TFTP服务的共享目录中。
# 查看TFTP服务配置文件 /etc/default/tftpd-hpa
linux@ubuntu:~$ cat /etc/default/tftpd-hpa
TFTP_DIRECTORY="/home/linux/tftpboot"
# 注释: TFTP服务的共享目录为 /home/linux/tftpboot。
# 将内核和设备树文件复制到TFTP目录
linux@ubuntu:~$ cp /path/to/zImage /home/linux/tftpboot/
linux@ubuntu:~$ cp /path/to/1.dtb /home/linux/tftpboot/
linux@ubuntu:~$ ls /home/linux/tftpboot/
1.dtb zImage
# 注释: 确保这两个文件存在于TFTP共享目录中。
3.2 启动流程 (手动)
- 进入U-Boot命令行: 在开发板上电倒计时期间按下回车键,阻止自动启动,进入U-Boot命令行。
- 配置网络: 设置开发板的IP地址、网关、DNS以及TFTP服务器的IP地址。
- 下载内核和设备树: 使用
tftp命令从PC下载zImage和*.dtb到内存。 - 设置启动参数: 使用
setenv命令设置内核启动所需的参数,特别是指定根文件系统为NFS。 - 启动内核: 使用
bootz命令启动内核。
# U-Boot 命令行操作示例
=> setenv ipaddr 192.168.1.100 # 设置开发板IP
=> setenv serverip 192.168.1.3 # 设置TFTP服务器IP (Ubuntu PC)
=> setenv gatewayip 192.168.1.1 # 设置网关
=> setenv netmask 255.255.255.0 # 设置子网掩码
=> saveenv # 保存环境变量
=> ping 192.168.1.3 # 测试网络连通性
=> tftp 0x80800000 zImage # 从TFTP下载zImage到内存地址0x80800000
=> tftp 0x83000000 1.dtb # 从TFTP下载1.dtb到内存地址0x83000000
=> setenv bootargs root=/dev/nfs nfsroot=192.168.1.3:/home/linux/nfs/imx6/rootfs,nfsvers=3 console=ttymxc0,115200 ip=192.168.1.100 init=/linuxrc
# 注释: 设置内核启动参数。root=/dev/nfs 指定根文件系统类型为NFS;nfsroot=... 指定NFS服务器地址和路径;console=... 指定控制台;ip=... 指定内核使用的IP;init=/linuxrc 指定init进程。
=> saveenv # 保存环境变量
=> bootz 0x80800000 - 0x83000000 # 启动内核,参数分别为内核地址、initrd地址(此处为空)、设备树地址。
理想结果:
- 开发板成功从TFTP下载内核和设备树。
- 内核成功启动,并挂载了位于Ubuntu PC上的NFS根文件系统。
- 系统启动完毕,可以正常登录并运行程序。
- 此时,任何对
/home/linux/nfs/imx6/rootfs目录的修改都会实时反映在开发板上,极大地方便了调试和开发。
四、 关键概念总结
- 交叉编译 (Cross Compilation): 在一个平台上编译另一个平台可运行的程序。
- 编译工具链 (Toolchain): 包含编译器、链接器、汇编器等一系列工具的集合。
- NFS (Network File System): 用于在网络上共享文件系统的协议。
- TFTP (Trivial File Transfer Protocol): 用于在网络上传输小文件的简单协议。
- Bootloader (U-Boot): 系统启动的第一阶段程序,负责初始化硬件和加载内核。
- 内核 (Kernel): 操作系统的核心,负责管理系统资源和硬件。
- 根文件系统 (Rootfs): 包含操作系统运行所需所有文件的顶层文件系统。
- 页表 (Page Table): 用于MMU进行虚拟地址到物理地址转换的数据结构。
- 地址相关/无关代码 (Position Dependent/Independent Code): 地址相关代码必须加载到固定地址才能正确运行;地址无关代码可以在任意地址加载运行。
- 相对跳转 (BL): 基于当前PC值进行跳转,适用于短距离跳转,代码具有位置无关性。
- 绝对跳转 (LDR PC, =address): 直接跳转到指定地址,适用于长距离跳转,代码具有地址相关性。
通过以上步骤,您已经掌握了ARM嵌入式Linux开发的基本环境搭建和系统启动原理,为后续的驱动开发和应用开发打下了坚实的基础。
更多推荐
所有评论(0)