文件系统与根文件系统制作:构建嵌入式Linux的基础¶
学习目标¶
完成本教程后,你将能够:
- 理解Linux文件系统的层次结构和标准
- 掌握常见文件系统类型及其特点
- 使用BusyBox构建最小根文件系统
- 配置系统启动脚本和初始化流程
- 制作initramfs和initrd镜像
- 优化根文件系统大小和性能
- 调试根文件系统启动问题
- 定制适合目标平台的文件系统
前置要求¶
在开始学习之前,建议你具备:
知识要求: - 熟悉Linux基本命令和操作 - 了解嵌入式Linux系统架构 - 掌握Linux内核编译流程 - 理解设备树和驱动基础
技能要求: - 能够使用交叉编译工具链 - 会使用Makefile构建项目 - 熟悉Shell脚本编程 - 了解文件权限和用户管理
硬件和软件准备: - Linux开发主机(Ubuntu 20.04+推荐) - 交叉编译工具链 - 目标开发板或QEMU模拟器 - 至少2GB可用磁盘空间 - 串口调试工具
Linux文件系统概述¶
文件系统的作用¶
文件系统是操作系统用于组织和管理文件的方法,它提供了:
┌─────────────────────────────────────────┐
│ 应用程序 │
│ (读写文件、创建目录等) │
└─────────────────────────────────────────┘
↕
┌─────────────────────────────────────────┐
│ 虚拟文件系统 (VFS) │
│ 统一的文件操作接口 │
└─────────────────────────────────────────┘
↕
┌─────────────────────────────────────────┐
│ 具体文件系统 │
│ ext4, jffs2, ubifs, tmpfs等 │
└─────────────────────────────────────────┘
↕
┌─────────────────────────────────────────┐
│ 块设备层/MTD层 │
│ 硬盘、Flash、SD卡等 │
└─────────────────────────────────────────┘
主要功能: - 文件的存储和组织 - 目录结构管理 - 文件权限和访问控制 - 文件元数据管理 - 空间分配和回收
文件系统层次标准 (FHS)¶
Linux遵循文件系统层次标准(Filesystem Hierarchy Standard):
/ # 根目录
├── bin/ # 基本命令二进制文件
├── boot/ # 启动文件(内核、initrd)
├── dev/ # 设备文件
├── etc/ # 系统配置文件
├── home/ # 用户主目录
├── lib/ # 共享库文件
├── media/ # 可移动媒体挂载点
├── mnt/ # 临时挂载点
├── opt/ # 可选应用程序
├── proc/ # 进程信息(虚拟文件系统)
├── root/ # root用户主目录
├── run/ # 运行时数据
├── sbin/ # 系统管理命令
├── srv/ # 服务数据
├── sys/ # 系统信息(虚拟文件系统)
├── tmp/ # 临时文件
├── usr/ # 用户程序和数据
│ ├── bin/ # 用户命令
│ ├── lib/ # 用户库文件
│ ├── local/ # 本地安装的程序
│ └── sbin/ # 用户系统管理命令
└── var/ # 可变数据
├── log/ # 日志文件
├── tmp/ # 临时文件
└── run/ # 运行时数据
目录说明:
| 目录 | 用途 | 是否必需 |
|---|---|---|
| /bin | 基本命令(ls, cp, cat等) | 必需 |
| /sbin | 系统管理命令(init, mount等) | 必需 |
| /etc | 配置文件 | 必需 |
| /lib | 共享库和内核模块 | 必需 |
| /dev | 设备文件 | 必需 |
| /proc | 进程和内核信息 | 推荐 |
| /sys | 设备和驱动信息 | 推荐 |
| /tmp | 临时文件 | 推荐 |
| /var | 可变数据 | 可选 |
| /usr | 用户程序 | 可选 |
常见文件系统类型¶
磁盘文件系统¶
1. ext2/ext3/ext4
- 特点:Linux标准文件系统,功能完善
- 适用:SD卡、硬盘、U盘
- 优势:成熟稳定,工具完善
- 劣势:不适合Flash存储
# 创建ext4文件系统
mkfs.ext4 /dev/mmcblk0p2
# 挂载
mount -t ext4 /dev/mmcblk0p2 /mnt
# 查看信息
tune2fs -l /dev/mmcblk0p2
2. FAT/FAT32/exFAT
- 特点:简单通用,跨平台兼容
- 适用:SD卡、U盘、数据交换
- 优势:兼容性好,实现简单
- 劣势:功能有限,不支持权限
Flash文件系统¶
1. JFFS2 (Journaling Flash File System 2)
- 特点:专为NOR Flash设计
- 适用:NOR Flash存储
- 优势:支持压缩,掉电保护
- 劣势:挂载慢,不适合大容量
# 创建JFFS2镜像
mkfs.jffs2 -r rootfs/ -o rootfs.jffs2 -e 0x20000 -p
# 挂载(需要MTD设备)
mount -t jffs2 /dev/mtdblock2 /mnt
2. UBIFS (Unsorted Block Image File System)
- 特点:专为NAND Flash设计
- 适用:NAND Flash存储
- 优势:性能好,适合大容量
- 劣势:配置复杂
# 创建UBIFS镜像
mkfs.ubifs -r rootfs/ -m 2048 -e 126976 -c 2048 -o rootfs.ubifs
# 创建UBI镜像
ubinize -o rootfs.ubi -m 2048 -p 128KiB ubinize.cfg
# 挂载
ubiattach /dev/ubi_ctrl -m 2
mount -t ubifs ubi0:rootfs /mnt
3. YAFFS2 (Yet Another Flash File System 2)
- 特点:轻量级Flash文件系统
- 适用:NAND Flash
- 优势:简单高效
- 劣势:不在主线内核中
内存文件系统¶
1. tmpfs
- 特点:基于内存和swap的文件系统
- 适用:临时文件、/tmp目录
- 优势:速度快,动态调整大小
- 劣势:掉电丢失
2. ramfs
- 特点:纯内存文件系统
- 适用:initramfs
- 优势:简单快速
- 劣势:不限制大小,可能耗尽内存
虚拟文件系统¶
1. proc
- 特点:提供进程和内核信息
- 挂载点:/proc
- 用途:查看系统状态
2. sysfs
- 特点:提供设备和驱动信息
- 挂载点:/sys
- 用途:设备管理和配置
3. devtmpfs
- 特点:自动管理设备节点
- 挂载点:/dev
- 用途:设备文件管理
文件系统对比¶
| 文件系统 | 存储介质 | 压缩 | 掉电保护 | 性能 | 适用场景 |
|---|---|---|---|---|---|
| ext4 | 磁盘/SD卡 | 否 | 是 | 高 | 通用存储 |
| FAT32 | 磁盘/SD卡 | 否 | 否 | 中 | 数据交换 |
| JFFS2 | NOR Flash | 是 | 是 | 低 | 小容量Flash |
| UBIFS | NAND Flash | 是 | 是 | 高 | 大容量Flash |
| tmpfs | 内存 | 否 | 否 | 极高 | 临时文件 |
| squashfs | 只读 | 是 | - | 高 | 只读根文件系统 |
BusyBox工具集¶
BusyBox简介¶
BusyBox是一个集成了数百个常用Linux命令的单一可执行文件,被称为"嵌入式Linux的瑞士军刀"。
特点: - 体积小:通常只有1-2MB - 功能全:包含300+常用命令 - 可配置:可以选择需要的功能 - 易移植:支持多种架构
包含的工具:
文件操作:ls, cp, mv, rm, mkdir, cat, more, less
文本处理:grep, sed, awk, cut, sort, uniq
网络工具:ifconfig, route, ping, telnet, wget, ftpget
系统管理:init, mount, umount, ps, top, kill
Shell:ash (轻量级shell)
下载和配置BusyBox¶
1. 下载源码
# 下载BusyBox
cd ~/embedded
wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
# 解压
tar -xjf busybox-1.35.0.tar.bz2
cd busybox-1.35.0
2. 配置BusyBox
重要配置选项:
Settings --->
[*] Build static binary (no shared libs)
(/opt/arm-linux-gnueabihf) Cross compiler prefix
Coreutils --->
[*] ls
[*] cp
[*] mv
[*] rm
[*] mkdir
Editors --->
[*] vi
Init Utilities --->
[*] init
[*] Support reading an inittab file
Networking Utilities --->
[*] ifconfig
[*] ping
[*] wget
Shells --->
[*] ash
[*] bash-compatible extensions
3. 编译BusyBox
# 设置交叉编译工具链
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
# 编译
make -j$(nproc)
# 安装到指定目录
make CONFIG_PREFIX=/path/to/rootfs install
编译结果:
# 查看生成的文件
ls -lh busybox
# -rwxr-xr-x 1 user user 1.2M Jan 15 10:00 busybox
# 查看包含的命令
./busybox --list
BusyBox安装结构¶
安装后的目录结构:
rootfs/
├── bin/
│ ├── busybox
│ ├── sh -> busybox
│ ├── ls -> busybox
│ ├── cp -> busybox
│ └── ... (其他命令的符号链接)
├── sbin/
│ ├── init -> ../bin/busybox
│ ├── mount -> ../bin/busybox
│ └── ... (系统命令的符号链接)
└── usr/
├── bin/
│ └── ... (用户命令的符号链接)
└── sbin/
└── ... (用户系统命令的符号链接)
工作原理: - BusyBox是单一可执行文件 - 其他命令都是指向BusyBox的符号链接 - BusyBox根据调用名称执行相应功能
# 验证符号链接
ls -l bin/ls
# lrwxrwxrwx 1 root root 7 Jan 15 10:00 bin/ls -> busybox
# 直接调用
./bin/busybox ls
# 通过符号链接调用
./bin/ls
制作根文件系统¶
创建目录结构¶
1. 创建基本目录
# 设置根文件系统路径
export ROOTFS=~/embedded/rootfs
mkdir -p $ROOTFS
# 创建标准目录
cd $ROOTFS
mkdir -p bin sbin etc dev proc sys tmp root home
mkdir -p usr/bin usr/sbin usr/lib
mkdir -p var/log var/run
mkdir -p lib/modules
# 设置权限
chmod 1777 tmp
chmod 700 root
2. 安装BusyBox
# 编译并安装BusyBox
cd ~/embedded/busybox-1.35.0
make CONFIG_PREFIX=$ROOTFS install
# 验证安装
ls -l $ROOTFS/bin/busybox
3. 复制库文件
# 设置工具链路径
export TOOLCHAIN=/opt/arm-linux-gnueabihf
# 复制C库
cp -a $TOOLCHAIN/libc/lib/*.so* $ROOTFS/lib/
# 或只复制必需的库
arm-linux-gnueabihf-readelf -d $ROOTFS/bin/busybox | grep NEEDED
# 根据输出复制所需的库文件
# 复制动态链接器
cp -a $TOOLCHAIN/libc/lib/ld-linux-armhf.so.3 $ROOTFS/lib/
# 创建符号链接
cd $ROOTFS/lib
ln -s ld-linux-armhf.so.3 ld-linux.so.3
库文件精简:
# 只复制必需的库(推荐)
mkdir -p $ROOTFS/lib
# 复制基本C库
cp $TOOLCHAIN/libc/lib/libc.so.6 $ROOTFS/lib/
cp $TOOLCHAIN/libc/lib/libm.so.6 $ROOTFS/lib/
cp $TOOLCHAIN/libc/lib/libdl.so.2 $ROOTFS/lib/
cp $TOOLCHAIN/libc/lib/libpthread.so.0 $ROOTFS/lib/
cp $TOOLCHAIN/libc/lib/libresolv.so.2 $ROOTFS/lib/
cp $TOOLCHAIN/libc/lib/ld-linux-armhf.so.3 $ROOTFS/lib/
# 去除调试符号(减小体积)
arm-linux-gnueabihf-strip $ROOTFS/lib/*.so*
创建设备文件¶
方法1:手动创建(静态设备节点)
cd $ROOTFS/dev
# 创建控制台设备
sudo mknod -m 666 console c 5 1
sudo mknod -m 666 null c 1 3
sudo mknod -m 666 zero c 1 5
# 创建串口设备
sudo mknod -m 666 ttyS0 c 4 64
sudo mknod -m 666 ttyS1 c 4 65
# 创建块设备
sudo mknod -m 660 mmcblk0 b 179 0
sudo mknod -m 660 mmcblk0p1 b 179 1
sudo mknod -m 660 mmcblk0p2 b 179 2
# 创建随机数设备
sudo mknod -m 644 random c 1 8
sudo mknod -m 644 urandom c 1 9
# 创建内存设备
sudo mknod -m 640 mem c 1 1
sudo mknod -m 666 kmem c 1 2
方法2:使用devtmpfs(推荐)
在内核配置中启用devtmpfs:
Device Drivers --->
Generic Driver Options --->
[*] Maintain a devtmpfs filesystem to mount at /dev
[*] Automount devtmpfs at /dev, after the kernel mounted the rootfs
在启动脚本中挂载:
方法3:使用mdev(BusyBox)
配置系统文件¶
1. 创建/etc/inittab
cat > $ROOTFS/etc/inittab << 'EOF'
# /etc/inittab
# 系统初始化
::sysinit:/etc/init.d/rcS
# 启动shell(串口控制台)
console::askfirst:-/bin/sh
# 系统重启和关机
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
EOF
2. 创建启动脚本/etc/init.d/rcS
mkdir -p $ROOTFS/etc/init.d
cat > $ROOTFS/etc/init.d/rcS << 'EOF'
#!/bin/sh
# 挂载虚拟文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs tmpfs /tmp
# 创建必要的目录
mkdir -p /dev/pts /dev/shm
mount -t devpts devpts /dev/pts
mount -t tmpfs tmpfs /dev/shm
# 设置主机名
hostname -F /etc/hostname
# 配置网络接口
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 "System initialization complete"
echo "Welcome to Embedded Linux!"
EOF
# 设置执行权限
chmod +x $ROOTFS/etc/init.d/rcS
3. 创建/etc/fstab
cat > $ROOTFS/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
tmpfs /dev/shm tmpfs defaults 0 0
devpts /dev/pts devpts defaults 0 0
EOF
4. 创建/etc/hostname
5. 创建/etc/passwd和/etc/group
# 创建用户文件
cat > $ROOTFS/etc/passwd << 'EOF'
root:x:0:0:root:/root:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/false
bin:x:2:2:bin:/bin:/bin/false
sys:x:3:3:sys:/dev:/bin/false
nobody:x:65534:65534:nobody:/nonexistent:/bin/false
EOF
# 创建组文件
cat > $ROOTFS/etc/group << 'EOF'
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:
tty:x:5:
disk:x:6:
lp:x:7:
mail:x:8:
news:x:9:
uucp:x:10:
man:x:12:
proxy:x:13:
kmem:x:15:
dialout:x:20:
fax:x:21:
voice:x:22:
cdrom:x:24:
floppy:x:25:
tape:x:26:
sudo:x:27:
audio:x:29:
dip:x:30:
www-data:x:33:
backup:x:34:
operator:x:37:
list:x:38:
irc:x:39:
src:x:40:
gnats:x:41:
shadow:x:42:
utmp:x:43:
video:x:44:
sasl:x:45:
plugdev:x:46:
staff:x:50:
games:x:60:
users:x:100:
nogroup:x:65534:
EOF
6. 创建/etc/profile
cat > $ROOTFS/etc/profile << 'EOF'
# /etc/profile
# 设置PATH
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
# 设置提示符
export PS1='[\u@\h \W]\$ '
# 设置语言
export LANG=C
# 设置编辑器
export EDITOR=vi
# 显示欢迎信息
echo "Welcome to Embedded Linux System"
echo "Kernel: $(uname -r)"
echo "Hostname: $(hostname)"
EOF
添加自定义应用程序¶
1. 编译应用程序
# 示例:编译一个简单的应用
cat > hello.c << 'EOF'
#include <stdio.h>
int main(void) {
printf("Hello from Embedded Linux!\n");
return 0;
}
EOF
# 交叉编译
arm-linux-gnueabihf-gcc -o hello hello.c -static
# 或动态链接
arm-linux-gnueabihf-gcc -o hello hello.c
# 去除调试符号
arm-linux-gnueabihf-strip hello
2. 复制到根文件系统
# 复制应用程序
cp hello $ROOTFS/usr/bin/
# 设置权限
chmod +x $ROOTFS/usr/bin/hello
# 如果是动态链接,复制依赖库
arm-linux-gnueabihf-readelf -d hello | grep NEEDED
# 根据输出复制所需库文件
3. 添加启动脚本
# 创建应用启动脚本
cat > $ROOTFS/etc/init.d/S99myapp << 'EOF'
#!/bin/sh
case "$1" in
start)
echo "Starting myapp..."
/usr/bin/hello &
;;
stop)
echo "Stopping myapp..."
killall hello
;;
restart)
$0 stop
$0 start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
exit 0
EOF
# 设置执行权限
chmod +x $ROOTFS/etc/init.d/S99myapp
# 在rcS中调用
echo "/etc/init.d/S99myapp start" >> $ROOTFS/etc/init.d/rcS
制作文件系统镜像¶
方法1:制作ext4镜像
# 创建空镜像文件(100MB)
dd if=/dev/zero of=rootfs.ext4 bs=1M count=100
# 格式化为ext4
mkfs.ext4 rootfs.ext4
# 挂载镜像
mkdir -p /mnt/rootfs
sudo mount -o loop rootfs.ext4 /mnt/rootfs
# 复制文件系统内容
sudo cp -a $ROOTFS/* /mnt/rootfs/
# 卸载
sudo umount /mnt/rootfs
# 压缩镜像(可选)
gzip rootfs.ext4
方法2:制作JFFS2镜像
# 安装工具
sudo apt install mtd-utils
# 创建JFFS2镜像
# -r: 根文件系统目录
# -o: 输出文件
# -e: 擦除块大小(根据Flash规格)
# -p: 填充大小
mkfs.jffs2 -r $ROOTFS -o rootfs.jffs2 -e 0x20000 -p
# 查看镜像信息
ls -lh rootfs.jffs2
方法3:制作UBIFS镜像
# 创建UBIFS镜像
# -r: 根文件系统目录
# -m: 最小I/O单元大小(页大小)
# -e: 逻辑擦除块大小
# -c: 最大逻辑擦除块数量
mkfs.ubifs -r $ROOTFS -m 2048 -e 126976 -c 2048 -o rootfs.ubifs
# 创建ubinize配置文件
cat > ubinize.cfg << 'EOF'
[ubifs]
mode=ubi
image=rootfs.ubifs
vol_id=0
vol_size=50MiB
vol_type=dynamic
vol_name=rootfs
vol_flags=autoresize
EOF
# 创建UBI镜像
ubinize -o rootfs.ubi -m 2048 -p 128KiB ubinize.cfg
方法4:制作initramfs
# 方法A:使用cpio
cd $ROOTFS
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
# 方法B:使用内核脚本
cd ~/linux-5.15
scripts/gen_initramfs_list.sh -o initramfs.cpio.gz $ROOTFS
# 方法C:编译到内核
# 在内核配置中设置
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/path/to/rootfs) Initramfs source file(s)
方法5:制作squashfs镜像(只读)
# 安装工具
sudo apt install squashfs-tools
# 创建squashfs镜像
mksquashfs $ROOTFS rootfs.squashfs -comp xz
# 查看镜像信息
unsquashfs -s rootfs.squashfs
启动和测试¶
使用QEMU测试¶
1. 安装QEMU
2. 准备内核和根文件系统
# 假设已有编译好的内核
KERNEL=~/linux-5.15/arch/arm/boot/zImage
DTB=~/linux-5.15/arch/arm/boot/dts/vexpress-v2p-ca9.dtb
ROOTFS=~/embedded/rootfs.ext4
3. 启动QEMU
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel $KERNEL \
-dtb $DTB \
-drive file=$ROOTFS,format=raw,if=sd \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-nographic
使用initramfs启动:
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel $KERNEL \
-dtb $DTB \
-initrd initramfs.cpio.gz \
-append "rdinit=/sbin/init console=ttyAMA0" \
-nographic
在真实硬件上测试¶
1. 准备SD卡
# 查看SD卡设备
lsblk
# 分区(假设SD卡为/dev/sdb)
sudo fdisk /dev/sdb
# 创建两个分区:
# 分区1:FAT32,100MB,用于boot
# 分区2:ext4,剩余空间,用于rootfs
# 格式化分区
sudo mkfs.vfat -F 32 /dev/sdb1
sudo mkfs.ext4 /dev/sdb2
# 挂载分区
sudo mkdir -p /mnt/boot /mnt/rootfs
sudo mount /dev/sdb1 /mnt/boot
sudo mount /dev/sdb2 /mnt/rootfs
2. 复制文件
# 复制内核和设备树到boot分区
sudo cp zImage /mnt/boot/
sudo cp imx6ull-myboard.dtb /mnt/boot/
# 复制根文件系统到rootfs分区
sudo cp -a $ROOTFS/* /mnt/rootfs/
# 同步并卸载
sync
sudo umount /mnt/boot /mnt/rootfs
3. 配置U-Boot
# 在U-Boot命令行中设置启动参数
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'fatload mmc 1:1 0x80800000 zImage; fatload mmc 1:1 0x83000000 imx6ull-myboard.dtb; bootz 0x80800000 - 0x83000000'
saveenv
# 启动系统
boot
验证系统功能¶
1. 检查启动日志
2. 测试基本命令
# 文件操作
ls -la /
cd /tmp
touch test.txt
echo "Hello" > test.txt
cat test.txt
rm test.txt
# 网络测试
ifconfig
ping -c 3 192.168.1.1
# 系统信息
uname -a
cat /proc/cpuinfo
cat /proc/meminfo
3. 测试设备
# 查看设备
ls -l /dev/
# 测试串口
echo "test" > /dev/ttyS0
# 测试GPIO(如果有驱动)
echo 1 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio1/direction
echo 1 > /sys/class/gpio/gpio1/value
系统优化¶
减小文件系统大小¶
1. 精简BusyBox
# 重新配置BusyBox,只选择必需的命令
cd ~/embedded/busybox-1.35.0
make menuconfig
# 禁用不需要的功能
# - 禁用不常用的命令
# - 禁用调试功能
# - 使用静态编译
# 重新编译
make clean
make -j$(nproc)
make CONFIG_PREFIX=$ROOTFS install
2. 精简库文件
# 只复制必需的库
# 使用ldd或readelf查看依赖
arm-linux-gnueabihf-readelf -d $ROOTFS/bin/busybox | grep NEEDED
# 去除调试符号
find $ROOTFS -name "*.so*" -exec arm-linux-gnueabihf-strip {} \;
find $ROOTFS -type f -executable -exec arm-linux-gnueabihf-strip {} \; 2>/dev/null
3. 删除不必要的文件
# 删除文档和手册
rm -rf $ROOTFS/usr/share/doc
rm -rf $ROOTFS/usr/share/man
rm -rf $ROOTFS/usr/share/info
# 删除开发文件
rm -rf $ROOTFS/usr/include
rm -rf $ROOTFS/usr/lib/*.a
# 删除临时文件
find $ROOTFS -name "*.o" -delete
find $ROOTFS -name "*.a" -delete
4. 使用压缩文件系统
# 使用squashfs(只读,高压缩比)
mksquashfs $ROOTFS rootfs.squashfs -comp xz -b 256K
# 使用JFFS2(读写,支持压缩)
mkfs.jffs2 -r $ROOTFS -o rootfs.jffs2 -e 0x20000 -p -n
# 对比大小
ls -lh rootfs.*
优化启动速度¶
1. 精简启动脚本
# 优化/etc/init.d/rcS
cat > $ROOTFS/etc/init.d/rcS << 'EOF'
#!/bin/sh
# 只挂载必需的文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
# 延迟挂载不紧急的文件系统
(
sleep 1
mount -t tmpfs tmpfs /tmp
mkdir -p /dev/pts /dev/shm
mount -t devpts devpts /dev/pts
mount -t tmpfs tmpfs /dev/shm
) &
# 快速配置网络
ifconfig lo 127.0.0.1 up &
ifconfig eth0 192.168.1.100 up &
echo "System ready"
EOF
2. 使用并行启动
3. 禁用不必要的服务
4. 优化内核启动参数
优化运行性能¶
1. 使用合适的文件系统
2. 调整内存分配
# 在/etc/sysctl.conf中配置
vm.swappiness = 10 # 减少swap使用
vm.dirty_ratio = 10 # 控制脏页比例
vm.dirty_background_ratio = 5
3. 优化I/O调度
# 设置I/O调度器
echo deadline > /sys/block/mmcblk0/queue/scheduler
# 调整读写缓存
echo 128 > /sys/block/mmcblk0/queue/read_ahead_kb
安全加固¶
1. 设置root密码
# 在开发主机上生成密码哈希
openssl passwd -1 "your_password"
# 输出:$1$xxxxx$yyyyy
# 修改/etc/shadow
cat > $ROOTFS/etc/shadow << 'EOF'
root:$1$xxxxx$yyyyy:18000:0:99999:7:::
daemon:*:18000:0:99999:7:::
bin:*:18000:0:99999:7:::
sys:*:18000:0:99999:7:::
nobody:*:18000:0:99999:7:::
EOF
chmod 640 $ROOTFS/etc/shadow
2. 限制网络服务
3. 文件权限设置
# 设置关键文件权限
chmod 600 $ROOTFS/etc/shadow
chmod 644 $ROOTFS/etc/passwd
chmod 644 $ROOTFS/etc/group
chmod 755 $ROOTFS/etc/init.d/*
故障排除¶
常见启动问题¶
问题1:内核panic - not syncing: VFS: Unable to mount root fs
原因: - 根文件系统路径错误 - 文件系统类型不支持 - 设备驱动未加载
解决方法:
# 检查内核启动参数
# 确保root=参数正确
root=/dev/mmcblk0p2 # 或其他正确的设备
# 检查文件系统支持
# 在内核配置中启用相应的文件系统
File systems --->
<*> Second extended fs support
<*> The Extended 4 (ext4) filesystem
# 检查设备驱动
# 确保SD卡或Flash驱动已编译到内核
问题2:Warning: unable to open an initial console
原因: - /dev/console设备节点不存在 - 设备权限错误
解决方法:
# 创建console设备节点
cd $ROOTFS/dev
sudo mknod -m 666 console c 5 1
# 或在内核中启用devtmpfs
Device Drivers --->
Generic Driver Options --->
[*] Maintain a devtmpfs filesystem to mount at /dev
[*] Automount devtmpfs at /dev
问题3:init: can't open /dev/ttyXXX: No such file or directory
原因: - 串口设备节点不存在 - inittab中的设备名称错误
解决方法:
# 创建串口设备节点
cd $ROOTFS/dev
sudo mknod -m 666 ttyS0 c 4 64 # 或ttyAMA0等
# 检查inittab配置
# 确保设备名称与实际设备匹配
console::askfirst:-/bin/sh
# 或
ttyS0::askfirst:-/bin/sh
问题4:/bin/sh: can't access tty; job control turned off
原因: - 终端设备配置问题 - 权限问题
解决方法:
# 修改inittab,使用askfirst而不是respawn
console::askfirst:-/bin/sh
# 确保设备权限正确
chmod 666 $ROOTFS/dev/console
调试技巧¶
1. 启用内核调试输出
2. 使用initramfs调试
# 创建调试用的initramfs
cat > init << 'EOF'
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
echo "=== Debug Shell ==="
echo "Press Ctrl+D to continue boot"
/bin/sh
# 继续正常启动
exec switch_root /mnt/rootfs /sbin/init
EOF
chmod +x init
find . | cpio -o -H newc | gzip > initramfs-debug.cpio.gz
3. 检查文件系统完整性
4. 使用串口调试
# 连接串口
sudo minicom -D /dev/ttyUSB0 -b 115200
# 或使用screen
sudo screen /dev/ttyUSB0 115200
# 查看启动日志
# 所有内核和init的输出都会显示在串口
5. 分析启动时间
# 在内核启动参数中添加
initcall_debug
# 查看启动时间
dmesg | grep "initcall"
# 使用systemd-analyze(如果使用systemd)
systemd-analyze
systemd-analyze blame
性能分析¶
1. 查看系统资源使用
# CPU使用率
top
# 或
mpstat 1
# 内存使用
free -h
cat /proc/meminfo
# 磁盘I/O
iostat -x 1
# 进程资源
ps aux --sort=-%mem | head
ps aux --sort=-%cpu | head
2. 分析启动过程
# 记录启动时间
cat /proc/uptime
# 查看服务启动时间
systemd-analyze blame # 如果使用systemd
# 手动计时
# 在启动脚本中添加
echo "Start: $(date +%s.%N)" > /tmp/boot.log
# ... 执行操作 ...
echo "End: $(date +%s.%N)" >> /tmp/boot.log
3. 内存泄漏检测
# 监控内存使用
watch -n 1 free -h
# 查看进程内存
cat /proc/<pid>/status | grep Vm
# 使用valgrind(需要交叉编译)
valgrind --leak-check=full ./your_app
高级主题¶
使用Buildroot¶
Buildroot是一个自动化构建嵌入式Linux系统的工具。
1. 下载和配置
# 下载Buildroot
git clone https://github.com/buildroot/buildroot.git
cd buildroot
# 查看可用的配置
make list-defconfigs
# 使用预定义配置
make raspberrypi3_defconfig
# 或自定义配置
make menuconfig
2. 配置选项
Target options --->
Target Architecture (ARM (little endian))
Target Architecture Variant (cortex-A7)
Toolchain --->
Toolchain type (External toolchain)
System configuration --->
System hostname (embedded-linux)
Root password (设置密码)
Filesystem images --->
[*] ext2/3/4 root filesystem
[*] tar the root filesystem
3. 编译
# 开始编译(需要较长时间)
make -j$(nproc)
# 输出文件在output/images/目录
ls output/images/
# rootfs.ext4
# zImage
# *.dtb
使用Yocto Project¶
Yocto是更强大的嵌入式Linux构建系统。
1. 安装依赖
sudo apt install gawk wget git diffstat unzip texinfo gcc build-essential \
chrpath socat cpio python3 python3-pip python3-pexpect xz-utils \
debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa \
libsdl1.2-dev pylint3 xterm python3-subunit mesa-common-dev zstd liblz4-tool
2. 下载Poky
3. 初始化环境
4. 配置和构建
# 编辑conf/local.conf
MACHINE = "qemuarm"
DISTRO = "poky"
# 构建最小镜像
bitbake core-image-minimal
# 或构建完整镜像
bitbake core-image-sato
只读根文件系统¶
优势: - 防止文件系统损坏 - 提高系统稳定性 - 适合工业应用
实现方法:
1. 使用squashfs
# 创建只读根文件系统
mksquashfs $ROOTFS rootfs.squashfs -comp xz
# 挂载为只读
mount -t squashfs -o loop,ro rootfs.squashfs /mnt
2. 配置可写分区
# 创建overlay文件系统
mount -t tmpfs tmpfs /tmp
mount -t tmpfs tmpfs /var
# 或使用overlayfs
mount -t overlay overlay \
-o lowerdir=/ro-root,upperdir=/rw-root,workdir=/work \
/root
3. 修改fstab
cat > /etc/fstab << 'EOF'
# 只读根文件系统
/dev/mmcblk0p2 / squashfs ro 0 0
# 可写分区
tmpfs /tmp tmpfs defaults 0 0
tmpfs /var tmpfs defaults 0 0
/dev/mmcblk0p3 /data ext4 defaults 0 2
EOF
网络文件系统 (NFS)¶
用途: - 开发调试 - 快速测试 - 共享文件
服务器端配置:
# 安装NFS服务器
sudo apt install nfs-kernel-server
# 配置导出目录
sudo vi /etc/exports
# 添加:
/path/to/rootfs 192.168.1.0/24(rw,sync,no_root_squash,no_subtree_check)
# 重启NFS服务
sudo exportfs -ra
sudo systemctl restart nfs-kernel-server
客户端配置:
# 内核启动参数
root=/dev/nfs nfsroot=192.168.1.100:/path/to/rootfs,v3,tcp ip=dhcp
# 或手动挂载
mount -t nfs -o nolock 192.168.1.100:/path/to/rootfs /mnt
多分区方案¶
典型分区布局:
/dev/mmcblk0
├── /dev/mmcblk0p1 # boot分区 (FAT32, 100MB)
│ ├── zImage
│ ├── *.dtb
│ └── boot.scr
├── /dev/mmcblk0p2 # rootfs分区 (ext4/squashfs, 500MB)
│ └── 根文件系统
├── /dev/mmcblk0p3 # data分区 (ext4, 剩余空间)
│ └── 用户数据
└── /dev/mmcblk0p4 # 备份分区 (可选)
└── 备份根文件系统
fstab配置:
cat > /etc/fstab << 'EOF'
/dev/mmcblk0p1 /boot vfat defaults 0 2
/dev/mmcblk0p2 / ext4 defaults 0 1
/dev/mmcblk0p3 /data ext4 defaults 0 2
tmpfs /tmp tmpfs defaults 0 0
EOF
实战项目:完整根文件系统制作¶
项目目标¶
制作一个功能完整的嵌入式Linux根文件系统,包含: - 基本系统工具(BusyBox) - 网络功能(静态IP和DHCP) - SSH服务(Dropbear) - 自定义应用程序 - 系统监控工具
项目步骤¶
步骤1:准备工作环境
# 创建工作目录
mkdir -p ~/embedded-project
cd ~/embedded-project
# 设置环境变量
export ROOTFS=$PWD/rootfs
export TOOLCHAIN=/opt/arm-linux-gnueabihf
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
步骤2:编译BusyBox
# 下载BusyBox
wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
tar -xjf busybox-1.35.0.tar.bz2
cd busybox-1.35.0
# 配置
make defconfig
make menuconfig
# Settings -> Build static binary: YES
# Settings -> Cross compiler prefix: arm-linux-gnueabihf-
# 编译和安装
make -j$(nproc)
make CONFIG_PREFIX=$ROOTFS install
cd ..
步骤3:创建目录结构
cd $ROOTFS
mkdir -p etc/init.d etc/network dev proc sys tmp root home
mkdir -p usr/lib var/log var/run lib/modules
chmod 1777 tmp
chmod 700 root
步骤4:复制库文件
# 复制必需的库
cp -a $TOOLCHAIN/libc/lib/libc.so.6 lib/
cp -a $TOOLCHAIN/libc/lib/libm.so.6 lib/
cp -a $TOOLCHAIN/libc/lib/libdl.so.2 lib/
cp -a $TOOLCHAIN/libc/lib/libpthread.so.0 lib/
cp -a $TOOLCHAIN/libc/lib/libresolv.so.2 lib/
cp -a $TOOLCHAIN/libc/lib/ld-linux-armhf.so.3 lib/
# 去除调试符号
$CROSS_COMPILE-strip lib/*.so*
步骤5:配置系统文件
# 创建inittab
cat > etc/inittab << 'EOF'
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
EOF
# 创建rcS
cat > etc/init.d/rcS << 'EOF'
#!/bin/sh
echo "Starting system..."
# 挂载文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs tmpfs /tmp
mkdir -p /dev/pts /dev/shm
mount -t devpts devpts /dev/pts
mount -t tmpfs tmpfs /dev/shm
# 设置主机名
hostname -F /etc/hostname
# 配置网络
/etc/init.d/S40network start
# 启动SSH
/etc/init.d/S50dropbear start
# 启动自定义应用
/etc/init.d/S99myapp start
echo "System initialization complete"
EOF
chmod +x etc/init.d/rcS
# 创建hostname
echo "embedded-linux" > etc/hostname
# 创建passwd和group
cat > etc/passwd << 'EOF'
root:x:0:0:root:/root:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/false
EOF
cat > etc/group << 'EOF'
root:x:0:
nogroup:x:65534:
EOF
# 创建fstab
cat > etc/fstab << 'EOF'
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
# 创建profile
cat > etc/profile << 'EOF'
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
export PS1='[\u@\h \W]\$ '
export LANG=C
echo "Welcome to Embedded Linux System"
EOF
步骤6:配置网络
# 创建网络配置目录
mkdir -p etc/network
# 创建interfaces文件
cat > etc/network/interfaces << 'EOF'
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 192.168.1.100
netmask 255.255.255.0
gateway 192.168.1.1
dns-nameservers 8.8.8.8
EOF
# 创建网络启动脚本
cat > etc/init.d/S40network << 'EOF'
#!/bin/sh
case "$1" in
start)
echo "Configuring network..."
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
echo "nameserver 8.8.8.8" > /etc/resolv.conf
;;
stop)
echo "Stopping network..."
ifconfig eth0 down
;;
restart)
$0 stop
$0 start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
EOF
chmod +x etc/init.d/S40network
步骤7:添加Dropbear SSH
# 下载Dropbear
cd ~/embedded-project
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=arm-linux-gnueabihf \
--prefix=/usr \
--disable-zlib
make PROGRAMS="dropbear dbclient dropbearkey scp"
make DESTDIR=$ROOTFS install
# 去除调试符号
$CROSS_COMPILE-strip $ROOTFS/usr/sbin/dropbear
$CROSS_COMPILE-strip $ROOTFS/usr/bin/dbclient
$CROSS_COMPILE-strip $ROOTFS/usr/bin/dropbearkey
$CROSS_COMPILE-strip $ROOTFS/usr/bin/scp
# 创建SSH目录
mkdir -p $ROOTFS/etc/dropbear
# 生成主机密钥(在目标系统首次启动时)
# dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key
# dropbearkey -t dss -f /etc/dropbear/dropbear_dss_host_key
# 创建Dropbear启动脚本
cat > $ROOTFS/etc/init.d/S50dropbear << 'EOF'
#!/bin/sh
DROPBEAR=/usr/sbin/dropbear
DROPBEAR_KEY_DIR=/etc/dropbear
case "$1" in
start)
echo "Starting dropbear sshd..."
# 生成主机密钥(如果不存在)
if [ ! -f $DROPBEAR_KEY_DIR/dropbear_rsa_host_key ]; then
echo "Generating RSA host key..."
dropbearkey -t rsa -f $DROPBEAR_KEY_DIR/dropbear_rsa_host_key
fi
# 启动dropbear
$DROPBEAR -R
;;
stop)
echo "Stopping dropbear sshd..."
killall dropbear
;;
restart)
$0 stop
sleep 1
$0 start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
EOF
chmod +x $ROOTFS/etc/init.d/S50dropbear
cd ~/embedded-project
步骤8:添加自定义应用
# 创建示例应用
cat > myapp.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
volatile int running = 1;
void signal_handler(int sig) {
running = 0;
}
int main(void) {
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
printf("MyApp started\n");
while (running) {
printf("MyApp is running...\n");
sleep(10);
}
printf("MyApp stopped\n");
return 0;
}
EOF
# 编译
$CROSS_COMPILE-gcc -o myapp myapp.c
$CROSS_COMPILE-strip myapp
# 安装
cp myapp $ROOTFS/usr/bin/
chmod +x $ROOTFS/usr/bin/myapp
# 创建启动脚本
cat > $ROOTFS/etc/init.d/S99myapp << 'EOF'
#!/bin/sh
DAEMON=/usr/bin/myapp
PIDFILE=/var/run/myapp.pid
case "$1" in
start)
echo "Starting myapp..."
start-stop-daemon -S -b -m -p $PIDFILE -x $DAEMON
;;
stop)
echo "Stopping myapp..."
start-stop-daemon -K -p $PIDFILE
rm -f $PIDFILE
;;
restart)
$0 stop
sleep 1
$0 start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
EOF
chmod +x $ROOTFS/etc/init.d/S99myapp
步骤9:创建设备节点
cd $ROOTFS/dev
sudo mknod -m 666 console c 5 1
sudo mknod -m 666 null c 1 3
sudo mknod -m 666 zero c 1 5
sudo mknod -m 666 ttyS0 c 4 64
sudo mknod -m 644 random c 1 8
sudo mknod -m 644 urandom c 1 9
cd ~/embedded-project
步骤10:制作文件系统镜像
# 创建ext4镜像
dd if=/dev/zero of=rootfs.ext4 bs=1M count=200
mkfs.ext4 rootfs.ext4
# 挂载并复制文件
mkdir -p /tmp/rootfs-mount
sudo mount -o loop rootfs.ext4 /tmp/rootfs-mount
sudo cp -a $ROOTFS/* /tmp/rootfs-mount/
sudo umount /tmp/rootfs-mount
# 压缩镜像
gzip rootfs.ext4
echo "Root filesystem created: rootfs.ext4.gz"
ls -lh rootfs.ext4.gz
项目测试¶
# 使用QEMU测试
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-drive file=rootfs.ext4,format=raw,if=sd \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-nographic
# 测试网络
ping 192.168.1.1
# 测试SSH(从另一台机器)
ssh root@192.168.1.100
# 测试自定义应用
ps | grep myapp
总结¶
关键要点¶
通过本教程,你已经学习了:
- 文件系统基础
- Linux文件系统层次结构(FHS)
- 常见文件系统类型及其特点
-
磁盘、Flash和内存文件系统的选择
-
BusyBox工具集
- BusyBox的配置和编译
- 符号链接的工作原理
-
功能裁剪和优化
-
根文件系统制作
- 目录结构创建
- 库文件复制和精简
- 设备节点管理
-
系统配置文件编写
-
启动配置
- init进程和inittab
- 启动脚本编写
- 网络配置
-
服务管理
-
系统优化
- 文件系统大小优化
- 启动速度优化
- 运行性能优化
-
安全加固
-
故障排除
- 常见启动问题
- 调试技巧
- 性能分析
最佳实践¶
1. 开发阶段 - 使用NFS根文件系统,便于快速测试 - 保持详细的构建日志 - 使用版本控制管理配置文件
2. 生产阶段 - 使用只读根文件系统提高稳定性 - 实施安全加固措施 - 优化启动时间和资源使用
3. 维护阶段 - 建立OTA升级机制 - 保留备份分区 - 实施日志管理策略
下一步学习¶
掌握了根文件系统制作后,你可以继续学习:
- 进程和线程编程
- 多进程应用开发
- 进程间通信(IPC)
-
线程同步机制
-
系统调用和应用开发
- Linux系统调用接口
- 文件I/O操作
-
网络编程
-
完整系统构建
- Bootloader配置
- 内核定制
- 驱动开发
-
应用程序集成
-
高级主题
- 容器技术(Docker)
- 实时性优化
- 安全启动
- OTA升级方案
常见问题¶
Q1: 根文件系统最小需要多大?¶
A: 最小的根文件系统可以小于10MB,包含: - BusyBox(静态编译):约1-2MB - 必需的库文件:约2-3MB - 配置文件和脚本:约1MB - 设备节点和目录:约1MB
实际大小取决于: - 选择的命令和功能 - 是否静态编译 - 是否包含额外应用 - 是否使用压缩文件系统
Q2: 静态编译和动态链接如何选择?¶
A: 选择依据:
静态编译: - 优点:不需要库文件,部署简单 - 缺点:文件大,不能共享库 - 适用:单一应用,资源充足
动态链接: - 优点:文件小,共享库,节省内存 - 缺点:需要管理库依赖 - 适用:多个应用,资源受限
推荐:嵌入式系统通常使用动态链接,但精简库文件。
Q3: 如何减小根文件系统大小?¶
A: 优化方法:
- 精简BusyBox:只选择必需的命令
- 去除调试符号:使用strip工具
- 精简库文件:只复制必需的库
- 使用压缩文件系统:squashfs、JFFS2
- 删除不必要文件:文档、头文件、静态库
- 优化配置文件:删除注释和空行
Q4: initramfs和initrd有什么区别?¶
A: 主要区别:
| 特性 | initramfs | initrd |
|---|---|---|
| 格式 | cpio归档 | 块设备镜像 |
| 挂载 | 直接解压到内存 | 需要挂载 |
| 大小 | 动态调整 | 固定大小 |
| 性能 | 更快 | 较慢 |
| 推荐 | 是 | 否(已过时) |
现代系统推荐使用initramfs。
Q5: 如何实现根文件系统的OTA升级?¶
A: 常见方案:
方案1:双分区方案 - 保留两个根文件系统分区 - 升级时写入备用分区 - 重启时切换到新分区 - 失败时回滚到旧分区
方案2:差分升级 - 只传输变化的部分 - 使用rsync或bsdiff - 节省带宽和时间
方案3:容器化升级 - 使用Docker容器 - 只升级应用层 - 保持系统层稳定
Q6: 如何调试根文件系统启动失败?¶
A: 调试步骤:
-
启用详细日志
-
使用initramfs调试
- 创建包含shell的initramfs
- 在启动过程中进入shell
-
手动执行挂载和初始化
-
检查设备节点
- 确保/dev/console存在
-
确保串口设备节点正确
-
验证文件系统
- 检查文件系统类型
- 验证文件系统完整性
-
确认挂载参数正确
-
分析内核日志
- 查看panic信息
- 检查设备驱动加载
- 确认根设备识别
参考资料¶
官方文档¶
- Linux Filesystem Hierarchy Standard
- BusyBox Documentation
- Buildroot User Manual
- Yocto Project Documentation
工具和资源¶
- BusyBox - 嵌入式Linux工具集
- Buildroot - 自动化构建系统
- Yocto Project - 嵌入式Linux发行版构建
- Dropbear SSH - 轻量级SSH服务器
推荐阅读¶
- 《Embedded Linux Primer》 - Christopher Hallinan
- 《Building Embedded Linux Systems》 - Karim Yaghmour
- 《Mastering Embedded Linux Programming》 - Chris Simmonds
- eLinux.org - 嵌入式Linux Wiki
在线资源¶
- Bootlin Training Materials - 免费的嵌入式Linux培训材料
- Linux From Scratch - 从零构建Linux系统
- Embedded Linux Conference - 嵌入式Linux会议
文档版本: 1.0
最后更新: 2024-01-15
作者: 嵌入式知识平台
相关教程: - Linux内核裁剪与配置 - 设备树详解 - Linux驱动开发入门 - Linux进程与线程编程