Linux性能优化

个人学习记录文档,欢迎补充

平时在实施、交付、运维时总是会遇到机子宕机的情况,遇到该紧急情况的时候,我们除了reboot、kill,还需要会精准定位系统问题,快速发现核心所在,所以阅读《Linux性能优化》并记录下相关笔记,在博客中也分享出来。

如文中有误,请联系笔者。

平均负载

平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数

所以,它不仅包括了正在使用 CPU 的进程,还包括等待 CPU等待 I/O的进程。

而 CPU 使用率,是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。比如:

  • CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
  • I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高;
  • 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高。

平均负载提供了一个快速查看系统整体性能的手段,反映了整体的负载情况。但只看平均负载本身,我们并不能直接发现,到底是哪里出现了瓶颈。所以,在理解平均负载时,也要注意:

  • 平均负载高有可能是 CPU 密集型进程导致的;
  • 平均负载高并不一定代表 CPU 使用率高,还有可能是 I/O 更繁忙了;
  • 当发现负载高的时候,你可以使用 mpstatpidstat等工具,辅助分析负载的来源。
1
2
3
4
5
相关命令:
top
uptime
mpstat -P ALL
pidstat -u

上下文切换

上下文切换是 CPU 从一个进程或线程切换到另一个进程或线程时所发生的操作,是多任务操作系统实现并发的基础,通常是由操作系统的调度程序(Scheduler)控制的,以确保多任务处理的效率。

过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,缩短进程真正运行的时间,成了系统性能大幅下降的一个元凶。

根据任务的不同,CPU 的上下文切换就可以分为几个不同的场景:

  1. 进程上下文切换

    进程上下文切换指的是操作系统从一个进程切换到另一个进程时,需要保存和恢复进程的状态。一个进程的状态包括进程的寄存器、程序计数器、内存管理信息等。当发生进程切换时,操作系统需要将当前进程的所有状态保存到内存中,并将新进程的状态加载到 CPU 中。

    • 开销:进程上下文切换开销较大,因为需要保存和恢复大量的数据,包括整个进程的虚拟内存、打开的文件描述符等。
    • 触发条件:进程调度(例如时间片用完)或中断(如系统调用)。
  2. 线程上下文切换

    线程上下文切换是指在同一个进程内部的多个线程之间切换。与进程上下文切换相比,线程上下文切换的开销较小,因为线程共享进程的地址空间和资源。

    • 开销:线程切换比进程切换要小,因为线程共享大部分资源,仅需要保存和恢复少量的线程上下文,如寄存器和栈指针。
    • 触发条件:线程调度(如多线程程序中的时间片用完)或系统调用。
  3. 中断上下文切换

    中断上下文切换是指由于外部硬件中断(如 I/O 设备请求)或软件中断(如系统调用)引发的上下文切换。中断发生时,CPU 会中断当前进程的执行,转而执行中断处理程序。中断上下文切换需要保存当前执行进程的状态,并加载中断处理程序的上下文。

    • 开销:中断上下文切换的开销较小,因为它通常只涉及少量的状态保存和恢复,但频繁的中断可能会影响系统性能。
    • 触发条件:硬件中断或软件中断。

    中断次数变多了,说明 CPU 被中断处理程序占用,需要通过查看 /proc/interrupts文件来分析具体的中断类型。

    /proc实际上是 Linux 的一个虚拟文件系统,用于内核空间与用户空间之间的通信。/proc/interrupts就是这种通信机制的一部分,提供了一个只读的中断使用情况

上下文切换的类型:

  • **自愿上下文切换 cswch:是指进程无法获取所需资源,导致的上下文切换**。

    比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换;

    自愿上下文切换多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题。

  • **非自愿上下文切换 nvcswch:是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换**。

    比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换;

    非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈。

不过不管是哪种场景导致的上下文切换,都应该知道:

  1. CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下不需要我们特别关注。
  2. 但过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短进程真正运行的时间,导致系统的整体性能大幅下降。
1
2
3
4
相关命令:
vmstat
pidstat -w -u
cat /proc/interrupts

CPU使用率

概述

Linux 作为一个多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。

为了维护 CPU 时间,Linux 通过事先定义的节拍率(内核中表示为 HZ),触发时间中断,并使用全局变量 Jiffies 记录了开机以来的节拍数。每发生一次时间中断,Jiffies 的值就加 1。

