嵌入式Linux系统的安装和配置

0 阅读14分钟

大家好,我是良许。

最近有不少读者朋友问我关于嵌入式 Linux 系统如何安装和配置的问题。

作为一名从事嵌入式开发多年的程序员,我深知这个话题对于初学者来说可能有些复杂,但只要掌握了正确的方法和步骤,其实并没有想象中那么困难。

今天我就结合自己这些年的实战经验,给大家详细讲解一下嵌入式 Linux 系统的安装和配置全过程。

1. 嵌入式 Linux 系统概述

1.1 什么是嵌入式 Linux 系统

嵌入式 Linux 系统是指运行在嵌入式设备上的 Linux 操作系统。

与我们平时在 PC 上使用的 Ubuntu、CentOS 等桌面 Linux 系统不同,嵌入式 Linux 系统通常针对特定硬件平台进行了裁剪和优化,去掉了很多不必要的功能模块,使其能够在资源受限的嵌入式设备上高效运行。

在我之前做汽车电子项目的时候,我们使用的就是基于 ARM 架构的嵌入式 Linux 系统。

整个系统的根文件系统只有几十 MB 大小,但却能够稳定运行各种车载应用程序。

这种精简高效的特性,正是嵌入式 Linux 系统的核心优势。

1.2 嵌入式 Linux 系统的组成

一个完整的嵌入式 Linux 系统通常由以下几个核心部分组成:

Bootloader(引导加载程序): 这是系统启动时最先运行的程序,负责初始化硬件、加载内核到内存并启动内核。

常见的 Bootloader 有 U-Boot、Barebox 等。

我在项目中最常用的就是 U-Boot,它功能强大且支持的硬件平台非常广泛。

Linux 内核: 这是整个系统的核心,负责管理硬件资源、提供系统调用接口、调度进程等。

嵌入式 Linux 内核通常会根据具体硬件平台进行裁剪和配置,只保留必要的功能模块。

根文件系统(Root Filesystem): 包含了系统运行所需的各种库文件、配置文件、应用程序等。

根文件系统可以存储在不同的介质上,比如 NOR Flash、NAND Flash、SD 卡、eMMC 等。

应用程序: 这是运行在用户空间的各种应用软件,实现具体的业务功能。

2. 开发环境准备

2.1 宿主机环境搭建

在开始嵌入式 Linux 系统的安装配置之前,我们首先需要准备一台运行 Linux 系统的宿主机作为开发环境。

我个人习惯使用 Ubuntu 作为开发主机,版本建议选择 18.04 LTS 或 20.04 LTS,这两个版本稳定性好且社区支持完善。

安装完 Ubuntu 系统后,需要安装一些必要的开发工具和依赖库:

sudo apt-get update
sudo apt-get install build-essential git vim
sudo apt-get install libncurses5-dev libssl-dev
sudo apt-get install bison flex bc
sudo apt-get install u-boot-tools device-tree-compiler
sudo apt-get install nfs-kernel-server tftp-hpa tftpd-hpa

这些工具包括编译工具链、版本控制工具、内核配置工具、设备树编译器以及网络调试工具等。

在我的实际工作中,这些工具几乎每天都会用到。

2.2 交叉编译工具链

由于嵌入式设备的处理器架构(如 ARM、MIPS 等)与开发主机(通常是 x86 架构)不同,我们需要使用交叉编译工具链来编译目标平台的代码。

交叉编译工具链可以从芯片厂商官网下载,也可以使用开源的工具链如 Linaro、Buildroot 等。

以 ARM 平台为例,下载并安装交叉编译工具链:

# 下载Linaro工具链
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
​
# 解压
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
​
# 配置环境变量
echo "export PATH=$PATH:/path/to/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin" >> ~/.bashrc
source ~/.bashrc
​
# 验证安装
arm-linux-gnueabihf-gcc --version

配置好环境变量后,我们就可以使用 arm-linux-gnueabihf-gcc 等命令来编译 ARM 平台的程序了。

3. Bootloader 的编译与烧写

