dushenda

李德胜大大粉丝

dushenda

前两天社群有个用户用top命令查看进程时,有一个进程100%,以为CPU占满了,其实不然,下面我们就详细聊一下这个top命令。

很多人用 top 用了几年,却一直在错误解读它

下面,我们就用一张真实的 top 截图,系统性讲清楚 top 的正确使用方式,以及最容易踩的认知坑

先看截图图

MySQL 为何CPU能跑到 1265% ?

截图中最“刺眼”的一行:

PID   USER   VIRT   RES   %CPU   %MEM   COMMAND 6308  mysql 220.7g 212.7g 1265   84.5   mysqld

很多人第一反应:

“CPU 都 1265% 了?服务器要炸了”

但这恰恰是对 top 的第一个误解。上面截图中,MySQL服务多少有点问题,但业务并没有受影响。

误区 1:%CPU 最大只能是 100%

错误理解:

%CPU 是“CPU 使用率”,最大 100%

正确理解:

top 中的 %CPU = 使用的 CPU 核心数 × 100%

我的机器是 16 核

  • 100% = 1 个核满载

  • 1265% ≈ 12.6 个核被 mysqld 占用,并没有达到上限

结论:这台机器的 MySQL 不是异常显示,而是在多核上疯狂并行执行

误区 2:load average很高 = CPU 已经打满

截图顶部:

load average: 12.17, 11.71, 10.50

很多人一看到 load > 10,立刻下结论:

“CPU负载太高 快扛不住了!”

但你要先清楚一个问题:

这台机器有多少核?

正确判断方式

CPU 核数 load ≈ 核数 含义
4 核 load ≈ 4 接近满载
16 核 load ≈ 12 完全可接受
32 核 load ≈ 12 偏空闲

load average ≠ CPU 使用率它表示的是:正在运行 + 等待 CPU 的进程数

误区 3:id 很低才说明 CPU 有问题

截图中的 CPU 行:

%Cpu(s): 39.9 us, 0.4 sy, 58.6 id

58.6% idle

这说明什么?

CPU 一半以上是空闲的

这时候再结合:

  • mysqld:1265%

  • idle:58%

唯一合理的解释是:

多核机器 + 单个进程高并发消耗

误区 4:VIRT 很大 = 内存要炸

VIRT 220.7g RES  212.7g

很多人看到 VIRT 直接慌了:

“虚拟内存 220G?是不是内存泄漏?”

正确理解

字段 含义
VIRT 进程可用的虚拟地址空间
RES 真正占用的物理内存
SHR 可共享内存

MySQL 的 VIRT 很大是正常现象

  • InnoDB buffer pool

  • 内存映射文件

  • malloc 预留

判断内存是否有问题,看 RES + 系统是否 OOM,而不是看 VIRT

误区 5:free 很小 = 内存不够

截图中:

KiB Mem: 263973326 total 8088860 free 28235512 buff/cache 30766036 avail Mem

很多人盯着:

free 只有 8GB,内存要满了!

但 Linux 的内存哲学是:

不用白不用,有些在缓存中

真正要看的字段是:

avail Mem

  • avail ≈ 30GB

  • 说明:系统仍然有足够内存可用

误区 6:top 能直接定位“根因”

这张图最多能得出结论:

MySQL 正在大量消耗 CPU

但你完全不知道为什么,这个就要进入MySQL数据库查看了,很大可能是慢SQL的问题。

正确使用 top

先确认 CPU 核数

lscpu

load 要和核数对比

load < CPU核数 ≠ 性能问题

%CPU > 100% 是常态

多核时代,不懂这个等于白用 top

内存优先看 avail,不是 free,不是 buff/cache

搬运

CPU占用达1265%,一点都不慌,90% 的人都误解了 top命令

qemu cheatsheet

一、基本虚拟机启动

1. 基本启动虚拟机

启动x86_64虚拟机加载镜像(基本用法)。

1
qemu-system-x86_64 -drive file=disk.img,if=virtio

2. 无图形界面启动

无头模式启动VM(-nographic)。

1
qemu-system-x86_64 -nographic -drive file=disk.img

3. 指定CPU核心数

指定4核CPU启动(-smp 4)。

1
qemu-system-x86_64 -smp 4 -drive file=disk.img

4. 指定内存大小

分配2GB内存启动(-m 2G)。

1
qemu-system-x86_64 -m 2G -drive file=disk.img

5. 启用KVM加速

启用KVM硬件加速(-enable-kvm)。

1
qemu-system-x86_64 -enable-kvm -drive file=disk.img

6. 指定机器类型