节拍率 HZ 是内核的可配选项,可以设置为 100、250、1000 等。不同的系统可能设置不同数值,你可以通过查询 /boot/config 内核选项来查看它的配置值。

同时,正因为节拍率 HZ 是内核选项,所以用户空间程序并不能直接访问。为了方便用户空间程序,内核还提供了一个用户空间节拍率 USER_HZ,它总是固定为 100,也就是 1100 秒。这样,用户空间程序并不需要关心内核中 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
ps -eo pid,ppid,state,cmd | grep Z

磁盘

磁盘与文件系统

同CPU、内存一样,磁盘和文件系统的管理,也是操作系统最核心的功能。

  • 磁盘为系统提供了最基本的持久化存储。
  • 文件系统则在磁盘的基础上,提供了一个用来管理文件的树状结构。

文件系统,是对存储设备上的文件,进行组织管理的一种机制。组织方式不同,就会形成不同的文件系统。

索引节点和目录项

Linux文件系统为每个文件都分配两个数据结构,索引节点(index node)和目录项(directory entry)。它们主要用来记录文件的元信息和目录结构。

  • 索引节点,简称为inode,用来记录文件的元数据,比如inode编号、文件大小、访问权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。所以索引节点同样占用磁盘空间

  • 目录项,简称为dentry,用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。

    不过,不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。

索引节点的容量,(也就是Inode个数)是在格式化磁盘时设定好的,一般由格式化工具自动生成。当你发现索引节点空间不足,但磁盘空间充足时,很可能就是过多小文件导致的

可以给df命令加上 -i 参数,查看索引节点的使用情况:

1
2
3
4
5
6
7
8
9
10
11
$ df -ihT
Filesystem Type Inodes IUsed IFree IUse% Mounted on
/dev/mapper/centos-root xfs 25M 59K 25M 1% /
devtmpfs devtmpfs 4.0M 458 4.0M 1% /dev
tmpfs tmpfs 4.0M 1 4.0M 1% /dev/shm
tmpfs tmpfs 4.0M 2.2K 4.0M 1% /run
tmpfs tmpfs 4.0M 16 4.0M 1% /sys/fs/cgroup
/dev/sdb ext4 38M 661K 37M 2% /data
/dev/sda1 xfs 512K 328 512K 1% /boot
/dev/mapper/centos-home xfs 21M 3 21M 1% /home
tmpfs tmpfs 4.0M 1 4.0M 1% /run/user/0

虚拟文件系统

为了支持各类不同的文件系统,Linux在各种文件系统实现上,抽象了一层虚拟文件系统(VFS)。

VFS 定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其他子系统,就只需要跟 VFS 提供的统一接口进行交互。

为了降低慢速磁盘对性能的影响,文件系统又通过页缓存、目录项缓存以及索引节点缓存,缓和磁盘延迟对应用程序的影响。

Buffer/Cache

磁盘与文件系统有各自的系统调用以及缓存方法,其中:

Buffer是对磁盘数据的读写缓存;Cache是文件数据的读写缓存。

缓存类型 作用位置 缓存内容 系统调用方法
Page Cache(页缓存) 内存 文件数据页 标准read()/write()
Buffer Cache(缓冲区缓存) 内存 磁盘块/元数据 sync()fsync()fdatasync()
Directory Cache(目录缓存) 内存 目录项信息 getdents()readdir()

在部分io使用率升高的场景中,也有可能是开发者未通过常规缓存方法进行IO操作导致的(如O_DIRECT),通常使用pidstat -dstrace可以跟踪到其调用的具体内容。

iowait

iowait 是指 CPU 处于空闲状态时,系统中有进程因为等待磁盘或网络等 I/O 操作 而进入 不可中断睡眠(D 状态) 的时间比例。

比如程序调用了读文件或读 socket,而数据还没到内存,CPU 只能等待,这段时间就算作 iowait。

如果系统或硬件发生了故障,进程可能会在不可中断状态保持很久,甚至导致系统中出现大量不可中断进程。这时,你就得注意下,系统是不是出现了 I/O 等性能问题。

不过,iowait 高不一定代表I/O 有性能瓶颈。当系统中只有 I/O 类型的进程在运行时,iowait 也会很高,但实际上,磁盘的读写远没有达到性能瓶颈的程度,可能是由于代码中对磁盘进行了直接读,绕过系统缓存导致。

