第一课:Linux 导论——前世今生、系统架构与发展脉络

本课定位:建立全局视角。暂时不追求记住所有细节,而是先弄清 Linux 从哪里来、由哪些部分组成、各部分怎样协作,以及嵌入式 Linux 开发究竟在做什么。

1. 本课目标

学完本课后,应当能够回答以下问题:

  1. Linux 是内核、操作系统,还是发行版?
  2. Unix、GNU、Linux 三者是什么关系?
  3. Linux 为什么能从个人项目发展成现代计算基础设施?
  4. 用户程序如何通过系统调用使用 CPU、内存、文件和网络?
  5. Linux 内核包含哪些主要子系统?
  6. 一块 ARM 开发板从上电到出现登录终端经历了什么?
  7. 主线内核、长期支持内核和厂商 BSP 有什么区别?
  8. 将 Linux 移植到开发板,真正需要修改哪些层?

2. Linux 到底是什么

“Linux”在不同语境中有三种常见含义,学习时必须区分。

2.1 严格含义:Linux 是内核

Linux kernel 是操作系统的核心。它运行在 CPU 的特权级中,负责管理:

  • 进程与线程;
  • CPU 调度;
  • 物理内存和虚拟内存;
  • 文件系统;
  • 设备与驱动;
  • 网络协议栈;
  • 进程间通信;
  • 权限、安全和资源隔离。

内核本身并不直接提供常用的文本编辑器、编译器、Shell、桌面环境和软件包管理器。只有内核,通常还不能构成一个方便使用的完整系统。

2.2 广义含义:基于 Linux 内核的操作系统

日常所说“我安装了 Linux”,通常是指一个完整系统,它往往包含:

  • Linux 内核;
  • C 标准库,例如 glibc 或 musl;
  • Shell,例如 Bash、Zsh;
  • 基础命令,例如 lscpgrep
  • 初始化与服务管理系统,例如 systemd、OpenRC、BusyBox init;
  • 软件包管理工具;
  • 编译器、网络工具及其他用户空间程序;
  • 可选的图形系统和桌面环境。

2.3 发行版:把组件整合成可安装、可维护的产品

Ubuntu、Debian、Fedora、Arch Linux 等是 Linux 发行版。发行版维护者会决定:

  • 使用哪个内核版本和补丁集;
  • 默认安装哪些软件;
  • 软件包格式和仓库;
  • 目录布局和系统默认配置;
  • 安全更新策略;
  • 发布周期和支持周期。

因此可以建立第一个重要公式:

Linux 系统 ≈ Linux 内核 + 用户空间 + 配置 + 软件包生态 + 维护策略

“GNU/Linux”强调很多桌面和服务器发行版同时大量使用 GNU 工具。实际交流中,“Linux”作为完整系统的简称已经非常普遍。技术讨论时,只要明确当前指内核还是完整系统即可。


3. Linux 的前世:从 Unix 说起

3.1 Multics:宏大系统带来的启发

20 世纪 60 年代,MIT、通用电气和贝尔实验室合作开发 Multics。它试图构建一个支持多用户、分时、权限控制和高可靠性的通用计算系统。

Multics 非常超前,但设计复杂、开发成本高。贝尔实验室后来退出项目。参与者 Ken Thompson、Dennis Ritchie 等人从中吸收了分时、多用户、层次文件系统等思想,同时希望做出一个更小、更清晰、更实用的系统。

3.2 Unix 的诞生

1969 年,Ken Thompson 在贝尔实验室使用 PDP-7 计算机实现了一个早期系统。1970 年左右,该系统开始被称为 Unix。

早期 Unix 使用汇编语言编写。Dennis Ritchie 设计并发展了 C 语言,Unix 随后在 1973 年前后大部分改用 C 重写。这件事意义重大:

  • 系统不再和某一种 CPU 汇编紧密绑定;
  • 移植到新硬件的成本显著下降;
  • 操作系统可以用相对高级、又能贴近硬件的语言开发;
  • C 与 Unix 由此相互推动,成为系统软件的重要基础。

3.3 Unix 哲学

Unix 不仅是一套代码,也形成了一组影响深远的工程思想:

  • 一个程序做好一件事;
  • 程序之间使用清晰接口组合;
  • 文本是常见的数据交换格式;
  • 用管道把简单工具组合成复杂流程;
  • 机制与策略尽量分离;
  • 尽量让系统行为可观察、可自动化。

例如:

journalctl -k | grep -i usb | less

这里多个程序通过管道组合:读取内核日志、筛选 USB 信息、分页查看。每个工具都不庞大,但组合能力很强。

“一切皆文件”也是 Unix/Linux 常见概括。更准确地说,Linux 尽量用文件描述符和统一的读写接口表示普通文件、终端、管道、套接字和部分设备。它是一种接口统一思想,并不是说所有内核对象在本质上都是磁盘文件。

3.4 Unix 的分化与标准化

Unix 后来形成了多个重要分支:

  • 加州大学伯克利分校发展的 BSD;
  • AT&T 推进的 System V;
  • 各硬件厂商维护的商业 Unix,例如 AIX、HP-UX、Solaris。

不同 Unix 之间出现接口差异,软件移植变得困难。POSIX 标准因此逐步形成,用于规范进程、文件、信号、线程、Shell 和基础工具等接口。

