Linux性能优化
个人学习记录文档,欢迎补充
平时在实施、交付、运维时总是会遇到机子宕机的情况,遇到该紧急情况的时候,我们除了reboot、kill,还需要会精准定位系统问题,快速发现核心所在,所以阅读《Linux性能优化》并记录下相关笔记,在博客中也分享出来。
如文中有误,请联系笔者。
平均负载
平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。
所以,它不仅包括了正在使用 CPU 的进程,还包括等待 CPU 和等待 I/O的进程。
而 CPU 使用率,是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。比如:
- CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
- I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高;
- 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高。
平均负载提供了一个快速查看系统整体性能的手段,反映了整体的负载情况。但只看平均负载本身,我们并不能直接发现,到底是哪里出现了瓶颈。所以,在理解平均负载时,也要注意:
- 平均负载高有可能是 CPU 密集型进程导致的;
- 平均负载高并不一定代表 CPU 使用率高,还有可能是 I/O 更繁忙了;
- 当发现负载高的时候,你可以使用
mpstat、pidstat等工具,辅助分析负载的来源。
1 | |
上下文切换
上下文切换是 CPU 从一个进程或线程切换到另一个进程或线程时所发生的操作,是多任务操作系统实现并发的基础,通常是由操作系统的调度程序(Scheduler)控制的,以确保多任务处理的效率。
过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,缩短进程真正运行的时间,成了系统性能大幅下降的一个元凶。
根据任务的不同,CPU 的上下文切换就可以分为几个不同的场景:
进程上下文切换
进程上下文切换指的是操作系统从一个进程切换到另一个进程时,需要保存和恢复进程的状态。一个进程的状态包括进程的寄存器、程序计数器、内存管理信息等。当发生进程切换时,操作系统需要将当前进程的所有状态保存到内存中,并将新进程的状态加载到 CPU 中。
- 开销:进程上下文切换开销较大,因为需要保存和恢复大量的数据,包括整个进程的虚拟内存、打开的文件描述符等。
- 触发条件:进程调度(例如时间片用完)或中断(如系统调用)。
线程上下文切换
线程上下文切换是指在同一个进程内部的多个线程之间切换。与进程上下文切换相比,线程上下文切换的开销较小,因为线程共享进程的地址空间和资源。
- 开销:线程切换比进程切换要小,因为线程共享大部分资源,仅需要保存和恢复少量的线程上下文,如寄存器和栈指针。
- 触发条件:线程调度(如多线程程序中的时间片用完)或系统调用。
中断上下文切换
中断上下文切换是指由于外部硬件中断(如 I/O 设备请求)或软件中断(如系统调用)引发的上下文切换。中断发生时,CPU 会中断当前进程的执行,转而执行中断处理程序。中断上下文切换需要保存当前执行进程的状态,并加载中断处理程序的上下文。
- 开销:中断上下文切换的开销较小,因为它通常只涉及少量的状态保存和恢复,但频繁的中断可能会影响系统性能。
- 触发条件:硬件中断或软件中断。
中断次数变多了,说明 CPU 被中断处理程序占用,需要通过查看
/proc/interrupts文件来分析具体的中断类型。/proc实际上是 Linux 的一个虚拟文件系统,用于内核空间与用户空间之间的通信。/proc/interrupts就是这种通信机制的一部分,提供了一个只读的中断使用情况
上下文切换的类型:
**自愿上下文切换 cswch:是指进程无法获取所需资源,导致的上下文切换**。
比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换;
自愿上下文切换多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题。
**非自愿上下文切换 nvcswch:是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换**。
比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换;
非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈。
不过不管是哪种场景导致的上下文切换,都应该知道:
- CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下不需要我们特别关注。
- 但过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短进程真正运行的时间,导致系统的整体性能大幅下降。
1 | |
CPU使用率
概述
Linux 作为一个多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。
为了维护 CPU 时间,Linux 通过事先定义的节拍率(内核中表示为 HZ),触发时间中断,并使用全局变量 Jiffies 记录了开机以来的节拍数。每发生一次时间中断,Jiffies 的值就加 1。
节拍率 HZ 是内核的可配选项,可以设置为 100、250、1000 等。不同的系统可能设置不同数值,你可以通过查询 /boot/config 内核选项来查看它的配置值。
同时,正因为节拍率 HZ 是内核选项,所以用户空间程序并不能直接访问。为了方便用户空间程序,内核还提供了一个用户空间节拍率 USER_HZ,它总是固定为 100,也就是 1⁄100 秒。这样,用户空间程序并不需要关心内核中 HZ 被设置成了多少,因为它看到的总是固定值 USER_HZ。
Linux 通过 /proc虚拟文件系统,向用户空间提供了系统内部状态的信息,而 /proc/stat提供的就是系统的 CPU 和任务统计信息。
性能分析工具给出的都是间隔一段时间的平均 CPU 使用率,所以要注意间隔时间的设置
比如:
top 显示了系统总体的 CPU 和内存使用情况,以及各个进程的资源使用情况。
top默认使用 3 秒时间间隔ps 则只显示了每个进程的资源使用情况。
ps使用的是进程的整个生命周期
调用分析
perf 是 Linux 2.6.31 以后内置的性能分析工具。它以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题。
可以使用
perf top -g -p 123查找指定进程中函数的递归调用情况。详见指令部分
CPU 使用率是最直观和最常用的系统性能指标,更是我们在排查性能问题时,通常会关注的第一个指标。所以我们更要熟悉它的含义,尤其要弄清楚用户(%user)、Nice(%nice)、系统(%system) 、等待 I/O(%iowait) 、中断(%irq)以及软中断(%softirq)这几种不同 CPU 的使用率。
比如说:
用户CPU 和NiceCPU 高,说明用户态进程占用了较多的 CPU,所以应该着重排查进程的性能问题。系统CPU 高,说明内核态占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题。I/O 等待CPU 高,说明等待 I/O 的时间比较长,所以应该着重排查系统存储是不是出现了 I/O 问题。软中断和硬中断高,说明软中断或硬中断的处理程序占用了较多的 CPU,所以应该着重排查内核中的中断服务程序。
碰到 CPU 使用率升高的问题,你可以借助 top、pidstat 等工具,确认引发 CPU 性能问题的来源;再使用 perf 等工具,排查出引起性能问题的具体函数。
但不是所有CPU使用率高的问题都可以这么分析,系统的 CPU 使用率不仅包括进程用户态和内核态的运行,还包括中断处理、等待 I/O 以及内核线程等。
进程
分享一个相关知乎回答:古董系统init进程的SIGSTOP与SIGCONT
僵尸进程
僵尸进程,这是多进程应用很容易碰到的问题,一般是子进程没有受到父进程的直接管理、回收导致。
通常僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由 init 进程回收后也会消亡。
但是一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。那么大量的僵尸进程会用尽 PID 进程号,会导致新进程不能创建。
此时对于代码层面,我们需要注意子进程结束的处理是否正确,比如有没有调用wait()或waitpid(),抑或是,有没有注册SIGCHLD信号的处理函数。
1 | |
中断
Linux 将中断处理过程分成了两个阶段,衍生出了硬中断和软中断,下面以网卡接受数据包为例:
上半部:当网卡接收到数据包,会向 CPU 发出 硬件中断信号,通知内核有新的数据到了。这时,内核就应该调用中断处理程序来响应它,把网卡的数据读到内存中,并更新一下硬件寄存器的状态(表示数据已经读好了),最后触发软中断信号,通知下半部分处理。
上半部直接处理硬件请求,也就是我们常说的硬中断,特点是快速执行;
下半部阶段:被软中断信号唤醒后,从内存中找到网络数据,再按网络协议栈(IP → TCP/UDP → Socket)进行解析,对数据进行逐层解析和处理,直到把它送给应用程序。
而下半部则是由内核触发,也就是我们常说的软中断,特点是延迟执行。
iowait
iowait 是指 CPU 处于空闲状态时,系统中有进程因为等待磁盘或网络等 I/O 操作 而进入 不可中断睡眠(D 状态) 的时间比例。
比如程序调用了读文件或读 socket,而数据还没到内存,CPU 只能等待,这段时间就算作 iowait。
如果系统或硬件发生了故障,进程可能会在不可中断状态保持很久,甚至导致系统中出现大量不可中断进程。这时,你就得注意下,系统是不是出现了 I/O 等性能问题。
不过,iowait 高不一定代表I/O 有性能瓶颈。当系统中只有 I/O 类型的进程在运行时,iowait 也会很高,但实际上,磁盘的读写远没有达到性能瓶颈的程度,可能是由于代码中对磁盘进行了直接读,绕过系统缓存导致。
后续在碰到 iowait 升高时,需要先用 dstat、pidstat 等工具,确认是不是磁盘 I/O 的问题,然后再找是哪些进程导致了 I/O。
softirq
SoftIRQ 的本质就是分摊和推迟工作,以保证系统对硬件中断的快速响应,所以对于软中断,往往是因为某些网络场景下导致大量数据包来不及处理而频繁触发导致。
软中断不只包括了硬件设备中断处理程序的下半部,一些内核自定义的事件也属于软中断,比如内核调度和RCU锁(Read-Copy Update 的缩写,RCU 是 Linux 内核中最常用的锁之一)等。
每个 CPU 都对应一个软中断内核线程,名字为 “ksoftirqd/CPU编号”,比如说, 0 号CPU对应的软中断内核线程的名字就是 ksoftirqd/0。
1 | |
我们可以通过下面的方式查看各种类型的软中断 在不同 CPU 上的累积运行次数:
1 | |
需要注意各软中断类型和在各CPU的分布情况,正常情况下,同一种中断在不同 CPU 上的累积次数应该差不多;
除了TASKLET,它是最常用的软中断实现机制,每个 TASKLET 只运行一次就会结束 ,并且只在调用它的函数所在的 CPU 上运行。
| 名称 | 全称 | 主要功能 / 场景 |
|---|---|---|
| HI | High Priority | 高优先级任务,用得较少。用于一些特殊高实时性需求的软中断。 |
| TIMER | Timer SoftIRQ | 定时器中断处理,比如jiffies增加、定时任务、超时检测等。 |
| NET_TX | Network Transmit | 网络发送完成后的处理,比如释放发送缓冲区。 |
| NET_RX | Network Receive | 网络接收处理,负责处理网卡收到的数据包。高负载网络时常见升高。 |
| BLOCK | Block SoftIRQ | 块设备(磁盘 I/O)中断的下半部处理(现代内核大多由工作队列接管,因此常为 0)。 |
| IRQ_POLL | IRQ Polling | 轮询机制下的中断处理(通常用于高性能网卡或磁盘驱动的 polling 模式)。 |
| TASKLET | Tasklet | 一种延迟执行机制,用于驱动中延迟处理任务(现在多被软中断或 workqueue 替代)。 |
| SCHED | Scheduler SoftIRQ | 调度相关的软中断,用于负载均衡、CFS 调度、唤醒任务等。 |
| HRTIMER | High Resolution Timer | 高精度定时器,用于纳秒级定时任务(例如实时系统)。 |
| RCU | Read-Copy-Update | 内核 RCU 机制的回调执行,用于锁的无锁读写同步清理工作。 |
分析链路:
NET_RX和TIMER很活跃:则系统网络包接收多;SCHED与RCU很高:CPU 调度、内核同步频繁;BLOCK、HRTIMER等为 0:系统磁盘和高精度定时器较空闲。
在遇到可疑的ksoftirqd升高时,可以通过/proc/softirq和工具sar来排查软中断问题。
软中断频繁触发且占用 CPU 过高的常见场景包括:
- 高数据包速率 (PPS): 即使带宽不大,但如果每秒数据包数量极高(如 DDoS 攻击或大量小连接),每个包都会产生 SoftIRQ 处理开销。
- 协议栈处理复杂性: 数据包需要经过复杂的协议栈解析、校验、路由查找等。
- 防火墙/LVS/NAT 处理: 规则数量过多、或者使用了复杂的 Netfilter 模块,导致处理时间过长
- 驱动问题或配置不当: 网卡驱动的队列(NAPI)配置不当,或网卡自身出现瓶颈。
事故案例:防火墙规则爆炸引发的软中断危机
现象
- 环境: 部署了 Tailscale 的 Linux 服务器,使用
nftables进行防火墙管理。 - 故障表现: 服务器在高流量时性能急剧恶化,内核线程
ksoftirqd单核占用率达到 **100%**。内网使用iperf3测试,打流性能从正常水平暴跌至不足 10Mbps。 - 初步排查:
top命令未发现异常用户进程;iptraf未发现明显的外部 DDoS 或异常流量。
事故原因:规则爆炸与内核缓存失效
- 事故起因: 为解决 Tailscale(使用 CGNAT
100.64.0.0/10网段)与内部业务 IP 冲突的问题,工程师设置了一个systemd定时任务,用于在 Tailscale 重启后向nftables中添加一条排除规则:1
iifname != "tailscal0" ip saddr 100.64.18.0/24 counter packets 0 bytes 0 drop - 脚本逻辑错误: 定时任务脚本逻辑编写错误,未能检查规则是否已存在。导致每隔一段时间,系统中就新增一条完全相同的
nft规则。 - 核心后果(规则爆炸): 在发现问题时,防火墙规则链的大小已经膨胀到 1MB 级别。
- 性能瓶颈:
- CPU 软中断 (SoftIRQ) 危机: 软中断负责处理网络数据包、协议栈解析、防火墙匹配等耗时操作。由于规则数量庞大,内核在处理每一个传入的数据包时,必须 遍历这 1MB 长的规则链。
- 缓存失效 (Cache Miss): 1MB 的规则数据超过了 CPU 的 L1/L2 缓存容量。每次遍历都会导致频繁的缓存失效,极大地增加了处理每个数据包所需的 CPU 周期。
ksoftirqd满载: CPU 被迫将所有资源投入到 SoftIRQ 任务中,ksoftirqd线程因此单核跑满 100%,却仍无法及时清空数据包队列,导致网络性能雪崩。
内存
概述
Buffer/Cache
Buffer是对磁盘数据的读写缓存;
Cache是文件数据的读写缓存。
相关工具
sysstat
sysstat包含了常用的 Linux 性能工具,用来监控和分析系统的性能。
1 | |
mpstat
mpstat是一个常用的多核 CPU 性能分析工具,用来实时查看每个 CPU 的性能指标,以及所有 CPU 的平均指标。
1 | |
输出数据样例
1 | |
pidstat
pidstat是一个常用的进程性能分析工具,用来实时查看进程的 CPU、内存、I/O 以及上下文切换等性能指标。
1 | |
输出数据样例
1 | |
perf
是Linux 2.6.31 以后内置的性能分析工具。它以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题
1 | |
第一行包含三个数据,分别是采样数(Samples)、事件类型(event)和事件总数量(Event count)。如采样数过少(比如只有十几个),下面的排序和百分比则没什么实际参考价值。
再往下是一个表格式样的数据,每一行包含四列,分别是:
- Overhead ,是该符号的性能事件在所有采样中的比例,用百分比来表示
- Shared ,是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等
- Object ,是动态共享对象的类型。比如
[.]表示用户空间的可执行程序、或者动态链接库,而[k]则表示内核空间 - Symbol 是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示
perf record:提供了保存数据的功能
perf report:解析展示保存后的数据
入参:
-p [pid]:指定pid来查看进程内的调用关系,最终定位到进程内资源占用最大的函数。-g:开启调用关系的采样,方便我们根据调用链来分析性能问题。
1 | |
调用方法
| 方法/栈函数 | 所属 | 说明 | 备注 |
|---|---|---|---|
| vfs_write | 内核 | Linux 文件系统写操作 | 常见于文件I/O |
| do_sync_write | 内核 | 同步写 | 常见于文件I/O |
| sys_write | 内核 | 系统调用写文件 | 常见于文件I/O |
| tcp_transmit_skb | 内核 | TCP 发送数据包 | 常见于网络I/O |
| ip_queue_xmit | 内核 | IP 层发送数据 | 常见于网络I/O |
| tcp_sendmsg | 内核 | 系统调用 | 常见于网络I/O |
| inet_sendmsg | 内核 | socket 层发送消息 | 常见于网络I/O |
| dev_queue_xmit | 内核 | 网络设备驱动实际发送数据 | 常见于网络I/O |
| sys_epoll_wait | 内核 | 等待网络事件,会阻塞线程等待I/O | 常见于网络I/O |
| 方法/栈函数 | 所属 | 说明 |
|---|---|---|
| perf-.map | perf map / JIT | 是JIT 编译后的 Java 方法地址(perf-72958.map),在查看java相关进程可以看见,如kafka。 |
| Parker::park | - | Java 层线程阻塞实现,结合 LockSupport.park() |
| KafkaApis.handleCreateTopicsRequest | Java | Controller 接收 CreateTopics 请求,校验参数 |
| Controller.createTopics | Java | Kafka方法,创建 Topic、分区和副本分配 |
| ReplicaManager.createPartitions | Java | 在各 broker 创建 partition 的 log 文件 |
| LogManager.getOrCreateLog | Java | 创建 log segment 文件,写入元数据 |
| Log.append / Log.flush | Java | 写入消息到文件系统(文件 I/O) |
| MetadataCache.updateMetadata | Java | 更新内存元数据,通知网络层广播 |
分析链路
磁盘直接IO:sys_read() -> new_sync_read -> blkdev_direct_IO,绕过系统缓存
sar
sar 是一个系统活动报告工具,既可以实时查看系统的当前活动,又可以配置保存和报告历史统计数据。
网络情况
sar 可以用来查看系统的网络收发情况,还有一个好处是,不仅可以观察网络收发的吞吐量(BPS,每秒收发的字节数),还可以观察网络收发的 PPS,即每秒收发的网络帧数。
1 | |
对于 sar 的输出界面,从左往右依次是:
- 第一列:表示报告的时间。
- 第二列:IFACE 表示网卡。
- 第三、四列:rxpck/s 和 txpck/s 分别表示每秒接收、发送的网络帧数,也就是 PPS。
- 第五、六列:rxkB/s 和 txkB/s 分别表示每秒接收、发送的千字节数,也就是 BPS。
execsnoop
专为短时进程 / 幽灵进程设计的工具,它通过 ftrace 实时监控进程的 exec() 行为,并输出短时进程的基本信息,包括进程 PID、父进程 PID、命令行参数以及执行的结果
当然通过pstree也可以捕获到父进程,但是子进程死的太快也无法捕获到
1 | |
strace
跟踪进程系统调用
1 | |
top
查看系统总体的 CPU 和内存使用情况,以及各个进程的资源使用情况:
1 | |
输出数据样例
1 | |
PID: 进程的唯一标识符(Process ID)。每个运行中的进程都有一个唯一的PID。
USER: 启动该进程的用户。如果进程是以特定用户身份运行的,这里会显示该用户的用户名。
PR: 进程的优先级(Priority)。数值越小,优先级越高。进程的优先级可以影响其CPU的分配。
NI: 进程的“nice”值(Nice Value)。这个值影响进程的优先级,范围通常是-20(最高优先级)到19(最低优先级)。负值表示更高的优先级,正值表示更低的优先级。
VIRT: 进程使用的虚拟内存总量,包括进程的所有代码和数据,以及可能未被实际使用的内存。
RES: 进程使用的常驻内存(Resident Memory),即实际在RAM中占用的内存量,不包括交换区(swap)。
SHR: 进程共享的内存量(Shared Memory),即与其他进程共享的内存量。
S: 进程的状态(Status)。常见的状态有:
- R: 运行中(Running)
- S: 睡眠中(Sleeping)
- Z: 僵尸进程(Zombie)
- T: 停止(Stopped)
- t:跟踪(Traced),断点调试常见
- X:消亡(Dead)
- 额外参数:
- s:该进程是一个会话的领导进程;会话是指共享同一个控制终端的一个或多个进程组
- +:前台进程组;进程组表示一组相互关联的进程,比如每个子进程都是父进程所在组的成员
%CPU: 进程使用的CPU占用率,表示该进程在总CPU时间中的占比。
%MEM: 进程使用的物理内存占用率,表示该进程占用的内存与系统总内存的百分比。
**TIME+**: 进程使用的总CPU时间,格式为hh:mm:ss,表示该进程在所有CPU上运行的总时间。
COMMAND: 启动该进程的命令名称或路径。
us用户、sy系统、ni-Nice、id空闲时间、wa等待IO的CPU时间、hi处理硬中断CPU时间、si处理软中断CPU时间、st系统运行在虚拟机中时其他虚拟机占用CPU时间
/proc
查看CPU核心数量:
1 | |
查看系统的 CPU 和任务统计信息:
1 | |
输出数据样例
1 | |
- user(通常缩写为 us),代表用户态 CPU 时间。注意,它不包括下面的 nice 时间,但包括了 guest 时间。
- nice(通常缩写为 ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。这里注意,nice 可取值范围是 -20 到 19,数值越大,优先级反而越低。
- system(通常缩写为 sys),代表内核态 CPU 时间。
- idle(通常缩写为 id),代表空闲时间。注意,它不包括等待 I/O 的时间(iowait)。
- iowait(通常缩写为 wa),代表等待 I/O 的 CPU 时间。
- irq(通常缩写为 hi),代表处理硬中断的 CPU 时间。
- softirq(通常缩写为 si),代表处理软中断的 CPU 时间。
- steal(通常缩写为 st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间。
- guest(通常缩写为 guest),代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的 CPU 时间。
- guest_nice(通常缩写为 gnice),代表以低优先级运行虚拟机的时间。
上下文
uptime:查看系统平均负载
1 | |
vmstat:分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数
1 | |
- cs(context switch)是每秒上下文切换的次数。
- in(interrupt)则是每秒中断的次数。
- r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
- b(Blocked)则是处于不可中断睡眠状态的进程数。
GPU
nvidia-smi:查看英伟达GPU占用
hping3
hping3 是一个可以构造 TCP/IP 协议数据包的工具,可以对系统进行安全审计、防火墙测试等。
模拟SYN FLOOD 攻击:
1 | |