第 1 章 Linux 与 Ubuntu 系统地图¶
机器人上位机的操作系统几乎清一色是 Ubuntu。装完系统、进入桌面就开始干活之前,有几张底图必须先画清楚:Linux 是内核而不是操作系统;Ubuntu 的发行版号与内核号分两条编号线;用户态程序进内核的唯一常规入口是系统调用;目录不是随便分的,FHS 约束每个顶层目录的语义;版本自查有三把各管一段的尺子;终端本身也是一类设备。这张地图决定后续排障能不能定位到具体一层——"网络不通"是内核路由还是 userspace NetworkManager、"GUI 起不来"是驱动还是显示服务器、"服务起不来"是配置还是终端环境,都要求先把这几层分开看。
1. Linux 是内核,不是操作系统¶
严格意义上 "Linux" 只指 Linus Torvalds 1991 年发起维护的内核(kernel),它负责进程调度、内存管理、文件系统、设备驱动与系统调用(system call,用户态进入内核态的唯一常规入口)。
一个可被日常使用的操作系统还需要一整套用户态组件:C 标准库(glibc 或 musl)、核心工具集(GNU coreutils,ls / cp / cat 这类)、Shell(Bash、Dash)、初始化系统(systemd)、包管理器(apt / dpkg)、桌面与图形栈(X.Org 或 Wayland、GNOME)等。这些组件大多来自 GNU 项目,所以严格称 "GNU/Linux"。
发行版(distribution,distro)做的是"组装与集成":挑选一组用户态组件、绑定一个内核版本、按目标场景调整默认配置、提供持续更新的包仓库。Debian、Ubuntu、Fedora、Arch 都是发行版;它们共用同一个 Linux 内核主线,差别在用户态组件与运维策略。
Ubuntu 的层级关系:
| 层 | 组件 | 由谁维护 |
|---|---|---|
| 内核 | Linux kernel | 上游 Linus 树 + Canonical 补丁(linux-* 系列包) |
| 系统库 | glibc、OpenSSL、libstdc++ | Debian 上游 + Canonical 打包 |
| 初始化与服务 | systemd | systemd 项目 + Canonical 默认配置 |
| 包管理 | apt / dpkg | Debian 项目(Ubuntu 沿用) |
| 桌面环境 | GNOME / KDE 等 | 上游项目 + Ubuntu 默认 GNOME |
| 安装器与云镜像 | subiquity / cloud-init | Canonical |
Canonical(Ubuntu 的商业母公司)从 Debian 拉取上游、按 LTS 冻结包版本并回迁安全补丁(backport),再发布桌面版、服务器版、Core、Cloud Image 等多种形态。这解释了一个常见现象:LTS 冻结的是包版本,不是安全边界——同一个包名在 22.04 与 24.04 上的版本可能差着两三代上游。
内核、用户态与发行版的分层关系:
flowchart TB
distro["发行版:挑选用户态组件 + 绑定内核 + 提供包仓库"]
subgraph user["用户态(GNU 系组件为主)"]
libc["C 标准库 glibc"]
core["GNU coreutils"]
init["systemd"]
pkg["apt / dpkg"]
gui["X.Org / Wayland + GNOME"]
end
kernel["Linux 内核<br/>调度 / 内存 / 驱动 / 系统调用"]
hw[("硬件")]
distro --> user
user -->|"系统调用:唯一常规入口"| kernel
kernel --> hw
2. Ubuntu 版本编号与内核策略¶
Ubuntu 的"版本"是两套独立编号在同时走:发行版号与内核号。混谈这两条线会导致大量"明明 Ubuntu 一样、行为却不同"的困惑。
2.1 发行版:数字版本 + 字母代号¶
发行版每半年发布一次,命名规则 YY.MM:2022 年 4 月即 22.04、2024 年 10 月即 24.10。每个版本还配一个"形容词 + 动物"代号,首字母按 a→b→c 递增:
- 22.04 Jammy Jellyfish(LTS)
- 24.04 Noble Numbat(LTS)
- 24.10 Oracular Oriole
- 25.04 Plucky Puffin(本板块基线外)
LTS(Long Term Support)每两年 4 月发布一次,桌面侧默认支持 5 年、服务器侧通过 Ubuntu Pro 可延至 10 年。本板块以 22.04 LTS(jammy)为主基线、24.04 LTS(noble)为对照基线,与 ROS 2 Humble、Jazzy 两个 LTS 配对同步。
2.2 GA 内核与 HWE 内核¶
同一 Ubuntu 版本可以换内核,这是理解"uname -r 与发行版号不一一对应"的关键:
- GA 内核(General Availability):随该版本发布时锁定的内核版本,整个支持周期只打安全补丁、不升特性。
- HWE 内核(Hardware Enablement):每半年从更新的 Ubuntu 版本 backport 一份内核,用于新硬件(GPU、网卡、笔记本平台)开箱可用。
| Ubuntu | GA 内核 | HWE 内核滚动范围 |
|---|---|---|
| 22.04 | 5.15 | 5.17 → 5.19 → 6.2 → 6.5 → 6.8(终点即 24.04 GA) |
| 24.04 | 6.8 | 6.11(滚动至 24.10 GA) |
linux-image-generic 装 GA、linux-image-generic-hwe-22.04 装 HWE。双基线纪律在正文里意味着:写任何与内核版本相关的结论(PREEMPT_RT 支持、eBPF BTF 可用性、SocketCAN 行为)都要同时注明 Ubuntu 版本与 uname -r 输出,只写"22.04"不够。
2.3 内核变体包¶
Ubuntu 的内核包不止一个:
| 包名 | 语义 | 与本板块的关系 |
|---|---|---|
linux-image-generic |
GA 通用内核 | 默认安装 |
linux-image-generic-hwe-22.04 |
HWE 通用内核 | 桌面版默认拉取 |
linux-image-lowlatency |
低延迟调度、非硬实时 | 「实时内核与 CPU 调优」章前对照 |
linux-image-rt / linux-image-rt-generic |
PREEMPT_RT 补丁版 | 22.04 起该变体的厂商支持形态属外部证据,未核实前只写机制不写安装路径 |
坑:发行版号和内核号是两条编号线
Ubuntu 22.04 说的是发行版,5.15 说的是内核;HWE 线还能让 22.04 跑更新的内核。查问题前先分清要查的是哪一条编号。
3. 用户态与内核态的分界¶
Linux 通过 CPU 的特权级(x86 上 ring 3 与 ring 0)把内存与指令划成两个世界:
- 用户态:应用程序、Shell、systemd、Python、ROS 2 节点、apt 进程都在这里。
- 内核态:内核代码、中断服务、调度器、虚拟文件系统层(VFS)与 dentry 缓存。
用户态程序进内核的唯一常规入口是系统调用(syscall 指令触发;read / write / openat / execve 都是)。异常与中断也会陷入内核,但那不是程序主动发起。C 标准库的 read(2) 一类 POSIX API 只是 syscall 的薄封装——观察一个程序"在跟内核要什么",最直接的手段就是追 syscall。
## 用 strace 记录 `ls /tmp` 到底向内核请求了什么(-c 只汇总调用次数)
strace -c ls /tmp
Ubuntu 22.04(内核 5.15)上典型输出片段(/tmp 内容不同会影响 getdents64 次数,重点是类别与 errors 计数):
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
44.43 0.000319 106 3 getdents64 # 批量读目录项
15.32 0.000110 2 43 36 openat # 试开共享库与目录,多数 miss
13.09 0.000094 4 19 mmap # 建立进程地址空间与映射共享库
9.19 0.000066 1 38 27 newfstatat # 逐文件取元信息
2.65 0.000019 3 5 read # 从文件系统读
1.39 0.000010 1 6 write # 输出到 stdout
0.14 0.000001 1 1 execve # 装载 ls ELF
------ ----------- ----------- --------- --------- ----------------
100.00 0.000718 4 155 70 total
155 次调用里 70 次带 error——openat 与 newfstatat 的高命中率 miss 就是动态链接器 ld.so 逐目录探索共享库的痕迹(/etc/ld.so.cache 未命中时回退到默认目录逐一尝试)。一行 ls 尚且要 155 次 syscall,一个 ROS 2 节点的每次收发、每帧图像落盘都跑在这条链上。
控制回路的实时性抖动往往就出在这里:用户态一次 malloc 触发缺页(page fault 走内核态)、printf 走 write 阻塞在磁盘、序列化分配大 buffer 被 OOM killer 干掉——能读 strace 与 /proc/<pid>/smaps,才能把"抖"定位到具体的用户态调用。strace 本身在第 3 章「调试三条切入线」再展开;mlockall、CPU 亲和性与优先级这条链在第 7 章补完。
4. 文件系统布局与 FHS¶
Linux 的目录树不是随便分的。FHS(Filesystem Hierarchy Standard,现行 3.0)约束每个顶层目录承担什么、哪些可只读挂载、哪些可跨主机共享、哪些重启必须清空。Ubuntu 是 FHS 的合规实现;下表按机器人上位机的实际用途整理:
| 目录 | 语义 | 机器人上位机场景 |
|---|---|---|
/usr |
主机间可共享、可只读;含绝大部分二进制与库 | 系统升级与回滚的操作对象;/usr/bin/python3 在这里 |
/bin /sbin |
Ubuntu 18.04 起是 /usr/bin /usr/sbin 的软链(usrmerge) |
老脚本里的 /bin/bash 实际解析到 /usr/bin/bash |
/etc |
主机特定配置,不放二进制 | .service 单元(/etc/systemd/system/)、udev rules、apt 源、sshd_config |
/var |
可变数据;跨主机不可共享 | 日志(/var/log)、apt 缓存、rosbag 落盘、模型权重 |
/opt |
第三方包独立安装 | Jetson SDK Manager 与商业软件常落这里 |
/dev |
设备节点(字符 / 块设备);tmpfs,重启重建 | /dev/ttyUSB0 /dev/video0 /dev/can0;权限在第 4 章、生成规则在第 9 章 |
/proc |
procfs,内核视图,不占磁盘;进程与内核参数 | /proc/<pid>/status /proc/interrupts /proc/version;观测入口在第 8 章 |
/sys |
sysfs,内核对象树;设备驱动与属性 | /sys/bus/usb/devices/ /sys/class/net/;udev 规则基于这里 |
/run |
运行时数据,tmpfs,重启清空 | systemd socket、PID 文件、/run/user/<uid> |
/tmp |
临时文件,可被 systemd-tmpfiles 定期清理 |
不放长期 bag 与模型;重启或几天后可能消失 |
/home |
用户数据 | 用户级 .bashrc、.vimrc、conda 环境目录 |
三条常见误判:
/tmp当万能临时区:systemd-tmpfiles-clean.timer默认十天未访问的文件会被清;长期数据放/var/tmp或/var/lib更稳。/var/log永远不占地方:journal 与 syslog 在无人维护时会写满磁盘(第 10 章的journalctl --vacuum与 logrotate 处理这条)。/etc改了立刻生效:systemd 需要daemon-reload、NetworkManager 需要reload、udev rules 需要udevadm control --reload——每一处都有自己的生效动作,第 4、6、9 章各自说明。
5. 版本自查的三把尺子¶
现场问"这台机器什么版本",不同命令回答的是不同的问题:内核、发行版、编译信息各在一处。
## 三把尺子各查各的:
uname -r # 内核版本与架构(不查发行版)
lsb_release -a # 发行版全称与代号(依赖 lsb-release 包)
cat /etc/os-release # 标准化字段(systemd 提供,最稳)
uname -a回答内核本身。输出Linux <hostname> 5.15.0-119-generic #129-Ubuntu SMP ... x86_64:5.15.0是主线内核、-119是 Ubuntu 打的补丁序号、generic是内核变体(对到 GA / HWE /-rt)。查是不是-rt或-lowlatency都靠这里。lsb_release -a回答发行版:Distributor / Description / Release / Codename 四项。精简系统(Docker 镜像、板端最小 rootfs)常常没装lsb-release,命令直接not found;此时退回/etc/os-release。/etc/os-release是 systemd 定义的标准文件(systemd.os-release(5)),字段稳定、脚本可 source:
## 直接 source 到当前 shell,读标准化变量
. /etc/os-release
echo "$ID $VERSION_ID $VERSION_CODENAME"
## 输出:ubuntu 22.04 jammy
判据:排障要一次问三处。只 lsb_release 会漏内核变体,只 uname 会漏发行版代号,只 os-release 看不到 PREEMPT_RT 补丁标记。CI 脚本与工单模板里三行都要贴。
判据:三把尺子各查一段
lsb_release//etc/os-release 查发行版,uname -r 查运行内核,apt list --installed 'linux-image*' 查装了哪些内核——三段合起来才是一次完整自查。
6. tty、pts 与终端环境¶
Linux 把"终端"也当成设备,出现在 /dev 下。机器人上位机经常无显示器、SSH 进来干活、tmux 会话里跑脚本——搞清楚自己在哪种终端上,能解释不少"为什么这个命令在这里行为不一样"。
- tty(teletype):最初指物理电传机;内核把虚拟控制台与物理串口都归一为 tty 设备。虚拟控制台是
/dev/tty1…/dev/tty6(Alt+F1…F6 切换),物理串口是/dev/ttyS0(主板 COM)、/dev/ttyAMA0(ARM 板载)、/dev/ttyUSB0(USB 转串口,第 9 章)。 - pts(pseudo-terminal slave):伪终端,成对出现——一个 master 在内核侧(由
sshd/xterm/tmux持有),一个 slave 是用户 shell 打开的/dev/pts/N。SSH 登录后拿到的不是物理 tty 而是 pts;容器里docker exec -it也是 pts。 $TERM:告诉 TUI 程序当前终端能力(xterm-256color/screen-256color/tmux-256color)。程序据此查 terminfo 数据库决定怎么画颜色、光标移动;CI 环境常见TERM=dumb。
现场查一行搞定:
tty # 当前 stdin 绑到的终端设备:/dev/pts/2 或 /dev/tty1
echo "$TERM" # 终端类型字符串
loginctl session-status # 当前登录会话:tty / x11 / wayland / ssh
systemd 用 getty@.service 与 serial-getty@.service 模板在每个 tty 上拉起登录提示;板端无显示器时关掉不需要的 tty、只在串口或 SSH 上留一个入口,是常见部署动作(第 10 章 systemd 部署范式)。
7. 要点¶
- "Linux 是内核不是操作系统"——真正在跑的 Ubuntu 由内核 + 系统库 + 服务层 + 应用层若干层组合而成,每层由不同上游维护;出问题时先问"落在哪一层"再谈具体工具。
- 发行版号与内核号是两条线:22.04 GA 内核 5.15、HWE 内核可滚到 6.8;同名 Ubuntu 下不同机器可能跑不同内核,写结论必须两处都注明。
- 用户态进内核走系统调用:
strace是能读的最小窗口;能把"服务抖一下"定位到mmap缺页或write阻塞,才谈得上第 7 章实时调优与第 8 章观测工具链。 - FHS 约束目录语义:
/usr可只读、/etc主机特定、/var可变但会写满、/dev重启重建、/proc/sys不占磁盘、/tmp会被systemd-tmpfiles清;长期 bag 不放/tmp。 - 版本自查三把尺子:
uname -a(内核与变体)、lsb_release -a(发行版)、/etc/os-release(标准化字段)各查各的;排障一次贴三处。 - 终端也是设备:tty(物理与虚拟控制台)与 pts(SSH / 容器)不是一回事,
$TERM决定 TUI 行为;无显示器部署要在 systemd getty 模板层做减法。