4、嵌入式Linux系统:交叉编译环境搭建、内核裁剪与编译、根文件系统制作、设备驱动移植

好,咱们进入第四章。这一章是实战的硬骨头,也是LTE网关方案里最核心的底层环节。说白了,你要让一个没有操作系统的裸板跑起Linux,就得过这四关:交叉编译环境、内核裁剪、根文件系统、驱动移植。

我刚开始做嵌入式那会儿,觉得交叉编译就是个“在电脑上编译,在板子上跑”的事儿。结果第一次自己搭环境,光一个工具链就折腾了两天。嗯,这里面的坑,我一个个给你说清楚。

本章核心目标:在x86主机上为ARM架构的LTE网关主控芯片(比如海思Hi3520或NXP i.MX6系列)构建完整的嵌入式Linux系统,并成功移植LTE模块驱动。

4.1 交叉编译环境搭建

为什么需要交叉编译?你想想看,你的开发电脑是x86架构,但网关板子是ARM架构。x86编译出来的二进制,ARM跑不了。所以我们需要在x86上安装一个“能生成ARM机器码的编译器”,这就是交叉编译工具链。

4.1.1 工具链的选择

我个人习惯用Linaro提供的GCC工具链,稳定且社区活跃。当然,芯片原厂也会提供自己的工具链,比如海思的HiTools。这里我推荐两种方式:

方式 工具链 适用场景 我的建议
官方预编译 gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf 快速开发,通用ARM Cortex-A系列 新手首选,省去编译工具链的麻烦
Buildroot 自动构建工具链+根文件系统 需要定制化根文件系统时 我项目中常用,一体化解决
Yocto 全功能嵌入式Linux发行版 大型项目,需要包管理 学习曲线陡,但功能强大

下载Linaro工具链后,解压到 /opt 目录,然后配置环境变量:

# 解压工具链
tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/

# 配置环境变量(建议写入 ~/.bashrc)
export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# 验证是否安装成功
arm-linux-gnueabihf-gcc --version

小技巧:我曾经在配置环境变量时,忘记把 export 写入 ~/.bashrc,结果每次新开终端都要重新设置。建议你一次性配好,省得后面抓狂。

4.1.2 测试交叉编译

写一个简单的Hello World测试一下:

// hello.c
#include <stdio.h>

int main() {
    printf("Hello from LTE Gateway!\n");
    return 0;
}

// 交叉编译
arm-linux-gnueabihf-gcc -o hello hello.c

// 查看生成的文件类型
file hello
// 输出:hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)

看到 ARM 字样,说明工具链工作正常。这个文件在x86电脑上是跑不了的,得放到板子上才能执行。

4.2 内核裁剪与编译

LTE网关不需要桌面系统,也不需要复杂的网络协议栈。内核裁剪的目的就是“去掉不需要的,留下刚需的”。我见过有人直接把全功能内核编译出来,结果镜像30多MB,Flash根本放不下。

4.2.1 获取内核源码

从芯片厂商获取内核源码包,或者从kernel.org下载主线内核。以海思Hi3520为例:

# 解压内核源码
tar -xvf linux-4.9.37.tar.gz
cd linux-4.9.37

# 配置内核(使用厂商提供的默认配置)
make ARCH=arm hi3520d_defconfig

# 或者手动配置
make ARCH=arm menuconfig

4.2.2 裁剪要点

进入 menuconfig 后,我一般按以下步骤裁剪:

  • 关闭无用架构支持:只保留ARM架构,其他全部取消。
  • 精简驱动:只保留串口、网卡、I2C、SPI、Flash控制器、USB Host等LTE模块需要的驱动。
  • 关闭调试选项:Kernel hacking 里的调试选项全部关掉,能省不少空间。
  • 网络协议栈:保留TCP/IP、UDP、PPP(如果LTE模块用PPP拨号),关闭IPv6(如果不需要)。

注意:我曾经在裁剪时,不小心把 EXT4 文件系统支持给关了,结果根文件系统挂载不上,板子直接kernel panic。裁剪时一定要确认你用的根文件系统类型对应的驱动是开启的。

4.2.3 编译内核

# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 uImage

# 编译设备树(Device Tree)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- hi3520d.dtb

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

编译完成后,在 arch/arm/boot/ 目录下会生成 uImage 和 hi3520d.dtb。这两个文件就是我们要烧录到板子上的核心文件。

4.3 根文件系统制作

内核启动后,需要挂载根文件系统。根文件系统里包含init进程、库文件、配置文件、应用程序等。说白了,它就是Linux的“家”。

4.3.1 使用BusyBox构建最小根文件系统

BusyBox是嵌入式Linux的瑞士军刀,一个二进制文件集成了上百个Linux命令。我项目中90%的场景都用它。

# 下载BusyBox
wget https://busybox.net/downloads/busybox-1.33.2.tar.bz2
tar -xvf busybox-1.33.2.tar.bz2
cd busybox-1.33.2

# 配置BusyBox
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
# 选择:Settings -> Build static binary (no shared libs)
# 静态编译,避免动态库依赖问题

