Linux 基础体系 · 第 1/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维

Linux 学习不应从“记住一批命令”开始,而应建立一条能够解释现象的因果链:

进程通过系统调用请求内核服务;内核通过调度、虚拟内存、文件系统、设备驱动和网络协议栈管理资源;用户态工具读取这些状态并修改配置;性能分析则需要把症状、指标、内核证据和业务结果串起来。

这条路线面向已经具备编程经验的工程师。示例以使用 systemdprocfssysfs 和常见 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 通常使用 ext4XFSBtrfs,但这不是所有发行版或系统的必然选择。
  • 经验建议:例如生产环境使用最小权限、变更前验证和可回滚,这些是工程要求,不是某个系统调用的语义。

同一条命令在不同系统上可能来自不同实现。例如:

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 路径解析与文件描述符

进程拿到的不是“文件内容”,而是一个文件描述符。文件描述符是进程打开文件表中的整数索引,例如 012 分别通常代表标准输入、标准输出和标准错误。

查看当前 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 保存上下文并跳转到内核入口,但触发原因和语义不同。

缺页异常的完整路径

假设程序访问一个尚未映射到物理内存的虚拟地址:

  1. CPU 根据页表翻译虚拟地址;
  2. 页表项显示页面不存在或权限不符;
  3. CPU 产生缺页异常,并记录故障地址及访问类型;
  4. 内核检查该地址是否属于合法的进程地址空间;
  5. 如果是文件映射,内核可能从页缓存或存储设备加载页面;
  6. 如果是匿名内存,内核可能分配并清零一个物理页;
  7. 更新页表;
  8. 返回用户态,重新执行导致异常的指令。

如果地址不合法,内核不会“修好”它,而是向进程发送 SIGSEGV。因此“发生 page fault”不等于“程序崩溃”;合法缺页是虚拟内存的正常机制,非法访问才会导致段错误。

中断与线程化处理

网卡收到数据后,设备可能触发中断。内核通常需要快速完成硬中断部分,再把较重的工作推迟到软中断、工作队列或线程化中断上下文。原因是:

  • 硬中断上下文不能任意睡眠;
  • 长时间占用中断处理会延迟其他设备;
  • 网络数据包处理量大,需要批处理和预算机制。

因此,CPU 高不一定来自用户进程,也可能来自中断和软中断。可以观察:

cat /proc/interrupts
cat /proc/softirqs

/proc/interrupts 中不同 CPU 列显示中断计数;/proc/softirqs 可观察 NET_RXTIMER 等软中断类型。计数增长本身不是故障,重点是结合时间差、CPU 使用率和业务现象判断。

2.4 硬件抽象:从设备到文件描述符

Linux 通过设备驱动和统一接口把不同硬件抽象出来:

  • 块设备通常暴露为 /dev/sda/dev/nvme0n1 等;
  • 字符设备通常用于终端、串口、特殊设备;
  • 网络设备通过网络接口暴露,例如 eth0ens3
  • 用户空间可通过 /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 常见值包括 updownunknown。它反映链路状态的一部分,不等于应用一定能通信。应用连通性还取决于地址、路由、防火墙、邻居解析、端口监听和远端服务。

很多 /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 执行 mkfsdd 或分区操作。/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)
  ▼
网卡队列与驱动
  ▼
链路
  ▼
远端主机

应用可达性不是单一条件。至少要区分:

  1. 网卡是否存在并启用;
  2. 是否有正确 IP 地址;
  3. 是否有匹配路由;
  4. 是否能解析下一跳邻居;
  5. 防火墙是否允许;
  6. 远端端口是否监听;
  7. 应用协议是否完成握手和认证。

常用观察命令:

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 再调用具体文件系统实现,例如 ext4XFS 或网络文件系统。

这解释了一个重要现象:同样的 read(),对本地文件、NFS 文件、管道和套接字,延迟、阻塞和错误语义都不同。

6.2 页缓存与“写入成功”

普通文件写入通常先修改页缓存,之后由内核异步写回存储设备。因此:

write()
  ▼
页缓存被修改
  ▼
write() 可能返回成功
  ▼
后台回写
  ▼
设备缓存 / 持久介质

write() 成功通常表示数据已被内核接受,不必然表示数据已到达断电后仍可恢复的介质。需要更强持久性语义时,应根据应用协议使用 fsync()fdatasync()、正确的文件替换流程以及数据库自身的日志机制。

典型的原子替换流程是:

  1. 在同一文件系统创建临时文件;
  2. 写入完整内容;
  3. fsync() 临时文件;
  4. rename() 替换目标文件;
  5. 必要时对父目录 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,平均值约为:

99×10+2000100=29.9 ms\frac{99 \times 10 + 2000}{100}=29.9\text{ ms}

但 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 利用率公式是:

U=1ΔidleΔtotalU = 1 - \frac{\Delta idle}{\Delta total}

其中 Δ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 常用于观察交换进出;freebuff/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();
}'

这只能说明发生了上下文切换,不能单独证明调度是瓶颈。生产分析应:

  1. 明确采样目标和持续时间;
  2. 先使用低开销聚合;
  3. 避免打印每个事件导致观测本身制造负载;
  4. 记录内核、工具和符号版本;
  5. 用业务指标验证修复结果。

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

不同发行版服务名可能是 sshsshd,应先用:

systemctl list-unit-files | grep -E '^(ssh|sshd)'

关键点:

  1. 先备份当前配置;
  2. 用守护进程自身的语法检查;
  3. 优先 reload,避免不必要的连接中断;
  4. 保留现有 SSH 会话;
  5. 从第二个会话验证新连接;
  6. 失败时恢复备份并重新检查。

如果配置导致远程登录失败,恢复路径可能需要云控制台、串口、带外管理或快照。没有恢复通道时,不应把远程服务器当作实验机。

8.3 包管理和服务依赖

不要直接覆盖系统包管理器管理的文件:

dpkg -S /usr/bin/curl 2>/dev/null || rpm -qf /usr/bin/curl

Debian 系列常用 aptdpkg;RHEL/Fedora 系列常用 dnfrpm。升级前应确认:

  • 依赖是否变化;
  • 配置文件是否冲突;
  • 服务是否需要重启;
  • 内核升级是否需要重启;
  • 是否有回滚包或镜像;
  • 数据格式是否向前兼容。

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 变量、管道、重定向、通配符、退出状态;
  • findxargssedawkgrep
  • 文件描述符和标准输入输出;
  • 用户、组、权限、ACL、符号链接;
  • /etc/passwd/etc/shadow/etc/group 的基本角色。

验收任务:编写脚本统计某目录下近一天修改的日志文件,正确处理空格、换行和权限错误。不要直接使用未经解释的 for f in $(find ...),因为命令替换会按空白切分文件名。

阶段二:进程、系统调用和内核观察

掌握:

  • forkexecwait、信号;
  • 文件描述符;
  • /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、页缓存;
  • mountumountfstab
  • dfdu、inode;
  • 本地文件系统与网络文件系统的边界;
  • 数据持久性与备份恢复。

验收任务:解释为什么 dfdu 数字不同,并用 lsof +L1 验证已删除但仍打开的文件。

阶段四:网络和服务

掌握:

  • 接口、地址、路由、邻居表;
  • DNS 与 NSS;
  • TCP 生命周期;
  • TLS 和 HTTP 的分层;
  • ssipcurltcpdump
  • systemd 服务状态和 journald。

验收任务:对一个“域名能解析但接口访问失败”的问题,依次给出 DNS、路由、TCP、TLS、HTTP 五个层次的证据,而不是只执行一次 ping

阶段五:性能与内核追踪

掌握:

  • 采样与计数;
  • CPU、内存、磁盘、网络指标;
  • 延迟分位数;
  • PSI;
  • perf
  • eBPF tracepoint、kprobe、聚合和开销控制。

验收任务:针对一次接口延迟上升,先记录业务基线,再判断是 CPU 计算、锁等待、I/O、网络重传、内存回收还是外部依赖,并说明每个结论对应的证据。

阶段六:生产运维和恢复

掌握:

  • 包管理;
  • 配置发布;
  • 权限与密钥;
  • 日志、监控、告警;
  • 备份、恢复、回滚;
  • 资源限制、容器和命名空间;
  • 变更审计与最小权限。

最终验收不应只是“能让服务启动”,而应能回答:

  1. 服务依赖哪些资源?
  2. 启动失败时从哪里取得证据?
  3. 变更前如何验证?
  4. 变更失败如何恢复?
  5. 服务活着但业务不可用时如何发现?
  6. 主机资源耗尽时如何避免波及其他服务?
  7. 数据损坏或误删后恢复路径是什么?

十、常见误解与正确替代

“Linux 一切都是文件”

这是有用但不完整的类比。统一的文件描述符接口确实覆盖普通文件、管道、套接字和部分设备,但不同对象的阻塞、定位、权限和错误语义不同。套接字不是磁盘文件,不能据此推断它支持 lseek() 或持久化。

“CPU 使用率低,所以系统不忙”

进程可能等待锁、磁盘、网络、内存回收或外部服务。应同时观察运行队列、I/O 延迟、网络重传、线程状态、PSI 和应用长尾。