Linux 不是原始 Unix 源代码的直接后代,也没有使用 AT&T Unix 代码,但它深受 Unix 设计影响,并实现了大量 POSIX 接口,所以通常称为“类 Unix 系统”。


4. GNU:自由操作系统的用户空间基础

4.1 GNU 项目的目标

1983 年,Richard Stallman 宣布启动 GNU 项目,目标是创建一套用户可以自由使用、研究、修改和分享的类 Unix 操作系统。GNU 是递归缩写:GNU’s Not Unix。

1985 年,自由软件基金会 FSF 成立。这里的“自由”重点是软件自由,不等同于价格必须为零。

4.2 GNU 提供了什么

GNU 项目逐步开发了大量关键组件:

  • GCC 编译器工具链;
  • GDB 调试器;
  • glibc C 标准库;
  • Bash Shell;
  • GNU Binutils,例如链接器 ld、汇编器 as、目标文件工具;
  • Coreutils,例如 lscpmvcat
  • Make 构建工具;
  • Emacs 编辑器;
  • GPL 自由软件许可证。

到 1990 年代初,GNU 已拥有较完整的用户空间,但 GNU 自己的 Hurd 内核尚未达到广泛实用的状态。Linux 内核出现后,GNU 用户空间与 Linux 内核很快结合,构成了可用的自由类 Unix 系统。

4.3 GPL 的作用

GNU General Public License 是 copyleft 许可证。粗略理解,它允许用户运行、研究、修改和再发布软件;当基于 GPL 代码发布衍生作品时,需要继续提供相应源码并保留同类自由。

许可证细节涉及法律边界,不能只靠一句话概括。但从 Linux 发展史看,GPLv2 使多家公司和个人可以共同改进内核,同时要求分发相关修改时履行源码义务。这降低了某一家公司独占公共内核成果的可能性。


5. Linux 的诞生与成长

5.1 Minix 的背景

1987 年,Andrew S. Tanenbaum 发布 Minix。它主要用于操作系统教学,采用微内核思想,并提供可阅读的源代码。Linus Torvalds 在学习和使用 Minix 的过程中,希望更充分地利用自己的 80386 PC,也希望获得一个更适合实际使用和自由改造的系统。

5.2 1991:一个个人项目公开了

1991 年 8 月 25 日,芬兰学生 Linus Torvalds 在 Usenet 新闻组发布消息,介绍自己正在为 386/486 AT 兼容机编写一个自由操作系统内核。他最初称其为“只是一个爱好”,并没有预料它后来会发展到今天的规模。

1991 年 9 月,早期 Linux 0.01 源码发布。最初版本功能有限,只支持特定 x86 硬件,开发工具和用户空间则大量来自 GNU。

1992 年前后,Linux 内核转为 GNU GPLv2 许可。互联网协作、GNU 工具链、GPL 许可与低成本 PC 共同为 Linux 的快速成长创造了条件。

5.3 关键时间线

时间 事件 意义
1969 Unix 早期系统诞生 奠定类 Unix 系统思想
1973 Unix 大部分以 C 重写 提高可移植性,推动 C 成为系统语言
1983 GNU 项目启动 建设自由的类 Unix 用户空间
1987 Minix 发布 操作系统教学与早期 PC 类 Unix 实践
1991 Linux 0.01 发布 Linux 内核项目开始公开发展
1992 Linux 采用 GPLv2 形成开放协作与共享改进基础
1993 Debian、Slackware 等早期发行版出现 完整 Linux 系统更容易安装和维护
1994 Linux 1.0 发布 网络等核心功能趋于成熟
1996 Linux 2.0 发布 支持 SMP 等能力,企鹅 Tux 成为标志
2003 Linux 2.6 发布 扩展性、调度、驱动和嵌入式支持持续增强
2005 Git 为 Linux 内核开发而创建 支撑大规模分布式协作
2008 Android 1.0 发布 Linux 内核大规模进入移动设备
2011 版本号进入 3.x 版本号策略简化,不表示架构突然重写
2015 版本号进入 4.x 内核持续渐进演进
2019 版本号进入 5.x 延续成熟的开发模型
2022 版本号进入 6.x Linux 继续在硬件、性能、安全方面演进

版本号的大整数变化通常不是“全新一代系统”的标志。Linux 内核采用持续演进方式,真正重要的是具体版本包含哪些功能、修复和硬件支持。

5.4 发行版生态形成

早期用户需要手工组合内核、GNU 工具与其他软件。发行版逐渐把这些内容打包,并加入安装器、依赖管理、软件仓库和安全维护。

重要发行版及其影响包括:

  • Debian:强调社区治理、自由软件原则和稳定的软件包体系;
  • Slackware:早期重要发行版,风格简洁传统;
  • Red Hat/Fedora:对企业 Linux、RPM 生态和上游开发影响深远;
  • SUSE/openSUSE:在企业和欧洲市场有长期影响;
  • Ubuntu:基于 Debian,改善安装、桌面与定期发布体验;
  • Arch Linux:滚动更新、简洁透明,适合希望主动理解系统的用户;
  • Gentoo:以源码构建和高度可配置著称;
  • Alpine Linux:轻量,常使用 musl 和 BusyBox,在容器中常见;
  • Android:使用 Linux 内核,但用户空间与典型 GNU/Linux 发行版明显不同;
  • OpenWrt:面向路由器和嵌入式网络设备;
  • Buildroot、Yocto Project:它们更准确地说是构建嵌入式 Linux 系统的工具体系,而不是普通桌面发行版。

