Linux 基础体系 · 第 1/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
Linux 学习不应从“记住一批命令”开始,而应建立一条能够解释现象的因果链:
进程通过系统调用请求内核服务;内核通过调度、虚拟内存、文件系统、设备驱动和网络协议栈管理资源;用户态工具读取这些状态并修改配置;性能分析则需要把症状、指标、内核证据和业务结果串起来。
这条路线面向已经具备编程经验的工程师。示例以使用 systemd、procfs、sysfs 和常见 GNU/Linux 工具的现代发行版为主,例如 Debian、Ubuntu、Fedora、RHEL 系列。命令、默认路径、服务管理器、网络配置工具和安全策略可能因发行版、内核版本、容器环境而不同。
一、学习 Linux 前先建立三个边界
1. 用户态、内核态和硬件
用户态是普通程序运行的权限级别。程序不能直接执行大多数特权指令,也不能随意访问其他进程内存、设备寄存器或物理内存。
内核态是内核运行的权限级别。内核负责:
- 进程创建、调度和退出;
- 虚拟内存与页表;
- 文件描述符、VFS 和具体文件系统;
- 网络协议栈;
- 设备驱动与中断处理;
- 权限检查、命名空间和资源限制。
应用程序不能直接“调用磁盘”或“调用网卡”。它只能通过系统调用进入内核,例如:
ssize_t n = read(fd, buf, size);
这里的 read() 不是直接从硬盘读取数据,而是向内核提出“从文件描述符 fd 对应的对象读取数据”的请求。数据可能来自页缓存、管道、套接字、终端或设备驱动。
2. 规范、实现与经验
学习时必须区分三种陈述:
- 规范保证:POSIX 对部分系统调用行为有定义;Linux man-pages 描述 Linux 实际接口。
- 常见实现:例如 Linux 通常使用
ext4、XFS、Btrfs,但这不是所有发行版或系统的必然选择。 - 经验建议:例如生产环境使用最小权限、变更前验证和可回滚,这些是工程要求,不是某个系统调用的语义。
同一条命令在不同系统上可能来自不同实现。例如:
ps --version
ip -V
systemctl --version
ps 通常来自 procps-ng,ip 通常来自 iproute2;BusyBox 提供的同名命令参数可能更少。不要把某个发行版的默认行为误认为 Linux 内核保证。
3. 先掌握观察工具,再掌握修改工具
建议先熟悉以下工具及其证据来源:
uname -a
cat /etc/os-release
id
pwd
ls -l
file /bin/sh
man 2 open
man 5 proc
man 7 socket
其中:
uname主要显示内核相关信息;/etc/os-release描述用户空间发行版;man 2 open查看系统调用;man 5 proc查看/proc文件格式;man 7 socket查看套接字相关语义。
man 的数字表示手册章节。常见章节包括:
1:用户命令;2:系统调用;3:库函数;4:设备文件;5:文件格式;7:概览、协议和约定;8:系统管理命令。
二、内核与用户态:系统调用、异常、中断和硬件抽象
2.1 系统调用不是普通函数调用
普通函数调用通常在同一权限级别内跳转到另一个地址;系统调用需要完成一次受控的权限边界切换。
以 write(fd, buf, len) 为例,逻辑过程可以抽象为:
用户程序
│
│ C 库包装函数
▼
体系结构相关的 syscall 指令
│
│ CPU 切换到内核入口,保存用户态上下文
▼
系统调用分发器
│
│ 根据系统调用号选择实现
▼
内核子系统:VFS / 网络 / 设备驱动
│
▼
返回值与 errno
│
▼
用户程序
具体入口和寄存器约定随 CPU 架构不同。x86-64 上常见的系统调用入口使用 syscall 指令,但应用程序通常不应依赖底层汇编细节,而应使用 C 库和稳定的系统调用接口。
系统调用返回:
- 非负值:通常表示成功,例如实际读写字节数;
-1:C 库包装函数通常返回-1,并设置线程局部的errno;- 某些接口会返回指针、文件描述符或其他特定值。
一个可运行的示例:
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void) {
int fd = open("/definitely/not/exist", O_RDONLY);
if (fd == -1) {
fprintf(stderr, "open failed: errno=%d (%s)\n",
errno, strerror(errno));
return 1;
}
close(fd);
return 0;
}
编译运行:
cc -Wall -Wextra -O2 open_error.c -o open_error
./open_error
典型输出类似:
open failed: errno=2 (No such file or directory)
这里的 ENOENT 表示路径解析过程中找不到目标文件或目录。它不一定表示“磁盘坏了”,也可能是工作目录错误、挂载点未挂载、容器内路径不同或符号链接目标不存在。
2.2 路径解析与文件描述符
进程拿到的不是“文件内容”,而是一个文件描述符。文件描述符是进程打开文件表中的整数索引,例如 0、1、2 分别通常代表标准输入、标准输出和标准错误。
查看当前 Shell 的描述符:
ls -l /proc/$$/fd
可能看到:
0 -> /dev/pts/0
1 -> /dev/pts/0
2 -> /dev/pts/0
$$ 是当前 Shell 的 PID。/proc/<pid>/fd/ 中的符号链接显示每个描述符当前指向的对象。
打开文件通常涉及三层关系:
进程 fd
│
▼
内核 open file 对象:当前偏移、打开标志
│
▼
inode / socket / pipe 等内核对象
fork() 后,父子进程可以共享同一个 open file 对象,因此共享当前文件偏移;两个进程各自重新 open() 同一路径,则通常得到不同的打开对象和独立偏移。这是并发写文件、日志重定向和锁设计中必须区分的语义。
2.3 异常、中断和系统调用入口的区别
这三个概念容易混淆:
- 异常(exception):CPU 执行当前指令时同步产生。例如缺页异常、除零、非法指令。
- 中断(interrupt):通常由外部设备异步通知 CPU,例如网卡收到数据、定时器到期。
- 系统调用:用户程序主动请求内核服务,现代 CPU 通常通过专用指令进入内核。
它们都可能导致 CPU 保存上下文并跳转到内核入口,但触发原因和语义不同。
缺页异常的完整路径
假设程序访问一个尚未映射到物理内存的虚拟地址:
- CPU 根据页表翻译虚拟地址;
- 页表项显示页面不存在或权限不符;
- CPU 产生缺页异常,并记录故障地址及访问类型;
- 内核检查该地址是否属于合法的进程地址空间;
- 如果是文件映射,内核可能从页缓存或存储设备加载页面;
- 如果是匿名内存,内核可能分配并清零一个物理页;
- 更新页表;
- 返回用户态,重新执行导致异常的指令。
如果地址不合法,内核不会“修好”它,而是向进程发送 SIGSEGV。因此“发生 page fault”不等于“程序崩溃”;合法缺页是虚拟内存的正常机制,非法访问才会导致段错误。
中断与线程化处理
网卡收到数据后,设备可能触发中断。内核通常需要快速完成硬中断部分,再把较重的工作推迟到软中断、工作队列或线程化中断上下文。原因是:
- 硬中断上下文不能任意睡眠;
- 长时间占用中断处理会延迟其他设备;
- 网络数据包处理量大,需要批处理和预算机制。
因此,CPU 高不一定来自用户进程,也可能来自中断和软中断。可以观察:
cat /proc/interrupts
cat /proc/softirqs
/proc/interrupts 中不同 CPU 列显示中断计数;/proc/softirqs 可观察 NET_RX、TIMER 等软中断类型。计数增长本身不是故障,重点是结合时间差、CPU 使用率和业务现象判断。
2.4 硬件抽象:从设备到文件描述符
Linux 通过设备驱动和统一接口把不同硬件抽象出来:
- 块设备通常暴露为
/dev/sda、/dev/nvme0n1等; - 字符设备通常用于终端、串口、特殊设备;
- 网络设备通过网络接口暴露,例如
eth0、ens3; - 用户空间可通过
/sys、/dev、ioctl、netlink 等接口观察或配置。
抽象并不代表所有设备语义相同。对普通文件调用 lseek() 通常有意义,对管道或套接字则可能返回 ESPIPE。对设备文件执行 read(),实际行为由具体驱动决定。
三、Linux 目录与文件系统层次:FHS、挂载点、proc、sysfs 与设备
3.1 路径名与文件系统不是一回事
Linux 的目录树从根目录 / 开始,但它可能由多个文件系统拼接而成:
/
├── /boot 可能是独立文件系统
├── /home 可能是独立文件系统
├── /proc procfs
├── /sys sysfs
├── /dev devtmpfs 或其他设备管理方案
└── /var 可能与根文件系统共用,也可能独立
**挂载(mount)**是把一个文件系统附着到目录树某个目录的操作。挂载点目录原有内容在挂载期间不可见,但不会被删除;卸载后通常重新可见。
查看挂载关系:
findmnt
findmnt -T /var
df -hT
三者用途不同:
findmnt关注挂载树、源设备、文件系统类型和挂载选项;df关注文件系统已用空间和可用空间;du递归统计目录项,可能跨越挂载点,也可能因为权限、稀疏文件和已删除打开文件而与df不一致。
3.2 FHS 的学习重点
FHS 是文件系统层次结构标准,用于约定常见目录的职责。实际发行版会有差异,但以下语义具有很强的通用性:
| 路径 | 主要用途 |
|---|---|
/bin、/usr/bin |
普通用户命令;现代发行版可能合并为 /usr/bin |
/sbin、/usr/sbin |
系统管理命令;现代系统也可能合并 |
/etc |
主机级配置文件 |
/var |
可变数据,如日志、缓存、队列、数据库状态 |
/home |
普通用户家目录 |
/root |
root 用户家目录 |
/tmp |
临时文件,权限和清理策略需结合发行版 |
/run |
启动后运行时状态,通常是 tmpfs |
/dev |
设备节点 |
/proc |
进程和内核状态的伪文件系统 |
/sys |
设备模型、驱动和内核对象的伪文件系统 |
/boot |
引导加载器、内核和 initramfs 相关文件 |
/opt |
可选的第三方软件 |
/srv |
面向服务的数据,实际使用取决于系统约定 |
“配置放在 /etc”并不表示程序只能把配置放在那里;这是层次结构约定。容器镜像还经常使用 /usr/local、应用自带目录或只读根文件系统。
3.3 /proc:进程与内核状态的接口
procfs 通常挂载在 /proc。它不是磁盘上的普通文件集合,读取时往往由内核动态生成。
查看当前进程:
ls /proc | head
cat /proc/$$/status
tr '\0' ' ' < /proc/$$/cmdline; echo
cat /proc/loadavg
/proc/<pid>/status 包含进程状态、虚拟内存规模、线程数和能力等信息。/proc/<pid>/cmdline 使用 NUL 分隔参数,因此不能总用普通 cat 直接阅读。
几个容易误读的指标:
VmSize是虚拟地址空间大小,不等于实际占用物理内存;VmRSS是当前驻留集大小,但共享页面归属不能简单按进程相加;/proc/meminfo中的MemFree不是“应用可用内存”的唯一指标,页缓存和可回收内存也很重要;/proc/loadavg是调度队列相关指标,不是 CPU 百分比。
3.4 /sys:设备模型与内核对象
sysfs 通常挂载在 /sys,用于表示内核设备模型、总线、驱动、类和设备属性。
例如:
ls /sys/class/net
cat /sys/class/net/lo/operstate
readlink -f /sys/class/net/eth0
接口名不一定是 eth0,应先用:
ip link
/sys/class/net/<接口>/operstate 常见值包括 up、down、unknown。它反映链路状态的一部分,不等于应用一定能通信。应用连通性还取决于地址、路由、防火墙、邻居解析、端口监听和远端服务。
很多 /sys 文件可写,但写入意味着改变内核或硬件配置。例如:
cat /sys/class/net/eth0/mtu
读取通常安全;写入前必须确认设备、权限、持久化方式和回滚方法。临时修改与 NetworkManager、systemd-networkd 或发行版网络配置文件之间可能互相覆盖。
3.5 /dev 与设备节点
/dev 中的设备节点通常包含主设备号和次设备号:
ls -l /dev/null /dev/zero
输出中的 c 表示字符设备;块设备会显示 b。设备节点只是用户空间访问设备驱动的入口,不等于“设备本身”。
生产环境尤其要注意:
lsblk -f
blkid
findmnt
在没有确认设备、挂载关系和数据备份前,不要直接对 /dev/sdX 执行 mkfs、dd 或分区操作。/dev/sdX 只是示例命名,真实机器上设备顺序可能变化;更稳定的引用通常来自 /dev/disk/by-id/ 或 UUID,但仍需核对。
3.6 权限、所有权、ACL 和能力
传统权限由三组主体控制:
user / owner
group
other
例如:
-rwxr-x---
可分解为:
- 所有者:
rwx - 所属组:
r-x - 其他用户:
---
目录的权限语义与普通文件不同:
r:能列出目录项;w:能创建、删除或重命名目录项;x:能穿过目录并访问其中已知名称。
因此,一个用户即使没有文件内容的读取权限,只要拥有目录的 x 权限,仍可能访问已知路径;反过来,没有目录 x 权限,即使文件本身是 644,也无法访问。
检查路径每一级权限:
namei -l /var/log/example.log
ACL 可以表达比三组权限更细的授权:
getfacl file
setfacl -m u:alice:r file
setfacl 会改变持久权限模型,使用前必须知道备份和清理方式。对服务进程而言,还要考虑 Linux capabilities、SELinux、AppArmor、seccomp、用户命名空间等额外约束。chmod 777 通常不是修复权限问题,而是掩盖了所有权、ACL、MAC 策略或路径设计错误,并可能造成数据篡改。
四、进程、线程、调度和服务生命周期
4.1 进程状态不是简单的“运行/停止”
进程拥有地址空间、文件描述符表、信号处理状态、凭据和线程。线程共享进程的大部分资源,但拥有独立的寄存器上下文和调度状态。
查看进程树:
ps -eo pid,ppid,stat,comm,args --forest
常见状态包括:
R:可运行或正在运行;S:可中断睡眠;D:不可中断睡眠,常与 I/O 等待有关;T:停止;Z:僵尸进程。
僵尸进程已经结束,只保留退出状态,等待父进程调用 wait() 回收。杀死僵尸本身通常无效,应该检查其父进程是否失效或没有正确回收子进程。
D 状态也不能简单理解为“CPU 被卡死”。它可能在等待块设备、网络文件系统或驱动完成 I/O;应进一步检查 I/O、内核日志和设备状态,而不是反复发送 SIGKILL。
4.2 信号和优雅终止
终止服务时,常见路径是:
SIGTERM
│
▼
程序捕获信号,停止接收新请求
│
▼
完成或取消正在处理的任务
│
▼
关闭监听套接字、刷新日志、退出
SIGKILL 不能被捕获或清理,可能留下临时文件、未提交事务或不完整状态。生产发布和故障处理应优先验证程序是否支持优雅退出,再决定是否强制终止。
4.3 systemd 服务的状态模型
在使用 systemd 的系统中:
systemctl status ssh
systemctl is-active ssh
systemctl is-enabled ssh
journalctl -u ssh --since "10 minutes ago"
需要区分:
active:当前运行状态;enabled:是否配置为在相应目标启动;failed:最近一次启动或运行失败;- 日志:服务进程输出、退出状态和启动上下文。
active 不一定表示业务健康。例如进程还活着,但已无法连接数据库。服务管理器只能观察进程生命周期,应用健康还需要探针、指标或端到端请求。
修改 unit 后通常需要:
sudo systemctl daemon-reload
sudo systemctl restart example.service
sudo systemctl status example.service
daemon-reload 只重新读取 unit 文件,不会自动重启服务。重启前应确认配置语法、依赖和回滚版本。
五、网络:从接口、路由到套接字和数据包路径
5.1 网络故障必须按层定位
一个 TCP 请求大致经过:
应用
▼
socket / connect / send / recv
▼
TCP
▼
IP 路由
▼
邻居解析(ARP 或 IPv6 NDP)
▼
网卡队列与驱动
▼
链路
▼
远端主机
应用可达性不是单一条件。至少要区分:
- 网卡是否存在并启用;
- 是否有正确 IP 地址;
- 是否有匹配路由;
- 是否能解析下一跳邻居;
- 防火墙是否允许;
- 远端端口是否监听;
- 应用协议是否完成握手和认证。
常用观察命令:
ip link
ip addr
ip route
ip neigh
ss -lntup
ip route get 8.8.8.8 可以询问内核对某个目标地址会选择哪条路由和哪个源地址:
ip route get 8.8.8.8
输出通常包含目标、经由的网关、设备和 src 地址。它比只看 ip route 更接近某一次实际发送决策。
5.2 TCP 连接的生命周期
服务器监听 TCP 端口通常经历:
socket()
▼
bind()
▼
listen()
▼
accept()
▼
read()/write()
▼
close()
客户端通常调用:
socket()
▼
connect()
▼
send()/recv()
▼
close()
listen() 后的套接字不是一个已连接会话,而是监听套接字。每次 accept() 返回一个新的已连接套接字。一个服务端口对应一个监听端点,但可以有许多已连接套接字。
观察状态:
ss -tan
ss -ltn 'sport = :8080'
常见 TCP 状态:
LISTEN:等待连接;SYN-SENT:客户端已发送 SYN,等待响应;SYN-RECV:服务端收到 SYN,等待握手完成;ESTAB:连接已建立;TIME-WAIT:主动关闭方保留状态,以处理延迟报文和避免旧连接干扰。
因此,看到大量 TIME-WAIT 不应立即执行“调小超时”之类操作。先确认主动关闭方、连接复用策略、短连接比例和端口资源是否真的成为瓶颈。
5.3 DNS、路由和端口是不同问题
以下测试针对不同层次:
getent hosts example.com
ip route get 1.1.1.1
nc -vz example.com 443
curl -v --connect-timeout 3 https://example.com/
getent hosts验证系统名称解析路径,可能使用/etc/hosts、DNS 或 NSS 模块;ip route get验证本机路由选择;nc -vz验证 TCP 连接是否能建立;curl -v进一步观察 TLS、HTTP 请求和响应。
DNS 成功不代表 TCP 可达;TCP 成功不代表 TLS 证书正确;TLS 成功也不代表 HTTP 应用健康。诊断时应保留每一步的失败边界。
5.4 防火墙与抓包
现代系统可能使用 nftables、iptables 兼容层、firewalld 或云平台安全组。查看规则前先确定管理层:
sudo nft list ruleset
sudo firewall-cmd --state 2>/dev/null || true
不要在不了解现有规则的情况下执行清空规则命令。远程操作中,错误的防火墙变更可能立刻切断 SSH。
抓包用于验证“包是否到达”和“在哪一侧消失”:
sudo tcpdump -ni any 'host 192.0.2.10 and port 443'
-i any 在 Linux 上便于快速观察多个接口,但链路层信息和方向判断可能不如指定物理接口准确。抓包结果应结合:
- SYN 是否发出;
- 是否收到 SYN-ACK 或 RST;
- 是否发生重传;
- TCP 窗口是否缩小;
- TLS 或应用层是否返回错误。
六、文件系统、I/O 与数据一致性
6.1 VFS 和具体文件系统
Linux 的 VFS(Virtual File System)提供统一对象模型,常见对象包括:
- superblock:文件系统级信息;
- inode:文件元数据和数据块映射;
- dentry:目录项与名称解析缓存;
- file:一次打开实例,保存偏移和标志。
应用使用 open/read/write/close,VFS 再调用具体文件系统实现,例如 ext4、XFS 或网络文件系统。
这解释了一个重要现象:同样的 read(),对本地文件、NFS 文件、管道和套接字,延迟、阻塞和错误语义都不同。
6.2 页缓存与“写入成功”
普通文件写入通常先修改页缓存,之后由内核异步写回存储设备。因此:
write()
▼
页缓存被修改
▼
write() 可能返回成功
▼
后台回写
▼
设备缓存 / 持久介质
write() 成功通常表示数据已被内核接受,不必然表示数据已到达断电后仍可恢复的介质。需要更强持久性语义时,应根据应用协议使用 fsync()、fdatasync()、正确的文件替换流程以及数据库自身的日志机制。
典型的原子替换流程是:
- 在同一文件系统创建临时文件;
- 写入完整内容;
fsync()临时文件;rename()替换目标文件;- 必要时对父目录
fsync()。
rename() 在同一文件系统内通常具有原子名称切换语义,但不自动保证断电后的持久化;跨文件系统则可能失败并返回 EXDEV。
6.3 磁盘空间、inode 和已删除文件
磁盘满有多个维度:
df -h
df -i
du -xhd1 /var 2>/dev/null | sort -h
df -h:块空间;df -i:inode 使用情况;du -x:限制在同一文件系统内统计目录大小。
当 df 显示空间紧张,但 du 找不到大文件,常见原因是文件已被删除但仍被进程打开:
sudo lsof +L1
进程仍持有文件描述符时,删除目录项不会立即释放数据块。重启服务或让进程关闭该描述符后空间才可能释放。直接重启生产服务前,必须确认高可用、数据一致性和恢复方案。
七、性能分析方法:CPU、内存、磁盘、网络与 eBPF 证据链
性能分析不是“看到一个高指标就下结论”,而是建立证据链:
业务症状
▼
时间范围与基线
▼
资源层定位
▼
内核 / 进程证据
▼
验证假设
▼
低风险修复
▼
复测与回归
7.1 先定义指标和时间窗口
至少记录:
- 请求延迟:平均值、P95、P99;
- 吞吐量:请求数、字节数、任务数;
- 错误率;
- CPU、内存、磁盘、网络;
- 变更时间、异常开始时间和恢复时间。
平均延迟可能掩盖长尾。若 100 个请求中 99 个耗时 10 ms、1 个耗时 2 s,平均值约为:
但 P99 接近 2 s,用户仍会遇到严重卡顿。
7.2 CPU:区分用户态、内核态、等待和窃取
uptime
top
pidstat -u -p <PID> 1
mpstat -P ALL 1
CPU 时间常被分为:
us:用户态;sy:内核态;ni:调整过 nice 的用户进程;id:空闲;wa:等待 I/O;st:虚拟机被宿主机窃取的时间。
一个简化的 CPU 利用率公式是:
其中 Δidle 和 Δtotal 是两个采样点之间的计数差。它说明 CPU 使用率应基于时间差,而不是某个瞬时累计值。
高 us 可能是业务计算、压缩、加密或忙循环;高 sy 可能是系统调用、网络协议栈、内存管理或驱动开销;高 wa 表示 CPU 在采样期间存在 I/O 等待,但不等价于“磁盘一定是唯一瓶颈”。
进一步可以使用:
perf stat -p <PID> sleep 10
perf record -g -p <PID> -- sleep 10
perf report
perf stat 统计硬件和软件性能事件;perf record 采样调用栈。权限可能受到 kernel.perf_event_paranoid、容器隔离和内核配置限制。采样结果还可能受符号信息、编译优化和帧指针影响。
7.3 内存:区分容量、回收和压力
free -h
vmstat 1
pidstat -r -p <PID> 1
cat /proc/pressure/memory
Linux 会利用空闲内存作为页缓存,因此“used 很高”不必然表示内存不足。更有意义的问题是:
- 是否持续发生 major fault;
- 是否频繁回收页缓存;
- 是否发生 swap in/out;
- 是否存在内存 PSI 压力;
- 进程 RSS 是否持续增长;
- 是否触发 cgroup memory limit 和 OOM kill。
vmstat 中的 si/so 常用于观察交换进出;free、buff/cache 的具体展示方式会因 procps 版本不同而略有差异。内存泄漏需要观察长期趋势,不能依据一次 ps 输出判断。
进程实际内存还应结合:
cat /proc/<PID>/smaps_rollup
cat /proc/<PID>/oom_score
共享库和共享内存会导致 RSS 简单相加超过物理内存,因此不能把所有进程 RSS 直接求和当作精确总占用。
7.4 磁盘与文件系统:延迟、吞吐和队列
iostat -xz 1
pidstat -d -p <PID> 1
cat /proc/diskstats
观察磁盘时关注:
- IOPS;
- 吞吐量;
- 请求延迟;
- 队列长度;
- 利用率;
- 读写比例;
- 随机或顺序访问;
- 文件系统和页缓存行为。
高 %util 在某些设备模型上表示设备忙碌,但现代多队列 NVMe、虚拟块设备和云盘的解释不能简单套用机械硬盘经验。更可靠的判断是把设备延迟、应用请求延迟、队列变化和业务吞吐放在同一时间窗口中。
7.5 网络:吞吐不等于延迟
sar -n DEV 1
ip -s link
ss -s
nstat
需要区分:
- 丢包;
- 重传;
- 接收队列溢出;
- 网卡错误;
- TCP 建连延迟;
- RTT;
- 拥塞窗口;
- 应用处理时间。
ip -s link 中的错误和丢弃计数需要按时间差观察。累计计数很大可能只是设备运行了很久;短时间快速增长才更有诊断价值。
7.6 eBPF:在内核事件上建立可验证证据
eBPF 允许在内核预定义钩子或跟踪点上运行受验证的程序,并把聚合结果传到用户空间。它常用于:
- 系统调用统计;
- 文件打开和 I/O 延迟;
- 调度延迟;
- TCP 连接与重传;
- run queue 延迟;
- 内核函数和 tracepoint 观测。
常见工具来自 bpftrace、BCC 或 bpfman 等生态,版本和发行版打包差异较大。示例:
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat
{
@[comm] = count();
}'
它统计运行期间各进程名进入 openat 系统调用的次数。按 comm 聚合会合并同名进程,不能直接区分不同 PID;短时间采样也不能代表全天行为。
观察调度延迟时可以使用系统已有工具,例如:
sudo bpftrace -e '
tracepoint:sched:sched_switch
{
@[comm] = count();
}'
这只能说明发生了上下文切换,不能单独证明调度是瓶颈。生产分析应:
- 明确采样目标和持续时间;
- 先使用低开销聚合;
- 避免打印每个事件导致观测本身制造负载;
- 记录内核、工具和符号版本;
- 用业务指标验证修复结果。
eBPF 工具依赖内核配置、BTF、权限、LSM 和容器安全策略。某台机器不能运行某个脚本,可能是工具语法、内核能力或权限问题,不应直接推断业务没有该事件。
八、生产运维:把系统能力变成可恢复的变更流程
8.1 配置、日志、状态和临时数据必须分离
一个服务至少要区分:
- 配置:通常位于
/etc或配置管理系统; - 持久状态:数据库、队列、证书私钥等;
- 日志:通常位于
/var/log或 journald; - 运行时状态:PID、socket、临时锁文件,通常位于
/run; - 临时数据:缓存、临时文件,可能位于
/tmp或应用目录。
把所有内容写入容器可写层或 /tmp,可能导致重启丢失;把临时数据写入根文件系统,可能造成磁盘耗尽并影响系统服务。
8.2 变更必须包含验证与恢复
以修改 SSH 配置为例,风险远高于编辑一个普通文本文件。正确思路是:
sudo cp -a /etc/ssh/sshd_config \
/etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S)
sudo sshd -t
sudo systemctl reload ssh
sudo systemctl status ssh
不同发行版服务名可能是 ssh 或 sshd,应先用:
systemctl list-unit-files | grep -E '^(ssh|sshd)'
关键点:
- 先备份当前配置;
- 用守护进程自身的语法检查;
- 优先
reload,避免不必要的连接中断; - 保留现有 SSH 会话;
- 从第二个会话验证新连接;
- 失败时恢复备份并重新检查。
如果配置导致远程登录失败,恢复路径可能需要云控制台、串口、带外管理或快照。没有恢复通道时,不应把远程服务器当作实验机。
8.3 包管理和服务依赖
不要直接覆盖系统包管理器管理的文件:
dpkg -S /usr/bin/curl 2>/dev/null || rpm -qf /usr/bin/curl
Debian 系列常用 apt、dpkg;RHEL/Fedora 系列常用 dnf、rpm。升级前应确认:
- 依赖是否变化;
- 配置文件是否冲突;
- 服务是否需要重启;
- 内核升级是否需要重启;
- 是否有回滚包或镜像;
- 数据格式是否向前兼容。
8.4 日志和审计:先确定数据源
systemd 系统常用:
journalctl -b
journalctl -p warning..alert
journalctl -u example.service --since today
dmesg -T
dmesg 主要查看内核环形缓冲区,日志可能因重启或配置而变化;journalctl 依赖 journald 是否启用及其持久化策略。应用日志还可能写入独立文件或日志代理,不能只看一个来源。
日志分析应保留:
- 时间和时区;
- PID、线程或请求 ID;
- 错误码;
- 变更版本;
- 相关主机和容器;
- 首次出现、峰值和恢复时间。
只执行 tail -f 不能证明没有错误,因为日志可能被采集到远端、写入 journald、被轮转或因权限不可见。
8.5 备份不等于可恢复
备份的价值取决于能否恢复。至少要验证:
- 备份是否完整;
- 是否包含权限、所有权、符号链接和扩展属性;
- 是否有一致性点;
- 恢复到什么环境;
- 恢复耗时是否符合 RTO;
- 可丢失数据量是否符合 RPO。
对正在变化的数据库目录直接 tar,可能得到逻辑上不一致的备份。应优先使用数据库提供的备份机制、快照一致性方案或停机窗口。
8.6 权限和生产风险的基本原则
以下操作具有高风险:
rm -rf
mkfs
dd
chmod -R
chown -R
iptables/nftables 清空规则
systemctl stop
mount/umount
风险不只来自命令本身,还来自变量展开、当前目录、符号链接、挂载关系和远程环境。执行前至少确认:
pwd
findmnt -T <目标路径>
ls -ld <目标路径>
id
对于递归操作,先使用只读检查命令展示目标集合,再执行修改。对于磁盘操作,优先使用稳定设备标识并核对容量、序列号和挂载状态。
九、推荐的完整学习顺序与验收方式
阶段一:Shell、文件和权限
掌握:
- Shell 变量、管道、重定向、通配符、退出状态;
find、xargs、sed、awk、grep;- 文件描述符和标准输入输出;
- 用户、组、权限、ACL、符号链接;
/etc/passwd、/etc/shadow、/etc/group的基本角色。
验收任务:编写脚本统计某目录下近一天修改的日志文件,正确处理空格、换行和权限错误。不要直接使用未经解释的 for f in $(find ...),因为命令替换会按空白切分文件名。
阶段二:进程、系统调用和内核观察
掌握:
fork、exec、wait、信号;- 文件描述符;
/proc/<pid>;strace;- 调度和上下文切换。
示例:
strace -f -o /tmp/trace.log sh -c 'printf hello >/tmp/hello'
grep -E 'openat|write|close' /tmp/trace.log
验收时应能解释:哪个进程打开了文件、返回了什么描述符、write 写了多少字节、为什么最终需要 close。
阶段三:文件系统、挂载和 I/O
掌握:
- FHS;
- VFS、inode、dentry、页缓存;
mount、umount、fstab;df、du、inode;- 本地文件系统与网络文件系统的边界;
- 数据持久性与备份恢复。
验收任务:解释为什么 df 与 du 数字不同,并用 lsof +L1 验证已删除但仍打开的文件。
阶段四:网络和服务
掌握:
- 接口、地址、路由、邻居表;
- DNS 与 NSS;
- TCP 生命周期;
- TLS 和 HTTP 的分层;
ss、ip、curl、tcpdump;- systemd 服务状态和 journald。
验收任务:对一个“域名能解析但接口访问失败”的问题,依次给出 DNS、路由、TCP、TLS、HTTP 五个层次的证据,而不是只执行一次 ping。
阶段五:性能与内核追踪
掌握:
- 采样与计数;
- CPU、内存、磁盘、网络指标;
- 延迟分位数;
- PSI;
perf;- eBPF tracepoint、kprobe、聚合和开销控制。
验收任务:针对一次接口延迟上升,先记录业务基线,再判断是 CPU 计算、锁等待、I/O、网络重传、内存回收还是外部依赖,并说明每个结论对应的证据。
阶段六:生产运维和恢复
掌握:
- 包管理;
- 配置发布;
- 权限与密钥;
- 日志、监控、告警;
- 备份、恢复、回滚;
- 资源限制、容器和命名空间;
- 变更审计与最小权限。
最终验收不应只是“能让服务启动”,而应能回答:
- 服务依赖哪些资源?
- 启动失败时从哪里取得证据?
- 变更前如何验证?
- 变更失败如何恢复?
- 服务活着但业务不可用时如何发现?
- 主机资源耗尽时如何避免波及其他服务?
- 数据损坏或误删后恢复路径是什么?
十、常见误解与正确替代
“Linux 一切都是文件”
这是有用但不完整的类比。统一的文件描述符接口确实覆盖普通文件、管道、套接字和部分设备,但不同对象的阻塞、定位、权限和错误语义不同。套接字不是磁盘文件,不能据此推断它支持 lseek() 或持久化。
“CPU 使用率低,所以系统不忙”
进程可能等待锁、磁盘、网络、内存回收或外部服务。应同时观察运行队列、I/O 延迟、网络重传、线程状态、PSI 和应用长尾。
“内存 used 高就是内存泄漏”
Linux 主动利用空闲内存做缓存。需要观察进程长期 RSS、匿名内存、回收压力、swap、cgroup 限制和 OOM 日志。
“端口能连通,所以服务正常”
TCP 连接只证明某个端点完成了传输层握手。TLS、认证、协议状态、依赖服务和业务数据仍可能失败。
“删掉日志文件就释放空间”
如果进程仍持有打开描述符,空间可能不释放。应使用日志轮转机制或让进程正确重新打开日志,并用 lsof +L1 验证。
“root 可以做任何事”
root 仍可能受 capability、SELinux、AppArmor、seccomp、用户命名空间、只读挂载、容器边界和云平台策略限制。更重要的是,root 的错误操作影响范围最大,因此应更强调验证和回滚。
Linux 的完整学习路线最终不是命令数量,而是解释能力:看到一个现象,能够指出它属于用户态还是内核态,经过哪个对象和状态转换,使用哪个内核接口可以观察,哪个变更可能影响它,以及发生错误后如何验证和恢复。掌握这条证据链,才能从基础命令逐步进入性能工程、平台工程和生产运维。
完整学习目录
一、系统基础
- Linux 内核与用户态:系统调用、异常、中断和硬件抽象
- Linux 启动完整流程:Firmware、Bootloader、Kernel、initramfs 与 systemd
- Linux 目录与文件系统层次:FHS、挂载点、proc、sysfs 与设备
- Linux 文件、Inode 与链接:描述符、硬链接、符号链接和删除语义
二、账户与安全
- Linux 权限完整指南:UID/GID、mode、umask、ACL 与 capabilities
- Linux 用户认证与提权:账号、PAM、sudo、密码策略和审计
- Linux 主机安全加固:最小化、SELinux/AppArmor、审计与漏洞治理
三、进程与服务
- Linux 进程、线程与信号:生命周期、父子关系、Zombie 和优雅退出
- systemd 服务管理:Unit、依赖、重启、资源限制和安全沙箱
- Bash 与 Shell 工程实践:展开、引用、管道、错误处理和脚本测试
- Linux 软件包管理:APT、DNF、仓库、签名、依赖与可重复安装
- Linux 定时任务:cron、systemd timer、时区、防重入和错过执行
四、存储与内存
- Linux 块设备与存储:分区、LVM、RAID、挂载和在线扩容
- Linux 文件系统:ext4、XFS、日志、缓存、一致性和损坏恢复
- Linux 虚拟内存:页表、缺页、Cache、Swap、OOM 与内存诊断
五、网络与性能
- Linux CPU 调度与负载:运行队列、上下文切换、Load 和软中断
- Linux 网络栈基础:网卡、协议栈、Socket、端口和连接状态
- Linux 路由、DNS 与网络诊断:ip、ss、dig、tcpdump 和抓包
- Linux 防火墙与 NAT:Netfilter、nftables、连接跟踪和规则治理
- Linux 性能分析方法:CPU、内存、磁盘、网络与 eBPF 证据链
六、生产运维
- Linux 日志体系:journald、rsyslog、轮转、结构化采集和磁盘治理
- OpenSSH 完整指南:密钥、Agent、跳板、隧道和服务端加固
- Linux cgroups 与 namespaces:资源控制、隔离和容器底层机制
- Linux 无法启动与系统故障排查:救援模式、磁盘、服务和内核证据
- Linux 备份与恢复:文件、块设备、快照、加密和恢复演练
- Linux 生产运行手册:容量、变更、监控、应急和复盘
七、内核深入
- Linux 系统调用与 strace:调用边界、阻塞、错误和性能诊断
- Linux 调度器深入:CFS、实时策略、优先级、Affinity 和 NUMA
- Linux 中断与 Softirq:IRQ、Bottom Half、ksoftirqd 和延迟
- Linux 内核模块:加载、参数、依赖、签名、DKMS 和故障恢复
- Linux 设备模型与 udev:设备号、sysfs、规则、热插拔和权限
- Linux procfs 与进程观测:状态、文件描述符、内存映射和线程
- Linux sysctl 与内核参数:来源、持久化、风险、压测和回滚
- Linux cgroups v2 深入:CPU、Memory、IO、PID、Pressure 和委派
- Linux namespaces 深入:PID、Mount、Network、User 和隔离组合
- Linux Seccomp 与 Capabilities:系统调用过滤、最小权限和沙箱
- Linux NUMA 与硬件拓扑:节点、内存亲和、跨节点访问和诊断
- Linux PSI 资源压力:CPU、内存、I/O Stall、告警和容量判断
八、命令行与自动化
- Linux 文件命令体系:ls、cp、mv、rm、stat、file 和安全操作
- Linux find 与 xargs:条件、批处理、空字符、安全和并发执行
- Linux 文本处理:grep、sed、awk、cut、sort、uniq 和正则
- Bash 展开与引用深入:变量、命令替换、通配、数组和 IFS
- Bash 函数与 Trap:退出码、错误传播、临时资源和信号清理
- Shell 脚本测试与质量:ShellCheck、Bats、Fixture 和故障注入
- 命令行结构化数据:jq、yq、JSON、YAML、流式处理和安全更新
- Linux 终端与作业控制:TTY、Session、前后台、nohup 和挂断
- tmux 终端复用:Session、窗口、Pane、恢复和远程运维边界
- Linux 归档与压缩:tar、gzip、xz、zstd、校验和和大文件
- rsync 文件同步:增量算法、权限、删除、断点和备份策略
- SSH 自动化与批量运维:Config、ProxyJump、ControlMaster 和密钥治理
- Ansible Linux 自动化:Inventory、Playbook、幂等、Secret 和滚动变更
九、存储深入
- Linux 块 I/O 栈:Bio、Queue、Scheduler、Direct I/O 和延迟
- Linux Page Cache 与 Writeback:脏页、回写、fsync 和数据持久性
- ext4 深入:Extent、Journal、分配器、fsck 和性能参数
- XFS 深入:Allocation Group、Journal、在线扩容、修复和大文件
- LVM 深入:PV、VG、LV、Thin Pool、Snapshot、扩缩容和恢复
- Linux 软件 RAID:mdadm、级别、重建、降级、监控和恢复
- Linux NFS:协议、挂载、缓存、一致性、权限和高可用
- Linux 磁盘性能诊断:iostat、延迟、队列深度、吞吐和饱和度
- Linux 磁盘加密:dm-crypt、LUKS、密钥槽、启动解锁和恢复
十、网络深入
- Linux TCP 深入:握手、状态机、窗口、重传、拥塞控制和队列
- Linux UDP 与 Datagram:缓冲、丢包、分片、批处理和适用边界
- Linux TCP 诊断:ss、tcpdump、重传、SYN 队列、TIME_WAIT 和延迟
- Linux DNS 解析链路:glibc、nsswitch、systemd-resolved、缓存和排障
- Linux 路由深入:多表、策略路由、ECMP、VRF 和反向路径检查
- Linux Bridge、VLAN、Bond 与 Team:二层网络、冗余和诊断
- Linux Network Namespace 与 veth:容器网络、路由和 NAT 实验
- Linux 网络内核调优:Socket Buffer、Backlog、Port、Conntrack 和验证
- Linux TLS 与证书运维:PKI、链、SNI、OCSP、续期和排障
- Linux Nginx 反向代理:进程模型、TLS、负载均衡、限流和日志
- Linux Keepalived 与 VRRP:虚拟 IP、健康检查、切换和脑裂边界
十一、安全与生产专题
- SELinux 完整基础:Label、Type Enforcement、Policy、布尔值和排障
- AppArmor 完整基础:Profile、Mode、规则、日志和服务加固
- Linux auditd 审计:规则、事件、性能、检索和合规留存
- Linux 资源限制:ulimit、RLIMIT、systemd Limit、文件句柄和进程数
- Linux 时间同步:Clocksource、NTP、chrony、漂移和分布式系统影响
- Linux Core Dump 与崩溃诊断:coredumpctl、gdb、符号和隐私
- Linux perf 与火焰图:采样、调用栈、符号、CPU 和 Off-CPU
- Linux eBPF 与 bpftrace:Hook、Map、Verifier、观测脚本和安全
- Linux logrotate 深入:轮转、压缩、信号、并发和磁盘保护
- Linux KVM 虚拟化:QEMU、vCPU、virtio、存储网络和隔离边界
- Linux cloud-init:实例初始化、Metadata、用户数据、幂等和安全
- Linux 补丁与发行版升级:安全更新、内核、重启、灰度和回滚
- Linux 主机事件响应:隔离、取证、时间线、证据保全和恢复
系列导航与关联阅读
- 下一篇:Linux 内核与用户态:系统调用、异常、中断和硬件抽象
- 延伸:Linux 目录与文件系统层次:FHS、挂载点、proc、sysfs 与设备
- 延伸:Linux 性能分析方法:CPU、内存、磁盘、网络与 eBPF 证据链
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论