“内存 used 高就是内存泄漏”

Linux 主动利用空闲内存做缓存。需要观察进程长期 RSS、匿名内存、回收压力、swap、cgroup 限制和 OOM 日志。

“端口能连通,所以服务正常”

TCP 连接只证明某个端点完成了传输层握手。TLS、认证、协议状态、依赖服务和业务数据仍可能失败。

“删掉日志文件就释放空间”

如果进程仍持有打开描述符,空间可能不释放。应使用日志轮转机制或让进程正确重新打开日志,并用 lsof +L1 验证。

“root 可以做任何事”

root 仍可能受 capability、SELinux、AppArmor、seccomp、用户命名空间、只读挂载、容器边界和云平台策略限制。更重要的是,root 的错误操作影响范围最大,因此应更强调验证和回滚。


Linux 的完整学习路线最终不是命令数量,而是解释能力:看到一个现象,能够指出它属于用户态还是内核态,经过哪个对象和状态转换,使用哪个内核接口可以观察,哪个变更可能影响它,以及发生错误后如何验证和恢复。掌握这条证据链,才能从基础命令逐步进入性能工程、平台工程和生产运维。

完整学习目录

一、系统基础

  1. Linux 内核与用户态:系统调用、异常、中断和硬件抽象
  2. Linux 启动完整流程:Firmware、Bootloader、Kernel、initramfs 与 systemd
  3. Linux 目录与文件系统层次:FHS、挂载点、proc、sysfs 与设备
  4. Linux 文件、Inode 与链接:描述符、硬链接、符号链接和删除语义

二、账户与安全

  1. Linux 权限完整指南:UID/GID、mode、umask、ACL 与 capabilities
  2. Linux 用户认证与提权:账号、PAM、sudo、密码策略和审计
  3. Linux 主机安全加固:最小化、SELinux/AppArmor、审计与漏洞治理

三、进程与服务

  1. Linux 进程、线程与信号:生命周期、父子关系、Zombie 和优雅退出
  2. systemd 服务管理:Unit、依赖、重启、资源限制和安全沙箱
  3. Bash 与 Shell 工程实践:展开、引用、管道、错误处理和脚本测试
  4. Linux 软件包管理:APT、DNF、仓库、签名、依赖与可重复安装
  5. Linux 定时任务:cron、systemd timer、时区、防重入和错过执行

四、存储与内存

  1. Linux 块设备与存储:分区、LVM、RAID、挂载和在线扩容
  2. Linux 文件系统:ext4、XFS、日志、缓存、一致性和损坏恢复
  3. Linux 虚拟内存:页表、缺页、Cache、Swap、OOM 与内存诊断

五、网络与性能

  1. Linux CPU 调度与负载:运行队列、上下文切换、Load 和软中断
  2. Linux 网络栈基础:网卡、协议栈、Socket、端口和连接状态
  3. Linux 路由、DNS 与网络诊断:ip、ss、dig、tcpdump 和抓包
  4. Linux 防火墙与 NAT:Netfilter、nftables、连接跟踪和规则治理
  5. Linux 性能分析方法:CPU、内存、磁盘、网络与 eBPF 证据链

六、生产运维

  1. Linux 日志体系:journald、rsyslog、轮转、结构化采集和磁盘治理
  2. OpenSSH 完整指南:密钥、Agent、跳板、隧道和服务端加固
  3. Linux cgroups 与 namespaces:资源控制、隔离和容器底层机制
  4. Linux 无法启动与系统故障排查:救援模式、磁盘、服务和内核证据
  5. Linux 备份与恢复:文件、块设备、快照、加密和恢复演练
  6. Linux 生产运行手册:容量、变更、监控、应急和复盘

七、内核深入

  1. Linux 系统调用与 strace:调用边界、阻塞、错误和性能诊断
  2. Linux 调度器深入:CFS、实时策略、优先级、Affinity 和 NUMA
  3. Linux 中断与 Softirq:IRQ、Bottom Half、ksoftirqd 和延迟
  4. Linux 内核模块:加载、参数、依赖、签名、DKMS 和故障恢复
  5. Linux 设备模型与 udev:设备号、sysfs、规则、热插拔和权限
  6. Linux procfs 与进程观测:状态、文件描述符、内存映射和线程
  7. Linux sysctl 与内核参数:来源、持久化、风险、压测和回滚
  8. Linux cgroups v2 深入:CPU、Memory、IO、PID、Pressure 和委派
  9. Linux namespaces 深入:PID、Mount、Network、User 和隔离组合
  10. Linux Seccomp 与 Capabilities:系统调用过滤、最小权限和沙箱
  11. Linux NUMA 与硬件拓扑:节点、内存亲和、跨节点访问和诊断
  12. Linux PSI 资源压力:CPU、内存、I/O Stall、告警和容量判断