5.5 Linux 为什么成功

Linux 的成功不是单一技术因素造成的,而是多种条件叠加:

  1. 开放源码与协作许可:个人、学校和企业都能检查并改进代码。
  2. Unix/POSIX 接口基础:已有大量知识、工具和应用可以迁移。
  3. GNU 工具链及时成熟:编译器、Shell、库和命令已经可用。
  4. 互联网兴起:邮件列表、补丁和源码可以全球协作。
  5. 低成本硬件普及:普通 PC 就能学习和贡献内核。
  6. 可移植性:Linux 后来支持 x86、ARM、RISC-V、PowerPC 等多种架构。
  7. 工程治理逐渐成熟:维护者层级、代码审查、自动测试和稳定分支共同支撑大项目。
  8. 企业投入:云计算、服务器、芯片和设备厂商都需要一个共享内核基础。
  9. 不绑定单一产品形态:同一个内核可用于服务器、手机、路由器、汽车和开发板。

6. Linux 系统的总体架构

先建立一张自上而下的地图:

┌──────────────────────────────────────────────────────┐
│ 用户应用:编辑器、编译器、浏览器、服务器、你的程序   │
├──────────────────────────────────────────────────────┤
│ Shell / 系统服务 / 桌面 / 容器运行时 / 基础命令       │
├──────────────────────────────────────────────────────┤
│ 用户空间库:glibc、musl、libstdc++、其他运行库         │
├──────────────── 系统调用边界 ────────────────────────┤
│ Linux 内核                                            │
│ 进程调度 | 内存管理 | VFS | 驱动 | 网络 | IPC | 安全  │
├──────────────────────────────────────────────────────┤
│ 硬件:CPU、RAM、存储、网卡、USB、GPIO、显示控制器等    │
└──────────────────────────────────────────────────────┘

6.1 用户空间与内核空间

现代 CPU 提供不同特权级。Linux 把系统大致分成两类执行环境:

  • 用户空间:普通应用运行的位置,权限受限,不能随意访问物理内存或直接控制硬件;
  • 内核空间:内核运行的位置,拥有更高权限,可以管理页表、中断、设备和调度。

这种隔离非常关键。若每个程序都能直接改写任意内存和硬件寄存器,一个程序的错误就可能破坏整个系统。

在 AArch64 中,Linux 常见地让用户程序运行在 EL0,内核运行在 EL1。更高异常级可能由固件或虚拟化层使用。x86 则常用 Ring 3 表示用户态、Ring 0 表示内核态。

6.2 系统调用:用户程序进入内核的正式入口

应用需要打开文件、创建进程、申请内存或发送网络数据时,通过系统调用请求内核服务。常见系统调用包括:

  • openat():打开文件;
  • read()write():读写文件描述符;
  • mmap():建立内存映射;
  • clone():创建进程或线程;
  • execve():装入并执行新程序;
  • socket()connect():使用网络;
  • ioctl():对设备或特殊对象执行控制操作。

普通 C 程序通常不是手写 CPU 陷入指令,而是调用 libc 包装函数。例如 printf() 先处理格式化和用户态缓冲,最终可能通过 write() 系统调用输出。

应用函数 → C 库包装 → 系统调用 → 内核子系统 → 驱动/硬件

系统调用与普通函数调用不同。它会跨越权限边界,内核必须验证地址、长度、权限和对象状态。

6.3 Linux 是单体内核,但支持模块化

Linux 通常归类为单体内核:调度、内存、文件系统、网络和大量驱动都运行在同一个内核地址空间中。它不是把多数服务拆到独立用户态进程的纯微内核。

“单体”不等于“所有代码编成一个不可拆文件”。Linux 支持可加载内核模块:

  • 功能可以直接内建进内核镜像;
  • 也可以编译成 .ko 模块,在运行时加载;
  • 模块仍然运行在内核空间,出错可能导致整个系统崩溃。

查看模块的常用命令:

lsmod
modinfo 模块名
sudo modprobe 模块名

7. Linux 内核的主要子系统

7.1 进程、线程与调度

Linux 用任务来描述可调度执行实体。一个程序运行后成为进程;同一进程可以拥有多个线程,它们共享地址空间和部分资源。

内核需要解决:

  • 哪个任务下一刻使用哪个 CPU;
  • 高优先级和实时任务如何响应;
  • 多核之间如何负载均衡;
  • 阻塞任务何时睡眠、何时被唤醒;
  • 如何保存和恢复任务的寄存器上下文。

常见概念:

  • PID:进程标识符;
  • 上下文切换:CPU 从一个任务切换到另一个任务;
  • 调度类:普通、公平、实时、截止期限等不同策略;
  • 进程状态:运行、可运行、可中断睡眠、不可中断睡眠、停止、僵尸等;
  • nice 值:影响普通任务的调度权重,不是绝对优先级承诺;
  • CPU affinity:限制任务可以在哪些 CPU 上运行。

观察命令:

ps -eo pid,ppid,stat,ni,comm
top
cat /proc/loadavg

负载平均值不仅统计“正在使用 CPU”的任务,也涉及处于特定不可中断等待状态的任务,因此不能简单等同于 CPU 使用率。

7.2 虚拟内存

