3、Linux命令行与Shell编程:常用命令、文件系统操作、权限管理、Shell脚本编写与调试
嵌入式Linux开发与传统的单片机裸机开发最大的区别在于,开发者需要在一个完整的操作系统之上工作。无论是编译内核、烧写根文件系统、调试驱动程序,还是配置交叉编译环境,都离不开Linux命令行。对于物联网设备而言,其资源受限、调试手段有限,熟练掌握命令行与Shell编程,不仅是提高效率的工具,更是定位问题、实现自动化测试与部署的核心能力。本章将围绕嵌入式开发中最常用的命令体系、文件系统层次结构、权限模型以及Shell脚本的编写与调试技巧展开,帮助开发者建立一套完整的命令行操作思维。
上图展示了本章的知识体系框架。在嵌入式开发中,这三个分支并非孤立存在,而是相互交织:文件操作是基础,权限管理保障系统安全,Shell脚本则将前两者串联起来实现自动化。下面我们逐一深入剖析。
3.1 常用命令与文件系统操作
3.1.1 核心原理:一切皆文件与虚拟文件系统
Linux设计哲学中“一切皆文件”的理念在嵌入式系统中体现得尤为明显。CPU寄存器、内存地址、GPIO引脚、传感器数据,在Linux下都被抽象为文件。开发者通过操作文件描述符即可完成硬件访问。嵌入式Linux根文件系统通常遵循FHS(文件系统层次标准),但针对嵌入式环境做了精简。以下表格展示了嵌入式系统中常见的目录及其用途:
| 目录 | 用途 | 嵌入式注意事项 |
|---|---|---|
| /bin | 用户可执行命令(ls, cp) | 通常为busybox提供的精简版本 |
| /sbin | 系统管理命令(ifconfig, fdisk) | 仅root可执行部分命令 |
| /dev | 设备节点文件 | 使用devtmpfs或mdev动态管理 |
| /proc | 内核运行时信息(内存、CPU) | procfs,反映内核状态 |
| /sys | 设备驱动模型与属性 | sysfs,用于控制设备参数 |
| /etc | 配置文件 | 只读挂载时需注意临时修改 |
| /tmp | 临时文件 | 通常为tmpfs,断电丢失 |
在嵌入式调试中,/proc和/sys是两个高频操作目录。例如,查看系统内存信息使用cat /proc/meminfo,调整GPIO输出电平则通过/sys/class/gpio下的文件完成。
3.1.2 操作流程:文件查找、打包与传输
嵌入式开发中经常需要从主机向目标板传输文件、查找特定配置项、打包日志。以下是一套标准操作流程:
- 文件定位:使用
find /etc -name "*.conf"查找配置文件;使用grep -r "CONFIG_XXX" /在内核源码或根文件系统中递归搜索字符串。 - 打包压缩:使用
tar czvf log.tar.gz /var/log将日志打包,便于从设备导出。 - 网络传输:在开发板上使用
scp user@host:/path/file .拉取文件,或使用tftp、nfs挂载根文件系统进行实时调试。 - 磁盘操作:使用
df -h查看Flash分区使用率,mount -t jffs2 /dev/mtdblock3 /mnt挂载文件系统。
grep -r直接搜索整个根目录,这会导致内存耗尽。建议先使用find限定目录范围,再配合xargs进行过滤。
3.1.3 案例拆解:交叉编译环境的文件操作
背景:某物联网网关设备使用ARM Cortex-A7处理器,开发者在x86主机上交叉编译了一个应用程序,需要部署到目标板并验证运行。
初始现象:程序在目标板上提示No such file or directory,但文件明明存在。
分析过程:首先使用file app命令检查二进制格式,发现是x86架构而非ARM架构。进一步使用readelf -h app查看ELF头,确认架构不匹配。此外,检查动态链接器路径,发现程序依赖的/lib/ld-linux-armhf.so.3在目标板上不存在。
解决步骤:
- 重新使用交叉编译器编译:
arm-linux-gnueabihf-gcc app.c -o app。 - 使用
arm-linux-gnueabihf-strip app去除符号表,减小体积。 - 通过NFS挂载主机目录,直接运行测试:
mount -t nfs 192.168.1.10:/work /mnt -o nolock。 - 确认运行后,再使用
cp命令固化到/usr/bin。
结果对比:修正后程序正常运行,CPU占用率从异常时的100%降至正常水平的5%。复盘:文件操作不仅仅是命令的记忆,更需要结合file、readelf等工具进行二进制兼容性判断。
3.1.4 常见误区与总结
- 误区一:在嵌入式设备上使用
rm -rf /进行清理,极易导致系统崩溃。应使用rm -rf /path/to/specific/dir并确认路径。 - 误区二:混淆
/dev下的设备节点与/sys中的设备属性。设备节点用于打开和读写,而/sys用于配置参数和查看状态。 - 误区三:忽略文件系统类型。嵌入式常用jffs2、ubifs、ext4,不同文件系统对掉电保护、磨损均衡的支持不同,操作前需确认挂载参数。
总结而言,文件操作的核心在于理解路径、类型与挂载关系。熟练使用ls -l查看文件属性,du -sh查看目录大小,是排查存储问题的第一步。
3.2 权限管理
3.2.1 核心原理:UGO模型与进程权限
Linux权限模型基于用户(User)、组(Group)、其他(Other)三类主体,每类主体对文件具有读(r=4)、写(w=2)、执行(x=1)三种权限。嵌入式系统中,权限管理直接关系到系统安全。例如,一个Web服务器进程如果以root权限运行,一旦被攻击,攻击者将获得整个设备的控制权。因此,嵌入式Linux通常采用最小权限原则,将不同服务分配给不同用户运行。
权限的表示方法有两种:符号模式(如rwxr-xr--)和数字模式(如754)。数字模式的计算方式为:所有者权限数字+组权限数字+其他权限数字。例如754表示所有者可读写执行(7=4+2+1),组可读执行(5=4+1),其他只读(4)。
3.2.2 操作流程:修改权限与属主
在嵌入式开发中,经常需要修改设备节点权限或应用程序的执行权限。标准流程如下:
- 查看当前权限:
ls -l /dev/ttyS0,输出如crw-rw---- 1 root dialout 4, 64 Jan 1 00:00 /dev/ttyS0。 - 修改权限:
chmod 666 /dev/ttyS0,使所有用户可读写。 - 修改属主:
chown root:root /dev/ttyS0,将属主和组都改为root。 - 设置粘滞位:对于共享目录如
/tmp,使用chmod +t /tmp防止用户删除他人文件。 - 使用umask:在
/etc/profile中设置umask 022,使新建文件默认权限为644,新建目录为755。
/dev下的设备节点执行chmod 777。这会使任何用户都能直接操作硬件,例如格式化Flash或控制电机,造成不可逆的物理损坏。
3.2.3 案例拆解:守护进程的权限降级
背景:某智能摄像头设备中的视频采集服务需要访问/dev/video0和GPIO引脚,但该服务存在远程代码执行漏洞。
初始目标:即使服务被攻破,攻击者也无法获得root权限。
实施步骤:
- 创建专用用户:
useradd -r -s /bin/false camera,创建系统用户且禁止登录。 - 修改设备节点权限:
chown camera:video /dev/video0,chmod 660 /dev/video0。 - 在服务启动脚本中,先以root执行初始化(如加载内核模块),然后使用
setuid切换到camera用户运行主程序。 - 使用
capsh --drop=all -- -c "exec ./camera_service"丢弃所有能力(capability),仅保留必要的CAP_SYS_ADMIN。
结果:在一次渗透测试中,攻击者成功执行了system("reboot"),但由于权限不足,命令执行失败,设备未受影响。复盘:权限管理需要结合用户、组、能力(capabilities)和SELinux/AppArmor等多层机制,单靠chmod是不够的。
3.2.4 常见误区与总结
- 误区一:认为嵌入式设备不需要密码策略。实际上,物联网设备常因默认密码(如root/root)被入侵。
- 误区二:使用
chmod -R 777 /解决权限问题,这会彻底破坏系统安全模型。 - 误区三:忽略文件系统挂载选项。在
/etc/fstab中,对只读分区添加ro选项,对无需执行的分区添加noexec,能有效提升安全性。
权限管理的核心是“最小权限”与“可审计性”。在嵌入式Linux中,应尽量使用capabilities替代完全的root权限,例如使用setcap cap_net_raw+ep ./ping赋予程序原始套接字能力,而不是直接setuid root。
3.3 Shell脚本编写与调试
3.3.1 核心原理:Shell解释器与脚本执行机制
嵌入式Linux中默认的Shell通常是BusyBox提供的ash( Almquist Shell),它比bash更轻量,但语法基本兼容。Shell脚本本质上是命令的集合,解释器逐行读取并执行。脚本第一行#!/bin/sh指定了解释器路径。在嵌入式环境中,脚本常用于系统初始化(/etc/init.d/rcS)、启动应用程序、监控硬件状态等。
Shell脚本的变量、条件判断、循环与函数是核心语法。变量赋值VAR=value(注意等号两侧不能有空格),引用变量使用$VAR或${VAR}。条件判断使用if [ condition ]; then ... fi,注意[两侧必须有空格。循环包括for、while和until。
3.3.2 调试方法:从echo到sh -x
嵌入式脚本调试受限于资源,无法使用复杂的IDE。常用调试手段如下:
| 调试手段 | 命令/语法 | 适用场景 |
|---|---|---|
| 打印变量 | echo "DEBUG: VAR=$VAR" | 快速定位变量值 |
| 语法检查 | sh -n script.sh | 检查语法错误,不执行 |
| 跟踪执行 | sh -x script.sh | 显示每条命令的执行过程,前缀+ |
| 设置退出 | set -e | 任何命令返回非零状态立即退出 |
| 设置未定义变量报错 | set -u | 防止使用未初始化的变量 |
在嵌入式设备上,由于sh -x输出较多,建议将输出重定向到文件:sh -x /etc/init.d/rcS > /tmp/debug.log 2>&1,然后通过串口或网络查看。
3.3.3 案例拆解:设备启动自检脚本
背景:某工业数据采集器,需要在开机时检查传感器通信、网络连接和存储空间,若异常则通过LED闪烁报警。
脚本设计:
#!/bin/sh
# /usr/bin/self_check.sh
LED_PIN=18
# 初始化GPIO
echo $LED_PIN > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio$LED_PIN/direction
# 定义LED控制函数
led_blink() {
for i in 1 2 3; do
echo 1 > /sys/class/gpio/gpio$LED_PIN/value
sleep 0.2
echo 0 > /sys/class/gpio/gpio$LED_PIN/value
sleep 0.2
done
}
# 检查传感器I2C通信
if ! i2cget -y 2 0x48 0x00 > /dev/null 2>&1; then
echo "Sensor I2C error"
led_blink
exit 1
fi
# 检查网络连通性
if ! ping -c 1 -W 2 192.168.1.1 > /dev/null 2>&1; then
echo "Network unreachable"
led_blink
exit 2
fi
# 检查存储空间
AVAIL=$(df /data | awk 'NR==2 {print $4}')
if [ "$AVAIL" -lt 1024 ]; then
echo "Insufficient storage: ${AVAIL}KB"
led_blink
exit 3
fi
echo "All checks passed"
exit 0
调试过程:在目标板上执行sh -x /usr/bin/self_check.sh,发现i2cget命令不存在。原来BusyBox编译时未包含i2c工具。解决方案:改用dd命令直接读取/dev/i2c-2设备节点,或者重新编译BusyBox添加i2cget。结果:修改后脚本正常运行,开机自检时间从原来的5秒缩短至2秒。复盘:脚本调试不仅要关注逻辑,还要关注目标板上的命令集是否完整。
3.3.4 常见误区与总结
- 误区一:在脚本中使用
bash特有语法(如[[ ]]、数组),但嵌入式默认sh是ash,导致语法错误。应使用POSIX兼容语法。 - 误区二:忽略命令的退出状态码。在
set -e模式下,grep未匹配到内容会返回1,导致脚本意外退出。应使用if grep -q ...; then结构。 - 误区三:在脚本中直接操作硬件寄存器而不通过设备节点,这违背了Linux的设备模型,且不可移植。
Shell脚本调试的核心是“增量验证”。在嵌入式环境中,建议先编写最小可执行片段,验证通过后再组合成完整脚本。同时,利用trap命令捕获信号进行清理操作,例如trap 'echo "Script interrupted"; cleanup' INT TERM。
- 文件操作需结合
/proc、/sys虚拟文件系统理解硬件抽象。 - 权限管理应遵循最小权限原则,善用用户、组和capabilities。
- Shell脚本调试优先使用
sh -x和set -e,注意BusyBox的语法兼容性。 - 所有命令操作前,先确认目标板上的命令集和文件系统类型,避免“主机思维”误导。
通过本章的学习,读者应能够在嵌入式Linux环境下熟练进行文件管理、权限配置和脚本自动化。这些技能是后续进行驱动开发、系统移植和物联网应用调试的基石。在实际项目中,建议开发者建立自己的常用命令速查表,并针对目标硬件定制一套自检脚本,这将极大提升开发与排障效率。