
1. 为什么嵌入式 Linux 也需要“安全加固”这件事先聊点直接的。很多做嵌入式 Linux 的朋友尤其是刚入行两三年的工程师对“安全”这个词的第一反应是那是搞服务器、搞网关、搞云平台的同事才需要操心的东西我做的是一个跑在板子上的裸机小系统不接公网不跑业务板子往机箱里一锁谁还能动它这个想法我前几年也这么认为。直到有一次做一款工业数据采集设备客户做渗透测试一根串口线接上去通过 busybox 自带的 telnetd 直接登陆了 root然后对着 NFS 挂载的根文件系统想看什么看什么。那一刻我才意识到所谓“别人动不了我的板子”在攻击者眼里根本不存在。你的板子物理上可接触软件上就不该裸奔你的设备一旦接入网络哪怕只是局域网就不再是“封闭环境”。嵌入式 Linux 的安全性和传统 IT 服务器的安全模型有很大区别。服务器安全讲究纵深防御、堡垒机、零信任、合规审计那是一整套复杂体系而嵌入式设备受限于 CPU 性能、Flash 容量、内存大小、内核版本老旧、开发周期短等现实条件没法把那一整套厚重的东西搬上去。嵌入式安全的本质是在有限的资源下把攻击面压到最小把攻击成本抬到最高。这篇文章聚焦的核心思路就两条裁剪和硬化。裁剪是减法能不要的组件一律不要少一个组件就少一个漏洞入口硬化是加法给保留下来要用的组件加上锁让已经进来的攻击者寸步难行。再配合日志审计做事后追溯配合轻量防火墙做网络侧的边界隔离四件事组合起来就是一套适合嵌入式 Linux 的、可落地的系统级安全加固方案。这一讲的内容相对综合把前面几讲里讲过的启动流程、文件系统构建、内核配置、BusyBox 裁剪、应用层守护策略全部串了起来算是嵌入式 Linux 开发进阶的一个综合应用场景。适合正在做量产产品、准备过安全测试、或者单纯想让自己的板子系统更“经打”的工程师参考。2. 最小化裁剪安全的第一层是“减少一切可被攻击的东西”2.1 裁剪的思路从需求倒推而不是从发行版正推嵌入式 Linux 的裁剪最常见的误区是从一个完整的发行版比如 Ubuntu/Debian 的 rootfs出发然后卸载不需要的软件包。这种“正推”思路在 X86 平台上勉强能用但在资源受限的 ARM 板子上会非常痛苦因为发行版的包管理系统、依赖关系、库文件版本都高度耦合最后裁出来的系统不光体积下不去还容易把运行环境的动态链接库搞乱。正确做法是“倒推”明确你的产品需要哪些功能然后只构建满足这些功能的最小集合。拿我做过的一款数据采集网关来说它的业务需求非常清晰开机启动采集程序从串口读取传感器数据将数据通过 MQTT 协议上报到云端支持远程通过 SSH 登录维护支持 OTA 升级固件日志写到本地 Flash掉电不丢失。基于这五个需求根文件系统的需求集合就是内核、init 进程、BusyBox提供 shell 和基本命令、libcglibc 或 musl、MQTT 客户端程序、SSH 服务端dropbear、日志写入工具syslogd 或自研轻量落盘模块、OTA 升级脚本。除此之外的一切都可以作为裁剪对象X11、桌面环境、gcc 工具链、编译器、Perl/Python 解释器、无线网卡驱动如果产品用有线网络、USB 存储支持如果产品没有外接 USB 需求……有人会问编译器、调试工具这些在开发阶段不是经常要用吗对但那是开发板的事。量产设备的 Flash 上不应该有 gcc、gdb、make不只因为体积更因为攻击者一旦拿到 shell这些工具会成为他进一步攻击的利器。开发阶段的事在开发板上解决量产镜像和开发板镜像从一开始就应该拆成两个独立目标来构建。2.2 内核裁剪的几个关键层从 config 到底层驱动内核的裁剪是整个最小化裁剪里工作量最大、也最容易出错的部分。内核配置里选项太多动辄上万项对新人来说简直是海量信息轰炸。我的习惯是抓住四个层面层层推进第一层是压缩掉不需要的文件系统支持和网络协议栈支持。量产设备一般只需要 ext4或 squashfs取决于你是否需要可写分区、proc、sysfs、devtmpfs、tmpfs再加一个设备树支持就够了。网络侧如果只是跑 TCP/UDP 和 MQTT那么 IPv6 如果不用就关掉蓝牙、802.11 无线协议栈不用就关掉Netfilteriptables 的内核模块如果系统里不用防火墙也可以先关掉——注意这一讲后面会说轻量防火墙如果你要启用 iptables 方案那 Netfilter 相关的配置就得保留裁剪的时候要给自己留好后路。第二层是驱动模型。裁剪驱动不是把整个 CONFIG_xxx 关掉就行而是要精确到具体芯片型号。比如你的板子用的是 SMSC LAN8720 的 PHY 芯片那就只保留对应的 PHY 驱动其他几百个 PHY 驱动全部关闭。这样不光内核体积缩小编译时间缩短更关键的是驱动的 probe 过程不会因为加载了多余驱动而引入不确定的中断或 GPIO 冲突。第三层是内核自身的安全子系统的取舍。内核里有大量安全相关的配置选项比如 SELinux、AppArmor、Yama、KASLR、Stack Protector、FORTIFY_SOURCE 等。嵌入式环境下SELinux 和 AppArmor 这两个重量级 LSMLinux Security Module一般不建议开原因很简单策略配置复杂调试成本高资源开销大对量产设备的 ROOTFS 和内核版本升级要求很高。但 KASLR内核地址空间布局随机化如果芯片支持建议打开Stack Protector 建议打开FORTIFY_SOURCE 建议打开dmesg_restrict 建议打开避免普通用户读取内核日志泄露地址信息。第四层是内核模块机制。如果所有驱动都编译进内核*而不是编译成模块m那么内核模块加载机制就可以整个关闭modprobe相关工具也可以从文件系统里移除。这样做的好处很明显文件系统里少一套 modules 目录和工具攻击者无法通过加载恶意内核模块来实现 rootkit这是嵌入式系统里性价比极高的一步裁剪。具体到 config 操作我的建议是不要从零手写.config那样太容易漏配置导致启动失败。正确姿势是先用厂商提供的默认配置一般在 kernel 源码的arch/arm/configs/或arch/arm64/configs/下作为基础比如make xxx_defconfig然后make menuconfig手动关掉不需要的项整个过程反复进行三轮。每一轮改完都编译烧录确认系统启动正常再进入下一轮。一次改动太多出了问题很难定位。2.3 文件系统裁剪BusyBox、glibc 与 musl 的取舍文件系统的裁剪核心看三块shell 命令集、C 库、第三方动态库。BusyBox 是目前嵌入式 Linux 的事实标准它把几百个常用 Linux 命令糅合进一个二进制文件通过符号链接和 argv[0] 来区分调用哪个命令。裁剪 BusyBox 很简单make menuconfig里把不要的命令取消勾选即可。值得一提的是BusyBox 里很多命令自带网络服务功能比如httpd、telnetd、ftpd、tftpd这些在量产设备上一律不要开启原因不用多解释了吧——攻击者最喜欢这类轻量服务配置简单、认证薄弱、漏洞多。C 库的选择对最终 bin 体积影响明显。glibc 功能完整、兼容性好但体积大musl libc 体积小、静态链接方便、安全实践更现代。如果产品没有用到 glibc 特有的一些行为比如 nss 复杂插件机制我建议项目早期就切换到 musl。切换成本主要在第三方库的编译兼容性上越早切越省事等到业务代码成型再切那才是给自己找麻烦。根文件系统的只读化某种程度上比裁剪本身更重要。把/usr、/bin、/sbin、/lib等目录放进只读的 squashfs 分区运行时数据/var、/tmp、日志文件单独放一个可写的小分区ext4 或 jffs2/ubifs挂载时加noexec、nosuid选项。这样即使攻击者把恶意文件写到 /tmp也没法直接执行先不说能不能提权至少第一步就被卡住了。2.4 裁剪的验证体积不是唯一指标功能回归才是裁剪完成后最忌讳的事是启动是正常了就以为大功告成。体积下来了、启动快了但某个业务功能在半年前就已经悄悄被裁掉了直到量产现场才暴露这种坑我踩过。我现在的做法是维护一份“功能回归清单”裁剪前后各跑一遍系统能否正常启动到 shell核心业务进程能否正常拉起进程数是否一致网络功能正常ping 通网关、DNS 解析正常时间同步是否正常NTP 或 RTC日志能否正常落盘远程维护SSH能否正常登录OTA 升级流程能否完整走通。这份清单不需要很复杂但每次裁剪后必须完整执行而且要连同客户现场的业务场景一起设计用例。光验证“能启动”远远不够嵌入式产品的价值在业务功能上不在 shell 能敲多少命令上。3. 权限硬化让攻击者“进来了也白进来”3.1 用户模型的建立从“全 root 裸奔”到最小权限账号嵌入式 Linux 开发阶段大家图方便所有进程都是 root 跑的文件权限全是 777完全没有用户体系。到了产品阶段这绝对不行。攻击者只要能拿到任意一个 shell第一件事就是看自己是不是 root如果是 root那系统基本就是他的了。合理的用户模型应该分三层root仅用于系统初始化和紧急维护平时不参与业务业务用户比如app权限只覆盖业务程序运行所需的目录和文件服务用户比如 SSH 登录维护的大人admin单独建一个用户权限低于 root某些管理操作通过 sudo 白名单控制。不要小看这个简单的分层。攻击者拿到admin或app权限之后他想读取其他业务模块的数据、篡改系统配置、加载内核模块都需要实打实的另一步提权。对大多数嵌入式攻击场景来说这就是一道足够高的门槛。3.2 关键文件权限与挂载选项的组合拳文件权限的加固重点看这几个目录和文件/etc/passwd、/etc/shadowshadow 文件必须只有 root 可读写权限 640 或 600里面不能有可直接登录的弱口令用户所有非业务用户密码一律锁定passwd -l或 shadow 中密码字段置!/etc/inittab如果还在用 init 进程这里面的终端登录选项要重点检查把不需要的 ttyS/ttyAMA 等串口登录关闭物理串口登录是嵌入式设备最容易被忽视的入口/etc/sudoers用visudo编辑只给有需要的用户授权最小白名单命令禁止任何ALL (ALL) ALL的通配授权根文件系统挂载后业务分区、日志分区一律nosuid、nodev、noexec可写分区尤甚尽量不挂exec除非业务必须在那个目录运行程序。另外/etc/sysctl.conf里的内核参数建议按下面的模板设置# 限制普通用户查看内核日志 kernel.dmesg_restrict 1 # 开启地址随机化 kernel.randomize_va_space 2 # 限制 ptrace 跨进程调试 kernel.yama.ptrace_scope 1 # 限制 core dump 泄漏内存 fs.suid_dumpable 0 net.ipv4.tcp_syncookies 1这些参数每一行都有明确的安全意图不是照抄了事建议逐条确认跟你业务的兼容性。比如kernel.dmesg_restrict 1之后普通用户无法执行dmesg了如果你的业务程序依赖读取 dmesg 做诊断那就要改成白名单方案或者把程序放到自己的用户态守护进程里处理。我的习惯是先把所有内核安全参数打开跑一周看业务有没有异常告警没有异常再固化到配置模板里。3.3 危险能力的收敛去掉 setuid 位、关闭防火墙“后门”注意setuid这个点专指文件权限里的 setuid/setgid 位不是什么网络后门过程中要确认系统里是否有不必要带setuid位的可执行文件。常见的有su、sudo、passwd、mount这些是正常的。但有的文件系统裁剪后可能因为打包工具的问题把一些不该带 setuid 位的文件也打上去了比如某个脚本、某个解压工具、某个日志工具。排查方法很简单find / -perm -4000 2/dev/null这条命令会把所有带 setuid 位的文件列出来然后逐个确认。完整系统里这是很常见的操作量产系统里务必多留个心眼。能去掉的就去掉保留的越少提权路径就越少。还有一点容易被忽略那就是内核构建时如果开了CONFIG_DEVMEM和CONFIG_DEVKMEM普通用户甚至可以通过/dev/mem直接访问物理内存完全绕过所有权限控制。量产设备建议在 kernel config 里直接关闭这两个选项。同理CONFIG_DEVKMEM从内核 4.x 后期开始逐渐被移除但老内核版本上仍然存在要主动检查。3.4 登录入口的收口SSH 配置与物理串口SSH 是嵌入式设备运维的标配但默认配置往往不够安全。先后台维护通道这条线有几点实操心得只用 dropbear 或轻量 OpenSSH老旧的 telnet 协议一律移除即使内网环境也不要用——内网攻击者的存在超乎你的想象禁止 root 直接通过 SSH 登录PermitRootLogin no或 dropbear 启动参数里禁用 root使用密钥认证代替密码认证私钥存储在维护人员的设备上公钥固化在/root/.ssh/authorized_keys或对应维护用户的~/.ssh/目录里密码认证直接关闭限制允许登录的用户白名单AllowUsers admin其他人即使有账号也进不来有条件的情况下设置 fail2ban 或类似的暴力破解防护策略但要注意嵌入式 CPU 性能有限日志分析会带来一点 CPU 开销需要评估。物理串口这块很多人容易疏忽开发板默认的 U-Boot 会等待串口输入中断引导流程如果产品允许用户接触电路板攻击者很可能通过 U-Boot 直接破坏引导环境。处理方式有两个方向修改 U-Boot 环境变量设置bootdelay-1或bootdelay0取消自动中断等待在量产阶段烧写完固件后将 U-Boot 环境变量锁定在 Flash 里禁止写入。这两条都能极大提高“拿到板子就能拿到 shell”的门槛量产产品强烈建议做到位。4. 日志审计出了事你得拿得出证据4.1 嵌入式日志审计的定位不是监控大海是回溯关键轨迹日志审计在嵌入式设备上的定位和服务器完全不同。服务器可以接 ELK、Splunk、SIEM日志量以 GB 为单位分析嵌入式设备 Flash 空间可能只有几十 MB日志不可能长期全量保存。嵌入式日志审计的目标很朴素出了问题之后能从有限的日志里回溯到关键的操作轨迹和异常事件知道发生了什么、谁干的、什么时候干的。基于这个定位日志记录的事项要精简但精准。我的建议是分三类系统类启动时间、内核 panic 记录、文件系统挂载异常、关键服务启动/退出网络类SSH 连接源 IP、登录成功/失败、防火墙拦截事件、端口扫描特征应用类业务进程启停、配置文件修改、OTA 升级发起与结果、管理员执行的关键命令。每一类日志都要有明确的上报用途和价值评估不要为了“审计”而真的把所有 debug 信息全量打出来。日志量失控在嵌入式设备上不只是浪费 Flash还会拖慢系统 IO间接影响业务实时性。4.2 常用日志链路从 syslog 到 logrotate 到远程集中嵌入式日志方案我习惯的默认组合是busybox syslogd本地日志 logrotate滚动 可选远程 syslog 转发。本地 syslogd 配置默认把日志写到/var/log/messages。由于/var通常是 tmpfs重启清空要在根文件系统挂载选项里给/var/log单独挂一个可写持久化分区否则设备重启后日志全丢审计就失去了意义。日志滚动的策略需要根据 Flash 寿命来设计。Flash 的擦写寿命有限如果每秒一条日志同一个位置反复擦写Flash 很快就废了。我的经验是把滚动周期和数量都控制得比较保守logrotate 每天切一次日志文件保留最近 7 天的量业务日志单文件大小上限 512KB超了就滚动关键操作日志单独写到一个只追加的 log 文件不允许删除直到 OTA 升级时统一归集。远程日志集中如果产品允许强烈建议启用。即使产品不能接公网在局域网上放一台日志服务器Rsyslog 或自研 UDP 收集端把多台设备的 syslog 汇聚到一处对现场问题的排查效率提升是巨大的。开启方式在 syslogd 启动参数或配置里加一个-R 服务器IP:514UDP 转发对网络带宽的占用几乎可以忽略。4.3 auditd 在嵌入式环境下的取舍与轻量替代一说日志审计很多人第一反应是 Linux 的 auditd。auditd 功能确实强大能精确记录哪个进程访问了哪个文件、哪个系统调用被执行了是服务器安全审计的标配。但到了嵌入式环境这玩意儿有点重auditd 的内核部分依赖CONFIG_AUDIT很多厂家内核默认没开auditd 用户态包含 auditd 守护进程、auditctl、aureport 等组件内存占用不小全量系统调用审计的 CPU 开销在 ARM Cortex-A7 这类芯片上会对实时业务造成可见影响规则配置需要专门的审计知识维护成本高。所以我在嵌入式设备上很少直接上 auditd。需要细粒度审计的场景我一般用这几种轻量替代核心配置文件的变更监控写一个 shell 守护脚本定期用sha256sum对关键配置文件做校验和其他节点对比后写入日志关键命令的执行记录让管理员统一从sudo通道执行管理命令sudo 自带日志功能是谁执行了什么命令一目了然网口流量异常检测抓 netfilter 的日志或定期用ss统计连接数发现异常连接写入告警日志。这些方法的审计精度不如 auditd但胜在轻量、可控、容易维护在嵌入式现场“够用”就是硬道理。4.4 日志防篡改攻击者改了日志怎么办这是日志审计里最关键也最容易忽视的问题。很多人把日志写到本地 Flash 就觉得完事了但如果攻击者拿到了 root 权限他做的第一件事往往不是继续攻击而是清理现场。日志文件可以直接删、可以改、可以rm -rf你事后拿到的“干净日志”恰恰是最不可信的部分。嵌入式环境下的日志防篡改不可能做到服务器等级的 WORM一次写入多次读取存储因为成本太高。但有几个实用技巧能大幅提高篡改成本日志文件权限设为只追加chattr a这个属性让文件只能追加不能删除和覆盖常规命令删不掉日志进程独立用户syslog 服务用单独的 syslog 用户运行业务进程即使被攻破也没有权限去动日志文件远程日志兜底本地日志无论如何都要配一份远程转发哪怕只转发“关键事件摘要”级别也比本地被清空后一无所知强关键操作日志和业务日志分文件存放避免攻击者“一个文件删干净”的便利。注意chattr a依赖文件系统支持。ext4 没问题但如果你用了 jffs2/ubifs这个属性可能无效。用之前先查一下文件系统对你所用特性的兼容性。5. 轻量防火墙网络边界上的“穷人的看门人”5.1 嵌入式场景该选 iptables 还是 nftables 还是干脆不用嵌入式设备的网络防护第一反应是上防火墙。但嵌入式环境复杂不是所有产品都需要防火墙也不是所有产品都能跑得动传统 iptables。这里我分三种情况说设备只接局域网、物理隔离、无外网连接防火墙优先级可以降低做好主机层面的端口关闭和访问控制就够了设备接入不可信网络比如工业物联网、公网边缘部署防火墙必须上至少要做到只允许业务端口进出设备资源极受限内存小于 32MB、CPU 主频极低的 MCU 类芯片别硬上 iptables改用内核仅编译需要的 Netfilter 模块配合应用层做连接白名单或者干脆用只允许特定端口监听的 bind 策略来收口。至于 iptables 和 nftables 的取舍。nftables 是新一代框架语法更清晰、性能更好、内核 3.13 之后逐步成熟但嵌入式设备上还在跑老内核的很常见老内核不支持 nftables。所以我的经验是内核版本 4.x 以上优先学 nftables内核版本 3.x 的老项目老老实实用 iptables。这不是技术偏好问题是兼容性决定论。5.2 一套可落地的 iptables 规则模板含注释下面这套规则是我在多个量产设备上反复打磨过的模板适合“设备主动外连、被动监听端口少”的典型物联网网关场景也适合做防火墙方案时的基础。按顺序执行默认策略是“入站拒绝、出站允许、回包放行”。注意规则要按顺序理解iptables 是链式的先匹配先生效# 清空所有规则初始化 iptables -F iptables -X iptables -Z # 默认策略入站拒绝、出站允许、转发拒绝 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # 允许回包连接跟踪机制 iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 允许本地回环 iptables -A INPUT -i lo -j ACCEPT # SSH 维护通道只允许内网网段访问 iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT # MQTT 业务端口根据实际需求放行示例 8883 为 TLS 加密端口 iptables -A INPUT -p tcp --dport 8883 -j ACCEPT # 日志远程转发端口只允许日志服务器 IP 通信 iptables -A INPUT -p udp --dport 514 -s 192.168.1.100 -j ACCEPT # 丢弃无效连接减少垃圾包 iptables -A INPUT -m state --state INVALID -j DROP # 记录并拒绝其他所有入站包审计日志即时产生 iptables -A INPUT -j LOG --log-prefix IPT-IN-DROP: --log-level 4 iptables -A INPUT -j DROP这套规则的核心逻辑其实就六个字白名单入站 全出站放行。物联网网关的业务方式绝大多数是“采集-处理-上报”是内向型流量为主所以入站口极其干净。如果你做的是类似远程控制的场景出站方向也要收紧那就得把 OUTPUT 链也改成 DROP 加白名单复杂度上一个台阶但原理一致。很多工程师觉得防火墙规则随便一写就行其实在嵌入式环境里防火墙的默认策略决定了系统默认的安全姿态默认放行还是默认拒绝。DevOps 里有一句话叫 “fail-secure vs fail-open”对嵌入式同样适用。默认拒绝你可能要花一点时间梳理业务端口但换来的是攻击者连端口探测都要被你 LOG 下来默认放行业务是省事了但安全事件大概率会在某个你没想到的端口上发生。我强烈建议产品从一开始就是默认拒绝的姿态不要给后期挖坑。5.3 防火墙规则落盘与开机自启的常见坑防火墙规则在嵌入式上有一个非常常见的坑iptables 命令是即时生效的但重启后所有规则全部丢失。如果你只把规则写在一个启动脚本里但启动脚本的执行顺序与网络初始化顺序冲突那就会出现“网络起来了、规则还没加载”的窗口期攻击者如果精准卡在这个时间窗口发起连接防火墙等于白装。我建议把规则固化到开机启动流程的特定阶段比如 systemd 里设置一个Afternetwork-pre.target的 service或者通过/etc/network/if-pre-up.d/脚本挂载在任何网络接口 UP 之前加载规则。同时脚本末尾加一层自检# 保存规则到文件用于事后对照 iptables-save /etc/iptables.rules # 启动后主动检查默认策略是否为 DROP不是则告警 if ! iptables -L INPUT -n | grep -q policy DROP; then logger -t iptables WARNING: INPUT policy is not DROP! fi这个自检逻辑很重要。我曾经遇到一个产品客户现场升级后防火墙规则静默丢失设备裸奔了一周没人发现直到安全扫描暴露了端口整个事故复盘下来最大的问题不是规则写得不好而是缺少“规则是否在应有的状态”的主动监测。安全加固不是配完就完它是一套持续监控的过程。5.4 防火墙日志与审计联动让每一次拦截都有迹可循防火墙之所以要配日志不只是为了排查网络问题更是安全审计的重要组成部分。试想一个场景攻击者在凌晨三点对你的 SSH 22 端口发起暴力破解扫描。你的 SSH 配置了密钥认证他无法登录但他在你的内网里有没有扫其他端口他在哪个时间段尝试了多少次这些信息如果没有防火墙日志事后根本无从查起。把 LOG 规则的日志输出到 syslog配合前面说的日志审计机制每一次拦截尝试都可以沉淀为审计线索。日志前缀IPT-IN-DROP可以定制成产品内部约定的代码方便后续用 grep 快速筛选。还要提醒一下日志频率问题一旦开启防火墙 LOG 规则如果设备正好被扫描攻击日志量会在短时间内暴增填满 Flash 分区。所以 LOG 规则要配合 limit 模块使用比如每秒只记录前几条避免日志洪峰对 Flash 寿命的影响iptables -A INPUT -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix IPT-IN-DROP: limit模块是 iptables 里很好的限流机制每秒几条的日志量既保留了证据链路又不会压垮系统。这套组合拳实战下来安全性和可用性是能兼顾的。6. 第 16 篇课后思考题完整解析把上一讲的知识点踩实既然这一讲的标题带了“第 16 篇课后思考题完整解析”那咱们就把上一讲留的几道思考题拿出来逐题拆解。第 16 讲的核心主题是“嵌入式 Linux 启动流程与 init 系统深度剖析”思考题从这个方向延展每道题的解析我都按“题目—考点—解题思路—参考答案—延伸思考”五个维度来拆这样不只是为了得到答案更关键的是理解背后的原理。6.1 思考题一init 进程的 PID 为什么永远是 1内核是怎么“生”出它的考点Linux 内核启动流程、第一个用户态进程的诞生方式。解题思路这道题的答案直接指向内核启动的最后阶段。内核完成初始化start_kernel 函数执行之后会调用kernel_init线程在这个线程里内核尝试加载并执行/sbin/init或其他通过rdinit、init参数指定的程序。kernel_init在执行 init 程序之前会先调用ramdisk_execute_command或者寻找根文件系统上的 init当这个程序被执行时内核会让它继承 PID 1。参考答案init 的 PID 固定为 1是因为它是由内核创建的第一个用户态进程。内核在 start_kernel 末尾的rest_init里创建了 kernel_init 内核线程该线程完成文件系统挂载等准备工作后通过run_init_process函数替换为用户态的 init 程序此时进程的 task_struct 仍然保留 PID 1。PID 1 的特殊性在于它是所有用户态进程的祖先kernel 在代码上保证 PID 1 不会被kill轻易杀死内核会忽略发送给 PID 1 且无 handler 的信号这也是“init 不可杀”的机制来源。延伸思考了解 PID 1 的机制后就可以理解为什么容器场景里 PID 1 进程的退出会导致整个容器退出——因为内核为容器命名空间内的 PID 1 同样设定了特殊处理逻辑。这个知识点在嵌入式里直接相关的是能否用 busybox 的 init 替换成 systemd或者在精简系统里要不要保留完整的 init 流程都取决于你对 PID 1 行为的理解程度。6.2 思考题二sysvinit 和 busybox init 在启动流程上最大的区别是什么什么场景下该选 busybox init考点init 系统的选型、启动流程机制。解题思路重点比较两者的设计哲学和适用范围。sysvinit 是传统的 SysV 风格 init通过/etc/inittab定义运行级别通过/etc/init.d下的脚本按顺序执行服务的启停具备完整的启动/停止/重启/查询状态机适合服务数量较多、管理要求高的场景。busybox init 是一个精简版 init它同样读取 /etc/inittab但支持的动作种类少、脚本编排能力弱更适合启动项少、依赖简单、资源最受限的嵌入式设备。参考答案最大区别在于“服务管理的复杂度”。sysvinit 的 inittab 可以定义运行级别切换、多个终端 getty、以及完整的服务脚本钩子支持service xxx start/stop这样的明确生命周期管理busybox init 更偏向“按顺序执行系统初始化和少量常驻进程”它的 inittab 格式更简动作类型主要是 sysinit、respawn、askfirst、wait、once缺少复杂的依赖关系和运行级别体系。当产品只需要开机拉起一两个业务进程、资源紧张的时候选 busybox init 是合理的当产品拥有多个服务、需要精细化启停管理时应该选用更完整的 init 系统或直接考虑 systemd如果内核和资源允许。延伸思考现在很多新项目直接跳过 sysvinit 用 systemd但在嵌入式资源受限场景里busybox init 依然有一席之地。这道题的核心价值在于让你理解“需求分析”永远是选型的第一步而不是别人用什么你也用什么。6.3 思考题三内核命令行里的 init/bin/sh 能启动系统吗和正常启动有什么区别考点内核命令行参数、init 进程与根文件系统挂载的关系。解题思路这道题是典型的“看着好像行实际上问题很多”的陷阱题。理解关键点在于 init 进程执行前内核已经把根文件系统挂载好了或通过 initramfs 提供了临时根fs所以 init/bin/sh 理论上能出来一个 shell。但这个 “shell” 起来后没有正常的初始化流程没有挂载 /proc、/sys、/dev没有启动服务没有配置网络很多命令都会因为缺少 /proc 等虚拟文件系统而异常。参考答案能用init/bin/sh启动启动后你会得到一个 root shell但处于“半初始化”状态。正常启动时 init 作为 PID 1 会执行系统的初始化脚本挂载虚拟文件系统、配置网络、启动服务等而直接跑 /bin/sh这些都不存在。很多嵌入式开发工程师用这个技巧做 rootfs 调试但必须手动执行mount -t proc proc /proc、mount -t sysfs sysfs /sys等操作才能让系统恢复到可用的状态。量产设备上必须禁掉这个启动参数入口否则攻击者可以在 U-Boot 里修改启动参数进入系统。延伸思考这个问题直接关联到这一讲的安全加固主题。量产 U-Boot 环境变量不加密保护的情况下攻击者修改 bootargs、加一个init/bin/sh就能拿到比你精心加固过的系统更高级的访问权。所以整个安全体系要从 U-Boot 到内核到 rootfs 全链路考虑单点加固是挡不住懂行的人的。6.4 思考题四为什么系统启动后 /dev 目录默认是空的devtmpfs 和 mdev 分别承担什么职责考点设备文件系统、动态设备管理的机制。解题思路这道题考察对 Linux 设备模型的理解。/dev 不是普通目录它是一个虚拟文件系统内容由内核动态生成或用户态守护进程填充。参考答案/dev 目录通常是 devtmpfs 或 tmpfs它不是持久化的目录所以开机时默认是空的。devtmpfs 由内核在启动时自动挂载内核会在设备注册时自动在 devtmpfs 里创建设备节点比如注册了某个平台设备内核会自动生成 /dev/xxx 节点这一过程是内核驱动的标准行为。mdev 是 busybox 提供的用户态设备管理器它通过监听内核的 uevent 来在 /dev 下创建设备节点适合处理 devtmpfs 不自动生成的那些“需要用户态规则”的设备节点比如按设备厂商/型号建立符号链接来方便应用程序访问。两者的关系是互补的devtmpfs 提供基础框架mdev或 udev/systemd-udevd做用户态的动态策略管理。延伸思考在嵌入式裁剪时要注意 devtmpfs 是 CONFIG_DEVTMPFS 内核选项裁剪掉了之后 /dev 节点不会自动创建很多驱动就会异常。mdev 则对应 BusyBox 的配置。两者配合是嵌入式系统常见的标准组合千万不要把 devtmpfs 裁没了然后疑惑为什么设备节点起不来。6.5 思考题五想要开机自动启动一个业务程序放在 rcS 脚本里的方式和做成 init.d 服务的方式哪种更好考点嵌入式系统服务管理的基本功、不同 init 机制的工程选择。解题思路这道题其实没有唯一标准答案但有一个明确的工程倾向性业务程序做成服务init.d 脚本或 systemd service比简单堆在 rcS 里更利于后续可维护性和稳定性。原因主要有三服务管理机制提供启停/重启/状态查询的统一接口崩溃后可以通过 respawn/restart 机制自动拉起对依赖关系和运行顺序有更清晰的描述。参考答案如果产品只有一个业务进程且启动逻辑极其简单放在 rcS 脚本里加点延迟和检查也能跑。但一旦业务规模增长到两个进程以上或者需要监听端口、需要崩溃自动重启就必须把启动逻辑做成一等公民的服务项通过 init 系统的 runlevels/服务脚本/依赖关系来管理。我的建议是从一开始就不要偷懒往 rcS 里堆程序哪怕只有一个进程也做成一个标准 init 脚本或 systemd unit配合日志重定向和退出码透传后续接手维护的人会感谢你的。延伸思考这个问题放到安全视角下来看更有价值。服务化之后你可以在服务脚本里天然加上权限控制用指定的低权限用户运行、chroot 束缚、资源限制 cgroups 等这比在 rcS 里全局 root 运行要安全得多。服务化不仅是工程规范是安全体系的一部分。7. 实操总结与避坑清单把整套方案落地时最值得记住的几件事这一讲覆盖的内容比较广从裁剪到硬化到审计到防火墙都是嵌入式 Linux 系统安全加固里很扎实的基础动作。最后按我这几年在真实项目里的经验浓缩成几条“没踩过坑的人不会特意告诉你”的提醒第一安全加固永远要和功能回归一起做。裁剪掉一个内核驱动、关掉某一个 sysctl 参数、把权限收紧一级都有可能在某个“你压根没想到”的业务路径上留下隐患。所有安全改动都要跑一轮完整的功能回归并且把安全改动单独做成可回退的单元不要跟业务功能混在一起发版。我见过有同事把防火墙规则和业务逻辑提交在同一个版本里结果现场业务故障回滚业务时防火墙也回滚了设备直接裸奔事后复盘就是版本管理上没把“安全配置”和“业务代码”分开。第二安全不是配完就结束要建立持续校验机制。最典型的就是防火墙规则配置好之后不校验规则什么时候丢了都不知道。我维护的脚本体系里有一个每天凌晨运行的安全自检脚本检查项包括iptables 默认策略是否为 DROP、dmesg_restrict 是否为 1、/etc/shadow 权限是否为 600、是否有新的 setuid 文件出现、syslog 是否有异常告警、关键配置文件最近 24 小时是否有变更。跑完把结果写到日志里并选择性地远程告警。这套机制虽然简单但在好几个项目里帮我提前发现了问题比事后补救强太多。第三裁剪的边界要在项目启动时就划清楚不要等产品要量产了才开始做安全。嵌入式 Linux 的安全加固不是附加项它是系统设计的一部分。你在选型芯片时就要评估Flash 容量够不够放裁剪后的 rootfs内存吃不吃得下防火墙规则表和审计缓冲内核版本能不能支撑 nftables 和现代安全特性这些决策越早做后面越从容。等到硬件定死了、业务上量了、再反过来“补课”做安全那是在拿量产时间表开玩笑。我个人的体会是嵌入式 Linux 的安全加固就在于两个词克制和敬畏。克制是对“默认开放”的本能说 NO是每一次裁剪、每一个配置都从“为什么需要”出发思考敬畏是承认系统没有绝对安全、一次加固解决不了所有问题始终把监控和审计作为安全体系不可分割的组成部分。这样的态度比记住任何一条具体命令都更重要。