指定q35机器类型启动(-machine q35)。

1
qemu-system-x86_64 -machine q35 -drive file=disk.img

7. 引导顺序指定

指定从硬盘引导(-boot order=c)。

1
qemu-system-x86_64 -boot order=c -drive file=disk.img

8. CD-ROM镜像启动

附加CD-ROM镜像启动(-cdrom)。

1
qemu-system-x86_64 -cdrom iso.img -drive file=disk.img

9. USB设备模拟

模拟USB设备启动(-usb)。

1
qemu-system-x86_64 -usb -drive file=disk.img

10. 守护进程模式启动

后台守护进程启动VM(-daemonize)。

1
qemu-system-x86_64 -daemonize -drive file=disk.img

二、设备配置与直通

11. VirtIO磁盘配置

使用VirtIO SCSI磁盘(-drive if=virtio)。

1
qemu-system-x86_64 -drive file=disk.img,if=virtio,format=qcow2

12. PCI设备直通

直通主机PCI设备到VM(-device vfio-pci)。

1
qemu-system-x86_64 -device vfio-pci,host=0000:01:00.0 -drive file=disk.img

13. USB主机设备直通

直通USB设备(-usbdevice host)。

1
qemu-system-x86_64 -usb -device usb-host,vendorid=0x1234,productid=0x5678 -drive file=disk.img

14. 网络设备配置

配置用户模式网络(-netdev user)。

1
qemu-system-x86_64 -netdev user,id=mynet -device virtio-net,netdev=mynet -drive file=disk.img

15. 桥接网络配置

桥接主机网络接口(-netdev bridge)。

1
qemu-system-x86_64 -netdev bridge,id=mynet,br=br0 -device virtio-net,netdev=mynet -drive file=disk.img

16. TAP网络配置

使用TAP接口网络(-netdev tap)。

1
qemu-system-x86_64 -netdev tap,id=mynet,ifname=tap0 -device virtio-net,netdev=mynet -drive file=disk.img

17. VFIO GPU直通

直通GPU设备并配置VGA(-vfio-pci -vga none)。

1
qemu-system-x86_64 -device vfio-pci,host=0000:01:00.0 -vga none -drive file=disk.img

18. 多磁盘附加

附加多个磁盘设备(-drive 多行)。

1
qemu-system-x86_64 -drive file=disk1.img,if=virtio -drive file=disk2.img,if=virtio -drive file=disk.img

19. 串口设备配置

配置串口重定向到stdio(-serial stdio)。

1
qemu-system-x86_64 -serial stdio -drive file=disk.img

20. 块设备缓存模式

指定磁盘缓存模式为none(cache=none)。

1
qemu-system-x86_64 -drive file=disk.img,cache=none,if=virtio

三、网络与迁移

21. 多网卡配置

配置多个网卡接口(-netdev 多行)。

1
qemu-system-x86_64 -netdev user,id=net1 -device virtio-net,netdev=net1 -netdev user,id=net2 -device virtio-net,netdev=net2 -drive file=disk.img

22. VLAN网络隔离

使用VLAN隔离网卡(-net vlan=1)。

1
qemu-system-x86_64 -net user,vlan=1 -drive file=disk.img

23. 实时迁移VM

迁移VM到远程主机(-incoming tcp)。

1
# 源: qemu-system-x86_64 -drive file=disk.img -incoming tcp:0:4444 (目标先启动)# 目标: migrate -d tcp:remote-host:4444

24. 迁移压缩优化

启用迁移压缩加速(-comp xbzrle)。

1
qemu-system-x86_64 -drive file=disk.img -enable-kvm -comp xbzrle

25. 多线程迁移

启用多线程迁移(-thread multi)。

1
qemu-system-x86_64 -drive file=disk.img -thread multi

26. 网络端口转发

转发主机端口到VM(-net user,hostfwd)。

1
qemu-system-x86_64 -net user,hostfwd=tcp::8080-:80 -drive file=disk.img

27. SMB网络共享

配置SMB网络共享到VM(-net user,smb)。

1
qemu-system-x86_64 -net user,smb=/path/to/share -drive file=disk.img

28. VDE网络配置

使用VDE交换机网络(-net vde)。

1
qemu-system-x86_64 -net vde,sock=/tmp/vde.sock -drive file=disk.img

29. 网络限速配置

限制网络带宽(-net user,throttle)。

1
qemu-system-x86_64 -net user,throttle=100 -drive file=disk.img

30. 迁移快照链

迁移时处理快照链(-drive snapshot=on)。

1
qemu-system-x86_64 -drive file=disk.img,snapshot=on

