4、Linux系统基础:嵌入式Linux开发必备知识回顾

嵌入式Linux开发与纯应用开发或服务器运维有着本质区别:开发者面对的是资源受限的硬件平台、交叉编译工具链、设备树(Device Tree)、内核模块加载以及底层设备驱动交互。在瑞芯微RK系列平台(如RK3568、RK3588)上,无论是进行系统移植、BSP开发还是上层应用优化,扎实的Linux基础都是不可或缺的。本章将系统梳理嵌入式Linux开发中必须掌握的命令行操作、文件系统结构、进程管理机制以及设备驱动模型,帮助开发者建立从“应用层”到“内核态”的完整知识链路。

嵌入式Linux开发知识体系与数据流向 用户空间 (User Space) • Shell命令 (ls, ps, top, mount) • 系统调用接口 (open, read, ioctl) • 交叉编译工具链 (aarch64-linux-gnu-) • 调试工具 (gdb, strace, dmesg) • 应用进程 (QT, Python, C程序) 内核空间 (Kernel Space) • 进程调度器 (CFS, 优先级) • 虚拟文件系统 (VFS, ext4, proc) • 内存管理 (MMU, 页表, DMA) • 设备驱动模型 (platform, I2C, SPI) • 网络协议栈 (TCP/IP, Netfilter) 硬件层 (Hardware) • RK3568/RK3588 SoC • DDR / eMMC / SPI Flash • 外设接口 (MIPI, USB, PCIe) • 电源管理 (PMIC, 时钟树) • 传感器 / 执行器 系统调用 驱动/中断 文件系统层次 (嵌入式典型布局) /bin → busybox (基础命令) /dev → 设备节点 (mknod, devtmpfs) /proc → 内核运行时信息 (虚拟) /sys → 设备驱动模型 (kobject) /etc → 配置文件 (inittab, fstab) /lib → 动态库 (ld-linux-aarch64.so) 进程管理与设备驱动交互 1. 应用进程 → open("/dev/i2c-0") 2. 内核VFS → 查找inode → 驱动file_operations 3. 驱动 → 操作硬件寄存器 (ioremap) 4. 中断/轮询 → 数据返回用户空间 5. 进程同步 → wait_queue, mutex, completion 6. 调试手段 → dmesg, /proc/interrupts, ftrace 图4-1 嵌入式Linux三层架构与关键交互路径

上图展示了嵌入式Linux开发的核心架构。从宏观视角看,开发者始终在“用户空间-内核空间-硬件层”三者之间穿梭。用户空间的应用程序通过系统调用(如open、read、ioctl)陷入内核,内核中的虚拟文件系统(VFS)将请求路由到具体的设备驱动,驱动最终操作硬件寄存器完成数据收发。理解这条链路,是后续进行RK平台外设调试、性能优化和驱动开发的基础。

4.1 命令行与Shell:嵌入式开发的“第一语言”

核心原理:BusyBox与交叉编译环境下的Shell差异

在PC上我们习惯使用bash或zsh,但在嵌入式目标板上(尤其是基于Buildroot或Yocto构建的系统),默认Shell往往是BusyBox提供的ash。BusyBox是一个精简的Unix工具集,它将常用的ls、cp、ps、mount等命令整合进一个单一可执行文件中,以节省存储空间。这意味着某些命令的选项可能被裁剪(例如ps可能不支持-e,而只支持ps aux)。此外,开发环境与目标环境的架构不同(x86_64 vs aarch64),因此必须使用交叉编译器(如aarch64-linux-gnu-gcc)编译程序,并通过NFS、TFTP或串口传输到目标板执行。

操作流程:从PC到目标板的调试会话

  1. 建立连接:使用串口(minicom或picocom)或网络SSH登录目标板。
  2. 文件传输:通过scp或tftp将交叉编译好的可执行文件拷贝到目标板/tmp目录。
  3. 权限调整:执行chmod +x app赋予执行权限。
  4. 运行与调试:前台运行./app,或使用gdb附加调试。
# 在PC上交叉编译
aarch64-linux-gnu-gcc -o hello hello.c
# 拷贝到目标板(假设IP为192.168.1.100)
scp hello root@192.168.1.100:/tmp/
# 在目标板上执行
chmod +x /tmp/hello
/tmp/hello

案例拆解:排查“命令找不到”问题

背景:在RK3568板卡上运行一个脚本,提示./test.sh: line 3: xxd: not found。分析:目标板BusyBox未包含xxd命令。此时不应盲目安装,而应检查脚本中xxd的具体用途。若仅用于十六进制转储,可用od -A x -t x1z替代。操作:修改脚本,将xxd替换为od命令,重新执行。复盘:嵌入式环境命令集有限,编写脚本时应优先使用POSIX标准命令,避免依赖PC特有工具。

经验提醒:在开发阶段,建议在目标板上挂载PC的NFS目录(mount -t nfs 192.168.1.10:/work /mnt),这样无需反复拷贝文件,直接运行NFS目录下的可执行文件,极大提升调试效率。

4.2 文件系统:从Flash布局到目录结构

核心原理:嵌入式文件系统的分层与只读/可写分区

嵌入式Linux的文件系统并非单一实体,而是由多个分区组成,每个分区可能有不同的格式和挂载属性。典型的RK平台分区包括:uboot(引导加载器)、boot(内核与DTB)、rootfs(根文件系统)、userdata(用户数据)。根文件系统通常使用ext4或squashfs(只读压缩格式),而userdata分区使用ext4或f2fs以支持读写。理解/proc和/sys这两个虚拟文件系统至关重要:/proc输出内核内部数据结构(如/proc/cpuinfo、/proc/interrupts),/sys则提供设备驱动模型的视图(如/sys/class/gpio)。

