嵌入式Linux完整系统构建实战¶
项目概述¶
项目简介¶
本项目将带你从零开始构建一个完整的嵌入式Linux系统,涵盖从Bootloader到应用程序的全部环节。通过本项目,你将深入理解嵌入式Linux系统的各个组成部分,掌握系统构建、内核裁剪、驱动开发和应用部署的完整流程。
这是一个综合性的高级项目,适合已经具备Linux基础知识和嵌入式开发经验的工程师。项目将使用真实的硬件平台(如树莓派、BeagleBone或STM32MP1),构建一个可以实际运行的嵌入式Linux系统。
项目演示¶
完成后的系统将具备以下功能: - 快速启动(<5秒) - 完整的网络功能 - GPIO/I2C/SPI等外设支持 - Web管理界面 - 远程SSH访问 - 自定义应用程序
学习目标¶
完成本项目后,你将掌握:
- Bootloader开发:U-Boot的配置、编译和移植
- Linux内核定制:内核配置、裁剪和编译
- 设备树应用:设备树的编写和调试
- 根文件系统制作:使用BusyBox构建最小根文件系统
- 驱动开发:字符设备驱动和平台驱动的开发
- 系统集成:将各组件集成为完整系统
- 启动优化:系统启动流程优化
- 应用开发:嵌入式Linux应用程序开发
项目特点¶
- ✨ 完整性:覆盖嵌入式Linux系统的所有核心组件
- ✨ 实战性:基于真实硬件平台,可实际运行
- ✨ 系统性:从底层到应用的完整开发流程
- ✨ 可扩展:提供多个扩展方向和优化思路
- ✨ 工程化:遵循工业级开发规范和最佳实践
技术栈¶
硬件平台¶
- 推荐平台1:树莓派 4B(ARM Cortex-A72)
- 推荐平台2:BeagleBone Black(ARM Cortex-A8)
- 推荐平台3:STM32MP157(ARM Cortex-A7 + Cortex-M4)
- 备选平台:任何支持Linux的ARM开发板
软件技术¶
- Bootloader:U-Boot 2023.01+
- Linux内核:Linux 6.1 LTS
- 根文件系统:BusyBox 1.36+
- 交叉编译器:ARM GCC 11.3+
- 构建系统:Buildroot / Yocto(可选)
- 开发工具:VS Code, Vim, GDB
核心组件¶
- U-Boot(通用引导加载程序)
- Linux Kernel(操作系统内核)
- Device Tree(设备树)
- BusyBox(嵌入式工具集)
- Dropbear(轻量级SSH服务器)
- lighttpd(轻量级Web服务器)
硬件清单¶
必需硬件¶
| 名称 | 型号/规格 | 数量 | 用途 | 参考价格 | 购买建议 |
|---|---|---|---|---|---|
| 开发板 | 树莓派4B (4GB) | 1 | 主控平台 | ¥400 | 官方授权店 |
| SD卡 | 32GB Class 10 | 1 | 系统存储 | ¥50 | SanDisk/三星 |
| 电源适配器 | 5V 3A USB-C | 1 | 供电 | ¥30 | 官方电源 |
| 串口转USB | CP2102/CH340 | 1 | 调试串口 | ¥15 | 任意品牌 |
| 网线 | CAT5e | 1 | 网络连接 | ¥10 | 标准网线 |
| 杜邦线 | 公对母 40根 | 1 | GPIO连接 | ¥5 | 任意品牌 |
可选硬件¶
| 名称 | 型号/规格 | 数量 | 用途 | 参考价格 |
|---|---|---|---|---|
| HDMI显示器 | 任意 | 1 | 图形界面显示 | ¥500+ |
| USB键鼠 | 任意 | 1套 | 本地操作 | ¥50 |
| LED灯 | 5mm | 5 | GPIO测试 | ¥2 |
| 按键 | 轻触开关 | 3 | 输入测试 | ¥3 |
| 面包板 | 标准 | 1 | 电路搭建 | ¥10 |
| 逻辑分析仪 | USB逻辑分析仪 | 1 | 信号调试 | ¥100 |
总成本:约 ¥500-600(必需硬件)
硬件说明¶
为什么选择树莓派4B? - 性能强大(4核1.5GHz Cortex-A72) - 社区支持完善 - 文档资料丰富 - 外设接口齐全 - 价格适中
其他平台适配: 本项目的方法论适用于任何ARM Linux平台,只需根据具体硬件调整: - Bootloader配置 - 内核配置选项 - 设备树文件 - 驱动程序
软件要求¶
开发主机环境¶
推荐配置: - 操作系统:Ubuntu 22.04 LTS(推荐)或其他Linux发行版 - CPU:4核心以上 - 内存:8GB以上(编译内核需要) - 硬盘:50GB以上可用空间 - 网络:稳定的互联网连接
Windows用户: - 使用WSL2(Windows Subsystem for Linux) - 或使用虚拟机(VMware/VirtualBox)
必需软件工具¶
# 基础开发工具
sudo apt-get update
sudo apt-get install -y \
build-essential \
git \
vim \
wget \
curl \
bc \
bison \
flex \
libssl-dev \
libncurses5-dev \
device-tree-compiler \
u-boot-tools
# 交叉编译工具链
sudo apt-get install -y \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf
# 调试工具
sudo apt-get install -y \
minicom \
screen \
gdb-multiarch \
tftp-hpa \
tftpd-hpa \
nfs-kernel-server
# 镜像制作工具
sudo apt-get install -y \
dosfstools \
mtools \
parted \
kpartx
可选工具¶
# 代码编辑器
# VS Code (推荐)
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
sudo install -o root -g root -m 644 packages.microsoft.gpg /etc/apt/trusted.gpg.d/
sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
sudo apt-get update
sudo apt-get install code
# 性能分析工具
sudo apt-get install -y \
htop \
iotop \
sysstat
# 网络工具
sudo apt-get install -y \
wireshark \
tcpdump \
netcat
项目架构¶
系统架构图¶
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (Application Layer) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Web界面 │ │ GPIO控制 │ │ 数据采集 │ │ 自定义App│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ 用户空间工具 (User Space) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ BusyBox │ │ Dropbear │ │ lighttpd │ │ 库文件 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ Linux内核 (Kernel Space) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 进程管理 │ │ 内存管理 │ │ 文件系统 │ │ 网络协议栈│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ GPIO驱动 │ │ I2C驱动 │ │ SPI驱动 │ │ 自定义驱动│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ Bootloader (U-Boot) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 硬件初始化│ │ 加载内核 │ │ 设备树 │ │ 启动参数 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ 硬件层 (Hardware) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ CPU │ │ 内存 │ │ 存储 │ │ 外设 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
启动流程¶
上电 → ROM代码 → U-Boot SPL → U-Boot → Linux内核 → Init进程 → 应用程序
↓ ↓ ↓ ↓ ↓ ↓ ↓
硬件 加载SPL 初始化DDR 加载内核 初始化系统 启动服务 用户交互
目录结构¶
embedded-linux-project/
├── bootloader/ # Bootloader相关
│ ├── u-boot/ # U-Boot源码
│ └── configs/ # 配置文件
├── kernel/ # Linux内核相关
│ ├── linux/ # 内核源码
│ ├── configs/ # 内核配置
│ └── patches/ # 补丁文件
├── rootfs/ # 根文件系统
│ ├── busybox/ # BusyBox源码
│ ├── overlay/ # 自定义文件
│ └── scripts/ # 构建脚本
├── drivers/ # 自定义驱动
│ ├── gpio-led/ # GPIO LED驱动
│ └── platform-demo/ # 平台驱动示例
├── applications/ # 应用程序
│ ├── web-interface/ # Web管理界面
│ └── gpio-control/ # GPIO控制程序
├── devicetree/ # 设备树文件
│ └── custom.dts # 自定义设备树
├── tools/ # 工具脚本
│ ├── build.sh # 构建脚本
│ ├── flash.sh # 烧录脚本
│ └── debug.sh # 调试脚本
└── docs/ # 文档
├── hardware.md # 硬件说明
└── troubleshooting.md # 故障排除
实施步骤¶
阶段一:环境准备(预计2小时)¶
步骤1.1:搭建开发环境¶
目标:配置Linux开发主机,安装必要的工具链
操作步骤:
-
安装Ubuntu系统(如果还没有)
-
更新系统并安装基础工具
-
安装交叉编译工具链
-
安装内核编译依赖
-
安装调试工具
验证:
步骤1.2:创建项目目录结构¶
操作步骤:
# 创建项目根目录
mkdir -p ~/embedded-linux-project
cd ~/embedded-linux-project
# 创建子目录
mkdir -p bootloader kernel rootfs drivers applications devicetree tools docs
# 创建工作目录
mkdir -p build output downloads
echo "项目目录结构创建完成"
tree -L 1
步骤1.3:配置串口调试¶
目标:配置串口通信,用于调试和查看启动日志
硬件连接:
配置minicom:
# 配置串口
sudo minicom -s
# 在配置界面中设置:
# Serial Device: /dev/ttyUSB0
# Bps/Par/Bits: 115200 8N1
# Hardware Flow Control: No
# Software Flow Control: No
# 保存配置为默认值
# 选择 "Save setup as dfl"
# 选择 "Exit"
# 连接串口
sudo minicom
测试连接: - 给树莓派上电 - 应该能看到启动日志输出 - 按Ctrl+A然后按Z查看帮助 - 按Ctrl+A然后按X退出
阶段二:U-Boot编译与配置(预计3小时)¶
步骤2.1:下载U-Boot源码¶
操作步骤:
cd ~/embedded-linux-project/bootloader
# 下载U-Boot源码
git clone https://github.com/u-boot/u-boot.git
cd u-boot
# 切换到稳定版本
git checkout v2023.01
git checkout -b my-uboot
# 查看支持的开发板配置
ls configs/ | grep rpi
输出示例:
rpi_0_w_defconfig
rpi_2_defconfig
rpi_3_32b_defconfig
rpi_3_defconfig
rpi_4_32b_defconfig
rpi_4_defconfig
步骤2.2:配置和编译U-Boot¶
对于树莓派4(64位):
# 设置交叉编译器
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
# 使用默认配置
make rpi_4_defconfig
# 可选:自定义配置
make menuconfig
# 编译U-Boot
make -j$(nproc)
# 编译完成后会生成 u-boot.bin
ls -lh u-boot.bin
对于树莓派4(32位):
步骤2.3:自定义U-Boot配置¶
创建自定义配置文件:
推荐配置项:
-
启用网络支持(用于TFTP加载)
-
启用USB支持
-
配置环境变量
-
启用命令行工具
保存配置并重新编译:
# 保存配置
make savedefconfig
cp defconfig configs/rpi_4_custom_defconfig
# 重新编译
make clean
make rpi_4_custom_defconfig
make -j$(nproc)
步骤2.4:准备启动文件¶
创建boot分区文件:
cd ~/embedded-linux-project/output
mkdir -p boot
# 复制U-Boot文件
cp ~/embedded-linux-project/bootloader/u-boot/u-boot.bin boot/
# 对于树莓派,还需要固件文件
# 下载树莓派固件
git clone --depth=1 https://github.com/raspberrypi/firmware.git
cp firmware/boot/*.dat boot/
cp firmware/boot/*.elf boot/
cp firmware/boot/bootcode.bin boot/
# 创建config.txt(树莓派启动配置)
cat > boot/config.txt << 'EOF'
# 启用串口
enable_uart=1
# 使用U-Boot
kernel=u-boot.bin
# 64位模式
arm_64bit=1
EOF
阶段三:Linux内核编译(预计4小时)¶
步骤3.1:下载Linux内核源码¶
cd ~/embedded-linux-project/kernel
# 下载Linux内核
git clone --depth=1 --branch v6.1 https://github.com/torvalds/linux.git
cd linux
# 或者下载树莓派专用内核(推荐)
git clone --depth=1 --branch rpi-6.1.y https://github.com/raspberrypi/linux.git
cd linux
步骤3.2:配置内核¶
使用默认配置:
# 设置交叉编译环境
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
# 对于树莓派4
make bcm2711_defconfig
# 或者使用通用ARM64配置
make defconfig
自定义内核配置:
推荐配置项:
-
启用设备树支持(通常已启用)
-
启用GPIO支持
-
启用I2C支持
-
启用SPI支持
-
网络支持
-
文件系统支持
步骤3.3:编译内核¶
# 编译内核镜像
make -j$(nproc) Image
# 编译设备树
make -j$(nproc) dtbs
# 编译内核模块
make -j$(nproc) modules
# 查看编译结果
ls -lh arch/arm64/boot/Image
ls -lh arch/arm64/boot/dts/broadcom/*.dtb
编译时间:根据CPU性能,大约需要30-60分钟
步骤3.4:安装内核模块¶
# 创建模块安装目录
mkdir -p ~/embedded-linux-project/rootfs/modules
# 安装模块到指定目录
make INSTALL_MOD_PATH=~/embedded-linux-project/rootfs/modules modules_install
# 查看安装的模块
ls ~/embedded-linux-project/rootfs/modules/lib/modules/
步骤3.5:复制内核文件¶
cd ~/embedded-linux-project/output/boot
# 复制内核镜像
cp ~/embedded-linux-project/kernel/linux/arch/arm64/boot/Image ./kernel8.img
# 复制设备树文件
cp ~/embedded-linux-project/kernel/linux/arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb ./
# 创建cmdline.txt(内核启动参数)
cat > cmdline.txt << 'EOF'
console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait rw
EOF
阶段四:根文件系统制作(预计3小时)¶
步骤4.1:编译BusyBox¶
下载BusyBox:
cd ~/embedded-linux-project/rootfs
# 下载BusyBox
wget https://busybox.net/downloads/busybox-1.36.0.tar.bz2
tar xjf busybox-1.36.0.tar.bz2
cd busybox-1.36.0
配置BusyBox:
# 设置交叉编译环境
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
# 使用默认配置
make defconfig
# 自定义配置
make menuconfig
重要配置项:
-
静态编译(推荐)
-
安装路径
编译和安装:
步骤4.2:创建根文件系统目录结构¶
cd ~/embedded-linux-project/rootfs
mkdir -p rootfs
# 复制BusyBox文件
cp -a rootfs_base/* rootfs/
# 创建必要的目录
cd rootfs
mkdir -p dev proc sys tmp root home mnt etc/init.d var/log
# 创建设备节点
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3
sudo mknod dev/zero c 1 5
# 设置权限
chmod 1777 tmp
chmod 755 etc/init.d
步骤4.3:创建启动脚本¶
创建init脚本:
cat > etc/init.d/rcS << 'EOF'
#!/bin/sh
# 挂载proc和sys文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
# 挂载devtmpfs
mount -t devtmpfs none /dev
# 设置主机名
hostname embedded-linux
# 配置网络接口
ifconfig lo 127.0.0.1 up
ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up
route add default gw 192.168.1.1
# 启动系统日志
syslogd
klogd
echo "系统启动完成"
echo "欢迎使用嵌入式Linux系统"
EOF
chmod +x etc/init.d/rcS
创建inittab:
cat > etc/inittab << 'EOF'
# /etc/inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
EOF
步骤4.4:添加C库文件¶
复制C库(如果BusyBox不是静态编译):
# 查找工具链的库文件位置
TOOLCHAIN_PATH=$(dirname $(which aarch64-linux-gnu-gcc))/../aarch64-linux-gnu/libc
# 创建lib目录
mkdir -p lib lib64
# 复制必要的库文件
cp -a $TOOLCHAIN_PATH/lib/ld-linux-aarch64.so.1 lib64/
cp -a $TOOLCHAIN_PATH/lib/libc.so.6 lib/
cp -a $TOOLCHAIN_PATH/lib/libm.so.6 lib/
cp -a $TOOLCHAIN_PATH/lib/libresolv.so.2 lib/
cp -a $TOOLCHAIN_PATH/lib/libdl.so.2 lib/
cp -a $TOOLCHAIN_PATH/lib/libpthread.so.0 lib/
注意:如果BusyBox是静态编译的,可以跳过这一步。
步骤4.5:添加内核模块¶
# 复制内核模块
cp -a ~/embedded-linux-project/rootfs/modules/lib/modules lib/
# 创建模块依赖
depmod -a -b . $(ls lib/modules/)
步骤4.6:创建用户配置文件¶
创建passwd文件:
创建group文件:
创建fstab文件:
cat > etc/fstab << 'EOF'
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
devtmpfs /dev devtmpfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
EOF
阶段五:设备树定制(预计2小时)¶
步骤5.1:理解设备树¶
设备树的作用: - 描述硬件信息(CPU、内存、外设等) - 与内核代码分离,便于移植 - 支持动态配置硬件
设备树文件格式:
/dts-v1/;
/ {
compatible = "raspberrypi,4-model-b", "brcm,bcm2711";
model = "Raspberry Pi 4 Model B";
memory@0 {
device_type = "memory";
reg = <0x0 0x0 0x0 0x40000000>; // 1GB
};
leds {
compatible = "gpio-leds";
led0 {
label = "led0";
gpios = <&gpio 42 0>;
linux,default-trigger = "heartbeat";
};
};
};
步骤5.2:创建自定义设备树¶
cd ~/embedded-linux-project/devicetree
# 复制基础设备树
cp ~/embedded-linux-project/kernel/linux/arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dts custom-rpi4.dts
# 编辑设备树
vim custom-rpi4.dts
添加自定义GPIO LED:
/ {
// ... 现有内容 ...
custom_leds {
compatible = "gpio-leds";
pinctrl-names = "default";
user_led1 {
label = "user:led1";
gpios = <&gpio 17 0>; // GPIO17
linux,default-trigger = "none";
default-state = "off";
};
user_led2 {
label = "user:led2";
gpios = <&gpio 27 0>; // GPIO27
linux,default-trigger = "heartbeat";
};
};
custom_buttons {
compatible = "gpio-keys";
button1 {
label = "User Button 1";
gpios = <&gpio 22 1>; // GPIO22, active low
linux,code = <28>; // KEY_ENTER
debounce-interval = <50>;
};
};
};
步骤5.3:编译设备树¶
# 编译设备树
dtc -I dts -O dtb -o custom-rpi4.dtb custom-rpi4.dts
# 或者使用内核的设备树编译器
cpp -nostdinc -I ~/embedded-linux-project/kernel/linux/include \
-undef -x assembler-with-cpp custom-rpi4.dts | \
dtc -I dts -O dtb -o custom-rpi4.dtb
# 复制到boot目录
cp custom-rpi4.dtb ~/embedded-linux-project/output/boot/
步骤5.4:验证设备树¶
# 反编译查看
dtc -I dtb -O dts custom-rpi4.dtb -o custom-rpi4-check.dts
# 查看内容
less custom-rpi4-check.dts
阶段六:驱动开发(预计4小时)¶
步骤6.1:创建简单的字符设备驱动¶
创建驱动目录:
编写驱动代码(gpio_led.c):
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "gpio_led"
#define GPIO_LED 17 // GPIO17
static dev_t dev_num;
static struct cdev led_cdev;
static struct class *led_class;
static int led_open(struct inode *inode, struct file *file)
{
pr_info("GPIO LED device opened\n");
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
pr_info("GPIO LED device closed\n");
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf,
size_t len, loff_t *offset)
{
char kbuf[10];
if (len > sizeof(kbuf) - 1)
return -EINVAL;
if (copy_from_user(kbuf, buf, len))
return -EFAULT;
kbuf[len] = '\0';
if (kbuf[0] == '1') {
gpio_set_value(GPIO_LED, 1);
pr_info("LED ON\n");
} else if (kbuf[0] == '0') {
gpio_set_value(GPIO_LED, 0);
pr_info("LED OFF\n");
}
return len;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
};
static int __init led_init(void)
{
int ret;
// 申请GPIO
ret = gpio_request(GPIO_LED, "led_gpio");
if (ret) {
pr_err("Failed to request GPIO %d\n", GPIO_LED);
return ret;
}
// 设置GPIO为输出
gpio_direction_output(GPIO_LED, 0);
// 分配设备号
ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
if (ret < 0) {
pr_err("Failed to allocate device number\n");
gpio_free(GPIO_LED);
return ret;
}
// 初始化字符设备
cdev_init(&led_cdev, &led_fops);
ret = cdev_add(&led_cdev, dev_num, 1);
if (ret < 0) {
unregister_chrdev_region(dev_num, 1);
gpio_free(GPIO_LED);
return ret;
}
// 创建设备类
led_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(led_class)) {
cdev_del(&led_cdev);
unregister_chrdev_region(dev_num, 1);
gpio_free(GPIO_LED);
return PTR_ERR(led_class);
}
// 创建设备节点
device_create(led_class, NULL, dev_num, NULL, DEVICE_NAME);
pr_info("GPIO LED driver loaded\n");
return 0;
}
static void __exit led_exit(void)
{
device_destroy(led_class, dev_num);
class_destroy(led_class);
cdev_del(&led_cdev);
unregister_chrdev_region(dev_num, 1);
gpio_set_value(GPIO_LED, 0);
gpio_free(GPIO_LED);
pr_info("GPIO LED driver unloaded\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Linux Platform");
MODULE_DESCRIPTION("Simple GPIO LED Driver");
步骤6.2:创建Makefile¶
创建Makefile:
# Makefile for GPIO LED driver
obj-m := gpio_led.o
KERNEL_DIR := ~/embedded-linux-project/kernel/linux
ARCH := arm64
CROSS_COMPILE := aarch64-linux-gnu-
all:
make -C $(KERNEL_DIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) clean
install:
cp gpio_led.ko ~/embedded-linux-project/rootfs/rootfs/lib/modules/
步骤6.3:编译驱动¶
步骤6.4:创建测试应用¶
创建测试程序(test_led.c):
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd;
char *cmd;
if (argc != 2) {
printf("Usage: %s <on|off>\n", argv[0]);
return 1;
}
fd = open("/dev/gpio_led", O_WRONLY);
if (fd < 0) {
perror("Failed to open device");
return 1;
}
if (strcmp(argv[1], "on") == 0) {
write(fd, "1", 1);
printf("LED turned ON\n");
} else if (strcmp(argv[1], "off") == 0) {
write(fd, "0", 1);
printf("LED turned OFF\n");
} else {
printf("Invalid command. Use 'on' or 'off'\n");
}
close(fd);
return 0;
}
编译测试程序:
aarch64-linux-gnu-gcc -o test_led test_led.c
# 复制到根文件系统
cp test_led ~/embedded-linux-project/rootfs/rootfs/usr/bin/
阶段七:应用程序开发(预计3小时)¶
步骤7.1:创建GPIO控制应用¶
创建应用目录:
编写GPIO控制程序(gpio_app.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define GPIO_PATH "/sys/class/gpio"
int gpio_export(int gpio)
{
int fd;
char buf[64];
fd = open(GPIO_PATH "/export", O_WRONLY);
if (fd < 0) {
perror("Failed to open export");
return -1;
}
snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, strlen(buf));
close(fd);
return 0;
}
int gpio_unexport(int gpio)
{
int fd;
char buf[64];
fd = open(GPIO_PATH "/unexport", O_WRONLY);
if (fd < 0) {
perror("Failed to open unexport");
return -1;
}
snprintf(buf, sizeof(buf), "%d", gpio);
write(fd, buf, strlen(buf));
close(fd);
return 0;
}
int gpio_set_direction(int gpio, const char *direction)
{
int fd;
char path[128];
snprintf(path, sizeof(path), GPIO_PATH "/gpio%d/direction", gpio);
fd = open(path, O_WRONLY);
if (fd < 0) {
perror("Failed to open direction");
return -1;
}
write(fd, direction, strlen(direction));
close(fd);
return 0;
}
int gpio_set_value(int gpio, int value)
{
int fd;
char path[128];
char buf[2];
snprintf(path, sizeof(path), GPIO_PATH "/gpio%d/value", gpio);
fd = open(path, O_WRONLY);
if (fd < 0) {
perror("Failed to open value");
return -1;
}
snprintf(buf, sizeof(buf), "%d", value);
write(fd, buf, 1);
close(fd);
return 0;
}
int gpio_get_value(int gpio)
{
int fd;
char path[128];
char buf[2];
snprintf(path, sizeof(path), GPIO_PATH "/gpio%d/value", gpio);
fd = open(path, O_RDONLY);
if (fd < 0) {
perror("Failed to open value");
return -1;
}
read(fd, buf, 1);
close(fd);
return (buf[0] == '1') ? 1 : 0;
}
int main(int argc, char *argv[])
{
int gpio, value;
if (argc < 3) {
printf("Usage:\n");
printf(" %s <gpio> out <0|1> - Set GPIO output\n", argv[0]);
printf(" %s <gpio> in - Read GPIO input\n", argv[0]);
return 1;
}
gpio = atoi(argv[1]);
// 导出GPIO
gpio_export(gpio);
usleep(100000); // 等待sysfs创建文件
if (strcmp(argv[2], "out") == 0) {
if (argc < 4) {
printf("Missing value for output\n");
return 1;
}
value = atoi(argv[3]);
gpio_set_direction(gpio, "out");
gpio_set_value(gpio, value);
printf("GPIO%d set to %d\n", gpio, value);
} else if (strcmp(argv[2], "in") == 0) {
gpio_set_direction(gpio, "in");
value = gpio_get_value(gpio);
printf("GPIO%d value: %d\n", gpio, value);
}
// 取消导出
gpio_unexport(gpio);
return 0;
}
编译应用:
aarch64-linux-gnu-gcc -o gpio_app gpio_app.c
# 复制到根文件系统
cp gpio_app ~/embedded-linux-project/rootfs/rootfs/usr/bin/
步骤7.2:添加网络服务¶
安装Dropbear SSH服务器:
cd ~/embedded-linux-project/downloads
# 下载Dropbear
wget https://matt.ucc.asn.au/dropbear/releases/dropbear-2022.83.tar.bz2
tar xjf dropbear-2022.83.tar.bz2
cd dropbear-2022.83
# 配置
./configure --host=aarch64-linux-gnu --prefix=/usr \
--disable-zlib --disable-syslog
# 编译
make -j$(nproc)
# 安装到根文件系统
make DESTDIR=~/embedded-linux-project/rootfs/rootfs install
# 创建SSH密钥目录
mkdir -p ~/embedded-linux-project/rootfs/rootfs/etc/dropbear
配置Dropbear启动:
# 修改rcS脚本,添加Dropbear启动
cat >> ~/embedded-linux-project/rootfs/rootfs/etc/init.d/rcS << 'EOF'
# 启动SSH服务器
if [ -x /usr/sbin/dropbear ]; then
# 生成主机密钥(如果不存在)
if [ ! -f /etc/dropbear/dropbear_rsa_host_key ]; then
dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key
fi
dropbear
echo "SSH server started"
fi
EOF
阶段八:系统集成与烧录(预计2小时)¶
步骤8.1:创建SD卡镜像¶
准备SD卡:
# 查看SD卡设备(假设为/dev/sdb)
lsblk
# 警告:以下操作会清除SD卡所有数据!
# 确保选择正确的设备
# 卸载SD卡分区
sudo umount /dev/sdb*
# 创建分区表
sudo parted /dev/sdb mklabel msdos
# 创建boot分区(FAT32, 256MB)
sudo parted /dev/sdb mkpart primary fat32 1MiB 257MiB
sudo parted /dev/sdb set 1 boot on
# 创建rootfs分区(ext4, 剩余空间)
sudo parted /dev/sdb mkpart primary ext4 257MiB 100%
# 格式化分区
sudo mkfs.vfat -F 32 -n BOOT /dev/sdb1
sudo mkfs.ext4 -L rootfs /dev/sdb2
步骤8.2:复制文件到SD卡¶
# 挂载分区
sudo mkdir -p /mnt/boot /mnt/rootfs
sudo mount /dev/sdb1 /mnt/boot
sudo mount /dev/sdb2 /mnt/rootfs
# 复制boot文件
sudo cp -r ~/embedded-linux-project/output/boot/* /mnt/boot/
# 复制根文件系统
sudo cp -a ~/embedded-linux-project/rootfs/rootfs/* /mnt/rootfs/
# 同步并卸载
sync
sudo umount /mnt/boot
sudo umount /mnt/rootfs
echo "SD卡制作完成!"
步骤8.3:创建自动化脚本¶
创建构建脚本(build.sh):
cd ~/embedded-linux-project/tools
cat > build.sh << 'EOF'
#!/bin/bash
set -e
PROJECT_ROOT=~/embedded-linux-project
CROSS_COMPILE=aarch64-linux-gnu-
ARCH=arm64
echo "=== 开始构建嵌入式Linux系统 ==="
# 编译U-Boot
echo "1. 编译U-Boot..."
cd $PROJECT_ROOT/bootloader/u-boot
make CROSS_COMPILE=$CROSS_COMPILE ARCH=$ARCH -j$(nproc)
# 编译Linux内核
echo "2. 编译Linux内核..."
cd $PROJECT_ROOT/kernel/linux
make CROSS_COMPILE=$CROSS_COMPILE ARCH=$ARCH -j$(nproc) Image dtbs modules
# 安装内核模块
echo "3. 安装内核模块..."
make CROSS_COMPILE=$CROSS_COMPILE ARCH=$ARCH \
INSTALL_MOD_PATH=$PROJECT_ROOT/rootfs/modules modules_install
# 编译BusyBox
echo "4. 编译BusyBox..."
cd $PROJECT_ROOT/rootfs/busybox-1.36.0
make CROSS_COMPILE=$CROSS_COMPILE ARCH=$ARCH -j$(nproc)
make CROSS_COMPILE=$CROSS_COMPILE ARCH=$ARCH install
# 编译驱动
echo "5. 编译驱动..."
cd $PROJECT_ROOT/drivers/gpio-led
make
# 编译应用
echo "6. 编译应用..."
cd $PROJECT_ROOT/applications/gpio-control
$CROSS_COMPILE-gcc -o gpio_app gpio_app.c
echo "=== 构建完成 ==="
EOF
chmod +x build.sh
阶段九:测试与调试(预计2小时)¶
步骤9.1:首次启动测试¶
启动系统:
- 将SD卡插入树莓派
- 连接串口线到电脑
- 打开串口终端(minicom)
- 给树莓派上电
预期启动日志:
U-Boot 2023.01 (Jan 15 2024 - 10:00:00 +0800)
DRAM: 1 GiB
RPI 4 Model B (0xc03111)
MMC: mmc@7e340000: 0
Loading Environment from FAT... OK
In: serial
Out: serial
Err: serial
Net: eth0: ethernet@7d580000
Hit any key to stop autoboot: 0
...
[ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd083]
[ 0.000000] Linux version 6.1.0 (user@host) (gcc version 11.3.0)
...
[ 2.345678] VFS: Mounted root (ext4 filesystem) on device 179:2.
[ 2.456789] devtmpfs: mounted
[ 2.567890] Freeing unused kernel memory: 2048K
[ 2.678901] Run /sbin/init as init process
系统启动完成
欢迎使用嵌入式Linux系统
Please press Enter to activate this console.
/ #
步骤9.2:基本功能测试¶
测试命令行:
# 查看系统信息
uname -a
cat /proc/cpuinfo
cat /proc/meminfo
free -h
# 查看文件系统
df -h
mount
# 测试网络
ifconfig
ping -c 3 192.168.1.1
# 查看进程
ps aux
top
测试GPIO:
# 使用sysfs控制GPIO
echo 17 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio17/direction
echo 1 > /sys/class/gpio/gpio17/value # LED亮
echo 0 > /sys/class/gpio/gpio17/value # LED灭
echo 17 > /sys/class/gpio/unexport
# 使用自定义应用
gpio_app 17 out 1 # LED亮
gpio_app 17 out 0 # LED灭
测试驱动模块:
# 加载驱动
insmod /lib/modules/gpio_led.ko
# 查看设备
ls -l /dev/gpio_led
# 测试驱动
echo 1 > /dev/gpio_led # LED亮
echo 0 > /dev/gpio_led # LED灭
# 或使用测试程序
test_led on
test_led off
# 卸载驱动
rmmod gpio_led
步骤9.3:网络功能测试¶
测试SSH连接:
设置root密码(可选):
测试文件传输:
# 从开发主机传输文件到目标板
scp test_file.txt root@192.168.1.100:/tmp/
# 从目标板下载文件到开发主机
scp root@192.168.1.100:/var/log/messages ./
步骤9.4:性能测试¶
启动时间测试:
# 查看启动时间
systemd-analyze # 如果使用systemd
# 或手动计时从上电到登录提示符
# 查看内核启动时间
dmesg | grep "Freeing unused kernel memory"
内存使用测试:
CPU性能测试:
# 简单的CPU测试
time dd if=/dev/zero of=/dev/null bs=1M count=1000
# 查看CPU频率
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
常见问题与解决方案¶
问题1:系统无法启动¶
症状:上电后串口无输出或停在U-Boot
可能原因: 1. SD卡分区或格式化错误 2. 内核镜像或设备树文件错误 3. cmdline.txt参数错误
解决方案:
# 检查SD卡分区
sudo parted /dev/sdb print
# 检查boot分区内容
ls -lh /mnt/boot/
# 应该包含:kernel8.img, *.dtb, config.txt, cmdline.txt
# 检查cmdline.txt内容
cat /mnt/boot/cmdline.txt
# 确保root=/dev/mmcblk0p2指向正确的分区
# 重新格式化并复制文件
sudo mkfs.vfat -F 32 /dev/sdb1
sudo mkfs.ext4 /dev/sdb2
# 重新复制文件
问题2:内核启动后panic¶
症状:内核启动日志显示"Kernel panic - not syncing: VFS: Unable to mount root fs"
原因:无法挂载根文件系统
解决方案:
# 检查cmdline.txt中的root参数
# 确保指向正确的分区和文件系统类型
# 检查根文件系统内容
ls -lh /mnt/rootfs/
# 必须包含:bin, sbin, etc, lib等目录
# 检查init程序
ls -lh /mnt/rootfs/sbin/init
# 或
ls -lh /mnt/rootfs/linuxrc
# 确保文件系统类型正确
sudo file -s /dev/sdb2
问题3:网络不工作¶
症状:ifconfig显示eth0但无法ping通
解决方案:
# 检查网络配置
ifconfig eth0
# 手动配置IP
ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up
route add default gw 192.168.1.1
# 检查物理连接
ethtool eth0
# 检查内核是否支持网卡驱动
lsmod | grep bcm
dmesg | grep eth
问题4:GPIO控制不工作¶
症状:无法控制GPIO或读取GPIO值
解决方案:
# 检查GPIO是否被导出
ls /sys/class/gpio/
# 检查设备树是否正确加载
ls /proc/device-tree/
# 检查GPIO驱动是否加载
lsmod | grep gpio
dmesg | grep gpio
# 检查权限
ls -l /sys/class/gpio/export
chmod 666 /sys/class/gpio/export
问题5:驱动模块加载失败¶
症状:insmod报错"Invalid module format"
原因:内核版本不匹配
解决方案:
# 检查内核版本
uname -r
# 检查模块编译时的内核版本
modinfo gpio_led.ko | grep vermagic
# 确保使用相同的内核源码编译驱动
cd ~/embedded-linux-project/drivers/gpio-led
make clean
make KERNEL_DIR=~/embedded-linux-project/kernel/linux
优化与扩展¶
启动优化¶
减少启动时间¶
1. 内核优化:
# 禁用不需要的驱动和功能
make menuconfig
# 关键优化项:
# - 禁用不使用的文件系统
# - 禁用不使用的网络协议
# - 禁用调试选项(CONFIG_DEBUG_*)
# - 启用内核压缩(CONFIG_KERNEL_GZIP)
2. 根文件系统优化:
# 使用initramfs代替ext4
# 优点:启动更快,无需fsck
# 缺点:只读,大小受限
# 创建initramfs
cd ~/embedded-linux-project/rootfs/rootfs
find . | cpio -H newc -o | gzip > ../initramfs.cpio.gz
# 将initramfs编译进内核
# 在内核配置中:
# General setup → Initial RAM filesystem and RAM disk support
# 设置 Initramfs source file(s) 为 initramfs.cpio.gz 的路径
3. 启动脚本优化:
# 并行启动服务
# 修改rcS脚本,使用后台执行
cat > etc/init.d/rcS << 'EOF'
#!/bin/sh
# 快速挂载
mount -t proc none /proc &
mount -t sysfs none /sys &
mount -t devtmpfs none /dev &
wait
# 并行启动服务
syslogd &
klogd &
dropbear &
echo "System ready"
EOF
功能扩展¶
添加Web管理界面¶
安装lighttpd:
cd ~/embedded-linux-project/downloads
# 下载lighttpd
wget https://download.lighttpd.net/lighttpd/releases-1.4.x/lighttpd-1.4.67.tar.gz
tar xzf lighttpd-1.4.67.tar.gz
cd lighttpd-1.4.67
# 配置
./configure --host=aarch64-linux-gnu --prefix=/usr \
--without-bzip2 --without-pcre
# 编译安装
make -j$(nproc)
make DESTDIR=~/embedded-linux-project/rootfs/rootfs install
# 创建Web目录
mkdir -p ~/embedded-linux-project/rootfs/rootfs/var/www/html
创建简单的Web界面:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>嵌入式Linux控制面板</title>
<meta charset="UTF-8">
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.control-panel {
border: 1px solid #ccc;
padding: 20px;
margin: 20px 0;
border-radius: 5px;
}
button {
padding: 10px 20px;
margin: 5px;
font-size: 16px;
cursor: pointer;
}
.on { background-color: #4CAF50; color: white; }
.off { background-color: #f44336; color: white; }
</style>
</head>
<body>
<h1>嵌入式Linux控制面板</h1>
<div class="control-panel">
<h2>GPIO控制</h2>
<p>GPIO17 LED控制:</p>
<button class="on" onclick="controlGPIO(17, 1)">开启</button>
<button class="off" onclick="controlGPIO(17, 0)">关闭</button>
</div>
<div class="control-panel">
<h2>系统信息</h2>
<div id="system-info">加载中...</div>
<button onclick="updateSystemInfo()">刷新</button>
</div>
<script>
function controlGPIO(gpio, value) {
fetch('/cgi-bin/gpio.sh?gpio=' + gpio + '&value=' + value)
.then(response => response.text())
.then(data => alert(data))
.catch(error => alert('错误: ' + error));
}
function updateSystemInfo() {
fetch('/cgi-bin/sysinfo.sh')
.then(response => response.text())
.then(data => {
document.getElementById('system-info').innerHTML =
'<pre>' + data + '</pre>';
})
.catch(error => alert('错误: ' + error));
}
// 页面加载时更新系统信息
updateSystemInfo();
</script>
</body>
</html>
创建CGI脚本:
# gpio.sh
#!/bin/sh
echo "Content-type: text/plain"
echo ""
GPIO=$(echo $QUERY_STRING | sed 's/.*gpio=\([0-9]*\).*/\1/')
VALUE=$(echo $QUERY_STRING | sed 's/.*value=\([0-9]*\).*/\1/')
/usr/bin/gpio_app $GPIO out $VALUE
echo "GPIO$GPIO 设置为 $VALUE"
# sysinfo.sh
#!/bin/sh
echo "Content-type: text/plain"
echo ""
echo "=== 系统信息 ==="
uname -a
echo ""
echo "=== 内存使用 ==="
free -h
echo ""
echo "=== 磁盘使用 ==="
df -h
echo ""
echo "=== 运行时间 ==="
uptime
添加数据采集功能¶
创建数据采集程序(data_logger.c):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#define LOG_FILE "/var/log/sensor_data.log"
#define INTERVAL 60 // 采集间隔(秒)
float read_cpu_temp(void)
{
FILE *fp;
float temp = 0.0;
fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
if (fp) {
fscanf(fp, "%f", &temp);
fclose(fp);
temp /= 1000.0; // 转换为摄氏度
}
return temp;
}
void log_data(void)
{
FILE *fp;
time_t now;
char timestamp[64];
float cpu_temp;
// 获取当前时间
time(&now);
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S",
localtime(&now));
// 读取传感器数据
cpu_temp = read_cpu_temp();
// 写入日志
fp = fopen(LOG_FILE, "a");
if (fp) {
fprintf(fp, "%s,%.2f\n", timestamp, cpu_temp);
fclose(fp);
}
printf("[%s] CPU温度: %.2f°C\n", timestamp, cpu_temp);
}
int main(int argc, char *argv[])
{
int daemon_mode = 0;
if (argc > 1 && strcmp(argv[1], "-d") == 0) {
daemon_mode = 1;
// 后台运行
if (fork() != 0) {
exit(0);
}
setsid();
chdir("/");
close(0);
close(1);
close(2);
}
printf("数据采集程序启动\n");
printf("采集间隔: %d秒\n", INTERVAL);
printf("日志文件: %s\n", LOG_FILE);
while (1) {
log_data();
sleep(INTERVAL);
}
return 0;
}
编译并添加到启动脚本:
# 编译
aarch64-linux-gnu-gcc -o data_logger data_logger.c
cp data_logger ~/embedded-linux-project/rootfs/rootfs/usr/bin/
# 添加到启动脚本
cat >> ~/embedded-linux-project/rootfs/rootfs/etc/init.d/rcS << 'EOF'
# 启动数据采集
/usr/bin/data_logger -d &
EOF
安全加固¶
1. 禁用不必要的服务¶
2. 配置防火墙¶
# 添加iptables规则
cat >> etc/init.d/rcS << 'EOF'
# 配置防火墙
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# 允许本地回环
iptables -A INPUT -i lo -j ACCEPT
# 允许已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 允许SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# 允许HTTP
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
EOF
3. 设置文件权限¶
# 设置关键文件权限
chmod 600 etc/passwd
chmod 600 etc/shadow
chmod 755 bin/*
chmod 755 sbin/*
chmod 4755 bin/busybox # setuid
项目总结¶
完成检查清单¶
完成本项目后,你应该能够:
- 环境搭建:配置完整的嵌入式Linux开发环境
- U-Boot:编译、配置和移植U-Boot引导加载程序
- Linux内核:配置、裁剪和编译Linux内核
- 设备树:理解和编写设备树文件
- 根文件系统:使用BusyBox构建最小根文件系统
- 驱动开发:开发字符设备驱动和平台驱动
- 应用开发:开发用户空间应用程序
- 系统集成:将所有组件集成为完整系统
- 调试技能:使用串口、GDB等工具调试系统
- 性能优化:优化启动时间和系统性能
学到的关键技能¶
1. 系统构建能力 - 理解嵌入式Linux系统的完整架构 - 掌握从底层到应用的开发流程 - 能够独立构建定制化的嵌入式系统
2. 内核开发能力 - 内核配置和裁剪 - 设备树的使用 - 驱动程序开发 - 内核调试技巧
3. 系统集成能力 - 组件间的协调配置 - 启动流程的理解 - 问题定位和解决
4. 工程实践能力 - 版本控制和项目管理 - 自动化构建脚本 - 文档编写
性能指标¶
完成后的系统应达到以下性能指标:
| 指标 | 目标值 | 说明 |
|---|---|---|
| 启动时间 | < 5秒 | 从上电到登录提示符 |
| 内存占用 | < 50MB | 空闲时的内存使用 |
| 存储空间 | < 100MB | 根文件系统大小 |
| GPIO响应 | < 1ms | GPIO控制延迟 |
| 网络延迟 | < 10ms | 本地网络ping延迟 |
项目成果¶
1. 可运行的系统 - 完整的嵌入式Linux系统 - 支持网络、GPIO等基本功能 - 可通过SSH远程访问
2. 源代码和文档 - 所有源代码和配置文件 - 构建脚本和工具 - 详细的项目文档
3. 可扩展的平台 - 模块化的设计 - 易于添加新功能 - 可移植到其他硬件平台
进阶方向¶
方向1:实时性增强¶
目标:将系统改造为实时Linux系统
技术要点: - 应用PREEMPT_RT补丁 - 配置实时调度策略 - 优化中断延迟 - 实时性能测试
参考资源: - Real-Time Linux Wiki - PREEMPT_RT补丁文档
方向2:容器化支持¶
目标:在嵌入式系统上运行Docker容器
技术要点: - 启用内核容器支持(cgroups, namespaces) - 移植Docker或Podman - 创建轻量级容器镜像 - 容器编排
参考资源: - Balena - 嵌入式容器平台 - Docker for ARM文档
方向3:OTA升级系统¶
目标:实现安全的远程固件升级
技术要点: - A/B分区设计 - 升级包签名验证 - 断电保护机制 - 回滚功能
参考资源: - SWUpdate - 嵌入式系统升级框架 - Mender - OTA升级解决方案
方向4:图形界面¶
目标:添加图形用户界面
技术要点: - 移植Qt或GTK - 配置Wayland/X11 - 触摸屏支持 - GPU加速
参考资源: - Qt for Embedded Linux - Wayland文档
方向5:工业协议支持¶
目标:支持工业通信协议
技术要点: - Modbus RTU/TCP - CAN总线 - EtherCAT - OPC UA
参考资源: - libmodbus库 - SocketCAN框架 - open62541 (OPC UA)
参考资料¶
官方文档¶
- U-Boot
- U-Boot官方文档
-
Linux内核
- Linux内核文档
- Linux设备驱动程序
-
BusyBox
- BusyBox官网
- BusyBox文档
推荐书籍¶
- 《嵌入式Linux系统开发完全手册》 - 韦东山
- 适合入门,实战性强
-
配套视频教程
-
《Linux设备驱动程序》(第3版) - Jonathan Corbet等
- 驱动开发经典教材
-
深入讲解内核机制
-
《嵌入式Linux应用开发完全手册》 - 韦东山
- 应用层开发指南
-
大量实例代码
-
《Building Embedded Linux Systems》 - Karim Yaghmour
- 系统构建权威指南
-
英文原版
-
《Mastering Embedded Linux Programming》 - Chris Simmonds
- 现代嵌入式Linux开发
- 涵盖最新技术
在线资源¶
- 教程和博客
- Bootlin培训材料 - 免费的嵌入式Linux培训资料
- eLinux.org - 嵌入式Linux Wiki
-
Linux From Scratch - 从零构建Linux系统
-
视频课程
- 韦东山嵌入式Linux - 中文视频教程
- Coursera - Introduction to Embedded Systems
-
YouTube上的嵌入式Linux频道
-
社区和论坛
- Stack Overflow
- Linux内核邮件列表
- 树莓派论坛
工具和软件¶
- 开发工具
- Buildroot - 自动化构建系统
- Yocto Project - 嵌入式Linux发行版构建框架
-
OpenWrt - 路由器Linux发行版
-
调试工具
- OpenOCD - JTAG调试器
- GDB - GNU调试器
-
Trace Compass - 性能分析工具
-
分析工具
- perf - Linux性能分析工具
- ftrace - 内核跟踪工具
- SystemTap - 动态跟踪工具
附录¶
附录A:常用命令速查¶
U-Boot命令¶
# 查看环境变量
printenv
# 设置环境变量
setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p2"
# 保存环境变量
saveenv
# 从SD卡加载内核
fatload mmc 0:1 ${kernel_addr_r} kernel8.img
# 从网络加载(TFTP)
tftp ${kernel_addr_r} kernel8.img
# 启动内核
booti ${kernel_addr_r} - ${fdt_addr_r}
# 查看内存
md.b ${kernel_addr_r} 0x100
# 重启
reset
Linux内核命令¶
# 查看内核版本
uname -r
uname -a
# 查看内核日志
dmesg
dmesg | grep -i error
# 查看内核模块
lsmod
modinfo <module_name>
# 加载/卸载模块
insmod <module.ko>
rmmod <module_name>
# 查看内核参数
cat /proc/cmdline
# 查看设备树
ls /proc/device-tree/
文件系统命令¶
# 查看磁盘使用
df -h
# 查看文件系统类型
mount | column -t
# 挂载文件系统
mount -t ext4 /dev/mmcblk0p2 /mnt
# 创建文件系统
mkfs.ext4 /dev/mmcblk0p2
mkfs.vfat -F 32 /dev/mmcblk0p1
# 检查文件系统
fsck.ext4 /dev/mmcblk0p2
网络命令¶
# 配置IP地址
ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up
# 添加路由
route add default gw 192.168.1.1
# 查看网络连接
netstat -an
ss -tuln
# 测试连接
ping -c 3 192.168.1.1
wget http://example.com/file
# 查看网络接口
ip addr show
ip link show
附录B:GPIO引脚映射¶
树莓派4 GPIO引脚¶
3V3 (1) (2) 5V
GPIO2 (3) (4) 5V
GPIO3 (5) (6) GND
GPIO4 (7) (8) GPIO14 (TXD)
GND (9) (10) GPIO15 (RXD)
GPIO17 (11) (12) GPIO18
GPIO27 (13) (14) GND
GPIO22 (15) (16) GPIO23
3V3 (17) (18) GPIO24
GPIO10 (19) (20) GND
GPIO9 (21) (22) GPIO25
GPIO11 (23) (24) GPIO8
GND (25) (26) GPIO7
GPIO0 (27) (28) GPIO1
GPIO5 (29) (30) GND
GPIO6 (31) (32) GPIO12
GPIO13 (33) (34) GND
GPIO19 (35) (36) GPIO16
GPIO26 (37) (38) GPIO20
GND (39) (40) GPIO21
常用GPIO功能: - GPIO14/15: UART (串口) - GPIO⅔: I2C - GPIO10/9/11/8: SPI - GPIO18: PWM
附录C:故障排除流程图¶
系统无法启动
↓
串口有输出?
├─ 否 → 检查硬件连接
│ ├─ 电源是否正常?
│ ├─ 串口线是否正确?
│ └─ SD卡是否插好?
│
└─ 是 → 停在哪个阶段?
├─ U-Boot阶段
│ ├─ 检查boot分区文件
│ ├─ 检查config.txt
│ └─ 重新编译U-Boot
│
├─ 内核加载阶段
│ ├─ 检查kernel8.img
│ ├─ 检查设备树文件
│ └─ 检查cmdline.txt
│
└─ 根文件系统挂载阶段
├─ 检查rootfs分区
├─ 检查文件系统完整性
└─ 检查init程序
附录D:项目文件清单¶
最终项目结构¶
embedded-linux-project/
├── bootloader/
│ └── u-boot/
│ ├── u-boot.bin # U-Boot二进制文件
│ └── configs/ # 配置文件
│
├── kernel/
│ └── linux/
│ ├── arch/arm64/boot/
│ │ ├── Image # 内核镜像
│ │ └── dts/broadcom/
│ │ └── *.dtb # 设备树文件
│ └── .config # 内核配置
│
├── rootfs/
│ ├── busybox-1.36.0/ # BusyBox源码
│ ├── rootfs/ # 根文件系统
│ │ ├── bin/ # 可执行文件
│ │ ├── sbin/ # 系统可执行文件
│ │ ├── etc/ # 配置文件
│ │ │ ├── inittab
│ │ │ ├── passwd
│ │ │ ├── group
│ │ │ └── init.d/rcS
│ │ ├── lib/ # 库文件
│ │ ├── usr/ # 用户程序
│ │ ├── dev/ # 设备节点
│ │ ├── proc/ # proc文件系统
│ │ ├── sys/ # sysfs文件系统
│ │ └── tmp/ # 临时文件
│ └── modules/ # 内核模块
│
├── drivers/
│ └── gpio-led/
│ ├── gpio_led.c # 驱动源码
│ ├── Makefile # 编译脚本
│ └── gpio_led.ko # 驱动模块
│
├── applications/
│ └── gpio-control/
│ ├── gpio_app.c # 应用源码
│ ├── test_led.c # 测试程序
│ └── data_logger.c # 数据采集程序
│
├── devicetree/
│ ├── custom-rpi4.dts # 自定义设备树源码
│ └── custom-rpi4.dtb # 编译后的设备树
│
├── tools/
│ ├── build.sh # 构建脚本
│ ├── flash.sh # 烧录脚本
│ └── debug.sh # 调试脚本
│
├── output/
│ ├── boot/ # boot分区内容
│ │ ├── kernel8.img # 内核镜像
│ │ ├── *.dtb # 设备树
│ │ ├── config.txt # 树莓派配置
│ │ ├── cmdline.txt # 内核参数
│ │ └── u-boot.bin # U-Boot
│ └── rootfs/ # 根文件系统内容
│
└── docs/
├── hardware.md # 硬件说明
├── build-guide.md # 构建指南
└── troubleshooting.md # 故障排除
关键文件说明¶
| 文件 | 说明 | 大小(约) |
|---|---|---|
| kernel8.img | Linux内核镜像 | 10-20MB |
| bcm2711-rpi-4-b.dtb | 设备树文件 | 50KB |
| u-boot.bin | U-Boot引导程序 | 500KB |
| config.txt | 树莓派启动配置 | 1KB |
| cmdline.txt | 内核启动参数 | 1KB |
| rootfs/ | 根文件系统 | 50-100MB |
| gpio_led.ko | GPIO驱动模块 | 10KB |
附录E:性能基准测试¶
测试脚本¶
#!/bin/bash
# benchmark.sh - 系统性能测试脚本
echo "=== 嵌入式Linux系统性能测试 ==="
echo ""
# 1. 启动时间测试
echo "1. 启动时间分析"
dmesg | grep "Freeing unused kernel memory"
echo ""
# 2. CPU性能测试
echo "2. CPU性能测试"
time dd if=/dev/zero of=/dev/null bs=1M count=1000
echo ""
# 3. 内存性能测试
echo "3. 内存使用情况"
free -h
echo ""
# 4. 磁盘I/O测试
echo "4. 磁盘I/O性能"
dd if=/dev/zero of=/tmp/test bs=1M count=100 conv=fdatasync
rm /tmp/test
echo ""
# 5. 网络性能测试
echo "5. 网络延迟测试"
ping -c 10 192.168.1.1 | tail -1
echo ""
# 6. GPIO响应时间测试
echo "6. GPIO响应时间"
time (for i in $(seq 1 1000); do
echo 1 > /sys/class/gpio/gpio17/value
echo 0 > /sys/class/gpio/gpio17/value
done)
echo ""
echo "=== 测试完成 ==="
预期性能指标¶
| 测试项 | 预期值 | 说明 |
|---|---|---|
| 启动时间 | 3-5秒 | 从内核启动到登录提示符 |
| CPU性能 | 1GB/s | dd测试吞吐量 |
| 内存占用 | 30-50MB | 空闲时使用量 |
| 磁盘读取 | 20-40MB/s | SD卡读取速度 |
| 磁盘写入 | 10-20MB/s | SD卡写入速度 |
| 网络延迟 | <5ms | 本地网络ping |
| GPIO切换 | <1ms | 单次GPIO操作 |
结语¶
恭喜你完成了这个综合性的嵌入式Linux系统构建项目!通过这个项目,你已经掌握了从底层硬件到上层应用的完整开发流程。
你已经学会了¶
- 系统架构理解:深入理解嵌入式Linux系统的各个层次和组件
- Bootloader开发:U-Boot的配置、编译和定制
- 内核开发:Linux内核的配置、裁剪和编译
- 驱动开发:字符设备驱动和平台驱动的开发
- 应用开发:用户空间应用程序的开发
- 系统集成:将各个组件集成为完整可运行的系统
- 调试技能:使用各种工具进行系统调试和问题定位
- 优化能力:系统性能优化和启动时间优化
下一步建议¶
- 深入某个方向:选择一个感兴趣的方向(如实时性、容器化、图形界面等)深入研究
- 参与开源项目:为Linux内核或相关开源项目贡献代码
- 实际项目应用:将所学知识应用到实际的产品开发中
- 持续学习:关注Linux内核和嵌入式技术的最新发展
社区交流¶
欢迎在以下平台分享你的项目成果和经验:
- GitHub:开源你的项目代码
- 技术博客:撰写项目总结和技术文章
- 论坛社区:参与讨论,帮助其他学习者
反馈与改进¶
如果你在项目实施过程中遇到问题或有改进建议,欢迎反馈:
- 提交Issue到项目仓库
- 在社区论坛发帖讨论
- 联系课程作者
致谢¶
感谢以下开源项目和社区:
- Linux内核社区
- U-Boot项目
- BusyBox项目
- 树莓派基金会
- 所有为嵌入式Linux生态做出贡献的开发者
项目完成时间:约240分钟(4小时)
难度等级:高级
适合人群:有Linux基础和嵌入式开发经验的工程师
最后更新:2024-01-15
版本:1.0
练习题¶
完成项目后,尝试以下练习来巩固所学知识:
基础练习¶
- 修改启动参数:尝试修改cmdline.txt中的参数,观察系统行为变化
- 添加新的GPIO控制:在设备树中添加更多GPIO LED,并编写控制程序
- 优化启动时间:尝试将启动时间优化到3秒以内
- 添加新的系统服务:编写一个自启动的系统服务
进阶练习¶
- 实现看门狗功能:添加硬件看门狗支持,防止系统死机
- 添加RTC支持:集成实时时钟模块,实现时间保持
- 实现OTA升级:设计并实现一个简单的远程升级系统
- 添加传感器支持:集成温湿度传感器,实现数据采集和显示
高级练习¶
- 移植到其他平台:将系统移植到BeagleBone或其他ARM开发板
- 实现容器支持:在系统中添加Docker支持,运行容器化应用
- 添加图形界面:使用Qt或GTK实现一个简单的图形界面
- 实现工业协议:添加Modbus或CAN总线支持
祝你在嵌入式Linux开发的道路上越走越远! 🚀