Linux 系统分层架构:从硬件通电到 systemd 进程管理

Linux 系统分层架构:从硬件通电到 systemd 进程管理

一句话总结:本文梳理了 Linux 系统从硬件通电到 systemd 系统管理的完整分层架构,核心结论是内核解决"硬件抽象"和"进程运行平台"两个机制问题,systemd 解决"跑什么进程、怎么管理"的策略问题,二者职责边界清晰。

流程图

通电,从固定地址执行
加载内核+DTB到内存,跳转
创建PID 1,执行/sbin/init
按依赖图并行启动
提供IPC/日志/设备管理
svc/syscall特权指令切换
read/write/ioctl/mmap
open/read/fork/exec
硬件层 CPU/内存/Flash/GPIO
引导层 U-Boot 裸机代码
Linux 内核 驱动+进程平台
systemd PID 1 首个用户态进程
守护进程 journald/udevd/networkd
应用层 bmcweb/ipmid/shell
系统调用 用户态↔内核态 唯一入口

内容梳理

一、硬件层:硅芯片

BMC(Baseboard Management Controller - 基板管理控制器)芯片(如 ASPEED AST2500)通电后,CPU 从硬件固定的复位向量地址(通常是 SPI Flash 的 0 地址)取第一条指令执行。此时没有"内核"、没有"进程"、没有"文件"------只有裸硅。

  • CPU 内核(ARM / x86)处于最底层,上电后 PC 指针指向固件入口
  • 内存控制器(DDR Controller)尚未初始化,DRAM 不可用
  • 外设(I2C 控制器、GPIO、SPI Flash 控制器)通过物理地址映射在总线上的寄存器操作

二、引导层:U-Boot(Das U-Boot - 通用引导加载器)

U-Boot 是一个裸机程序,不依赖任何操作系统,跑在 CPU 的特权模式,负责三件事:

  1. 初始化 CPU 和 DDR 内存(芯片刚通电时内存还不能用,U-Boot 先跑在 CPU 内部 SRAM 里)
  2. 从 Flash 找到 Linux 内核镜像(Image/zImage)和设备树(DTB, Device Tree Blob - 设备树二进制)
  3. 把内核加载到内存,设置 r0/r1 寄存器,跳转过去

U-Boot 的边界:内核一旦启动,U-Boot 就从内存里消失了------它的代码段被内核覆盖,再无关联。它是一次性的。

三、Linux 内核层:两个核心职责

内核是持续运行的平台,从开机到关机一直在工作。

启动序列与 PID 初始化
复制代码
start_kernel()
  → setup_arch()            // 根据设备树初始化平台硬件
  → mm_init()               // 初始化内存管理子系统
  → sched_init()            // 初始化进程调度器
  → rest_init()
      → 手动构造 PID 0      // idle task, 每个CPU一个, CPU空闲时跑halt指令
      → kernel_thread(kernel_init)  // 创建 PID 1 (内核线程)
          → 加载驱动, mount 根文件系统
          → run_init_process("/sbin/init")
          → PID 1 从内核线程变为用户态进程 systemd
      → 创建 PID 2          // kthreadd, 后续所有内核线程由其 fork

关键纠正:PID 0 不是"进程",是内核本身的 idle task 化身,永远不退出。PID 1 是第一个用户态进程。所有后续用户态进程由 PID 1 fork,所有内核线程由 PID 2 fork。

职责一:硬件抽象(驱动)

驱动全在内核态,但通过三种接口向用户态暴露能力:

接口 例子 用户态操作
/dev/ 设备节点 /dev/i2c-3 open()ioctl(I2C_RDWR) → 内核操作 I2C 控制器寄存器
/sys/ sysfs /sys/class/hwmon/hwmon0/temp1_input cat 读 → 驱动当场读传感器寄存器 → 返回值
/proc/ procfs /proc/cpuinfo 内核汇总信息暴露

驱动分层结构:

复制代码
硬件寄存器 (物理地址 0x1e78a000)
  → 平台驱动 (i2c-aspeed.c, AST2500 I2C 控制器)
    → 驱动框架 (i2c-dev.c 字符设备 / hwmon.c sysfs 框架)
      → 暴露给用户态 (/dev/i2c-3 / /sys/class/hwmon/)
职责二:进程运行平台(调度与隔离)
  • 调度器(CFS, Completely Fair Scheduler - 完全公平调度器):单核物理 CPU 通过时间片轮流跑各个进程,一秒切换几十次,看起来像同时运行
  • MMU(Memory Management Unit - 内存管理单元):每个进程有独立的虚拟地址空间,0x400000 在进程 A 里和进程 B 里映射到完全不同的物理内存,互相不可见
系统调用:用户态和内核态的唯一合法入口