每个用户进程看到的是自己的虚拟地址空间。CPU 的 MMU 根据页表把虚拟地址翻译为物理地址。这样可以实现:

  • 进程之间的地址隔离;
  • 内存保护和权限检查;
  • 程序使用连续虚拟地址,而物理页不必连续;
  • 共享库和共享内存;
  • 文件映射;
  • 按需分配;
  • 写时复制;
  • 交换空间。

进程的典型虚拟地址区域可能包括:

低地址
  程序代码段 text
  只读数据 rodata
  已初始化数据 data
  未初始化数据 bss
  堆 heap           向高地址扩展(概念模型)
  mmap 映射区       共享库、文件映射、匿名映射
  栈 stack          常向低地址扩展(概念模型)
高地址

这只是帮助理解的简化图。实际布局受 CPU 架构、ABI、地址随机化和动态链接器影响。

Linux 内存管理还包括:

  • 页分配器;
  • slab/slub 小对象分配器;
  • 页缓存;
  • 内存回收;
  • OOM 处理;
  • NUMA 策略;
  • huge pages;
  • 内存控制组。

“空闲内存很少”不一定代表内存不足。Linux 会利用闲置内存做缓存,并可在应用需要时回收。观察时应综合 MemAvailable、交换活动、回收压力和 OOM 记录。

free -h
cat /proc/meminfo
cat /proc/pressure/memory

7.3 VFS 与文件系统

VFS 是虚拟文件系统层。它向上提供统一接口,向下适配 ext4、XFS、Btrfs、F2FS、NFS、procfs、sysfs 等不同文件系统。

几个重要对象:

  • inode:描述文件对象的元数据和底层操作,不等同于文件名;
  • dentry:目录项和路径查找缓存,把名字关联到 inode;
  • file:一次已打开文件实例,保存偏移量、标志等状态;
  • file descriptor:进程中的小整数,指向已打开对象。

同一个 inode 可以通过硬链接拥有多个名字。多个进程也可以打开同一个文件,并拥有不同或共享的打开状态。

特殊虚拟文件系统很重要:

  • /proc:进程和部分内核状态;
  • /sys:设备模型、总线、驱动和内核对象;
  • /dev:设备节点;
  • tmpfs:以内存为主要后端的临时文件系统;
  • cgroup2:资源控制层次。

7.4 设备模型与驱动

驱动是内核理解和控制硬件的代码。Linux 设备模型把硬件组织为总线、设备、驱动、类等对象,并通过 sysfs 暴露部分关系。

常见总线与设备类型:

  • platform device:SoC 内部不可自动枚举的外设常用;
  • PCI/PCIe:可枚举高速设备;
  • USB:可枚举外设;
  • I2C、SPI:嵌入式低速外设总线;
  • UART:串口;
  • GPIO:通用输入输出;
  • MMC/SD/SDIO:存储卡或无线模块等;
  • MIPI DSI/CSI:显示和摄像头相关接口。

驱动通常处理:

  • 寄存器和内存映射 I/O;
  • 中断;
  • DMA;
  • 时钟、复位、电源域;
  • pinctrl 引脚复用;
  • 设备与用户空间之间的接口;
  • 挂起、恢复和运行时电源管理。

7.5 设备树

ARM SoC 上大量硬件不能像 PCI 那样自动发现。设备树用于描述“板上有什么硬件、地址在哪里、如何连接、使用哪些资源”。

常见文件:

  • .dts:设备树源文件;
  • .dtsi:可复用的包含文件;
  • .dtb:编译后的设备树二进制;
  • overlay:对基础设备树进行增量修改的机制,使用方式依平台而异。

设备树描述硬件,不应塞入本可由通用驱动逻辑决定的策略。驱动读取 compatible 等属性,与能处理该设备的驱动匹配。

简化示例:

uart2: serial@fe660000 {
    compatible = "vendor,soc-uart";
    reg = <0x0 0xfe660000 0x0 0x100>;
    interrupts = <0 116 4>;
    status = "disabled";
};

&uart2 {
    pinctrl-names = "default";
    pinctrl-0 = <&uart2m0_xfer>;
    status = "okay";
};

示例中的地址、属性和中断号只是结构演示,不能直接用于我们的开发板。

7.6 网络协议栈

Linux 内核实现从套接字到网卡的网络路径,包括:

  • socket API;
  • TCP、UDP、IP、ICMP;
  • IPv4 与 IPv6;
  • 路由;
  • 邻居子系统;
  • 网络设备层;
  • 防火墙与包过滤;
  • 流量控制;
  • bridge、VLAN、bonding、tunnel;
  • 网络命名空间。

发送数据的简化路径:

应用 write/send
  → socket
  → TCP/UDP
  → IP 与路由
  → qdisc
  → 网络设备驱动
  → DMA/网卡

接收路径大体反向,但还会涉及中断、NAPI、软中断和协议处理。

7.7 IPC、同步与信号

进程间通信方式包括:

  • 管道和命名管道;
  • Unix domain socket;
  • TCP/UDP socket;
  • 共享内存;
  • 消息队列;
  • futex;
  • 信号;
  • eventfd、timerfd、signalfd 等文件描述符接口。

信号是一种异步通知机制。SIGKILLSIGSTOP 不能被普通进程捕获、阻塞或忽略;其他信号通常可以按规则处理。

7.8 权限与安全

传统 Unix 权限模型包含:

  • 用户 UID;
  • 用户组 GID;
  • 文件的 owner/group/other 权限位;
  • 特殊位 setuid、setgid、sticky;
  • root 用户。