后续在碰到 iowait 升高时,需要先用 dstat、pidstat 等工具,确认是不是磁盘 I/O 的问题,然后再使用iostat/iotoppidstat -dstracefiletopopensnoop找是哪些进程导致了 I/O:

iostat:

1
2
3
4
$ iostat -d -x 1
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %util
loop0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sda 0.00 71.00 0.00 32912.00 0.00 0.00 0.00 0.00 0.00 18118.31 241.89 0.00 463.55 13.86 98.40

pidstat:

1
2
3
$ pidstat -d 1 
14:39:14 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
14:39:15 0 12280 0.00 335716.00 0.00 0 python

strace:

1
2
3
4
5
6
7
8
9
10
11
12
$ strace -p 18940 
strace: Process 18940 attached
...
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0f7aee9000
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0f682e8000
write(3, "2018-12-05 15:23:01,709 - __main"..., 314572844
) = 314572844
munmap(0x7f0f682e8000, 314576896) = 0
write(3, "\n", 1) = 1
munmap(0x7f0f7aee9000, 314576896) = 0
close(3) = 0
stat("/tmp/logtest.txt.1", {st_mode=S_IFREG|0644, st_size=943718535, ...}) = 0

filetop:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -C 选项表示输出新内容时不清空屏幕 
$ ./filetop -C

TID COMM READS WRITES R_Kb W_Kb T FILE
514 python 0 1 0 2832 R 669.txt
514 python 0 1 0 2490 R 667.txt
514 python 0 1 0 2685 R 671.txt
514 python 0 1 0 2392 R 670.txt
514 python 0 1 0 2050 R 672.txt

...

TID COMM READS WRITES R_Kb W_Kb T FILE
514 python 2 0 5957 0 R 651.txt
514 python 2 0 5371 0 R 112.txt
514 python 2 0 4785 0 R 861.txt
514 python 2 0 4736 0 R 213.txt
514 python 2 0 4443 0 R 45.txt

opensnoop:

1
2
3
4
$ opensnoop 
12280 python 6 0 /tmp/9046db9e-fe25-11e8-b13f-0242ac110002/650.txt
12280 python 6 0 /tmp/9046db9e-fe25-11e8-b13f-0242ac110002/651.txt
12280 python 6 0 /tmp/9046db9e-fe25-11e8-b13f-0242ac110002/652.txt

注:在利用磁盘空间处理大量数据时,可能会生成许多碎片小文件,对于高并发场景下大量临时文件的IO也有可能会导致io使用率和饱和度升高

中断

Linux 将中断处理过程分成了两个阶段,衍生出了硬中断和软中断,下面以网卡接受数据包为例:

上半部:当网卡接收到数据包,会向 CPU 发出 硬件中断信号,通知内核有新的数据到了。这时,内核就应该调用中断处理程序来响应它,把网卡的数据读到内存中,并更新一下硬件寄存器的状态(表示数据已经读好了),最后触发软中断信号,通知下半部分处理。

上半部直接处理硬件请求,也就是我们常说的硬中断,特点是快速执行;

下半部阶段:被软中断信号唤醒后,从内存中找到网络数据,再按网络协议栈(IP → TCP/UDP → Socket)进行解析,对数据进行逐层解析和处理,直到把它送给应用程序。

而下半部则是由内核触发,也就是我们常说的软中断,特点是延迟执行。

softirq 软中断

SoftIRQ 的本质就是分摊和推迟工作,以保证系统对硬件中断的快速响应,所以对于软中断,往往是因为某些网络场景下导致大量数据包来不及处理而频繁触发导致。

软中断不只包括了硬件设备中断处理程序的下半部,一些内核自定义的事件也属于软中断,比如内核调度和RCU锁(Read-Copy Update 的缩写,RCU 是 Linux 内核中最常用的锁之一)等。

每个 CPU 都对应一个软中断内核线程,名字为 “ksoftirqd/CPU编号”,比如说, 0 号CPU对应的软中断内核线程的名字就是 ksoftirqd/0。

1
2
3
$ ps aux | grep softirq
root 7 0.0 0.0 0 0 ? S Oct10 0:01 [ksoftirqd/0]
root 16 0.0 0.0 0 0 ? S Oct10 0:01 [ksoftirqd/1]

