找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1426

积分

0

好友

208

主题
发表于 3 天前 | 查看: 8| 回复: 0

OpenWrt的启动流程根植于标准Linux系统,但因其典型的嵌入式环境(如无硬盘、只读文件系统、低内存)而引入了独特的初始化机制。本文将从硬件上电开始,系统性地剖析OpenWrt的完整启动链条,涵盖Bootloader(通常为U-Boot)、Linux内核初始化、根文件系统挂载、init进程启动以及OpenWrt特有的procd初始化系统与服务脚本执行逻辑。

Bootloader阶段:U-Boot的引导作用

设备上电后,首先执行的是固化在ROM或Flash中的Bootloader。在绝大多数MIPS/ARM架构的路由器中,这一角色由U-Boot(Universal Boot Loader)承担。

U-Boot的核心任务包括:

  1. 硬件初始化:设置CPU频率、初始化内存控制器(DDR)、串口(用于早期调试输出)等基础外设。
  2. 加载内核镜像:从Flash存储(如NOR/NAND/SPI-NOR)或网络(通过TFTP协议)读取压缩的Linux内核镜像(通常为vmlinuzzImage)到内存中。
  3. 传递启动参数:通过bootargs环境变量向内核传递关键参数,例如:
    bootargs=console=ttyS0,115200 root=/dev/mtdblock2 rootfstype=squashfs,jffs2

    其中root=指定了根文件系统所在的MTD分区,rootfstype=指明了文件系统类型(OpenWrt常用SquashFS + JFFS2的组合)。

  4. 跳转至内核入口:将CPU的控制权移交至内核镜像在内存中的入口地址。

关键点:U-Boot本身并非OpenWrt项目的一部分,它是由设备厂商或硬件平台提供的底层固件。OpenWrt构建系统主要负责生成内核和根文件系统镜像,由用户或刷机工具将其写入Flash的指定分区。

Linux内核初始化阶段

内核镜像被加载到内存并解压后,开始执行。此阶段与通用Linux系统基本一致,主要包括:

  • 架构相关初始化:初始化CPU、内存管理单元(MMU)、中断控制器等。
  • 设备树(Device Tree)解析(针对ARM/MIPS等新平台):内核根据编译时嵌入或U-Boot传递的.dtb(Device Tree Blob)文件来识别硬件资源,例如GPIO、UART、Flash分区布局等。
  • MTD子系统初始化:OpenWrt设备通常使用SPI-NOR或NAND Flash,内核通过MTD驱动将其划分为多个逻辑分区。可以通过cat /proc/mtd命令查看:
    dev:    size   erasesize  name
    mtd0: 00040000 00010000 "u-boot"
    mtd1: 00100000 00010000 "kernel"
    mtd2: 00e80000 00010000 "rootfs"
    mtd3: 006a0000 00010000 "rootfs_data"
  • 根文件系统挂载:内核根据U-Boot传递的bootargs中的root=参数挂载根文件系统。OpenWrt采用了SquashFS + JFFS2 overlay的经典方案:
    • SquashFS:只读的压缩文件系统,存放基础系统文件(/bin, /sbin, /etc等),具有节省空间和防篡改的优点。
    • JFFS2:可读写的日志型闪存文件系统,用于存储用户配置和运行时数据。
    • OverlayFS:内核利用OverlayFS,将只读的SquashFS作为lowerdir,可读写的JFFS2分区作为upperdir,合并挂载为一个对用户而言可写的根目录/
      挂载顺序示例:
      1. 内核挂载SquashFS分区到/rom
      2. 挂载JFFS2分区到/overlay
      3. 使用OverlayFS将/rom/overlay合并挂载为/
  • 启动init进程:内核完成所有初始化后,会尝试执行用户空间的第一个进程——/sbin/init(其进程ID为1)。在OpenWrt中,这个程序就是procd

用户空间初始化:procd与init脚本体系

OpenWrt为了适应嵌入式设备的资源限制,放弃了传统的SysV init或systemd,转而使用自主开发的轻量级procd作为init系统。procd不仅是PID 1,还集成了服务管理、热插拔事件处理和系统日志收集等职责,是云原生/IaaS理念在嵌入式领域的轻量化实践。

1. procd的启动流程