现代 Linux 又加入:

  • capabilities:把 root 的庞大权限拆成更细粒度能力;
  • ACL:比传统三组权限更细;
  • LSM:Linux Security Modules 框架;
  • SELinux、AppArmor 等强制访问控制方案;
  • seccomp:限制进程可调用的系统调用;
  • namespaces:隔离进程视图;
  • cgroups:限制和统计资源;
  • 内核加固、模块签名、安全启动等机制。

容器主要利用 namespaces、cgroups、capabilities、联合文件系统等 Linux 能力。容器通常共享宿主机内核,所以它不是硬件虚拟机,也不能天然提供与虚拟机完全相同的隔离边界。


8. 用户空间由什么组成

8.1 C 标准库

glibc、musl 等 C 库提供:

  • ISO C 标准函数;
  • POSIX 接口;
  • 系统调用包装;
  • 动态链接器;
  • 线程、名称解析、区域设置等运行时能力。

应用二进制能否运行,不只取决于 CPU 架构,也取决于 ABI、动态链接器路径、库版本和依赖。

8.2 Shell 不是内核

Shell 是普通用户空间程序,负责解析命令、展开变量、设置重定向、创建管道、启动程序和管理作业。

command_a < input.txt | command_b > output.txt

Shell 解析这行文本后,会调用 pipe()fork()/clone()dup2()execve()wait() 等接口组织多个进程。真正执行权限隔离、管道传输和进程调度的是内核。

8.3 PID 1 与服务管理

内核启动第一个用户空间进程后,该进程通常获得 PID 1。它负责启动后续服务、处理孤儿进程,并参与关机流程。

常见实现包括:

  • systemd;
  • SysV init;
  • OpenRC;
  • BusyBox init;
  • 面向容器或专用系统的极简 init。

systemd 不属于 Linux 内核。它是常见的用户空间系统与服务管理器。

8.4 BusyBox

BusyBox 把许多常用命令实现集中在一个可执行文件中,通过不同命令名调用不同功能。它体积小、依赖少,在 initramfs、路由器和嵌入式根文件系统中非常常见。

BusyBox 命令与 GNU 同名工具的选项不一定完全一致。编写跨嵌入式系统脚本时,不能默认所有 GNU 扩展都存在。

8.5 图形系统是可选层

Linux 可以完全没有图形界面。图形栈可能包含 DRM/KMS、Mesa、Wayland/X11、窗口合成器和桌面环境。服务器与嵌入式设备经常只运行命令行或专用 UI。

内核与桌面环境不是一回事。GNOME、KDE Plasma 等是用户空间桌面系统。


9. 从上电到登录:Linux 启动链路

不同 SoC 的细节不同,但 ARM 开发板常见链路可以概括为:

上电/复位
  ↓
芯片 Boot ROM
  ↓
第一阶段加载程序(TPL/SPL 或厂商 loader)
  ↓
固件与第二阶段 Bootloader(可能含 TF-A、U-Boot)
  ↓
加载 Linux kernel + DTB + 可选 initramfs
  ↓
内核解压、初始化 CPU/内存/中断/驱动
  ↓
挂载临时或真实根文件系统
  ↓
启动 PID 1
  ↓
启动系统服务、终端、网络和应用

9.1 Boot ROM

Boot ROM 固化在 SoC 内部,由芯片厂商提供。它会根据启动引脚、熔丝或内部策略,从 eMMC、SD、SPI Flash、USB 等介质寻找下一阶段代码。

Boot ROM 能做的事情有限,通常只初始化最基础硬件并验证或加载小型启动代码。

9.2 SPL/TPL 与 DRAM 初始化

片上 SRAM 很小,而完整 U-Boot 和内核通常需要 DRAM。早期加载阶段的重要任务是初始化时钟、引脚和 DRAM,然后把更大程序载入内存。

厂商提供的 DDR 初始化代码或训练参数经常是板级启动的关键,也可能是厂商 BSP 中最难替换的部分之一。

9.3 U-Boot

U-Boot 是嵌入式系统常见 Bootloader。它可以:

  • 从存储或网络读取内核、设备树和 initramfs;
  • 设置内核命令行;
  • 选择启动槽或恢复系统;
  • 提供串口命令行;
  • 读写环境变量;
  • 执行启动脚本;
  • 在某些平台参与固件更新。

Bootloader 不等于 Linux。它在 Linux 内核接管硬件之前运行。

9.4 内核启动

内核获得控制权后会建立自身运行环境,初始化调度器、内存管理、IRQ、时钟、设备模型和驱动,并根据内核命令行寻找根文件系统。

串口日志是开发板启动排错的第一现场。常见参数包括 console=root=rootfstype=init= 等,但具体值必须根据板卡和存储布局确定。

9.5 initramfs 与根文件系统

initramfs 是内核启动早期可使用的内存文件系统。它可以装载根存储所需驱动、解密磁盘、组装 RAID、执行恢复逻辑,然后切换到真实 rootfs。

嵌入式设备也可以直接把小型 initramfs 当作最终系统,或直接挂载 eMMC/SD/NAND 上的根文件系统。

根文件系统 rootfs 是以 / 为根的用户空间目录树。它包含程序、库、配置、设备节点挂载点和运行目录等。rootfs 不是内核镜像。


10. Linux 目录树的基本角色

