阅读边界: 这页不写内核源码逐行分析,而是解释应用在 Linux 上运行时,内核如何调度 CPU、管理内存、抽象文件、处理网络和限制容器资源。
本页主责: 只负责“应用与 Linux 内核资源之间的边界”:调度、虚拟内存、VFS、网络栈、cgroup、namespace。CPU 微架构、语言 GC/JIT、SSD 介质和 GPU 数据搬运只做跳转,不在本页展开。
前置设计训练: 如果你想先从“为什么需要进程、内存隔离、系统调用、文件系统和驱动”开始,而不是直接进入 Linux 机制,可以先看
如何设计操作系统。
一句话判断: 线上很多“应用问题”,其实是应用、运行时、内核和资源限制共同形成的系统问题。
调度
决定哪个线程在什么时候获得 CPU,以及等待、唤醒、抢占和优先级如何影响延迟。
内存
通过虚拟内存、页表、缺页异常、页缓存和回收策略管理进程地址空间。
文件与设备
用 VFS、文件描述符、块设备和驱动把磁盘、Socket、Pipe 等统一成可操作对象。
网络
处理 TCP/IP 协议栈、连接状态、缓冲区、拥塞、重传、软中断和路由。
隔离与限制
namespace 提供视图隔离,cgroup 提供 CPU、内存、IO 等资源限制。
| 应用现象 | 内核机制 | 为什么会发生 | 证据 |
| CPU 不高但接口慢 | 调度、阻塞、唤醒、IO 等待 | 线程可能都在等锁、磁盘、网络、连接池或 cgroup quota,而不是在 CPU 上运行 | 线程栈、run queue、上下文切换、iowait、Trace span |
| 内存看起来满了 | Page Cache、匿名页、RSS、内存回收 | Linux 会积极使用空闲内存做缓存,满不等于泄漏;容器 limit 又会改变判断边界 | RSS、cache、available、cgroup memory、OOM 日志 |
| 磁盘满但删文件没用 | VFS、inode、打开文件描述符 | 文件被删除后,如果进程仍持有 fd,空间仍然被占用 | lsof deleted、df / du 差异、进程 fd |
| 网络偶发超时 | TCP 状态、重传、队列、软中断、路由 | 网络不是一条线,DNS、建连、TLS、发送队列、接收队列、下游处理都可能排队 | 重传率、连接状态、网卡队列、软中断、Trace |
| 容器里慢,本机不慢 | cgroup、namespace、CNI、挂载和安全上下文 | 容器看到的是被限制和隔离后的资源视图,不等于宿主机真实全局视图 | cpu.stat、memory.events、mount、resolv.conf、iptables / CNI |
| 场景 | 错误猜测 | 真实机制 | 证据 | 动作 |
| 容器 CPU 看起来不高,但接口周期性变慢 | 应用代码偶发慢 | cgroup CPU quota 用完后被 throttling,线程虽然可运行但拿不到 CPU | cpu.stat 里的 throttled_time / nr_throttled、P99 周期性尖刺、宿主机负载 | 调整 request / limit,减少同节点混部,优化 CPU 热点,避免用平均 CPU 掩盖 throttling |
| 删除大日志后磁盘空间没有释放 | df 命令坏了或文件系统异常 | 进程仍持有已删除文件的 fd,VFS 只有最后引用释放后才回收空间 | df 与 du 不一致,lsof 看到 deleted 文件,进程 fd 指向已删除路径 | 让进程重开日志、重启持有进程或触发日志框架 reopen,不要继续盲删目录 |
| 服务连接数很多,重启后短时间又耗尽 | ulimit 太小 | 连接泄漏、TIME_WAIT、半开连接、下游慢或连接池归还异常 | ss 连接状态、fd 数、连接池 active / idle、下游耗时和超时配置 | 先分类连接状态,再修复释放路径、超时、keepalive 和池化策略 |
| 内存满但看不到明显堆泄漏 | 监控不准 | RSS 可能来自堆外内存、线程栈、mmap、page cache 或 native 库 | heap 与 RSS 差距、pmap、smaps、direct memory、线程数、cgroup memory.events | 分层拆 heap / non-heap / native / cache / cgroup,不要只调 JVM heap |
| 步骤 | 判断 | 关键证据 | 典型动作 |
| 1. 先确认是谁杀的 | 应用退出、Kubernetes OOMKilled、内核 OOM killer、运行时主动退出不是一回事 | Pod lastState、exit code 137、dmesg、memory.events、容器重启时间线 | 先固定事件类型,再谈泄漏或扩容 |
| 2. 拆 heap 与 RSS | heap 低只能说明语言堆没满,不能说明进程内存不高 | JVM heap / non-heap、RSS、PSS、pmap、/proc/<pid>/smaps_rollup | 把内存拆成 heap、direct、metaspace、线程栈、mmap、native、page cache |
| 3. 看容器边界 | 容器 OOM 看的是 cgroup limit,不是宿主机整体 free 内存 | memory.max、memory.current、memory.peak、memory.events、Pod limit | 核对 request / limit,避免只看宿主机 available |
| 4. 找增长斜率 | 一次 OOM 只能证明撞线,连续曲线才能区分泄漏、峰值、缓存和突发流量 | RSS 曲线、线程数、连接数、请求体大小、DirectBuffer、mmap 文件数 | 把内存曲线和发布、流量、批任务、GC、缓存刷新对齐 |
| 5. 分层止血 | 先降低杀进程概率,再做根因修复;不要只无脑调大 heap | OOM 前后 profile、heap dump、native memory tracking、连接池和缓存指标 | 限制并发 / 请求体,修复堆外泄漏,调整容器 limit,给 GC 与运行时参数留安全边界 |
| 步骤 | 判断 | 关键证据 | 典型动作 |
| 1. 区分 df 与 du | df 看文件系统已分配空间,du 看目录树可达文件大小;两者不一致通常是引用还没释放 | df -h、du -sh、挂载点、容器内外路径 | 先确认满的是哪个挂载点,不要跨目录盲删 |
| 2. 查 deleted fd | 文件名被删除,只是目录项没了;进程还持有 fd 时,inode 和磁盘块仍在 | lsof | grep deleted、/proc/<pid>/fd、进程启动时间和日志路径 | 找到持有者,而不是继续删除更多文件 |
| 3. 判断能否安全释放 | 直接 kill 进程可能扩大故障;重开日志通常比重启服务更安全 | 服务角色、是否有 reload / reopen 信号、日志框架能力、实例副本数 | 优先 logrotate reopen、发送 USR1 / HUP、滚动重启单实例 |
| 4. 看 inode 与小文件 | 磁盘满不只可能是字节满,也可能是 inode 耗尽 | df -i、目录文件数量、日志切割策略、临时文件清理策略 | 清理小文件风暴,调整切割、保留期和目录分片 |
| 5. 固化预防 | 磁盘事故常常不是“空间不足”,而是日志生命周期没人负责 | 日志大小、保留天数、切割频率、采集延迟、告警阈值 | 设置磁盘水位告警、强制日志轮转、限制单文件大小,保证删除后进程能重开文件 |
| 入口症状 | 第一层判断 | 继续下钻 | 核心证据 | 容易走偏的方向 |
| 慢 | 先拆 CPU 计算慢、线程等待慢、下游慢、网络慢 | CPU 高看 perf / 火焰图;CPU 不高看锁、IO、连接池、run queue、cgroup throttling | Trace、top、pidstat、vmstat、线程栈、cpu.stat | 只凭平均 CPU 判断服务没压力 |
| OOM / 重启 | 先确认是 cgroup OOM、内核 OOM、运行时崩溃还是健康检查杀死 | heap 低时继续查 RSS、direct memory、线程栈、mmap、page cache、native 库 | memory.events、dmesg、RSS、smaps、heap dump、NMT | 只调大 JVM heap,反而挤压堆外和系统余量 |
| 磁盘满 | 先看字节满、inode 满、挂载点满还是容器层满 | df / du 不一致查 deleted fd;IO 慢查队列深度、fsync、远程盘、写回 | df、du、df -i、lsof deleted、iostat、mount | 跨目录盲删,或把日志删掉却不让进程重开 |
| 连接异常 | 先分 DNS、建连、TLS、发送、下游处理、回包、连接池 | 连接多查 fd、TIME_WAIT、CLOSE_WAIT;超时多查重传、队列、软中断、下游 P99 | ss、sar、网关日志、连接池指标、重传率、Trace | 无脑加重试,把下游和连接池压得更坏 |
| 容器异常 | 先确认容器视图和宿主机视图是否一致 | 资源类看 cgroup;网络类看 namespace / CNI / iptables;文件类看 mount namespace | kubectl describe、cpu.stat、memory.events、nsenter、mount、resolv.conf | 拿宿主机 top / free 直接解释容器里的限制 |
| 问题 | 优先证据 | 常用工具 / 位置 | 看错会怎样 |
| CPU / 调度 | 用户态 / 系统态、run queue、上下文切换、throttling | top、pidstat、vmstat、/sys/fs/cgroup/*/cpu.stat | 把 quota 限制误判成代码偶发慢 |
| 内存 | RSS、heap、page cache、available、OOM 事件 | free、pmap、smaps、dmesg、memory.events | 把 page cache 当泄漏,或只看 heap 漏掉堆外 |
| 磁盘 / 文件 | iowait、队列深度、inode、打开 fd、deleted file | iostat、df、du、lsof、/proc/<pid>/fd | 删文件不释放时继续清目录 |
| 网络 | 连接状态、重传、监听端口、队列、DNS | ss、ip、resolv.conf、sar、网关日志 | 把连接池耗尽直接归因于下游不稳定 |
| 容器 | namespace 视图、limit、mount、CNI、资源事件 | kubectl describe、cgroup files、nsenter、iptables | 用宿主机视图解释容器里的问题 |