八、命令行与自动化

  1. Linux 文件命令体系:ls、cp、mv、rm、stat、file 和安全操作
  2. Linux find 与 xargs:条件、批处理、空字符、安全和并发执行
  3. Linux 文本处理:grep、sed、awk、cut、sort、uniq 和正则
  4. Bash 展开与引用深入:变量、命令替换、通配、数组和 IFS
  5. Bash 函数与 Trap:退出码、错误传播、临时资源和信号清理
  6. Shell 脚本测试与质量:ShellCheck、Bats、Fixture 和故障注入
  7. 命令行结构化数据:jq、yq、JSON、YAML、流式处理和安全更新
  8. Linux 终端与作业控制:TTY、Session、前后台、nohup 和挂断
  9. tmux 终端复用:Session、窗口、Pane、恢复和远程运维边界
  10. Linux 归档与压缩:tar、gzip、xz、zstd、校验和和大文件
  11. rsync 文件同步:增量算法、权限、删除、断点和备份策略
  12. SSH 自动化与批量运维:Config、ProxyJump、ControlMaster 和密钥治理
  13. Ansible Linux 自动化:Inventory、Playbook、幂等、Secret 和滚动变更

九、存储深入

  1. Linux 块 I/O 栈:Bio、Queue、Scheduler、Direct I/O 和延迟
  2. Linux Page Cache 与 Writeback:脏页、回写、fsync 和数据持久性
  3. ext4 深入:Extent、Journal、分配器、fsck 和性能参数
  4. XFS 深入:Allocation Group、Journal、在线扩容、修复和大文件
  5. LVM 深入:PV、VG、LV、Thin Pool、Snapshot、扩缩容和恢复
  6. Linux 软件 RAID:mdadm、级别、重建、降级、监控和恢复
  7. Linux NFS:协议、挂载、缓存、一致性、权限和高可用
  8. Linux 磁盘性能诊断:iostat、延迟、队列深度、吞吐和饱和度
  9. Linux 磁盘加密:dm-crypt、LUKS、密钥槽、启动解锁和恢复

十、网络深入

  1. Linux TCP 深入:握手、状态机、窗口、重传、拥塞控制和队列
  2. Linux UDP 与 Datagram:缓冲、丢包、分片、批处理和适用边界
  3. Linux TCP 诊断:ss、tcpdump、重传、SYN 队列、TIME_WAIT 和延迟
  4. Linux DNS 解析链路:glibc、nsswitch、systemd-resolved、缓存和排障
  5. Linux 路由深入:多表、策略路由、ECMP、VRF 和反向路径检查
  6. Linux Bridge、VLAN、Bond 与 Team:二层网络、冗余和诊断
  7. Linux Network Namespace 与 veth:容器网络、路由和 NAT 实验
  8. Linux 网络内核调优:Socket Buffer、Backlog、Port、Conntrack 和验证
  9. Linux TLS 与证书运维:PKI、链、SNI、OCSP、续期和排障
  10. Linux Nginx 反向代理:进程模型、TLS、负载均衡、限流和日志
  11. Linux Keepalived 与 VRRP:虚拟 IP、健康检查、切换和脑裂边界

十一、安全与生产专题

  1. SELinux 完整基础:Label、Type Enforcement、Policy、布尔值和排障
  2. AppArmor 完整基础:Profile、Mode、规则、日志和服务加固
  3. Linux auditd 审计:规则、事件、性能、检索和合规留存
  4. Linux 资源限制:ulimit、RLIMIT、systemd Limit、文件句柄和进程数
  5. Linux 时间同步:Clocksource、NTP、chrony、漂移和分布式系统影响
  6. Linux Core Dump 与崩溃诊断:coredumpctl、gdb、符号和隐私
  7. Linux perf 与火焰图:采样、调用栈、符号、CPU 和 Off-CPU
  8. Linux eBPF 与 bpftrace:Hook、Map、Verifier、观测脚本和安全
  9. Linux logrotate 深入:轮转、压缩、信号、并发和磁盘保护
  10. Linux KVM 虚拟化:QEMU、vCPU、virtio、存储网络和隔离边界
  11. Linux cloud-init:实例初始化、Metadata、用户数据、幂等和安全
  12. Linux 补丁与发行版升级:安全更新、内核、重启、灰度和回滚
  13. Linux 主机事件响应:隔离、取证、时间线、证据保全和恢复

系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。