文章目录
0. 引言
本文将介绍如何使用 perf lock
诊断锁竞争,并通过示例代码和分析。
1. perf lock record
和 perf lock report
的工作原理
perf lock record
和 perf lock report
是用来监控和分析锁竞争的工具。
-
perf lock record
:该命令用于监控运行中的程序并记录锁操作数据,包括锁的争用和等待时间。 -
perf lock report
:该命令生成一个报告,展示通过perf lock record
捕获到的锁数据统计信息,包括锁的获取次数、争用次数、平均等待时间等。
2. 自定义编译 perf
工具
一般需要自定义编译perf才能启动 perf lock
功能
2.1 内核配置
为了使 perf lock
能够追踪锁的名称和相关数据,,需要启用与 lockstat
和 perf events
相关的选项:
CONFIG_PERF_EVENTS
:启用 perf 事件子系统。CONFIG_LOCK_STAT
:启用锁统计功能,允许perf lock
追踪锁的状态。
使用 perf
命令确认是否启用锁统计
可以通过以下命令确认 perf lock
是否被启用:
perf lock record --help
3. 示例代码:多线程锁竞争
以下是一个多线程示例代码 lock_example.c :
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#define NUM_THREADS 100
#define NUM_ITERATIONS 100000
// 定义共享资源
long shared_resource = 0;
// 定义互斥锁和自旋锁
pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_spinlock_t spin_lock;
// 每个线程将执行的任务
void* mutex_worker(void* arg) {
for (int i = 0; i < NUM_ITERATIONS; i++) {
pthread_mutex_lock(&mutex_lock); // 获取互斥锁
shared_resource++;
pthread_mutex_unlock(&mutex_lock); // 释放互斥锁
}
return NULL;
}
void* spinlock_worker(void* arg) {
for (int i = 0; i < NUM_ITERATIONS; i++) {
pthread_spin_lock(&spin_lock); // 获取自旋锁
shared_resource--;
pthread_spin_unlock(&spin_lock); // 释放自旋锁
}
return NULL;
}
void* mixed_worker(void* arg) {
for (int i = 0; i < NUM_ITERATIONS; i++) {
pthread_mutex_lock(&mutex_lock); // 获取互斥锁
shared_resource += 10;
pthread_mutex_unlock(&mutex_lock); // 释放互斥锁
pthread_spin_lock(&spin_lock); // 获取自旋锁
shared_resource -= 10;
pthread_spin_unlock(&spin_lock); // 释放自旋锁
}
return NULL;
}
int main() {
srand(time(NULL));
// 初始化自旋锁
pthread_spin_init(&spin_lock, PTHREAD_PROCESS_PRIVATE);
// 创建线程
pthread_t threads[NUM_THREADS];
// 随机选择任务类型
for (int i = 0; i < NUM_THREADS; i++) {
int task_type = rand() % 3; // 随机选择任务类型(0: mutex, 1: spinlock, 2: mixed)
if (task_type == 0) {
pthread_create(&threads[i], NULL, mutex_worker, NULL);
} else if (task_type == 1) {
pthread_create(&threads[i], NULL, spinlock_worker, NULL);
} else {
pthread_create(&threads[i], NULL, mixed_worker, NULL);
}
}
// 等待所有线程完成
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
// 输出最终的共享资源值
printf("Final value of shared resource: %ld\n", shared_resource);
// 销毁自旋锁
pthread_spin_destroy(&spin_lock);
return 0;
}
使用gcc -o lock_example lock_example.c -lpthread -O2 -g
编译以上程序
4. 使用 perf lock
命令进行分析
- 运行
perf lock record
sudo perf lock record ./lock_example
此命令会跟踪并记录在运行 lock_example
时发生的所有锁竞争事件。
- 生成
perf lock report
sudo perf lock report
示例输出解析
以下是一个 perf lock report
的示例输出:
Name acquired contended avg wait total wait max wait min wait
--------------------------------------------------------------------------
64693 64693 785 ns 50.79 ms 9.09 us 463 ns
jiffies_lock 3 3 858 ns 2.57 us 1.09 us 685 ns
2 2 1.79 us 3.57 us 1.80 us 1.78 us
2 2 1.46 us 2.93 us 1.72 us 1.20 us
rcu_state 2 2 1.68 us 3.37 us 2.59 us 778 ns
1 1 1.55 us 1.55 us 1.55 us 1.55 us
字段解释
- Name:锁的名称或标识符。
- acquired:锁被获取的次数。
- contended:锁竞争的次数。
- avg wait:平均等待时间。
- total wait:总等待时间。
- max wait:最大等待时间。
- min wait:最小等待时间。
5. 锁竞争调用栈分析
使用 perf report
可以查看锁竞争的调用栈。以下是一个 lock:contention_begin
事件的调用栈分析:
$ sudo ./perf report
# To display the perf.data header info, please use --header/--header-only options.
#
#
# Total Lost Samples: 0
#
# Samples: 64K of event 'lock:contention_begin'
# Event count (approx.): 64703
#
# Children Self Trace output
# ........ ........ ...............................
#
99.98% 99.98% 0xffff000100b6f4c4 (flags=SPIN)
|
|--55.16%--invoke_syscall
| __arm64_sys_futex
| do_futex
| futex_wake
| _raw_spin_lock
| queued_spin_lock_slowpath
| __traceiter_contention_begin
| __traceiter_contention_begin
|
--44.82%--futex_wait
__futex_wait
futex_wait_setup
futex_q_lock
_raw_spin_lock
queued_spin_lock_slowpath
__traceiter_contention_begin
__traceiter_contention_begin
0.00% 0.00% 0xffffa00085e96a40 (flags=SPIN)
0.00% 0.00% 0xffff0001feefb240 (flags=SPIN)
0.00% 0.00% 0xffff0001fef63240 (flags=SPIN)
0.00% 0.00% 0xffffa00085f4e980 (flags=SPIN)
0.00% 0.00% 0xffff0001fef2f240 (flags=SPIN)
# Samples: 64K of event 'lock:contention_end'
# Event count (approx.): 64703
#
# Children Self Trace output
# ........ ........ ..........................
#
99.98% 99.98% 0xffff000100b6f4c4 (ret=0)
|
|--55.16%--invoke_syscall
| __arm64_sys_futex
| do_futex
| futex_wake
| _raw_spin_lock
| queued_spin_lock_slowpath
| __traceiter_contention_end
| __traceiter_contention_end
|
--44.82%--futex_wait
__futex_wait
futex_wait_setup
futex_q_lock
_raw_spin_lock
queued_spin_lock_slowpath
__traceiter_contention_end
__traceiter_contention_end
0.00% 0.00% 0xffffa00085e96a40 (ret=0)
0.00% 0.00% 0xffff0001feefb240 (ret=0)
0.00% 0.00% 0xffff0001fef63240 (ret=0)
0.00% 0.00% 0xffffa00085f4e980 (ret=0)
0.00% 0.00% 0xffff0001fef2f240 (ret=0)
#
# (Tip: Show current config key-value pairs: perf config --list)
#
调用栈解析
lock:contention_begin
事件调用栈:
- 主要锁:
0xffff000100b6f4c4 (flags=SPIN)
表示发生竞争的自旋锁。 - 关键函数调用路径:
invoke_syscall
→__arm64_sys_futex
→do_futex
→futex_wake
→_raw_spin_lock
→queued_spin_lock_slowpath
→__traceiter_contention_begin
。这个路径表明线程正在与futex
机制交互以处理锁竞争。- 竞争比例:99.98%的竞争发生在
0xffff000100b6f4c4
锁上。
lock:contention_end
事件调用栈:
- 主要锁:
0xffff000100b6f4c4 (ret=0)
表示竞争结束,锁成功被获取。 - 关键函数调用路径:
- 与
contention_begin
类似,包括futex
操作和_raw_spin_lock
,但最终ret=0
表示锁被成功获取,竞争结束。
- 与
- 解析:
- 锁竞争的高频率表明该自旋锁有频繁的竞争。
futex
系统调用和自旋锁机制在竞争过程中起着关键作用。- 锁的竞争结束时,路径返回
ret=0
表示锁已成功获取。