procd的执行分为两个阶段:

  • 第一阶段(preinit)
    内核启动/sbin/init(即procd)后,procd首先执行/etc/preinit脚本。该脚本负责最基础的初始化工作:
    • 挂载必要的虚拟文件系统(/proc, /sys, /tmp)。
    • 初始化设备文件系统/dev(通常通过mdev)。
    • 挂载/rom(SquashFS)和/overlay(JFFS2)。
    • 最后,通过exec重新执行/sbin/procd进入第二阶段。
      关键代码片段(/etc/preinit):
      mount -t proc proc /proc
      mount -t sysfs sysfs /sys
      mount -t tmpfs tmpfs /tmp
      # 挂载 squashfs 到 /rom
      mount -t squashfs /dev/mtdblock2 /rom
      # 挂载 jffs2 到 /overlay
      mount -t jffs2 /dev/mtdblock3 /overlay
      # 启动 procd 第二阶段
      exec /sbin/procd
  • 第二阶段(主init循环)
    procd以PID 1的身份重新运行,开始解析/etc/inittab并进入主事件循环,执行系统初始化任务。
2. /etc/inittab:初始化行为定义

OpenWrt的/etc/inittab通常非常简单:

::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
ttyS0::askfirst:/bin/ash --login
  • ::sysinit:...:系统初始化时,执行/etc/init.d/rcS S boot
  • ::shutdown:...:系统关机时,执行清理脚本。
  • ttyS0::askfirst:...:在串口ttyS0上启动一个登录shell(通常需要用户按回车键激活)。

注意:OpenWrt没有运行级别(runlevel)的概念,因此其inittab被极大简化。

3. rcS脚本与/etc/init.d/服务体系

/etc/init.d/rcS是OpenWrt服务启动的核心调度器。其逻辑是:按数字顺序执行/etc/rc.d/目录下所有以S开头的符号链接。这些链接指向/etc/init.d/目录中的实际服务脚本。

例如,/etc/rc.d/目录结构可能如下:

/etc/rc.d/
├── S10boot -> ../init.d/boot
├── S20network -> ../init.d/network
├── S50dropbear -> ../init.d/dropbear
└── S99done -> ../init.d/done

每个/etc/init.d/xxx服务脚本必须支持startstopenabled等标准命令,并通过USE_PROCD=1声明由procd管理。一个典型的后端与架构服务脚本结构(以/etc/init.d/network为例)如下:

#!/bin/sh /etc/rc.common
START=20
USE_PROCD=1

start_service() {
    procd_open_instance
    procd_set_param command /sbin/netifd
    procd_set_param respawn ${PROCD_RESTART_THRESHOLD:-3} ${PROCD_RESTART_TIMEOUT:-5} ${PROCD_RESTART_RETRY:-5}
    procd_close_instance
}

procd会解析这些脚本,在系统启动时调用start_service()函数来启动守护进程,并利用respawn参数监控其生命周期,实现进程崩溃后自动重启。

启动顺序与依赖管理

OpenWrt不提供显式的依赖声明机制(如LSB headers)。服务启动顺序完全由脚本中的START=数值决定,数值越小,启动越早。开发者需要手动规划START值以确保依赖关系,例如:

  • S10boot:挂载文件系统、初始化系统时钟(最早启动)。
  • S20network:配置网络接口(依赖boot已完成)。
  • S50dropbear:启动SSH服务(依赖network已就绪)。

如果顺序设置错误,可能导致依赖服务未就绪而启动失败。

关键日志与调试手段

分析和调试启动过程离不开日志。在OpenWrt中,可以通过以下方式获取启动日志:

  1. 串口输出(最可靠):通过UART连接设备,可以观察从U-Boot到内核再到用户空间的完整输出。
  2. dmesg命令:查看内核环形缓冲区中的日志,包含MTD分区、设备驱动加载、文件系统挂载等关键信息。
  3. logread命令:查看由procdlogd守护进程收集的用户空间系统日志与服务日志。
  4. *`/tmp/log/_boot.log`文件**:部分OpenWrt版本会将启动阶段的日志缓存至此目录。

一段典型的启动日志片段如下:

[    0.000000] Linux version 5.4.188 ...
[    1.234567] 4 fixed-partitions partitions found on MTD device firmware
[    1.235000] Creating 4 MTD partitions on "firmware":
[    1.235001] 0x000000000000-0x000000040000 : "u-boot"
[    1.235002] 0x000000040000-0x000000140000 : "kernel"
[    1.235003] 0x000000140000-0x000000fc0000 : "rootfs"
[    1.235004] 0x0000006a0000-0x000000fc0000 : "rootfs_data"
...
[    5.123456] VFS: Mounted root (squashfs filesystem) readonly on device 31:2.
...
- preinit -
[   10.000000] procd: - early -
[   10.100000] procd: - watchdog -
[   10.200000] procd: - ubus -
[   10.300000] procd: - init -
[   10.400000] kmodloader: loading kernel modules from /etc/modules-boot.d/*
[   10.500000] kmodloader: done loading modules from /etc/modules-boot.d/*
[   10.600000] procd: - init complete -

常见启动问题与排查思路

1. 内核无法挂载根文件系统
  • 现象:启动卡在 VFS: Cannot open root deviceKernel panic - not syncing
  • 原因
    • U-Boot的bootargsroot=参数指向了错误的MTD分区。
    • Flash的实际分区布局与内核设备树(Device Tree)中的定义不匹配。
    • SquashFS镜像损坏。
  • 解决
    • 进入U-Boot命令行,检查并修正bootargs环境变量。
    • 核对内核编译所用的DTS文件中的partition@节点定义。
    • 重新刷写完整的固件。
2. procd未启动或卡死
  • 现象:内核启动信息打印完后,串口无任何用户空间输出,也无法获得shell。
  • 原因
    • /sbin/init(procd)文件丢失或执行权限错误。
    • /etc/preinit脚本存在语法错误导致执行中断。
    • JFFS2分区损坏,导致/overlay挂载失败。
  • 解决
    • 通过U-Boot加载initramfs镜像进入临时系统进行修复。
    • 在U-Boot中设置bootargs,添加init=/bin/sh参数,直接进入单用户模式shell进行排查。
3. 服务未按预期启动
  • 现象:Web管理界面无法访问、SSH服务连接失败。
  • 原因
    • /etc/rc.d/目录中缺少对应服务的Sxx启动链接(可通过/etc/init.d/<service> enable创建)。
    • 服务脚本的START=值设置过大,其所依赖的服务(如网络)还未准备好。
    • 服务脚本中未设置USE_PROCD=1,导致procd未能正确接管。
  • 解决
    • 手动执行/etc/init.d/<service> start测试服务是否能独立运行。
    • 使用logread | grep <service>过滤查看该服务的详细启动日志。
    • 检查/etc/init.d/<service>脚本结构是否正确。

附:关键路径与命令速查

  • 内核启动参数cat /proc/cmdline
  • MTD分区信息cat /proc/mtd
  • 服务脚本目录/etc/init.d/
  • 启动链接目录/etc/rc.d/
  • 早期初始化脚本/etc/preinit
  • 系统日志logread
  • 内核日志dmesg

通过结合分析OpenWrt源码(如GitHub仓库中的package/system/procdtarget/linux/generic/base-files等目录)与实际设备的运行日志,可以更深入地理解和验证整个启动机制。

总结

OpenWrt的启动过程是一个从硬件底层到用户空间服务的精密协作链,其核心设计思想体现了嵌入式Linux系统的特点:

  1. Bootloader与系统解耦:U-Boot负责最底层的硬件初始化,OpenWrt聚焦于内核与根文件系统。
  2. 只读+可写的混合文件系统:SquashFS + JFFS2 + OverlayFS的组合,在确保基础系统安全性与空间效率的同时,提供了必要的可写配置存储。
  3. 轻量级Init系统:procd替代了传统笨重的init,集成了服务管理、事件处理等核心功能,非常适合资源受限的运维与DevOps环境。
  4. 简单直观的服务启动:基于/etc/rc.d/SXX数字排序的启动方式,虽然需要手动管理依赖,但足够简单、清晰。

深入掌握OpenWrt的启动流程,不仅有助于进行自定义开发(如编写初始化脚本、调试内核模块),更能为固件维护和线上故障排查提供坚实的理论依据与实践指导。




上一篇:dma-buf systemheap mmap性能优化:从逐页映射到批量处理的35倍提升
下一篇:POSIX线程实时调度核心机制:SCHED_FIFO与SCHED_RR策略详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 19:14 , Processed in 0.309560 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表