四、图形与输入输出

31. VNC图形界面

启用VNC远程桌面(-vnc)。

1
qemu-system-x86_64 -vnc :0 -drive file=disk.img

32. SPICE图形加速

使用SPICE协议图形(-spice)。

1
qemu-system-x86_64 -spice port=5900,disable-ticketing=on -drive file=disk.img

33. SDL图形界面

使用SDL库图形输出(-sdl)。

1
qemu-system-x86_64 -sdl -drive file=disk.img

34. 键盘布局指定

指定键盘布局为en-us(-k en-us)。

1
qemu-system-x86_64 -k en-us -drive file=disk.img

35. 鼠标输入配置

配置绝对鼠标输入(-usbdevice tablet)。

1
qemu-system-x86_64 -usb -device usb-tablet -drive file=disk.img

36. 图形分辨率设置

设置VGA分辨率(-vga std -full-screen)。

1
qemu-system-x86_64 -vga std -full-screen -drive file=disk.img

37. 多监视器配置

配置多个图形输出(-device virtio-gpu)。

1
qemu-system-x86_64 -device virtio-gpu -drive file=disk.img

38. 音频设备模拟

模拟音频设备输出(-soundhw ac97)。

1
qemu-system-x86_64 -soundhw ac97 -drive file=disk.img

39. 剪贴板共享

启用SPICE剪贴板共享(-spice clipboard=on)。

1
qemu-system-x86_64 -spice port=5900,clipboard=on -drive file=disk.img

40. 图形加速Virgl

启用Virgl 3D加速(-device virtio-gpu-virgl)。

1
qemu-system-x86_64 -device virtio-gpu-virgl -drive file=disk.img

五、调试与监视

41. GDB调试连接

启用GDB调试服务器(-s -S)。

1
qemu-system-x86_64 -s -S -drive file=disk.img

42. 监视器控制台

启用QMP监视器(-monitor stdio)。

1
qemu-system-x86_64 -monitor stdio -drive file=disk.img

43. QMP套接字监视

使用UNIX套接字QMP监视(-qmp unix)。

1
qemu-system-x86_64 -qmp unix:/tmp/qmp.sock,server,nowait -drive file=disk.img

44. 性能跟踪

启用性能跟踪日志(-d cpu)。

1
qemu-system-x86_64 -d cpu -drive file=disk.img

45. 日志文件输出

输出日志到文件(-D logfile)。

1
qemu-system-x86_64 -D qemu.log -drive file=disk.img

46. 内核调试模式

调试Linux内核(-kernel -initrd -append)。

1
qemu-system-x86_64 -kernel bzImage -initrd initrd.img -append "console=ttyS0" -drive file=disk.img

47. 内存转储

转储VM内存到文件(-mem-path)。

1
qemu-system-x86_64 -mem-path /tmp/mem.dump -drive file=disk.img

48. 事件监视脚本

使用脚本监视事件(-trace events=events.txt)。

1
qemu-system-x86_64 -trace events=events.txt -drive file=disk.img

49. CPU热插拔监视

启用CPU热插拔并监视(-smp 1,maxcpus=4)。

1
qemu-system-x86_64 -smp 1,maxcpus=4 -drive file=disk.img

50. 调试符号加载

加载调试符号启动(-gdb tcp::1234)。

1
qemu-system-x86_64 -gdb tcp::1234 -drive file=disk.img

常见QEMU语句

常用命令

启动与关闭

操作 命令 说明
启动 virsh start <虚拟机名> 启动一个处于关闭状态的虚拟机
正常关机 virsh shutdown <虚拟机名> 模拟按下电源键,优雅关机(推荐)
强制关闭 virsh destroy <虚拟机名> 相当于直接拔掉电源,仅在卡死时使用
重启 virsh reboot <虚拟机名> 重启虚拟机
强制重置 virsh reset <虚拟机名> 强制硬重启

 挂起与恢复

操作 命令 说明
挂起 virsh suspend <虚拟机名> 暂停虚拟机,内存数据保留在主机
恢复 virsh resume <虚拟机名> 从挂起状态恢复运行

 自启动配置

  • 设置开机自启: virsh autostart <虚拟机名>
  • 取消开机自启: virsh autostart --disable <虚拟机名>
  • 查看自启列表: virsh autostart --list

虚拟机信息查询

 基本状态查询

1
2
3
4
5
6
7
8
# 列出所有虚拟机(运行中+已关闭)
virsh list --all

# 查看虚拟机详细信息(UUID、OS类型、最大内存等)
virsh dominfo <虚拟机名>