Linux 没有 Windows 式盘符。所有已挂载文件系统共同组成从 / 开始的一棵树。

路径 常见用途
/boot 内核、initramfs、Bootloader 相关文件,具体布局依系统而异
/dev 设备节点
/etc 系统级配置
/home 普通用户主目录
/proc 进程与内核状态的虚拟文件系统
/sys 设备模型与内核对象的虚拟文件系统
/run 本次启动期间的运行时状态
/tmp 临时文件
/usr 大量用户空间程序、库和共享数据
/var 经常变化的数据,如日志、缓存、队列
/mnt/media 临时或可移动文件系统的常见挂载点

现代发行版常采用 merged /usr 布局,/bin/sbin/lib 可能是指向 /usr 对应目录的符号链接。不要把某个历史目录布局误认为所有 Linux 系统都必须完全相同。


11. Linux 内核如何开发和发布

11.1 上游主线

Linux 内核在 kernel.org 及其 Git 仓库中协作开发。代码按子系统由多层维护者审查,再逐步汇入 Linus Torvalds 的主线。

一个典型开发周期包含:

  1. 新版本发布后开启合并窗口;
  2. 维护者提交已准备好的新功能;
  3. 发布多个 -rc 候选版本;
  4. 集中测试和修复回归;
  5. 发布新的正式版本。

Linux 内核开发不是把所有代码直接发给 Linus。绝大多数补丁先经过具体子系统维护者和邮件列表评审。

11.2 Stable 与 LTS

  • mainline:当前主线开发;
  • stable:正式版本后的重要修复分支;
  • LTS:被选中进行较长期维护的稳定内核系列;
  • 发行版内核:发行版在某个基础版本上加入补丁和配置;
  • 厂商 BSP 内核:芯片或板卡厂商维护的硬件支持版本。

LTS 不表示永远维护,也不表示每个厂商分支都能自动获得全部上游修复。必须确认具体维护期限、补丁来源和厂商同步策略。

11.3 上游与厂商 BSP

厂商 BSP 往往能最快支持新 SoC 的所有功能,但可能存在:

  • 基于较旧内核;
  • 大量未上游补丁;
  • 驱动质量和接口风格不统一;
  • 二进制固件或闭源组件;
  • 更新安全修复困难;
  • 与特定 Android 版本深度耦合。

主线内核通常拥有更好的长期维护性、代码审查和通用生态,但新芯片的某些功能可能尚未支持完整。

我们后续会同时问两个问题:

  1. 怎样用厂商 BSP 先让板子完整工作?
  2. 怎样减少厂商绑定,逐步靠近上游 Linux?

这比简单地认为“版本号越新越好”更符合真实工程。


12. 嵌入式 Linux 与桌面 Linux 有何异同

二者使用相同的 Linux 核心概念:进程、虚拟内存、VFS、驱动、网络、权限和系统调用都存在。但嵌入式系统通常更关注:

  • 启动时间;
  • 存储与内存占用;
  • 交叉编译;
  • 只读根文件系统;
  • 断电可靠性;
  • 看门狗;
  • OTA 与 A/B 更新;
  • 硬件驱动和设备树;
  • 实时性与低延迟;
  • 功耗和热管理;
  • 安全启动与固件签名;
  • 长期维护和供应链稳定性。

12.1 交叉编译

开发主机和目标板 CPU 架构不同,例如:

开发主机:x86_64 Linux
目标开发板:AArch64 Linux

此时在主机上运行 AArch64 交叉编译器,生成供目标板执行的二进制:

aarch64-linux-gnu-gcc
└──── target ────┘

工具链需要匹配目标 ABI、C 库与动态链接环境。能生成 AArch64 指令并不自动代表程序能在任意 AArch64 根文件系统运行。

12.2 BSP 是什么

BSP 即 Board Support Package,通常包含:

  • Bootloader;
  • Linux 内核及补丁;
  • 设备树;
  • 驱动;
  • 固件;
  • 交叉工具链或构建配置;
  • 根文件系统构建方案;
  • 烧录、打包与升级工具;
  • 板级示例和文档。

不同厂商对“SDK”和“BSP”的命名边界并不一致。拿到泰山派资料后,我们要先检查目录和构建脚本,而不是仅凭名称判断内容。

12.3 “移植 Linux”实际在做什么

把 Linux 带到新板卡通常不是从零编写操作系统,而是让现有各层适配硬件:

  1. 确认 Boot ROM 支持的启动介质与镜像格式;
  2. 让早期固件和 Bootloader 初始化 DRAM、时钟与存储;
  3. 配置或修改内核;
  4. 编写或调整设备树;
  5. 补充缺失驱动;
  6. 构建 rootfs;
  7. 设计分区、打包和烧录流程;
  8. 验证串口、存储、网络、USB、显示等外设;
  9. 完成更新、恢复、安全和量产策略。

真正的难点往往不是“编译成功”,而是硬件资料、启动链、驱动依赖、时序、电源管理、稳定性和长期维护。


13. 现代 Linux 开发工具与方法

我们的路线以 Linux 为开发主机,并优先采用可复现、可自动化、可观察的工具。

13.1 源码与协作

  • Git:版本管理、分支、补丁和历史追踪;
  • 邮件补丁工作流:内核社区仍大量使用;
  • Forge 平台:GitLab、GitHub 或自建服务可承载项目协作;
  • CI:自动构建、静态检查、测试与制品归档。