目录用途嵌入式注意事项
/bin, /sbin基本命令与系统管理命令通常为BusyBox符号链接,不可随意删除
/dev设备节点使用devtmpfs自动创建,也可手动mknod
/proc内核参数与进程信息只读挂载,用于动态调试
/sys设备驱动模型可读写部分属性,用于运行时配置
/etc配置文件需持久化,通常放在可写分区或overlayfs
/var日志与临时文件链接到/tmp或tmpfs,重启丢失

操作流程:查看与修改分区挂载

# 查看当前挂载情况
mount
# 查看分区表
cat /proc/partitions
# 重新挂载根文件系统为读写(开发阶段)
mount -o remount,rw /
# 挂载一个ext4分区到/data
mount /dev/mmcblk0p5 /data

常见误区:误改只读分区导致系统无法启动

初学者常试图直接修改/etc下的文件,但根文件系统可能是只读挂载的。正确做法是:在开发阶段将根文件系统挂载为可写(mount -o remount,rw /),或者使用overlayfs将修改层叠加到只读层之上。若直接强制写入,可能导致文件系统损坏,进而无法启动。总结:操作前先确认挂载属性,修改关键文件前备份原文件。

风险提示:在量产阶段,务必保持根文件系统只读,防止意外断电导致文件系统损坏。所有需要动态修改的数据应重定向到/data或/tmp分区。

4.3 进程管理:理解调度、优先级与守护进程

核心原理:Linux进程模型与嵌入式实时性

Linux是分时操作系统,默认调度器为CFS(完全公平调度器)。但在嵌入式场景中,我们往往需要保证某些关键任务的实时性。RK平台支持PREEMPT_RT补丁,可将内核配置为实时内核。进程管理的关键概念包括:进程优先级(nice值,范围-20到19)、调度策略(SCHED_OTHER, SCHED_FIFO, SCHED_RR)以及进程状态(运行、睡眠、停止、僵尸)。在嵌入式调试中,ps、top、kill是最常用的命令。

操作流程:定位CPU占用率过高的进程

  1. 执行top命令,按P键按CPU使用率排序。
  2. 记录高占用进程的PID。
  3. 使用cat /proc/PID/status查看进程详细信息。
  4. 若为异常进程,使用kill -9 PID强制终止。
# 查看所有进程(包含内核线程)
ps aux
# 动态监视进程状态
top -d 1
# 查看特定进程的线程
ps -T -p PID

案例拆解:RK3588多核CPU负载均衡问题

背景:在RK3588(8核,4×A76+4×A55)上运行视频编解码任务,发现A76核心负载100%,而A55核心空闲。分析:默认CFS调度器可能未将任务迁移到空闲核心,或者任务绑定了CPU亲和性。操作:使用taskset -pc 0-3 PID将进程绑定到A76集群,或使用sched_setaffinity系统调用。结果:通过合理分配任务到不同核心,整体吞吐量提升20%。复盘:在异构多核平台上,必须考虑CPU的容量差异,必要时手动设置亲和性或使用cgroup进行资源隔离。

关键命令:kill -l列出所有信号;kill -s SIGKILL PID发送特定信号;nohup使进程忽略挂断信号,常用于后台启动服务。

4.4 设备驱动:从模块加载到与硬件交互

核心原理:Linux设备驱动模型与字符设备

设备驱动是内核与硬件之间的桥梁。在RK平台上,驱动开发主要涉及三类设备:字符设备(如串口、GPIO)、块设备(如eMMC)、网络设备(如以太网MAC)。驱动通过file_operations结构体向用户空间提供open、read、write、ioctl等接口。设备树(Device Tree)是描述硬件信息的配置文件,内核通过解析DTB(Device Tree Blob)来匹配驱动与设备节点。

操作流程:加载/卸载内核模块

# 加载模块
insmod my_driver.ko
# 查看已加载模块
lsmod
# 查看内核日志
dmesg | tail -20
# 卸载模块
rmmod my_driver

案例拆解:编写一个简单的GPIO字符设备驱动

目标:控制RK3568上的一个LED灯。步骤:1. 在设备树中定义GPIO节点(gpio-leds);2. 编写驱动,在probe函数中获取GPIO资源并注册字符设备;3. 实现write回调,根据用户传入的0/1值控制GPIO电平。关键代码:

static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {
    char kbuf[4];
    if (copy_from_user(kbuf, buf, count)) return -EFAULT;
    if (kbuf[0] == '1') gpiod_set_value(led_gpio, 1);
    else gpiod_set_value(led_gpio, 0);
    return count;
}

验证:在用户空间执行echo 1 > /dev/led0,观察LED点亮。复盘:驱动开发的核心在于理解设备树与驱动模型的匹配机制,以及正确使用内核API(如gpiod_get、devm_request_irq)。

常见误区:在驱动中直接使用物理地址访问硬件是错误的,必须通过ioremap将物理地址映射为虚拟地址。此外,中断处理函数中不能调用可能导致睡眠的函数(如kmalloc),应使用tasklet或workqueue。

4.5 本章总结与实战要点

嵌入式Linux开发是一个系统工程,本章回顾的命令行、文件系统、进程管理和设备驱动知识是后续所有RK平台开发工作的基石。在实际项目中,建议开发者养成以下习惯:第一,始终使用交叉编译工具链,并注意目标板与PC的架构差异;第二,熟悉/proc和/sys文件系统,它们是内核与用户空间交互的窗口;第三,在调试驱动时,充分利用dmesg和ftrace跟踪内核行为;第四,理解设备树是RK平台硬件初始化的关键,任何外设的添加或修改都离不开对DTB的调整。掌握这些基础,你将能够自信地面对RK3568、RK3588等复杂SoC的BSP开发与系统优化任务。