3.1 U-Boot 源码获取与配置

U-Boot 是目前最流行的开源 Bootloader,支持众多硬件平台。

首先从官方仓库获取 U-Boot 源码:

git clone https://github.com/u-boot/u-boot.git
cd u-boot
git checkout v2021.01  # 选择一个稳定版本

获取源码后,需要根据目标硬件平台进行配置。

U-Boot 为不同的开发板提供了默认配置文件,存放在 configs 目录下。以 STM32MP157 开发板为例:

# 查看支持的配置
ls configs/ | grep stm32mp1
​
# 使用默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- stm32mp15_basic_defconfig
​
# 进行详细配置(可选)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

在 menuconfig 界面中,我们可以根据实际需求配置各种功能选项,比如网络支持、存储设备支持、环境变量存储位置等。

我在项目中通常会启用网络功能,这样可以通过 TFTP 方式快速下载和调试内核镜像。

3.2 编译 U-Boot

配置完成后,执行编译命令:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8

其中 -j8 表示使用 8 个线程并行编译,可以根据主机 CPU 核心数调整。

编译成功后,会在当前目录生成 u-boot.binu-boot.img 等文件,这就是我们需要的 Bootloader 镜像。

3.3 烧写 U-Boot 到目标板

将编译好的 U-Boot 烧写到目标板的方法有多种,常见的有:

通过 SD 卡烧写: 适用于支持 SD 卡启动的开发板。使用 dd 命令将 U-Boot 写入 SD 卡的特定位置:

sudo dd if=u-boot.bin of=/dev/sdb bs=1k seek=8 conv=fsync

通过 USB/串口烧写: 使用芯片厂商提供的烧写工具,通过 USB 或串口接口将 U-Boot 烧写到 Flash 中。

通过 JTAG 调试器烧写: 使用 J-Link、ST-Link 等调试器,通过 JTAG 接口烧写。

我在实际项目中,通常会先使用 SD 卡方式快速验证 U-Boot 功能,确认无误后再烧写到板载 Flash 中。

4. Linux 内核的编译与部署

4.1 内核源码获取

Linux 内核源码可以从 kernel.org 官网下载,也可以使用芯片厂商提供的定制版本。

对于 STM32MP1 系列,ST 官方提供了专门优化的内核:

git clone https://github.com/STMicroelectronics/linux.git
cd linux
git checkout v5.10-stm32mp  # 选择对应版本

使用厂商提供的内核版本的好处是,很多硬件驱动已经集成好了,可以大大减少我们的开发工作量。

4.2 内核配置

内核配置是一个非常重要的步骤,直接影响到系统的功能和性能。

首先使用默认配置:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- multi_v7_defconfig

然后进入 menuconfig 进行详细配置:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

在配置界面中,有几个关键选项需要注意:

文件系统支持: 根据实际使用的文件系统类型,启用相应的支持,如 ext4、jffs2、ubifs 等。

我在项目中经常使用 ext4 文件系统,它稳定可靠且性能良好。

网络协议栈: 如果设备需要联网,必须启用 TCP/IP 协议栈以及相应的网络设备驱动。

设备驱动: 根据硬件配置,启用对应的驱动程序,如 I2C、SPI、UART、USB、以太网等。

这一步非常关键,如果驱动没有正确配置,相应的硬件就无法正常工作。

电源管理: 对于电池供电的设备,需要配置好电源管理选项,以降低功耗。

4.3 设备树配置

设备树(Device Tree)是描述硬件配置的数据结构,Linux 内核通过解析设备树来识别硬件资源。

设备树源文件(dts)位于 arch/arm/boot/dts 目录下。

以 STM32MP157 为例,可能需要修改或创建自己的设备树文件:

// stm32mp157c-myboard.dts
/dts-v1/;
#include "stm32mp157.dtsi"
#include "stm32mp15xc.dtsi"
#include "stm32mp15-pinctrl.dtsi"