系统调用不是独立的一层,而是内核层顶部的"门"。用户态进程通过特权指令(ARM: svc / x86: syscall / 旧 x86: int 0x80)触发 CPU 异常,CPU 切换到特权模式,内核处理请求后返回,CPU 切回非特权模式。

复制代码
用户态: open("/dev/i2c-3", O_RDWR)
  → glibc 翻译 → svc #0 (ARM 特权指令)
  → CPU 切到内核态 → sys_call_table[__NR_openat]
  → 内核找到 inode → 找到驱动 open 函数 → 返回 fd
  → CPU 切回用户态 → 进程拿到 fd

四、systemd 层:系统管理

systemd 是内核启动的第一个用户态进程(PID 1),它的边界是:只管理进程,不管硬件

systemd 做 systemd 不做
成为 PID 1,是所有用户态进程的祖先 不管硬件驱动(那是内核的活)
管理服务启动顺序和依赖(DAG 依赖图并行启动) 不管理内存页表和虚拟地址(MMU 在内核里)
监控进程状态,崩溃自动重启 不调度进程(CFS 调度器在内核里)
收集日志(journald) 不暴露设备节点(udev 创建 /dev/ 但实际 IO 由内核驱动处理)
管理 cgroup 资源限制 不写驱动代码
提供基于 D-Bus 的 IPC 通信 socket 机制由内核提供
核心设计哲学
  • cgroup 集成 :每个服务属于一个 cgroup,能精确跟踪所有子进程,kill 不会漏掉 fork 出来的子进程
  • socket 激活:systemd 先创建 socket 监听,有请求才 wake up 真正服务,服务挂了不影响 socket 队列
  • 统一状态机:所有 unit(service/socket/mount/timer/device)用同一套状态机(active/inactive/failed/reloading)

五、Init 系统历史演化

SysV init(1983 年)

通过 /etc/rc.d/rc?.d/ 下的 shell 脚本按编号串行启动服务。

  • 痛点:完全串行、依赖靠数字编号暗示、启动完就不管、没有 cgroup、shell 脚本脆弱、碎片化严重
Upstart(2006 年,Ubuntu 主导)

引入事件驱动启动,"网络就绪"事件可同时触发 sshd 和 httpd 并行启动,支持崩溃自动重启。

  • 遗留问题:依赖表达仍模糊、无 cgroup 集成、配置文件与 Debian/Fedora 不通用、社区分裂
systemd(2010 年,Red Hat)

不是单纯的 init 重写,而是重新定义了 Linux 系统管理层的设计。整合了 init + inetd + crond + syslogd + udev + ConsoleKit + ...,用统一设计哲学替代一堆零散组件。

总结与展望

总结

  • 内核提供"机制"(硬件抽象 + 进程平台),systemd 提供"策略"(跑什么、怎么管)
  • PID 0 是内核 idle task 化身而非进程,PID 1 是第一个用户态进程,PID 2 创建所有内核线程
  • 驱动全在内核态,通过 /dev、/sys、/proc 三种接口向用户态暴露能力
  • 系统调用是穿越用户态/内核态边界的唯一合法入口,通过 CPU 特权指令实现
  • systemd 的统一整合思路用 cgroup + socket 激活 + 统一状态机解决 SysV init 的根本缺陷

展望/趋势

  • systemd 在嵌入式领域的渗透:OpenBMC 全面使用 systemd 管理 BMC 服务,其 socket 激活对资源受限芯片尤为有利(按需启动,省内存)
  • 引导层简化:U-Boot 正在被 Linux 内核自带的直接启动方案(如 LinuxBoot)部分替代,减少不必要的引导代码
  • 内核态/用户态边界演变:eBPF 模糊了这条边界------允许用户编写的安全代码在内核态运行,未来设备驱动的一部分逻辑可能被推到用户态(DPDK/SPDK 思路)
  • systemd 的争议与接受:systemd 的"大一统"哲学至今有争议,但它解决了 Linux 生态碎片化的实际问题,已成为事实标准

深度思考:大一统 vs 小零件的哲学之争

争议不浮于表面,根源在耦合强度

systemd 争议看似是"技术好不好用"的争论,但本质上是两种软件设计哲学的对立。理解这个对立,比记住 systemd 本身更重要。

两种哲学定义

A. 大一统(Monolithic / Integrated --- 单体整合设计)

把相关的事放在一起做,统一设计,统一接口。

systemd 是典型。另一个典型是 Linux 内核本身------驱动、文件系统、网络栈全在内核态同一个地址空间里跑。PostgreSQL(PostgreSQL --- 关系型数据库)也是一个引擎统一处理关系数据、JSON、时序和向量存储。

B. 小零件(Modular / Composable --- 模块化可组合设计)

每个工具只做一件事,通过标准接口组合成复杂系统。