我们可以通过下面的方式查看各种类型的软中断 在不同 CPU 上的累积运行次数:

1
2
3
4
5
6
7
8
9
10
11
12
$ cat /proc/softirqs
CPU0 CPU1
HI: 0 0
TIMER: 811613 1972736
NET_TX: 49 7
NET_RX: 1136736 1506885
BLOCK: 0 0
IRQ_POLL: 0 0
TASKLET: 304787 3691
SCHED: 689718 1897539
HRTIMER: 0 0
RCU: 1330771 1354737

需要注意各软中断类型和在各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_RXTIMER 很活跃:则系统网络包接收多;
  • SCHEDRCU 很高:CPU 调度、内核同步频繁;
  • BLOCKHRTIMER 等为 0:系统磁盘和高精度定时器较空闲。

在遇到可疑的ksoftirqd升高时,可以通过/proc/softirq和工具sar来排查软中断问题。

软中断频繁触发且占用 CPU 过高的常见场景包括:

  1. 高数据包速率 (PPS): 即使带宽不大,但如果每秒数据包数量极高(如 DDoS 攻击或大量小连接),每个包都会产生 SoftIRQ 处理开销。
  2. 协议栈处理复杂性: 数据包需要经过复杂的协议栈解析、校验、路由查找等。
  3. 防火墙/LVS/NAT 处理: 规则数量过多、或者使用了复杂的 Netfilter 模块,导致处理时间过长
  4. 驱动问题或配置不当: 网卡驱动的队列(NAPI)配置不当,或网卡自身出现瓶颈。

事故案例:防火墙规则爆炸引发的软中断危机

现象

  • 环境: 部署了 Tailscale 的 Linux 服务器,使用 nftables 进行防火墙管理。
  • 故障表现: 服务器在高流量时性能急剧恶化,内核线程 ksoftirqd 单核占用率达到 **100%**。内网使用 iperf3 测试,打流性能从正常水平暴跌至不足 10Mbps
  • 初步排查:top 命令未发现异常用户进程;iptraf 未发现明显的外部 DDoS 或异常流量。

事故原因:规则爆炸与内核缓存失效

  1. 事故起因: 为解决 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
  2. 脚本逻辑错误: 定时任务脚本逻辑编写错误,未能检查规则是否已存在。导致每隔一段时间,系统中就新增一条完全相同的 nft 规则。
  3. 核心后果(规则爆炸): 在发现问题时,防火墙规则链的大小已经膨胀到 1MB 级别。
  4. 性能瓶颈:
    • CPU 软中断 (SoftIRQ) 危机: 软中断负责处理网络数据包、协议栈解析、防火墙匹配等耗时操作。由于规则数量庞大,内核在处理每一个传入的数据包时,必须 遍历这 1MB 长的规则链
    • 缓存失效 (Cache Miss): 1MB 的规则数据超过了 CPU 的 L1/L2 缓存容量。每次遍历都会导致频繁的缓存失效,极大地增加了处理每个数据包所需的 CPU 周期。
    • ksoftirqd 满载: CPU 被迫将所有资源投入到 SoftIRQ 任务中,ksoftirqd 线程因此单核跑满 100%,却仍无法及时清空数据包队列,导致网络性能雪崩。

内存

相关工具

sysstat

sysstat包含了常用的 Linux 性能工具,用来监控和分析系统的性能。

1
yum install sysstat

mpstat

mpstat是一个常用的多核 CPU 性能分析工具,用来实时查看每个 CPU 的性能指标,以及所有 CPU 的平均指标。

1
2
# 显示所有 CPU 的指标,并在间隔 5 秒输出一组数据
mpstat -P ALL 5 1

输出数据样例

1
2
3
4
5
Linux 4.15.0 (ubuntu) 09/22/18 _x86_64_ (2 CPU)
13:30:06 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
13:30:11 all 50.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 49.95
13:30:11 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
13:30:11 1 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

pidstat

pidstat是一个常用的进程性能分析工具,用来实时查看进程的 CPU、内存、I/O 以及上下文切换等性能指标。

1
2
3
4
5
6
7
8
# -u 表示 CPU 指标
# -d 表示 I/O 数据
# -w 表示 进程上下文切换情况
# -t 表示 线程上下文切换情况