13.2 构建系统

  • Kbuild/Kconfig:Linux 内核自身的配置与构建体系;
  • Make、CMake、Meson、Ninja:应用和库常用;
  • Buildroot:较直接地构建工具链、rootfs、内核和 Bootloader;
  • Yocto Project/OpenEmbedded:适合复杂产品、分层元数据和长期产品化;
  • ccache/sccache:减少重复编译时间。

13.3 调试与观测

  • GDB:用户程序和远程调试;
  • strace:观察系统调用;
  • ltrace:观察部分动态库调用;
  • perf:性能分析与硬件计数器;
  • ftrace、trace-cmd:内核跟踪;
  • eBPF/bpftrace:可编程观测;
  • gdbserver:目标板远程调试;
  • JTAG/OpenOCD:早期启动和底层硬件调试,是否可用取决于芯片与板卡;
  • QEMU:模拟架构或整机,适合自动化与部分无硬件开发;
  • 串口终端:开发板启动调试最基础也最重要的工具。

13.4 可复现环境

容器、声明式构建文件和锁定的工具链可以减少“我的机器能编译”的问题。但也要注意:

  • 容器共享宿主内核;
  • USB/JTAG/串口设备需要显式授权;
  • 内核构建对文件权限、挂载和缓存有特殊需求;
  • 不能让容器掩盖真实依赖关系;
  • 最终制品仍需记录编译器、配置、源码提交和补丁版本。

“最先进”不等于追逐最新名词,而是使用可维护、可验证、自动化且适合目标的工程方法。


14. Linux 今天在哪里

Linux 已广泛用于:

  • 云计算与数据中心;
  • Web、数据库和基础设施服务器;
  • 超级计算机;
  • Android 手机和平板;
  • 路由器、交换机和防火墙;
  • NAS 和家庭服务器;
  • 智能电视、机顶盒与家电;
  • 汽车系统;
  • 工业控制与机器人;
  • 无人机、摄像机和物联网设备;
  • 开发板和单板计算机;
  • 开发者桌面;
  • Windows Subsystem for Linux 等跨平台开发环境。

它不是在所有领域都占据同样份额,也不是所有“使用 Linux 内核”的设备都提供相同用户体验。Linux 的核心优势是可塑性:从几十 MB 甚至更小的专用系统,到大型服务器,都能按需求组合。

14.1 持续发展的方向

现代 Linux 持续关注:

  • 新 CPU 架构和硬件支持,例如 ARM64、RISC-V;
  • 调度、存储、网络和内存性能;
  • 实时性;
  • Rust 等内核开发新语言的受控引入;
  • eBPF 与可观测性;
  • 安全加固、机密计算和供应链安全;
  • 容器、虚拟化与云原生工作负载;
  • 能耗、移动设备和异构计算;
  • 图形、AI 加速器及其他专用硬件;
  • 驱动上游化和长期维护。

Linux 的发展模式不是推倒重来,而是在保持用户空间兼容性和工程稳定性的同时持续演进。


15. 常见误解

误解 1:Linux 就是 Ubuntu

Ubuntu 是 Linux 发行版之一。Linux 内核还被 Debian、Fedora、Arch、OpenWrt、Android 及大量定制系统使用。

误解 2:Linux 是某家公司开发的

Linux 由全球社区与大量企业共同开发。Linus Torvalds 和维护者体系负责关键技术治理,但项目不等于某一家公司的私有产品。

误解 3:开源等于没有版权、可以任意使用

开源软件仍受版权和许可证约束。使用和分发时必须遵守 GPL、MIT、BSD、Apache 等具体许可证条件。

误解 4:root 可以绕过一切

root 权限很大,但仍可能受到内核机制、只读挂载、capabilities、LSM、安全启动、硬件保护和外部系统限制。root 也不能修复不存在的驱动或错误的硬件连接。

误解 5:内存占用越低越好

Linux 会主动把内存用于页缓存。评价内存状态要看可回收性、工作集、交换和压力,而不是只看 free 一列。

误解 6:设备树就是驱动

设备树描述硬件,驱动实现操作硬件的逻辑。只有设备树而没有匹配驱动,设备通常仍不能工作。

误解 7:换一个新内核就能支持所有硬件

硬件支持还依赖设备树、驱动、固件、Bootloader、时钟电源配置和厂商补丁。新版本可能改善支持,也可能需要迁移工作。

误解 8:能启动就算移植完成

能看到 Shell 只是早期里程碑。稳定性、外设、功耗、异常恢复、升级、安全、压力测试和维护策略同样属于移植成果。

误解 9:Shell 命令就是 Linux 内核

多数命令是用户空间程序。Shell 负责组织它们,内核提供底层资源管理和系统调用。

误解 10:容器里运行的是一套独立内核

普通容器共享宿主机内核,只隔离部分用户空间视图和资源。虚拟机通常拥有自己的客体内核。


16. 用命令观察本机 Linux 架构

以下命令都是观察性练习。先理解输出含义,不必死记参数。

16.1 内核与架构

uname -a
uname -r
uname -m
cat /etc/os-release

注意:uname -r 显示内核版本,/etc/os-release 描述发行版。两者不是同一个概念。

16.2 CPU 与内存

lscpu
free -h
cat /proc/meminfo | head -n 20

16.3 进程与 PID 1

ps -p 1 -o pid,comm,args
ps -ef | head

16.4 文件系统与挂载