Unix 管道的极致表达:ls | grep | sort | wc,每个工具只做一件事,拼接完成复杂任务。微服务架构也是这个思路------几百个独立的小服务各自部署,通过 API 互相调用。

两种哲学的优缺点对比

维度 大一统 小零件
调试排查 同一进程内看调用堆栈,边界清晰 问题跨越几十个服务,需要分布式追踪
理解成本 新人要面对一个巨大的整体才能上手 每个零件简单,但零件间的配合规则同样要学,总认知负担未必小
接口稳定性 内部可以快速重构,私有边界改动自由 零件靠公开接口协作,一改接口就破坏兼容性,不敢轻易动
创新速度 所有改动要经过核心团队,遗留决策会逐渐让系统变重 每个零件独立演进,坏掉只影响自己;但版本兼容矩阵会慢慢爆炸
故障半径 核心崩了全崩,边界少所以故障源少 一个零件崩了其他还能跑,但零件太多每个都可能是故障源
标准化 一个仓库风格统一 每个团队按自己的偏好来,生态碎片化
强制绑定 用 A 就绑死 B/C/D,想换代价大 按需自选零件,但也意味着你自己负责组装和验证兼容性

谁在什么场景赢了

场景 赢家 原因
操作系统内核(Linux vs 微内核) 大一统 内核组件间调用极频繁,IPC(Inter-Process Communication --- 进程间通信)开销承受不起;微内核(Minix / GNU Hurd)在这个领域失败了
init 系统(systemd vs SysV 脚本) 大一统 碎片化 shell 脚本各自为政,统一管理才解决依赖编排和资源控制的刚需
Web 后端(微服务 vs 单体) 来回摇摆 当前微服务占优(独立扩缩容),但拆太细后调试和事务一致性成新灾难;近年"Modular Monolith"(逻辑模块化但部署一体)开始回归
Unix 命令行(pipe 组合) 小零件 至今未被挑战,设计典范
数据库(PostgreSQL vs 专用存储引擎) 大一统 一个引擎覆盖多种数据类型,运维成本低
前端框架(全栈 vs 微前端) 来回摇摆 目前全栈框架在回归

本质规律:耦合强度决定正确选择

识别系统组件之间的耦合强度,是判断用哪种哲学的第一性原理。

复制代码
紧密耦合(调用频繁、共享状态、低延迟要求)
  → 大一统更合适
  → 拆开反而引入 IPC 开销和状态同步问题
  → 例:内核、数据库引擎、init 系统

松散耦合(独立运行、偶尔通信、对延迟不敏感)
  → 小零件更合适
  → 硬绑在一起反而拖慢迭代、放大故障半径
  → 例:命令行管道、微服务、浏览器插件

systemd 争议的真正根,在于 Linux 系统管理的各个组件(日志、网络、设备管理)到底是"紧密耦合"还是"松散耦合"------反对派认为它们是松散的(不该绑在一起),支持派认为它们实际上是紧密的(进程日志必然和进程生命周期挂钩,网络状态必然影响服务启动依赖)。事实证明支持派的判断更准确。

我从中得出的判断框架

  1. 不要先站队,先问耦合强度。遇到"大一统还是拆零件"的选择,不做风格偏好判断,先画组件间的调用关系图,看频率、看共享状态、看延迟要求
  2. 在错误的地方用错误的哲学是灾难。把 Linux 内核拆成微服务是灾难,把命令行工具写成大一统框架也是灾难
  3. 两种哲学不是对错问题,是场景适配问题。一个人的"单体怪兽"是另一个人的"内部重构空间";一个人的"灵活组合"是另一个人的"集成噩梦"
  4. 争论本身常常浮于表面。systemd 十年论战中,喊"违反 Unix 哲学"的人和喊"解决了真实问题"的人,讨论的经常不是同一层------前者在谈美学,后者在谈实践
相关推荐
hehelm3 小时前
Linux 信号(Signal)
linux
cui_ruicheng3 小时前
Linux网络编程(九):应用层协议与序列化
linux·运维·服务器·网络
kobe_OKOK_4 小时前
ubuntu server 存儲空間占滿的原因
linux·运维·ubuntu
Nian.Baikal4 小时前
从零搭建离线地图服务:Nginx + Cesium/Leaflet 实战指南
运维·前端·nginx
百度智能云技术站4 小时前
当 CPU 成为 GPU 的隐性瓶颈:Btune 2.0 用自动化耗时分析打破性能黑盒
运维·自动化·gpu算力
电商API_180079052474 小时前
京东API对接|实现批量自动化获取京东商品价格更新商品库
大数据·运维·数据挖掘·自动化·网络爬虫
诸神缄默不语4 小时前
在Linux中使用Vim编辑文本
linux·vim
菜鸟是大神4 小时前
07-Claude Code 的常用命令和快捷键
linux·运维·服务器