# 仅查看虚拟机当前状态(running/shut off/paused)
virsh domstate <虚拟机名>

配置与连接信息

1
2
3
4
5
6
7
8
# 导出完整的XML配置(非常重要!)
virsh dumpxml <虚拟机名>

# 搜索特定配置(例如查看磁盘路径)
virsh dumpxml <虚拟机名> | grep -A 5 "<source file"

# 查看VNC远程连接端口
virsh vncdisplay <虚拟机名>

资源监控

1
2
3
4
5
6
7
8
9
10
11
# 查看CPU使用统计
virsh cpu-stats <虚拟机名>

# 查看内存使用详情
virsh dommemstat <虚拟机名>

# 查看挂载的硬盘列表
virsh domblklist <虚拟机名>

# 查看虚拟网卡列表及MAC地址
virsh domiflist <虚拟机名>

虚拟机配置管理

XML配置编辑(核心)

KVM的所有配置都存储在XML文件中:

编辑配置(推荐)

1
virsh edit <虚拟机名>

使用此命令会自动检查XML语法,保存生效。不要直接去改 /etc/libvirt/qemu/ 下的文件,容易出错。

备份配置

1
virsh dumpxml <虚拟机名> > vm-config.xml

从XML恢复/定义

1
virsh define vm-config.xml   # 注册虚拟机(不启动)

删除与重命名

取消定义(删除配置)

1
virsh undefine <虚拟机名>
> 此命令只删除配置文件,不会删除虚拟磁盘文件! 彻底删除(配置+磁盘)
1
virsh undefine --remove-all-storage <虚拟机名>
> 高危操作: 请务必确认磁盘数据已不再需要,执行后不可恢复!

重命名虚拟机

1
virsh domrename <旧名称> <新名称>

Typing vagrant from the command line will display a list of all available commands.

Be sure that you are in the same directory as the Vagrantfile when running these commands!

Creating a VM

  • vagrant init – Initialize Vagrant with a Vagrantfile and ./.vagrant directory, using no specified base image. Before you can do vagrant up, you’ll need to specify a base image in the Vagrantfile.
  • vagrant init <boxpath> – Initialize Vagrant with a specific box. To find a box, go to the public Vagrant box catalog. When you find one you like, just replace it’s name with boxpath. For example, vagrant init ubuntu/trusty64.

Starting a VM

  • vagrant up – starts vagrant environment (also provisions only on the FIRST vagrant up)
  • vagrant resume – resume a suspended machine (vagrant up works just fine for this as well)
  • vagrant provision – forces reprovisioning of the vagrant machine
  • vagrant reload – restarts vagrant machine, loads new Vagrantfile configuration
  • vagrant reload --provision – restart the virtual machine and force provisioning

Getting into a VM

  • vagrant ssh – connects to machine via SSH
  • vagrant ssh <boxname> – If you give your box a name in your Vagrantfile, you can ssh into it with boxname. Works from any directory.

Stopping a VM

  • vagrant halt – stops the vagrant machine
  • vagrant suspend – suspends a virtual machine (remembers state)

Cleaning Up a VM

  • vagrant destroy – stops and deletes all traces of the vagrant machine
  • vagrant destroy -f – same as above, without confirmation

Boxes

  • vagrant box list – see a list of all installed boxes on your computer
  • vagrant box add <name> <url> – download a box image to your computer
  • vagrant box outdated – check for updates vagrant box update
  • vagrant box remove <name> – deletes a box from the machine
  • vagrant package – packages a running virtualbox env in a reusable box

Saving Progress

-vagrant snapshot save [options] [vm-name] <name> – vm-name is often default. Allows us to save so that we can rollback at a later time

Tips

  • vagrant -v – get the vagrant version
  • vagrant status – outputs status of the vagrant machine
  • vagrant global-status – outputs status of all vagrant machines
  • vagrant global-status --prune – same as above, but prunes invalid entries
  • vagrant provision --debug – use the debug flag to increase the verbosity of the output
  • vagrant push – yes, vagrant can be configured to deploy code!
  • vagrant up --provision | tee provision.log – Runs vagrant up, forces provisioning and logs all output to a file

Plugins

  • vagrant-hostsupdater : $ vagrant plugin install vagrant-hostsupdater to update your /etc/hosts file automatically each time you start/stop your vagrant box.

Notes

  • If you are using VVV, you can enable xdebug by running vagrant ssh and then xdebug_on from the virtual machine’s CLI.

状态检查与配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# IB设备的状态
ibstat

# IB设备的细节
ibv_devinfo

# 列出所有的IB设备
ibdevices