# 可以-uw查看CPU、-wt查看上下文切换;配合使用
# 间隔 5 秒后输出 1 组数据
pidstat -u 5 1

输出数据样例

1
2
3
4
5
6
Linux 4.15.0 (ubuntu)     09/22/18     _x86_64_    (2 CPU)
13:42:08 UID PID %usr %system %guest %wait %CPU CPU Command
13:42:13 0 104 0.00 3.39 0.00 0.00 3.39 1 kworker/1:1H
13:42:13 0 109 0.00 0.40 0.00 0.00 0.40 0 kworker/0:1H
13:42:13 0 2997 2.00 35.53 0.00 3.99 37.52 1 stress
13:42:13 0 3057 0.00 0.40 0.00 0.00 0.40 0 pidstat

perf

是Linux 2.6.31 以后内置的性能分析工具。它以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题

1
2
3
4
5
6
7
$ perf top
Samples: 833 of event 'cpu-clock', Event count (approx.): 97742399
Overhead Shared Object Symbol
7.28% perf [.] 0x00000000001f78a4
4.72% [kernel] [k] vsnprintf
4.32% [kernel] [k] module_get_kallsym
3.65% [kernel] [k] _raw_spin_unlock_irqrestore

第一行包含三个数据,分别是采样数(Samples)事件类型(event)事件总数量(Event count)。如采样数过少(比如只有十几个),下面的排序和百分比则没什么实际参考价值。

再往下是一个表格式样的数据,每一行包含四列,分别是:

  • Overhead ,是该符号的性能事件在所有采样中的比例,用百分比来表示
  • Shared ,是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等
  • Object ,是动态共享对象的类型。比如 [.]表示用户空间的可执行程序、或者动态链接库,而 [k]则表示内核空间
  • Symbol 是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示

perf record:提供了保存数据的功能

perf report:解析展示保存后的数据

入参:

  • -p [pid]:指定pid来查看进程内的调用关系,最终定位到进程内资源占用最大的函数。
  • -g:开启调用关系的采样,方便我们根据调用链来分析性能问题。
1
2
3
4
$ perf record # 按 Ctrl+C 终止采样
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.452 MB perf.data (6093 samples) ]
$ perf report # 展示类似于 perf top 的报告

调用方法

方法/栈函数 所属 说明 备注
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 的-n参数可以用来查看系统的网络收发情况,还有一个好处是,不仅可以观察网络收发的吞吐量(BPS,每秒收发的字节数),还可以观察网络收发的 PPS,即每秒收发的网络帧数。

1
2
3
4
5
6
7
# -n DEV 表示显示网络收发的报告,间隔1秒输出一组数据
$ sar -n DEV 1
15:03:46 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
15:03:47 eth0 12607.00 6304.00 664.86 358.11 0.00 0.00 0.00 0.01
15:03:47 docker0 6302.00 12604.00 270.79 664.66 0.00 0.00 0.00 0.00
15:03:47 lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
15:03:47 veth9f6bbcd 6302.00 12604.00 356.95 664.66 0.00 0.00 0.00 0.05

对于 sar 的输出界面,从左往右依次是:

  • 第一列:表示报告的时间。
  • 第二列:IFACE 表示网卡。
  • 第三、四列:rxpck/s 和 txpck/s 分别表示每秒接收、发送的网络帧数,也就是 PPS。
  • 第五、六列:rxkB/s 和 txkB/s 分别表示每秒接收、发送的千字节数,也就是 BPS。

内存情况

sar 的-r可以查看内存各个指标的变化情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -r表示显示内存使用情况,-S表示显示Swap使用情况
$ sar -r -S 1
04:39:56 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:39:57 6249676 6839824 1919632 23.50 740512 67316 1691736 10.22 815156 841868 4

04:39:56 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:39:57 8388604 0 0.00 0 0.00

04:39:57 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:39:58 6184472 6807064 1984836 24.30 772768 67380 1691736 10.22 847932 874224 20

04:39:57 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:39:58 8388604 0 0.00 0 0.00




04:44:06 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
04:44:07 152780 6525716 8016528 98.13 6530440 51316 1691736 10.22 867124 6869332 0