# 编译并安装
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- install CONFIG_PREFIX=/home/user/rootfs

安装完成后,/home/user/rootfs 目录下会生成 bin、sbin、usr 等目录。但这还不够,还需要手动创建一些关键目录和文件:

cd /home/user/rootfs
mkdir -p dev etc lib proc sys tmp var mnt

# 创建必要的设备节点
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3

# 创建init脚本(/etc/inittab)
cat > etc/inittab << "EOF"
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
EOF

# 创建rcS启动脚本
mkdir -p etc/init.d
cat > etc/init.d/rcS << "EOF"
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
/sbin/mdev -s
EOF
chmod +x etc/init.d/rcS

4.3.2 制作文件系统镜像

根文件系统制作完成后,需要打包成镜像文件,烧录到Flash中。常用的格式有 jffs2、yaffs2、squashfs。我推荐使用 squashfs,只读压缩,节省空间且稳定。

# 安装squashfs工具
sudo apt-get install squashfs-tools

# 制作squashfs镜像
mksquashfs /home/user/rootfs rootfs.squashfs -comp xz -b 131072

避坑指南:我曾经用 jffs2 格式,结果板子掉电后文件系统损坏,导致无法启动。后来换成 squashfs + 可写分区(overlay)的方案,再也没出过问题。

4.4 设备驱动移植

LTE网关最关键的驱动就是LTE模块的驱动。以华为ME909s-821模块为例,它通过USB接口连接主控,需要移植USB串口驱动和PPP协议驱动。

4.4.1 确认内核驱动支持

在 menuconfig 中确认以下选项已开启:

  • Device Drivers -> USB support -> USB Serial Converter support
  • Device Drivers -> USB support -> USB Serial -> USB driver for GSM and CDMA modems(选项名可能为 USB_ACM 或 option)
  • Device Drivers -> Network device support -> PPP (point-to-point protocol) support

4.4.2 编写驱动测试程序

驱动移植完成后,写一个简单的测试程序验证LTE模块是否正常工作:

// lte_test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

int main() {
    int fd;
    char buffer[256];
    
    // 打开LTE模块对应的串口设备
    fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
    if (fd < 0) {
        perror("Failed to open LTE module");
        return -1;
    }
    
    // 发送AT指令测试
    write(fd, "AT\r\n", 4);
    sleep(1);
    read(fd, buffer, sizeof(buffer));
    printf("Response: %s\n", buffer);
    
    // 查询信号强度
    write(fd, "AT+CSQ\r\n", 8);
    sleep(1);
    read(fd, buffer, sizeof(buffer));
    printf("Signal: %s\n", buffer);
    
    close(fd);
    return 0;
}

// 交叉编译
arm-linux-gnueabihf-gcc -o lte_test lte_test.c

4.4.3 驱动自动加载配置

为了让LTE模块在系统启动时自动识别,需要在根文件系统中添加udev规则或mdev配置:

# 在/etc/mdev.conf中添加
ttyUSB0 0:0 666
ttyUSB1 0:0 666
ttyUSB2 0:0 666

注意:不同LTE模块的USB串口数量不同。ME909s会虚拟出3个串口:ttyUSB0用于AT指令,ttyUSB1用于PPP拨号,ttyUSB2用于日志。我曾经因为只配置了ttyUSB0,结果PPP拨号一直失败,排查了半天才发现是ttyUSB1没权限。

4.5 本章知识体系

下面这张图总结了嵌入式Linux系统构建的完整流程,你可以对照着梳理自己的思路:

嵌入式Linux系统构建流程 交叉编译环境 内核裁剪与编译 根文件系统制作 设备驱动移植(LTE模块驱动) 烧录镜像 → 启动验证 关键说明: • 交叉编译环境是基础,必须先搭建好才能进行后续步骤。 • 内核裁剪直接影响镜像大小和启动速度,LTE网关建议控制在5MB以内。 • 根文件系统建议使用squashfs+overlay方案,兼顾稳定性和可写需求。 • 驱动移植后务必用AT指令测试,确认LTE模块通信正常。

4.6 常见问题与避坑

最后,我把这些年遇到的典型问题列出来,你遇到了可以直接对照排查:

问题现象 可能原因 解决方案
编译内核时报错 arm-linux-gnueabihf-gcc: command not found 环境变量未配置或路径错误 检查 ~/.bashrc 中的 PATH 设置,重新 source
内核启动后 Kernel panic - not syncing: VFS: Unable to mount root fs 根文件系统驱动未编译进内核 确认 menuconfig 中开启了对应的文件系统支持(如SquashFS)
LTE模块AT指令无响应 串口权限不足或驱动未加载 检查 /dev/ttyUSB* 权限,确认内核模块已加载
PPP拨号失败,日志显示 Timeout APN配置错误或SIM卡未注册网络 用AT指令 AT+COPS? 检查网络注册状态

嗯,这一章的内容就到这里。嵌入式Linux系统的构建是个熟能生巧的过程,第一次可能磕磕绊绊,但多来几次就顺手了。记住,交叉编译环境是地基,内核是骨架,根文件系统是血肉,驱动是神经——四者缺一不可。