# 检查IB设备端口状态
iblinkinfo

# 查看IB地址配置
ibaddr

# 显示IB端口状态细节信息
ibportstate

# 显示IB端口统计信息
ibportinfo

配置和管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 配置IB设备,显示IB和以太设备的映射关系
ibdev2netdev
ibdev2netdev -v

# 设置IB设备到一个特定的驱动
ibdriver

# 更新IB设备固件
mstflint -d <device> qflash <firmware_file.bin>

# 列出IB设备所有可获取的路径
ibpathln

# 节点间所有的IB连接
ibnetdiscover

# 查询固件参数
mlxconfig -d ${PCI_ID} q

# 设置端1为以太模式,端口2为IB模式
mlxconfig -d ${PCI_ID} set LINK_TYPE_P1=2 LINK_TYPE_P2=1

# 配置设置的SRIOV开启和个数
mlxconfig -d ${PCI_ID} set SRIOV_EN=1 NUM_OF_VFS=8

# 配置链路聚合模式,队列亲和性
mlxconfig -d ${PCI_ID} s LAG_RESOURCE_ALLOCATION=0

# 允许压缩CQE(小包调优,其他可能性能劣化)
mlxconfig -d ${PCI_ID} s CQE_COMPRESSION=1

固件更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 更新NIC卡固件(非OEM卡)
mlxfwmanager

# 常用的固件烧录命令(卡和包PSID要一致)
mstflint -d "${PCI_ID}" -i "${NEW_FIRMWARE_BIN}" burn
flint -d "${PCI_ID}" -i "${NEW_FIRMWARE_BIN}" burn

# 查询固件的全量信息
mstflint -d ${PCI_ID} query full

# 忽略PSID强制烧入固件
mstflint -d "${PCI_ID}" -i "${NEW_FIRMWARE_BIN}" -allow_psid_change burn


性能测试和监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 测试两设备见IB性能(时延和带宽)
ib_send_bw <device> <destination>

# 测试IB RDMA性能
ib_send_lat <device> <destination>

# 监控IB设备流量
ibstat -i <device> -s

# 监控IB设备错误
iberrdump

# mlnx调优
mlnx_tune -h
mlnx_tune -v
mlnx_tune -q
mlnx_tune -p HIGH_THROUGHPUT

配置RDMA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 检查RDMA配置
rdma link

# 设置IB设备RDMA用途
rdma_resolve_route

# 测试IB的RDMA性能
ib_send_lat -D <device> <destination>

# 检查RDMA安全和权限
rdma_sec

# 配置RDMA加密和安全
rdma_auth

定位问题

1
2
3
4
5
6
7
8
9
10
11
# 检查多节点的IB配置
ibnetdiscover

# dmesg显示ib 相关打印
dmesg | grep -i ib

# 查看IB日志
tail -f /var/log/ib_log

# 检查IB端口状态
ibportstate

配置和管理IB子网

1
2
3
4
5
6
7
8
# 显示IB网络信息
ibnetdiscover -v

# 设置IB网络管理
sm_profile

# 检查IB网口状态
ibstat -s

Mellanox管理命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 网卡状态查询
mst status -v

# 开启mst服务
mst start

# 配置RDMA的Mellanox设备
rdma_configure_device

# 安装配置Mellanox管理软件
mstflint -d <device> qflash <firmware_file.bin>

# 获取网卡qos配置
mlnx_qos -i eth2

# 设置网卡端口的0~7 PFC优先级
mlnx_qos -i eth2 -f 0,0,0,1,0,0,0,0

配置IB QoS

1
2
3
4
5
6
7
8
9
# Set quality of service policy for InfiniBand
# 设置IB qos
ib_qos_policy

# 配置IB流优先级
ib_flowcontrol

# 检查QoS配置和流优先级
ib_qos_show

参考

介绍

Ftrace 是一款内部跟踪器,旨在帮助系统开发人员和设计人员了解内核内部的运行情况。它可用于调试或分析用户空间之外发生的延迟和性能问题。

Debugfs 文件结构

Ftrace 使用 debugfs 文件系统来保存控制文件以及用于显示输出的文件。

当 debugfs 配置到内核中时(选择任何 ftrace 选项都会执行此操作),将创建目录 /sys/kernel/debug。要挂载此目录,可以将其添加到 /etc/fstab 文件中:

1
2
# /etc/fstab
debugfs /sys/kernel/debug debugfs default 0 0
或者,也可以在运行时使用以下命令挂载它:
1
mount -t debugfs nodev /sys/kernel/debug
为了更快地访问该目录,可以创建一个指向它的软链接:
1
ln -s /sys/kernel/debug /debug
## function跟踪