findmnt
df -hT
lsblk -f

df 关注已挂载文件系统容量,lsblk 关注块设备结构,两者观察角度不同。

16.5 设备与驱动

lspci -k          # 主要用于 PCI/PCIe 设备
lsusb
lsmod
ls -l /sys/class

开发板不一定有 lspci 工具或 PCI 设备。/sys 与设备树通常更重要。

16.6 内核日志

journalctl -k -b
dmesg --color=always | less -R

是否允许普通用户读取 dmesg 取决于系统安全配置。

16.7 系统调用

strace -o /tmp/ls.strace ls /etc
less /tmp/ls.strace

观察 openatreadmmapclose 等调用,理解一个简单命令背后也需要内核提供许多服务。


17. 面向开发板的核心心智模型

以后遇到任何问题,先判断它属于哪一层:

应用程序
  ↓ API/库
用户空间运行库与系统服务
  ↓ 系统调用
Linux 内核通用子系统
  ↓ 驱动模型
具体设备驱动
  ↓ 总线/寄存器/中断/DMA
SoC 与板级硬件

启动问题再使用另一条链:

Boot ROM
  → 早期 loader/DRAM 初始化
  → 固件/U-Boot
  → Kernel + DTB
  → rootfs
  → PID 1
  → 服务与应用

排错时不要跨层猜测。例如:

  • Bootloader 根本没加载 DTB,就不要先调用户程序;
  • 驱动没有 probe,就不要先怀疑应用 API;
  • 网络接口没有 link,就不要先排查 DNS;
  • rootfs 缺少动态链接器,程序就算架构正确也启动不了。

能定位层级,通常就解决了排错的一半。


18. 本课总结

  1. 严格来说,Linux 是内核;完整可用系统还需要用户空间和发行版维护体系。
  2. Unix 提供了重要的系统思想和接口传统,GNU 提供了大量自由用户空间工具,Linux 补上了关键内核。
  3. Linux 是类 Unix、POSIX 兼容取向的系统,但不是从原始 Unix 源码直接派生出来的产品。
  4. Linux 采用单体但模块化的内核架构,核心子系统运行在内核空间。
  5. 应用通过系统调用跨越用户态与内核态边界,请求内核管理资源。
  6. 内核的主要组成包括调度、内存、VFS、驱动、网络、IPC 和安全。
  7. 嵌入式启动链包含 Boot ROM、早期加载器、Bootloader、内核、设备树、rootfs 和 PID 1。
  8. BSP 是支撑具体板卡的一组软件与配置,不只是一个内核压缩包。
  9. 厂商 BSP 功能可能完整但维护成本高,主线内核维护性好但新硬件支持可能不完整。
  10. Linux 移植的目标不是“编译通过”,而是形成可启动、可验证、可更新、可维护的完整系统。

19. 课后练习

19.1 观察练习

在 Linux 开发主机执行第 16 节命令,把结果整理到后续实验记录中,并回答:

  1. 当前发行版是什么?
  2. 当前内核版本是什么?
  3. CPU 架构是什么?
  4. PID 1 是哪个程序?
  5. 根文件系统是什么类型?
  6. 系统加载了多少内核模块?
  7. 从内核日志中找一条 USB 或存储设备初始化信息。

19.2 思考题

  1. 为什么说 Bash 不是 Linux 内核的一部分?
  2. 为什么应用不能直接访问任意物理内存?
  3. printf()write() 是什么关系?
  4. 单体内核与内核模块化是否矛盾?
  5. 设备树和设备驱动分别解决什么问题?
  6. 为什么厂商 BSP 已经能运行,却仍值得推动驱动上游化?
  7. 容器和虚拟机在内核使用方式上最根本的区别是什么?
  8. 开发板已经出现登录提示符,为什么不能据此宣布移植完成?

19.3 动手题

选择一个简单命令,例如 cat /etc/os-release,使用 strace 观察它。尝试找出:

  • 它加载了哪些共享库;
  • 它用哪个系统调用打开文件;
  • 它如何读取内容;
  • 它如何把结果写到终端;
  • 程序最后以什么状态退出。

20. 术语表

术语 含义
Kernel 内核,管理硬件与系统资源的核心软件
User space 普通应用和系统服务运行的受限空间
System call 用户程序请求内核服务的正式接口
ABI 二进制接口约定,包括调用约定、数据布局等
POSIX 一组类 Unix 操作系统接口标准
Distribution 将内核、用户空间和维护体系整合起来的发行版
BSP 板级支持包,支撑具体 SoC/板卡的软件集合
Bootloader 在内核前运行并负责加载、准备系统的软件
Root filesystem / 为根的用户空间文件系统
Device tree 描述不可自动枚举硬件及其连接关系的数据结构
Driver 控制设备并接入内核框架的代码
VFS 为不同文件系统提供统一接口的虚拟文件系统层
PID 进程标识符
MMU 完成虚拟地址转换和内存保护的硬件单元
DMA 设备与内存之间直接传输数据的机制
IRQ 中断请求
IPC 进程间通信
LTS 长期支持版本或分支,期限必须查具体维护声明
Upstream 上游项目,通常指官方主线社区
Downstream 基于上游修改的发行版或厂商分支

21. 延伸阅读

下一课:Linux 开发环境与命令行基础,从终端、Shell、路径、权限、帮助系统和文本流开始,为后续编译内核与操作开发板打基础。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