04:44:06 kbswpfree kbswpused %swpused kbswpcad %swpcad
04:44:07 8384508 4096 0.05 52 1.27
  • kbcommit,表示当前系统负载需要的内存。它实际上是为了保证系统内存不溢出,对需要内存的估计值。%commit,就是这个值相对总内存的百分比。
  • kbactive,表示活跃内存,也就是最近使用过的内存,一般不会被系统回收。
  • kbinact,表示非活跃内存,也就是不常访问的内存,有可能会被系统回收。

execsnoop

专为短时进程 / 幽灵进程设计的工具,它通过 ftrace 实时监控进程的 exec() 行为,并输出短时进程的基本信息,包括进程 PID、父进程 PID、命令行参数以及执行的结果

当然通过pstree也可以捕获到父进程,但是子进程死的太快也无法捕获到

1
2
3
4
5
6
7
8
9
10
$ execsnoop
PCOMM PID PPID RET ARGS
sh 30394 30393 0
stress 30396 30394 0 /usr/local/bin/stress -t 1 -d 1
sh 30398 30393 0
stress 30399 30398 0 /usr/local/bin/stress -t 1 -d 1
sh 30402 30400 0
stress 30403 30402 0 /usr/local/bin/stress -t 1 -d 1
sh 30405 30393 0
stress 30407 30405 0 /usr/local/bin/stress -t 1 -d 1

strace

跟踪进程系统调用

1
$ strace -p 22

top

查看系统总体的 CPU 和内存使用情况,以及各个进程的资源使用情况:

1
$ top

输出数据样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
top - 11:58:59 up 9 days, 22:47,  1 user,  load average: 0.03, 0.02, 0.00
Tasks: 123 total, 1 running, 72 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8169348 total, 5606884 free, 334640 used, 2227824 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 7497908 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 78088 9288 6696 S 0.0 0.1 0:16.83 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.05 kthreadd
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
...
# 按下数字 1 ,切换到每个 CPU 的使用率
...
%Cpu0 : 98.7 us, 1.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 99.3 us, 0.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
...
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
21514 daemon 20 0 336696 16384 8712 R 41.9 0.2 0:06.00 php-fpm
21513 daemon 20 0 336696 13244 5572 R 40.2 0.2 0:06.08 php-fpm
21515 daemon 20 0 336696 16384 8712 R 40.2 0.2 0:05.67 php-fpm
21512 daemon 20 0 336696 13244 5572 R 39.9 0.2 0:05.87 php-fpm
21516 daemon 20 0 336696 16384 8712 R 35.9 0.2 0:05.61 php-fpm
  • 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

按VmSwap使用量对进程排序,输出进程名称、进程ID以及SWAP用量:

1
$ for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head

查看CPU核心数量:

1
$ grep 'model name' /proc/cpuinfo | wc -l

查看系统的 CPU 和任务统计信息:

1
2
# 只保留各个 CPU 的数据
$ cat /proc/stat | grep ^cpu

输出数据样例

1
2
3
cpu  280580 7407 286084 172900810 83602 0 583 0 0 0
cpu0 144745 4181 176701 86423902 52076 0 301 0 0 0
cpu1 135834 3226 109383 86476907 31525 0 282 0 0 0
  • 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
2
3
# -d 参数表示高亮显示变化的区域
$ watch -d uptime
09:45:07 up 6 min, 3 users, load average: 0.11, 0.15, 0.09

vmstat:分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数

1
2
3
4
5
# 每隔 5 秒输出 1 组数据
$ vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7005360 91564 818900 0 0 0 0 25 33 0 0 100 0 0
  • cs(context switch)是每秒上下文切换的次数
  • in(interrupt)则是每秒中断的次数
  • r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
  • b(Blocked)则是处于不可中断睡眠状态的进程数

GPU

nvidia-smi:查看英伟达GPU占用

hping3

hping3 是一个可以构造 TCP/IP 协议数据包的工具,可以对系统进行安全审计、防火墙测试等。

模拟SYN FLOOD 攻击:

1
2
3
# -S参数表示设置TCP协议的SYN(同步序列号),-p表示目的端口为80
# -i u100表示每隔100微秒发送一个网络帧
$ hping3 -S -p 80 -i u100 192.168.0.30

Linux性能优化
https://www.fishingrodd.cn/2024/11/26/Linux性能优化/
作者
FishingRod
发布于
2024年11月26日
更新于
2026年1月12日
许可协议