下表汇总了 ftrace最常用的操作命令,适用于直接操作 /sys/kernel/debug/tracing目录下的文件。

操作目的 命令示例 简要说明
查看可用跟踪器 cat available_tracers 显示系统支持的所有跟踪器 。
设置当前跟踪器 echo function > current_tracer 设置跟踪器,如 functionfunction_graphsched_switch等 。
开始/停止跟踪 echo 1 > tracing_on
echo 0 > tracing_on
控制跟踪的启动和停止 。
查看跟踪结果 cat trace 显示跟踪缓冲区的内容 。
过滤跟踪函数 echo sys_* > set_ftrace_filter 只跟踪函数名匹配 sys_*的函数 。
过滤跟踪进程 echo 1234 > set_ftrace_pid 只跟踪 PID 为 1234 的进程 。
跟踪特定模块 echo ':mod:ext4' > set_ftrace_filter 只跟踪 ext4模块内的函数 。
启用特定事件 echo 1 > events/sched/sched_switch/enable 启用 sched_switch调度事件 。
清空跟踪缓冲区 echo > trace 清空当前跟踪缓冲区以便重新记录 。

可通过调试文件系统启用函数跟踪器。确保已设置 ftrace_enabled;否则,此跟踪器将不执行任何操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 # sysctl kernel.ftrace_enabled=1
# echo function > current_tracer
# echo 1 > tracing_enabled
# usleep 1
# echo 0 > tracing_enabled
# cat trace
# tracer: function
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
bash-4003 [00] 123.638713: finish_task_switch <-schedule
bash-4003 [00] 123.638714: _spin_unlock_irq <-finish_task_switch
bash-4003 [00] 123.638714: sub_preempt_count <-_spin_unlock_irq
bash-4003 [00] 123.638715: hrtick_set <-schedule
bash-4003 [00] 123.638715: _spin_lock_irqsave <-hrtick_set
bash-4003 [00] 123.638716: add_preempt_count <-_spin_lock_irqsave
bash-4003 [00] 123.638716: _spin_unlock_irqrestore <-hrtick_set
bash-4003 [00] 123.638717: sub_preempt_count <-_spin_unlock_irqrestore
bash-4003 [00] 123.638717: hrtick_clear <-hrtick_set
bash-4003 [00] 123.638718: sub_preempt_count <-schedule
bash-4003 [00] 123.638718: sub_preempt_count <-preempt_schedule
bash-4003 [00] 123.638719: wait_for_completion <-__stop_machine_run
bash-4003 [00] 123.638719: wait_for_common <-wait_for_completion
bash-4003 [00] 123.638720: _spin_lock_irq <-wait_for_common
bash-4003 [00] 123.638720: add_preempt_count <-_spin_lock_irq
[...]

irqoff跟踪

当中断被关闭时,CPU 无法响应外部事件(如硬件中断),这会导致系统响应延迟。irqsoff跟踪器的工作原理是 : - 挂钩关键函数:它在 local_irq_disable()local_irq_enable()等关键函数中插入钩子。 - 记录时间戳:当中断被关闭时开始计时,并在中断重新开启时停止计时。 - 记录最大延迟:它会记录下中断关闭的最大时间,并将导致该延迟的调用路径信息保存下来,帮助开发者定位问题根源。

当中断被禁用时,CPU 将无法响应任何其他外部事件(NMI 和 SMI 除外)。这会阻止定时器中断触发,或鼠标中断无法将新的鼠标事件告知内核。其结果是响应时间出现延迟。

irqsoff 跟踪器会跟踪中断被禁用的时间。当达到新的最大延迟时,跟踪器会保存达到该延迟点之前的跟踪记录,以便每次达到新的最大值时,都会丢弃旧的跟踪记录并保存新的跟踪记录。

要重置最大值,请将 0 回显到 tracing_max_latency 中。以下是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 # echo irqsoff > current_tracer
# echo 0 > tracing_max_latency
# echo 1 > tracing_enabled
# ls -ltr
[...]
# echo 0 > tracing_enabled
# cat latency_trace
# tracer: irqsoff
#
# irqsoff latency trace v1.1.5 on 4.0.0
# --------------------------------------------------------------------
# latency: 259 us, #4/4, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: ps-6143 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
# => started at: __lock_task_sighand
# => ended at: _raw_spin_unlock_irqrestore
#
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
ps-6143 0d.h1 0us+: _raw_spin_lock_irqsave <-__lock_task_sighand
ps-6143 0d.h. 259us : _raw_spin_unlock_irqrestore <-__lock_task_sighand
- latency: 259 us:这是最重要的信息,表示本次跟踪中发现的最大中断关闭延迟为 259 微秒 。 - task: ps-6143:发生该延迟时正在执行的进程是 ps(PID 为 6143)。 - started atended at:指出了中断关闭开始和结束的函数位置 。 - irqs-offd表示中断被关闭 。 - delay符号:输出中可能使用特殊符号(如 !表示大于100ms)来直观表示延迟大小。 - 函数调用栈:在跟踪条目下方,通常会显示导致此次延迟的完整函数调用栈,这对于定位问题代码至关重要 。