/ {
    model = "My Custom Board";
    compatible = "st,stm32mp157c-myboard""st,stm32mp157";

    chosen {
        stdout-path = "serial0:115200n8";
    };

    memory@c0000000 {
        device_type = "memory";
        reg = <0xc0000000 0x20000000>; // 512MB DDR
    };
};

&uart4 {
    pinctrl-names = "default";
    pinctrl-0 = <&uart4_pins_a>;
    status = "okay";
};

&ethernet0 {
    status = "okay";
    pinctrl-0 = <&ethernet0_rgmii_pins_a>;
    pinctrl-names = "default";
    phy-mode = "rgmii";
};

设备树的配置需要根据实际硬件电路来编写,包括内存大小、外设接口、引脚复用等信息。

在我做汽车电子项目时,设备树的调试占据了不少时间,因为任何一个小错误都可能导致系统无法启动或外设无法工作。

4.4 编译内核和设备树

配置完成后,开始编译:

# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage -j8

# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

# 编译内核模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j8

编译成功后,会生成以下重要文件:

  • arch/arm/boot/zImage: 压缩的内核镜像
  • arch/arm/boot/dts/*.dtb: 编译后的设备树二进制文件
  • 各个目录下的 *.ko 文件: 内核模块

5. 根文件系统的制作

5.1 使用 Buildroot 构建根文件系统

Buildroot 是一个自动化构建工具,可以帮助我们快速生成定制的根文件系统。

首先获取 Buildroot 源码:

git clone https://github.com/buildroot/buildroot.git
cd buildroot
git checkout 2021.02  # 选择稳定版本

配置 Buildroot:

make menuconfig

在配置界面中,需要设置以下关键选项:

Target options: 选择目标架构(如 ARM)、CPU 类型、浮点运算方式等。

Toolchain: 选择使用外部工具链还是让 Buildroot 自动构建工具链。

我通常选择使用外部工具链,因为这样编译速度更快。

System configuration: 配置系统主机名、root 密码、启动脚本等。

Filesystem images: 选择生成的文件系统格式,如 ext4、tar、cpio 等。

Package Selection: 选择需要包含的软件包,如 busybox、网络工具、开发库等。

配置完成后执行编译:

make -j8

编译过程可能需要较长时间,因为 Buildroot 会下载并编译所有选中的软件包。

编译完成后,生成的根文件系统位于 output/images 目录下。

5.2 手动构建根文件系统

除了使用 Buildroot,我们也可以手动构建根文件系统。

这种方式更加灵活,但需要更多的工作量。基本步骤如下:

# 创建根文件系统目录结构
mkdir rootfs
cd rootfs
mkdir -p bin sbin lib usr/bin usr/sbin usr/lib etc dev proc sys tmp var home root

# 安装busybox
cd /path/to/busybox
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- CONFIG_PREFIX=/path/to/rootfs install

# 复制必要的库文件
cd /path/to/toolchain/arm-linux-gnueabihf/libc/lib
cp -a *.so* /path/to/rootfs/lib/

# 创建设备节点
cd /path/to/rootfs/dev
sudo mknod console c 5 1
sudo mknod null c 1 3

5.3 配置根文件系统

创建基本的配置文件,如 /etc/inittab/etc/fstab 等:

# /etc/inittab
::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L ttySTM0 115200 vt100
::shutdown:/bin/umount -a -r
::restart:/sbin/init

# /etc/fstab
proc    /proc   proc    defaults    0   0
sysfs   /sys    sysfs   defaults    0   0
tmpfs   /tmp    tmpfs   defaults    0   0

# /etc/init.d/rcS
#!/bin/sh
mount -a
echo "System initialization complete"

这些配置文件定义了系统启动流程、文件系统挂载等基本行为。

6. 系统烧写与启动

6.1 制作 SD 卡启动盘

将编译好的各个组件烧写到 SD 卡中:

# 分区(假设SD卡为/dev/sdb)
sudo fdisk /dev/sdb
# 创建两个分区:
# 第一个分区: 100MB, FAT32, 用于存放内核和设备树
# 第二个分区: 剩余空间, ext4, 用于根文件系统

# 格式化分区
sudo mkfs.vfat /dev/sdb1
sudo mkfs.ext4 /dev/sdb2

# 挂载分区
sudo mount /dev/sdb1 /mnt/boot
sudo mount /dev/sdb2 /mnt/rootfs

# 复制文件
sudo cp zImage /mnt/boot/
sudo cp stm32mp157c-myboard.dtb /mnt/boot/
sudo cp -a rootfs/* /mnt/rootfs/

# 卸载
sudo umount /mnt/boot
sudo umount /mnt/rootfs

6.2 配置 U-Boot 启动参数

将 SD 卡插入开发板,上电启动,进入 U-Boot 命令行,配置启动参数:

# 设置bootargs
setenv bootargs 'console=ttySTM0,115200 root=/dev/mmcblk0p2 rootwait rw'

# 设置bootcmd
setenv bootcmd 'fatload mmc 0:1 0xc2000000 zImage; fatload mmc 0:1 0xc4000000 stm32mp157c-myboard.dtb; bootz 0xc2000000 - 0xc4000000'

# 保存环境变量
saveenv

# 启动系统
boot

如果一切配置正确,系统应该能够正常启动,最终进入登录界面。

7. 系统优化与调试

7.1 启动时间优化

在很多应用场景中,快速启动是一个重要需求。

可以通过以下方法优化启动时间:

精简内核: 去掉不必要的驱动和功能模块,减小内核体积。

并行启动: 修改启动脚本,让多个服务并行启动而不是串行启动。

使用静态链接: 减少动态库加载时间。

延迟加载: 将非关键服务延迟到系统启动后再加载。

我在一个车载项目中,通过这些优化手段,将系统启动时间从 15 秒缩短到了 5 秒以内。

7.2 调试技巧

在开发过程中,难免会遇到各种问题。以下是一些常用的调试技巧:

串口调试: 这是最基本也是最重要的调试手段。

通过串口可以看到内核启动日志、应用程序输出等信息。

网络调试: 配置好网络后,可以通过 SSH 远程登录系统,使用 gdb 进行远程调试。

内核日志: 使用 dmesg 命令查看内核日志,分析驱动加载、硬件初始化等信息。

procfs 和 sysfs: 通过 /proc/sys 目录可以查看系统运行状态、硬件信息等。

# 查看CPU信息
cat /proc/cpuinfo

# 查看内存使用情况
cat /proc/meminfo

# 查看设备树信息
ls /proc/device-tree/

# 查看GPIO状态
cat /sys/class/gpio/export

7.3 性能优化

对于性能要求较高的应用,可以从以下几个方面进行优化:

编译器优化: 使用 -O2-O3 优化级别编译应用程序。

实时性优化: 如果需要实时性,可以使用 RT-Preempt 补丁,或者配置内核为低延迟模式。

内存管理: 合理配置内存分配器,使用内存池等技术减少内存碎片。

I/O 优化: 使用 DMA 传输、异步 I/O 等技术提高数据传输效率。

在我之前的项目中,通过这些优化,将 CAN 总线的响应延迟从 10ms 降低到了 2ms 以内,大大提升了系统的实时性能。

8. 总结

嵌入式 Linux 系统的安装和配置是一个系统工程,涉及到 Bootloader、内核、根文件系统等多个组成部分。

虽然过程看起来复杂,但只要按照正确的步骤一步步来,并在实践中不断积累经验,就能够熟练掌握。

我从最初接触嵌入式 Linux 到现在,也是经历了无数次的失败和调试,才逐渐摸索出了一套高效的开发流程。

希望这篇文章能够帮助大家少走一些弯路,更快地掌握嵌入式 Linux 系统的开发技能。

在实际项目中,我们还需要根据具体的硬件平台和应用需求,对系统进行深度定制和优化。

这需要我们不断学习新的技术,积累更多的实战经验。

嵌入式 Linux 的世界博大精深,值得我们持续探索和钻研。

更多编程学习资源