第十六章 移植 Linux 内核
16.1 Linux内核
Linux 内核(Linux Kernel)是操作系统最核心的部分,负责管理计算机硬件资源,并为上层应用程序提供统一的服务接口。它位于硬件和应用程序之间,充当"中间人"的角色。
16.1.1 主要功能
| 功能模块 | 职责 |
|---|---|
| 进程管理 | 调度 CPU 时间,管理进程/线程的创建、执行和终止 |
| 内存管理 | 分配和回收内存,实现虚拟内存机制 |
| 文件系统 | 管理磁盘存储,提供文件读写接口 |
| 设备驱动 | 与硬件设备通信(键盘、鼠标、网卡、显卡等) |
| 网络协议栈 | 处理网络通信,支持 TCP/IP 等协议 |
| 安全机制 | 权限控制、用户认证、访问控制 |
6.1.2 架构特点
┌─────────────────────────────────────┐
│ 用户空间 (应用程序) │
│ (Shell、浏览器、编辑器、游戏等) │
├─────────────────────────────────────┤
│ 系统调用接口 │
├─────────────────────────────────────┤
│ Linux 内核空间 │
│ ┌─────────┐ ┌─────────┐ ┌────────┐ │
│ │进程管理 │ │内存管理 │ │文件系统│ │
│ ├─────────┤ ├─────────┤ ├────────┤ │
│ │设备驱动 │ │网络协议 │ │安全模块│ │
│ └─────────┘ └─────────┘ └────────┘ │
├─────────────────────────────────────┤
│ 硬件层 (CPU、内存、设备) │
└─────────────────────────────────────┘
16.1.3 Linux-6.19 目录
16.2 Linux 内核移植的主要步骤
┌─────────────────────────────────────────────────────────┐
│ 1. 准备阶段 │
│ ├── 获取内核源码 (kernel.org 或芯片厂商仓库) │
│ ├── 安装交叉编译工具链 │
│ └── 准备目标板硬件资料 (SOC datasheet、原理图) │
├─────────────────────────────────────────────────────────┤
│ 2. 配置阶段 │
│ ├── 选择参考配置 (arch/arm/configs/xxx_defconfig) │
│ ├── 使用 menuconfig 进行详细配置 │
│ └── 关键配置: 处理器架构、驱动支持、文件系统 │
├─────────────────────────────────────────────────────────┤
│ 3. 修改与适配阶段 │
│ ├── 修改设备树 (Device Tree) - 描述硬件信息 │
│ ├── 添加/修改板级支持代码 (arch/arm/mach-xxx/) │
│ └── 移植/调试设备驱动 │
├─────────────────────────────────────────────────────────┤
│ 4. 编译阶段 │
│ ├── make zImage/uImage (内核镜像) │
│ ├── make dtbs (设备树二进制文件) │
│ └── make modules (内核模块) │
├─────────────────────────────────────────────────────────┤
│ 5. 部署与调试阶段 │
│ ├── 烧录内核镜像到存储介质 │
│ ├── 配置 bootargs 启动参数 │
│ └── 调试启动过程 (串口打印) │
└─────────────────────────────────────────────────────────┘
核心认知:内核移植 90% 的工作是“配参数” ,不是“写代码”。把硬件信息准确告诉内核,内核自带的通用驱动就能跑起来。
上电 → BootROM → U-Boot (第一阶段)
↓
┌──────────────────┐
│ 初始化 DDR │
│ 初始化网卡/串口 │
│ 从存储介质加载内核 │
│ 设置 bootargs │
│ 加载设备树(dtb) │
└──────────────────┘
↓
跳转到内核入口点
↓
Linux 内核启动
↓
挂载根文件系统
↓
进入用户空间
16.2.1 与 U-Boot 的关键联系
| 联系点 | 说明 |
|---|---|
| 启动链关系 | U-Boot → Linux 内核,U-Boot 是内核的"引导者" |
| 镜像加载 | U-Boot 通过 bootm/bootz 命令加载内核镜像 |
| 参数传递 | U-Boot 通过 bootargs 向内核传递启动参数 |
| 设备树支持 | U-Boot 加载 dtb 文件并传递给内核 |
| 硬件初始化 | U-Boot 完成基础硬件初始化,内核在此基础上运行 |
U-Boot 负责“把内核搬进内存 + 递上硬件说明书”,Kernel 负责“按说明书初始化硬件 + 挂载文件系统”。
移植内核不是重写驱动,而是精准配置参数、保证 U-Boot 与 Kernel 的握手协议一致。
第17章 构建Linux根文件系统
17.1 Linux 文件系统概述
文件系统是操作系统用于管理和存储数据的机制,它定义了数据如何组织、存储和访问。
在 Windows 里,C盘、D盘是平行的,互相独立。但在 Linux 里,并没有C、D、E等盘符的概念,它以树状结构管理所有目录、文件,这个目录被称为挂接点或安装点(mount point),然后就可以通过这个目录来访问这个分区上的文件了。(挂载完成后,进入这个空目录,就等于进入了那个物理存储分区。)
比如根文件系统被挂接在根目录“/”上后,在根目录下就有根文件系统的各个目录、文件:/bin、/sbin、/mnt等;再将其他分区挂接到/mnt目录上, /mnt 目录下就有这个分区的各个目录、文件。而且起点只有一个,那就是根目录(用 / 表示) 。所有的硬盘、U盘、网络存储,都只能作为这棵树的“树枝”接入。
根文件系统的本质:系统运行的"最小依赖集合",其他分区/设备都是"可选挂载",根文件系统是"必须存在"
根文件系统 (Root File System):它是 Linux 内核启动后,强制挂载的第一个文件系统。包含这些必需、基本的文件:
-
init进程(内核启动后运行的第一个程序)。 -
Shell 程序(提供命令行的黑框框交互界面)。
-
基础依赖库(如 glibc,供其他程序调用)。
文件系统:其他分区上所有目录、文件的集合。在阅读技术文档时,必须根据语境区分“文件系统”到底在指代什么:
-
指“数据集合”(实物实体) :
-
- 指的是存放在某个分区上的所有目录和文件的总和。
-
指“存储格式”(Type/规范)(文件系统类型 (File System Type)) :
-
- 指的是数据在物理介质上存放的排列规则。常见的有针对普通磁盘的
ext2/ext3/ext4,针对 Windows 兼容的fat32/ntfs,针对嵌入式裸片 Flash 的yaffs/jffs2。
- 指的是数据在物理介质上存放的排列规则。常见的有针对普通磁盘的
虚拟文件系统 (Virtual File System)
-
实体文件系统:真实把数据写死在物理硬盘或 Flash 芯片上的结构(如 yaffs, ext3),掉电数据不丢失。
-
虚拟文件系统 (Virtual File System) :如
/proc和/sys目录。- 它们不占用任何实际的磁盘空间。
- 它们是内核在内存 (RAM) 中凭空捏造、动态生成的。
- 作用:作为内核向用户展示底层状态的“全息投影台”。比如读取
/proc/uptime文件,其实是触发了内核的计算函数,实时返回当前的开机时间,每次读都不一样。 Linux 文件属性: Linux 系统有如表17.5所示的几种文件类型。
17.2 FHS目录结构思想
Linux 根文件系统目录结构:
Linux中所有的目录不能乱建。无论是服务器还是嵌入式开发板,都建议遵循FHS标准(Filesystem Hierarchy Standard,文件系统层次标准)
| 目录 | 核心作用 | 嵌入式特别注意 |
|---|---|---|
/bin | 基本用户命令(ls, cp, cat) | 必须跟根文件系统在同一分区 |
/sbin | 系统管理命令(fdisk, reboot) | 启动修复时需要 |
/lib | 核心共享库 + 内核模块 | 动态库必须在此 |
/etc | 配置文件 | inittab, fstab, init.d/rcS |
/dev | 设备文件 | 现代用udev/systemd,嵌入式用mdev或devtmpfs |
/proc | 内核运行时信息(虚拟文件系统) | 调试神器:cat /proc/cpuinfo |
/sys | 设备驱动信息(sysfs) | 现代设备管理基础 |
/tmp | 临时文件 | 必须可写,建议挂载tmpfs |
/mnt | 临时挂载点 | 挂载U盘/网络文件系统 |
/usr | 只读的用户程序 | 可单独分区,嵌入式常精简 |
/bin + /sbin + /lib = 系统启动最小集合(必须在根分区)
/usr = 可共享的只读程序(可网络挂载)
/var = 可变数据(日志、缓存)
/tmp = 临时数据(内存文件系统)
现代实践:嵌入式系统通常把
/usr合并到根分区,用tmpfs挂载/tmp和/var/run减少Flash写入
17.3 Busybox原理
在桌面版 Ubuntu 里,/bin 下的 ls、cp、mv 是几百个独立的可执行文件,占用几十到上百 MB 的存储空间。 但在嵌入式系统里,Flash 空间极其昂贵;
Busybox 的解决方案: 它把这几百个常用的 Linux 命令,用极其精简的 C 代码重新实现了一遍,并且全部打包成唯一的、极小的可执行文件
文件系统视图
/bin/busybox ← 唯一真实二进制(几百KB~1MB)
/bin/ls -> busybox ← 软链接
/bin/cp -> busybox
/bin/cat -> busybox
/sbin/init -> busybox ← 关键:init也是busybox
/sbin/ifconfig -> busybox
17.4 Init进程启动流程
init 进程:
是由内核启动的第一个(也是惟一的一个)用户进程(进程ID为1),它根据配置文件决定启动哪些程序,比如执行某些脚本、启动shell、运行用户指定的程序等。
init进程是后续所有进程的发起者 (前面的 Bootloader(U-Boot)和 Linux Kernel 做了那么多脏活累活(初始化内存、接管中断、挂载文件系统) ,全都是为了这最后一刻:把接力棒交到用户态的手里。),比如init进程启动/bin/sh程序后,才能够在控制台上输入各种命令。
- 唯一职责:解析配置文件 → 按需创建/监控子进程(不是干活的,是派活的)
- 工控机/边缘设备:Busybox init(轻量、可裁剪)
- 复杂系统:systemd(功能强、依赖多)
启动链(从内核到shell)
内核启动
↓
挂载根文件系统
↓
执行 /sbin/init(Busybox init)
↓
├─ 执行 sysinit 动作(/etc/init.d/rcS)
│ └─ 配置网络、挂载其他文件系统
│
├─ 执行 wait 动作(等待完成)
│
├─ 执行 once 动作(一次性任务)
│
└─ 启动 respawn/askfirst 进程(shell/login)
└─ ttySAC0::askfirst:-/bin/sh
(串口控制台,按回车启动shell)
17.5 动态库依赖管理
根文件系统最小库集合
/lib/
├── ld-linux.so.3 ← 动态链接器/加载器(必须第一个存在)
├── libc.so.6 ← C标准库(几乎所有程序需要)
├── libm.so.6 ← 数学库(部分程序需要)
├── libgcc_s.so.1 ← GCC运行时(C++程序需要)
└── libpthread.so.0 ← 线程库(多线程程序需要)
动态库核心概念:静态 vs 动态
| 特性 | 静态链接 | 动态链接 |
|---|---|---|
| 链接时机 | 编译时 | 运行时 |
| 库代码位置 | 在可执行文件内部 | 在独立的.so文件中 |
| 文件体积 | 大(包含所有库代码) | 小(仅含引用信息) |
| 内存占用 | 多份拷贝(每个程序一份) | 共享一份(内存中只加载一次) |
| 更新维护 | 需重新编译程序 | 替换.so文件即可 |
| 启动速度 | 快(无需查找库) | 稍慢(需动态加载解析) |
对比示例
# 源代码:hello.c
#include <stdio.h>
int main() {
printf("Hello\n");
return 0;
}
# 静态编译
arm-linux-gcc hello.c -o hello_static -static
# 结果:约 500KB(包含完整的libc代码)
# 动态编译(默认)
arm-linux-gcc hello.c -o hello_dynamic
# 结果:约 10KB(仅代码,printf调用外部libc)
交叉编译工具链的组成架构
完整工具链 = 编译器 + 二进制工具 + C库 + 内核头文件
binutils(二进制工具)
├── as 汇编器(.s → .o)
├── ld 链接器(.o → 可执行文件)
├── objdump 反汇编/查看目标文件
├── objcopy 格式转换(生成bin/srec)
├── readelf 解析ELF格式
└── strip 去除符号表(减小体积)
gcc(编译器集合)
├── gcc C编译器
├── g++ C++编译器
└── 内部调用:预处理器 → 编译器 → 汇编器 → 链接器
C库(二选一)
├── glibc GNU C库(功能全,体积大)
├── musl 轻量级C库(体积小,静态链接优)
└── uclibc 已逐渐被淘汰
gdb(调试器)
└── arm-linux-gdb 远程调试目标程序