请注意,上面的示例中未设置 ftrace_enabled。如果我们设置了 ftrace_enabled,则会得到更大的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# tracer: irqsoff
#
irqsoff latency trace v1.1.5 on 2.6.26-rc8
--------------------------------------------------------------------
latency: 50 us, #101/101, CPU#0 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
-----------------
| task: ls-4339 (uid:0 nice:0 policy:0 rt_prio:0)
-----------------
=> started at: __alloc_pages_internal
=> ended at: __alloc_pages_internal

# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| /
# ||||| delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
ls-4339 0...1 0us+: get_page_from_freelist (__alloc_pages_internal)
ls-4339 0d..1 3us : rmqueue_bulk (get_page_from_freelist)
ls-4339 0d..1 3us : _spin_lock (rmqueue_bulk)
ls-4339 0d..1 4us : add_preempt_count (_spin_lock)
ls-4339 0d..2 4us : __rmqueue (rmqueue_bulk)
ls-4339 0d..2 5us : __rmqueue_smallest (__rmqueue)
ls-4339 0d..2 5us : __mod_zone_page_state (__rmqueue_smallest)
ls-4339 0d..2 6us : __rmqueue (rmqueue_bulk)
ls-4339 0d..2 6us : __rmqueue_smallest (__rmqueue)
ls-4339 0d..2 7us : __mod_zone_page_state (__rmqueue_smallest)
ls-4339 0d..2 7us : __rmqueue (rmqueue_bulk)
ls-4339 0d..2 8us : __rmqueue_smallest (__rmqueue)
[...]
ls-4339 0d..2 46us : __rmqueue_smallest (__rmqueue)
ls-4339 0d..2 47us : __mod_zone_page_state (__rmqueue_smallest)
ls-4339 0d..2 47us : __rmqueue (rmqueue_bulk)
ls-4339 0d..2 48us : __rmqueue_smallest (__rmqueue)
ls-4339 0d..2 48us : __mod_zone_page_state (__rmqueue_smallest)
ls-4339 0d..2 49us : _spin_unlock (rmqueue_bulk)
ls-4339 0d..2 49us : sub_preempt_count (_spin_unlock)
ls-4339 0d..1 50us : get_page_from_freelist (__alloc_pages_internal)
ls-4339 0d..2 51us : trace_hardirqs_on (__alloc_pages_internal)
这里我们追踪到了 50 微秒的延迟。但我们也看到了这段时间内调用的所有函数。请注意,启用函数追踪会增加额外的开销。这种开销可能会延长延迟时间。尽管如此,这次追踪仍然提供了一些非常有用的调试信息。

scdule wakeup跟踪

在实时环境中,了解最高优先级任务从被唤醒到执行所需的唤醒时间至关重要。这也被称为“调度延迟”。

实时环境关注的是最大延迟,也就是事件发生所需的最长延迟,而不是平均延迟。我们可以拥有一个速度非常快的调度器,它可能只是偶尔出现较大的延迟,但这并不适用于实时任务。唤醒跟踪器旨在记录实时任务的最大唤醒延迟。非实时任务不会被记录,因为跟踪器只记录一个最大延迟,而跟踪不可预测的非实时任务会覆盖实时任务的最大延迟。

由于此追踪器仅处理实时任务,因此我们将采用与之前追踪器略有不同的运行方式。我们不会执行“ls”命令,而是在“chrt”命令下运行“sleep 1”命令,这将改变任务的优先级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 # echo wakeup > current_tracer
# echo 0 > tracing_max_latency
# echo 1 > tracing_enabled
# chrt -f 5 sleep 1
# echo 0 > tracing_enabled
# cat latency_trace
# tracer: wakeup
#
wakeup latency trace v1.1.5 on 2.6.26-rc8
--------------------------------------------------------------------
latency: 4 us, #2/2, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
-----------------
| task: sleep-4901 (uid:0 nice:0 policy:1 rt_prio:5)
-----------------

# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| /
# ||||| delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
<idle>-0 1d.h4 0us+: try_to_wake_up (wake_up_process)
<idle>-0 1d..4 4us : schedule (cpu_idle)

在空闲系统上运行此程序,我们发现任务切换仅耗时 4 微秒。请注意,由于调度中的跟踪标记位于实际“切换”之前,因此当记录的任务即将调度时,我们会停止跟踪。如果我们在调度程序末尾添加新的标记,则此情况可能会发生变化。

请注意,记录的任务是“sleep”,进程 ID 为 4901,其 rt_prio 为 5。此优先级为用户空间优先级,而非内核内部优先级。策略为:SCHED_FIFO 为 1,SCHED_RR 为 2。

使用 chrt -r 5 和 ftrace_enabled set 执行相同的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# tracer: wakeup
#
wakeup latency trace v1.1.5 on 2.6.26-rc8
--------------------------------------------------------------------
latency: 50 us, #60/60, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:2)
-----------------
| task: sleep-4068 (uid:0 nice:0 policy:2 rt_prio:5)
-----------------

# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| /
# ||||| delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
ksoftirq-7 1d.H3 0us : try_to_wake_up (wake_up_process)
ksoftirq-7 1d.H4 1us : sub_preempt_count (marker_probe_cb)
ksoftirq-7 1d.H3 2us : check_preempt_wakeup (try_to_wake_up)
ksoftirq-7 1d.H3 3us : update_curr (check_preempt_wakeup)
ksoftirq-7 1d.H3 4us : calc_delta_mine (update_curr)
ksoftirq-7 1d.H3 5us : __resched_task (check_preempt_wakeup)
ksoftirq-7 1d.H3 6us : task_wake_up_rt (try_to_wake_up)
ksoftirq-7 1d.H3 7us : _spin_unlock_irqrestore (try_to_wake_up)
[...]
ksoftirq-7 1d.H2 17us : irq_exit (smp_apic_timer_interrupt)
ksoftirq-7 1d.H2 18us : sub_preempt_count (irq_exit)
ksoftirq-7 1d.s3 19us : sub_preempt_count (irq_exit)
ksoftirq-7 1..s2 20us : rcu_process_callbacks (__do_softirq)
[...]
ksoftirq-7 1..s2 26us : __rcu_process_callbacks (rcu_process_callbacks)
ksoftirq-7 1d.s2 27us : _local_bh_enable (__do_softirq)
ksoftirq-7 1d.s2 28us : sub_preempt_count (_local_bh_enable)
ksoftirq-7 1.N.3 29us : sub_preempt_count (ksoftirqd)
ksoftirq-7 1.N.2 30us : _cond_resched (ksoftirqd)
ksoftirq-7 1.N.2 31us : __cond_resched (_cond_resched)
ksoftirq-7 1.N.2 32us : add_preempt_count (__cond_resched)
ksoftirq-7 1.N.2 33us : schedule (__cond_resched)
ksoftirq-7 1.N.2 33us : add_preempt_count (schedule)
ksoftirq-7 1.N.3 34us : hrtick_clear (schedule)
ksoftirq-7 1dN.3 35us : _spin_lock (schedule)
ksoftirq-7 1dN.3 36us : add_preempt_count (_spin_lock)
ksoftirq-7 1d..4 37us : put_prev_task_fair (schedule)
ksoftirq-7 1d..4 38us : update_curr (put_prev_task_fair)
[...]
ksoftirq-7 1d..5 47us : _spin_trylock (tracing_record_cmdline)
ksoftirq-7 1d..5 48us : add_preempt_count (_spin_trylock)
ksoftirq-7 1d..6 49us : _spin_unlock (tracing_record_cmdline)
ksoftirq-7 1d..6 49us : sub_preempt_count (_spin_unlock)
ksoftirq-7 1d..4 50us : schedule (__cond_resched)
中断发生在 ksoftirqd 运行期间。该任务运行在 SCHED_OTHER 调度级别。为什么我们没有提前看到“N”被置位?这可能是 x86_32 架构和 4K 堆栈的一个无害 bug。在配置了 4K 堆栈的 x86_32 架构上,中断和软中断使用它们自己的堆栈运行。一些信息保存在任务堆栈的顶部(need_resched 和 preempt_count 都存储在那里)。NEED_RESCHED 位的设置直接作用于任务堆栈,但读取 NEED_RESCHED 位是通过查看当前堆栈完成的,在本例中,当前堆栈是硬中断的堆栈。这隐藏了 NEED_RESCHED 已被设置的事实。直到我们切换回任务分配的堆栈,才能看到“N”。