요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
열 하나하나의 의미
lockstat.rst:43-82| 항목 | 해석 |
|---|---|
| contentions | 즉시 획득하지 못하고 기다린 횟수 |
| waittime min/max/total/avg | 획득 전 대기 시간 분포 |
| acquisitions | 전체 획득 횟수 |
| holdtime min/max/total/avg | 획득 후 release까지 보유 시간 |
| con-bounces | contention이 CPU 간 cache data 이동을 동반한 횟수 |
| acq-bounces | 획득이 CPU 간 cache data 이동을 동반한 횟수 |
평균만 보면 드문 긴 stall을 놓칠 수 있습니다. waittime-max가 latency spike와 맞는지, holdtime-max가 긴 critical section인지 owner preemption인지 함께 봐야 합니다. Bounce가 높으면 lock algorithm뿐 아니라 보호 데이터의 cacheline 배치와 CPU affinity도 점검합니다.
수집 활성화와 /proc/lock_stat
lockstat.rst:83-175# CONFIG_LOCK_STAT=y kernel
echo 1 > /proc/sys/kernel/lock_stat
# workload 실행
less /proc/lock_stat
echo 0 > /proc/sys/kernel/lock_stat
첫 행은 lock class의 집계값이고, read/write lock은 -R과 -W로 나뉩니다. 첫 separator 아래는 획득에 실패해 기다린 call site, 두 번째 separator 아래는 그때 lock을 보유하고 있던 call site입니다. waittime-max만 보고 끝내지 말고 어느 waiter와 owner 조합에서 발생했는지 함께 연결해야 합니다.
double_rq_lock()은 같은 rq lock class의 두 instance를 정해진 순서로 잡습니다. Lockdep subclass를 지정하면 lockstat도 두 번째 nesting level을 &rq->lock/1로 따로 집계합니다. /1은 다른 C type이라는 뜻이 아니라 같은 class의 중첩 위치가 1이라는 뜻입니다.
정렬되지 않은 원본에서 head만 보는 것은 출발점일 뿐입니다. 횟수가 많은 lock, 누적 wait가 큰 lock, 한 번의 max wait가 긴 lock은 서로 다를 수 있습니다. 재현 workload를 고정하고 class를 고른 뒤 call-site 묶음으로 내려가야 원인과 개선 지점을 구분할 수 있습니다.
각 class 행 뒤에는 획득을 기다린 contention point와 당시 lock holder 측 contended point가 최대 네 곳씩 표시됩니다. symbol과 횟수를 함께 보아 hot call site를 찾습니다. 시간 값의 정수부 단위는 microsecond입니다.
Read/write lock은 -R과 -W 행으로 나뉘며 nested subclass는 class/1처럼 suffix가 붙습니다. double_rq_lock처럼 동일 class를 nested level로 잡는 경로를 별도 통계로 구분할 수 있습니다.
성능 분석 순서
lockstat.rst:176-205- 측정 전 echo 0으로 이전 통계를 지운다.
- 재현 가능한 workload 구간만 echo 1로 수집한다.
- contentions 수와 waittime-total로 전체 비용이 큰 class를 찾는다.
- waittime-max와 contention symbol로 tail latency 원인을 찾는다.
- holdtime과 acquisition 수를 함께 보고 lock 분할, batching, per-CPU화 또는 RCU 전환을 검토한다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
===============
Lock Statistics
===============
What
====
As the name suggests, it provides statistics on locks.
Why
===
Because things like lock contention can severely impact performance.
How
===
Lockdep already has hooks in the lock functions and maps lock instances to
lock classes. We build on that (see Documentation/locking/lockdep-design.rst).
The graph below shows the relation between the lock functions and the various
hooks therein::
__acquire
|
lock _____
| \
| __contended
| |
| <wait>
| _______/
|/
|
__acquired
|
.
<hold>
.
|
__release
|
unlock
lock, unlock - the regular lock functions
__* - the hooks
<> - states
With these hooks we provide the following statistics:
con-bounces
- number of lock contention that involved x-cpu data
contentions
- number of lock acquisitions that had to wait
wait time
min
- shortest (non-0) time we ever had to wait for a lock
max
- longest time we ever had to wait for a lock
total
- total time we spend waiting on this lock
avg
- average time spent waiting on this lock
acq-bounces
- number of lock acquisitions that involved x-cpu data
acquisitions
- number of times we took the lock
hold time
min
- shortest (non-0) time we ever held the lock
max
- longest time we ever held the lock
total
- total time this lock was held
avg
- average time this lock was held
These numbers are gathered per lock class, per read/write state (when
applicable).
It also tracks 4 contention points per class. A contention point is a call site
that had to wait on lock acquisition.
Configuration
-------------
Lock statistics are enabled via CONFIG_LOCK_STAT.
Usage
-----
Enable collection of statistics::
# echo 1 >/proc/sys/kernel/lock_stat
Disable collection of statistics::
# echo 0 >/proc/sys/kernel/lock_stat
Look at the current lock statistics::
( line numbers not part of actual output, done for clarity in the explanation
below )
# less /proc/lock_stat
01 lock_stat version 0.4
02-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
03 class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg
04-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
05
06 &mm->mmap_sem-W: 46 84 0.26 939.10 16371.53 194.90 47291 2922365 0.16 2220301.69 17464026916.32 5975.99
07 &mm->mmap_sem-R: 37 100 1.31 299502.61 325629.52 3256.30 212344 34316685 0.10 7744.91 95016910.20 2.77
08 ---------------
09 &mm->mmap_sem 1 [<ffffffff811502a7>] khugepaged_scan_mm_slot+0x57/0x280
10 &mm->mmap_sem 96 [<ffffffff815351c4>] __do_page_fault+0x1d4/0x510
11 &mm->mmap_sem 34 [<ffffffff81113d77>] vm_mmap_pgoff+0x87/0xd0
12 &mm->mmap_sem 17 [<ffffffff81127e71>] vm_munmap+0x41/0x80
13 ---------------
14 &mm->mmap_sem 1 [<ffffffff81046fda>] dup_mmap+0x2a/0x3f0
15 &mm->mmap_sem 60 [<ffffffff81129e29>] SyS_mprotect+0xe9/0x250
16 &mm->mmap_sem 41 [<ffffffff815351c4>] __do_page_fault+0x1d4/0x510
17 &mm->mmap_sem 68 [<ffffffff81113d77>] vm_mmap_pgoff+0x87/0xd0
18
19.............................................................................................................................................................................................................................
20
21 unix_table_lock: 110 112 0.21 49.24 163.91 1.46 21094 66312 0.12 624.42 31589.81 0.48
22 ---------------
23 unix_table_lock 45 [<ffffffff8150ad8e>] unix_create1+0x16e/0x1b0
24 unix_table_lock 47 [<ffffffff8150b111>] unix_release_sock+0x31/0x250
25 unix_table_lock 15 [<ffffffff8150ca37>] unix_find_other+0x117/0x230
26 unix_table_lock 5 [<ffffffff8150a09f>] unix_autobind+0x11f/0x1b0
27 ---------------
28 unix_table_lock 39 [<ffffffff8150b111>] unix_release_sock+0x31/0x250
29 unix_table_lock 49 [<ffffffff8150ad8e>] unix_create1+0x16e/0x1b0
30 unix_table_lock 20 [<ffffffff8150ca37>] unix_find_other+0x117/0x230
31 unix_table_lock 4 [<ffffffff8150a09f>] unix_autobind+0x11f/0x1b0
This excerpt shows the first two lock class statistics. Line 01 shows the
output version - each time the format changes this will be updated. Line 02-04
show the header with column descriptions. Lines 05-18 and 20-31 show the actual
statistics. These statistics come in two parts; the actual stats separated by a
short separator (line 08, 13) from the contention points.
Lines 09-12 show the first 4 recorded contention points (the code
which tries to get the lock) and lines 14-17 show the first 4 recorded
contended points (the lock holder). It is possible that the max
con-bounces point is missing in the statistics.
The first lock (05-18) is a read/write lock, and shows two lines above the
short separator. The contention points don't match the column descriptors,
they have two: contentions and [<IP>] symbol. The second set of contention
points are the points we're contending with.
The integer part of the time values is in us.
Dealing with nested locks, subclasses may appear::
32...........................................................................................................................................................................................................................
33
34 &rq->lock: 13128 13128 0.43 190.53 103881.26 7.91 97454 3453404 0.00 401.11 13224683.11 3.82
35 ---------
36 &rq->lock 645 [<ffffffff8103bfc4>] task_rq_lock+0x43/0x75
37 &rq->lock 297 [<ffffffff8104ba65>] try_to_wake_up+0x127/0x25a
38 &rq->lock 360 [<ffffffff8103c4c5>] select_task_rq_fair+0x1f0/0x74a
39 &rq->lock 428 [<ffffffff81045f98>] scheduler_tick+0x46/0x1fb
40 ---------
41 &rq->lock 77 [<ffffffff8103bfc4>] task_rq_lock+0x43/0x75
42 &rq->lock 174 [<ffffffff8104ba65>] try_to_wake_up+0x127/0x25a
43 &rq->lock 4715 [<ffffffff8103ed4b>] double_rq_lock+0x42/0x54
44 &rq->lock 893 [<ffffffff81340524>] schedule+0x157/0x7b8
45
46...........................................................................................................................................................................................................................
47
48 &rq->lock/1: 1526 11488 0.33 388.73 136294.31 11.86 21461 38404 0.00 37.93 109388.53 2.84
49 -----------
50 &rq->lock/1 11526 [<ffffffff8103ed58>] double_rq_lock+0x4f/0x54
51 -----------
52 &rq->lock/1 5645 [<ffffffff8103ed4b>] double_rq_lock+0x42/0x54
53 &rq->lock/1 1224 [<ffffffff81340524>] schedule+0x157/0x7b8
54 &rq->lock/1 4336 [<ffffffff8103ed58>] double_rq_lock+0x4f/0x54
55 &rq->lock/1 181 [<ffffffff8104ba65>] try_to_wake_up+0x127/0x25a
Line 48 shows statistics for the second subclass (/1) of &rq->lock class
(subclass starts from 0), since in this case, as line 50 suggests,
double_rq_lock actually acquires a nested lock of two spinlocks.
View the top contending locks::
# grep : /proc/lock_stat | head
clockevents_lock: 2926159 2947636 0.15 46882.81 1784540466.34 605.41 3381345 3879161 0.00 2260.97 53178395.68 13.71
tick_broadcast_lock: 346460 346717 0.18 2257.43 39364622.71 113.54 3642919 4242696 0.00 2263.79 49173646.60 11.59
&mapping->i_mmap_mutex: 203896 203899 3.36 645530.05 31767507988.39 155800.21 3361776 8893984 0.17 2254.15 14110121.02 1.59
&rq->lock: 135014 136909 0.18 606.09 842160.68 6.15 1540728 10436146 0.00 728.72 17606683.41 1.69
&(&zone->lru_lock)->rlock: 93000 94934 0.16 59.18 188253.78 1.98 1199912 3809894 0.15 391.40 3559518.81 0.93
tasklist_lock-W: 40667 41130 0.23 1189.42 428980.51 10.43 270278 510106 0.16 653.51 3939674.91 7.72
tasklist_lock-R: 21298 21305 0.20 1310.05 215511.12 10.12 186204 241258 0.14 1162.33 1179779.23 4.89
rcu_node_1: 47656 49022 0.16 635.41 193616.41 3.95 844888 1865423 0.00 764.26 1656226.96 0.89
&(&dentry->d_lockref.lock)->rlock: 39791 40179 0.15 1302.08 88851.96 2.21 2790851 12527025 0.10 1910.75 3379714.27 0.27
rcu_node_0: 29203 30064 0.16 786.55 1555573.00 51.74 88963 244254 0.00 398.87 428872.51 1.76
Clear the statistics::
# echo 0 > /proc/lock_stat
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Lock 통계가 필요한 이유
1-20Lockstat은 lock에 관한 통계를 제공한다. Lock contention은 system 성능을 심각하게 떨어뜨릴 수 있으므로 획득 대기와 보유 시간을 실제 call site별로 관찰할 필요가 있다.
Lockdep은 이미 lock function에 hook을 두고 lock instance를 lock class로 mapping한다. Lockstat은 Documentation/locking/lockdep-design.rst에 설명된 이 기반을 활용한다.
Lock lifecycle과 hook
21-47원문의 ASCII 흐름은 홈페이지에서 구조화 도식으로 다시 그렸다. 일반 lock() 호출은 __acquire hook을 지나고 즉시 획득하지 못하면 __contended에서 wait 상태를 기록한다. 획득 시 __acquired를 거쳐 hold 구간을 측정하며 __release 뒤 unlock()으로 끝난다.
__acquire
|
lock ---------
| |
| __contended
| |
| <wait>
|-----------
|
__acquired
|
<hold>
|
__release
|
unlock
그림에서 lock과 unlock은 일반 lock function, __*는 instrumentation hook, angle bracket 표기는 측정하는 상태를 뜻한다.
수집하는 metric
48-81| Metric | 의미 |
|---|---|
| con-bounces | 다른 CPU의 data를 건드린 lock contention 횟수 |
| contentions | 기다려야 했던 lock acquisition 횟수 |
| wait time min | lock을 기다린 0이 아닌 가장 짧은 시간 |
| wait time max | lock을 기다린 가장 긴 시간 |
| wait time total | 이 lock을 기다린 시간의 합 |
| wait time avg | 이 lock을 기다린 평균 시간 |
| acq-bounces | 다른 CPU의 data를 건드린 lock acquisition 횟수 |
| acquisitions | lock을 획득한 총 횟수 |
| hold time min | lock을 보유한 0이 아닌 가장 짧은 시간 |
| hold time max | lock을 보유한 가장 긴 시간 |
| hold time total | 이 lock을 보유한 시간의 합 |
| hold time avg | 이 lock을 보유한 평균 시간 |
통계는 lock class별로, 해당하는 경우 read/write state별로 수집한다. Class마다 acquisition을 기다린 call site인 contention point도 네 개까지 추적한다.
CONFIG_LOCK_STAT과 runtime control
83-99CONFIG_LOCK_STAT을 enable한 kernel에서 /proc/sys/kernel/lock_stat으로 수집을 제어한다.
# enable collection
echo 1 > /proc/sys/kernel/lock_stat
# disable collection
echo 0 > /proc/sys/kernel/lock_stat
# inspect statistics
less /proc/lock_stat
/proc/lock_stat 출력 구조
101-13601 lock_stat version 0.4
02 ------------------------------------------------------------------------
03 class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg
04 ------------------------------------------------------------------------
05
06 &mm->mmap_sem-W: 46 84 0.26 939.10 16371.53 194.90 47291 2922365 0.16 2220301.69 17464026916.32 5975.99
07 &mm->mmap_sem-R: 37 100 1.31 299502.61 325629.52 3256.30 212344 34316685 0.10 7744.91 95016910.20 2.77
08 ---------------
09 &mm->mmap_sem 1 [<ffffffff811502a7>] khugepaged_scan_mm_slot+0x57/0x280
10 &mm->mmap_sem 96 [<ffffffff815351c4>] __do_page_fault+0x1d4/0x510
11 &mm->mmap_sem 34 [<ffffffff81113d77>] vm_mmap_pgoff+0x87/0xd0
12 &mm->mmap_sem 17 [<ffffffff81127e71>] vm_munmap+0x41/0x80
13 ---------------
14 &mm->mmap_sem 1 [<ffffffff81046fda>] dup_mmap+0x2a/0x3f0
15 &mm->mmap_sem 60 [<ffffffff81129e29>] SyS_mprotect+0xe9/0x250
16 &mm->mmap_sem 41 [<ffffffff815351c4>] __do_page_fault+0x1d4/0x510
17 &mm->mmap_sem 68 [<ffffffff81113d77>] vm_mmap_pgoff+0x87/0xd0
18
19 ........................................................................
20
21 unix_table_lock: 110 112 0.21 49.24 163.91 1.46 21094 66312 0.12 624.42 31589.81 0.48
22 ---------------
23 unix_table_lock 45 [<ffffffff8150ad8e>] unix_create1+0x16e/0x1b0
24 unix_table_lock 47 [<ffffffff8150b111>] unix_release_sock+0x31/0x250
25 unix_table_lock 15 [<ffffffff8150ca37>] unix_find_other+0x117/0x230
26 unix_table_lock 5 [<ffffffff8150a09f>] unix_autobind+0x11f/0x1b0
27 ---------------
28 unix_table_lock 39 [<ffffffff8150b111>] unix_release_sock+0x31/0x250
29 unix_table_lock 49 [<ffffffff8150ad8e>] unix_create1+0x16e/0x1b0
30 unix_table_lock 20 [<ffffffff8150ca37>] unix_find_other+0x117/0x230
31 unix_table_lock 4 [<ffffffff8150a09f>] unix_autobind+0x11f/0x1b0
원문 예시의 line number는 설명을 위해 붙인 것이며 실제 출력에는 없다. 01은 format이 바뀔 때 갱신되는 output version이고 02-04는 column header다. 05-18과 20-31이 두 lock class의 실제 통계다.
Acquirer와 holder call site
139-155각 통계는 짧은 separator를 기준으로 main stats와 contention point 두 부분으로 나뉜다. Line 09-12는 lock을 얻으려 한 첫 네 contention point이고 line 14-17은 그 요청이 경쟁한 현재 lock holder 쪽 call site 네 개다. Max con-bounces 지점이 통계에서 빠질 수 있다.
첫 lock인 &mm->mmap_sem은 read/write lock이므로 separator 위에 -W와 -R 두 줄이 있다. Contention point 줄은 main column과 형식이 다르고 count와 [<IP>] symbol 두 값을 가진다. 두 번째 point 묶음은 acquisition이 경쟁한 holder 지점을 나타낸다.
Time value의 정수 부분 단위는 microsecond다.
Nested lock subclass
157-18634 &rq->lock: 13128 13128 0.43 190.53 103881.26 7.91 97454 3453404 0.00 401.11 13224683.11 3.82
35 ---------
36 &rq->lock 645 [<ffffffff8103bfc4>] task_rq_lock+0x43/0x75
37 &rq->lock 297 [<ffffffff8104ba65>] try_to_wake_up+0x127/0x25a
38 &rq->lock 360 [<ffffffff8103c4c5>] select_task_rq_fair+0x1f0/0x74a
39 &rq->lock 428 [<ffffffff81045f98>] scheduler_tick+0x46/0x1fb
40 ---------
41 &rq->lock 77 [<ffffffff8103bfc4>] task_rq_lock+0x43/0x75
42 &rq->lock 174 [<ffffffff8104ba65>] try_to_wake_up+0x127/0x25a
43 &rq->lock 4715 [<ffffffff8103ed4b>] double_rq_lock+0x42/0x54
44 &rq->lock 893 [<ffffffff81340524>] schedule+0x157/0x7b8
48 &rq->lock/1: 1526 11488 0.33 388.73 136294.31 11.86 21461 38404 0.00 37.93 109388.53 2.84
49 -----------
50 &rq->lock/1 11526 [<ffffffff8103ed58>] double_rq_lock+0x4f/0x54
51 -----------
52 &rq->lock/1 5645 [<ffffffff8103ed4b>] double_rq_lock+0x42/0x54
53 &rq->lock/1 1224 [<ffffffff81340524>] schedule+0x157/0x7b8
54 &rq->lock/1 4336 [<ffffffff8103ed58>] double_rq_lock+0x4f/0x54
55 &rq->lock/1 181 [<ffffffff8104ba65>] try_to_wake_up+0x127/0x25a
Line 48의 &rq->lock/1은 &rq->lock class의 두 번째 subclass다. Subclass index는 0부터 시작한다. Line 50의 double_rq_lock은 두 spinlock 중 nested lock을 실제로 획득하므로 /1로 구분된다.
상위 contention lock과 통계 초기화
188-204# show top contending locks
grep : /proc/lock_stat | head
clockevents_lock: ...
tick_broadcast_lock: ...
&mapping->i_mmap_mutex: ...
&rq->lock: ...
&(&zone->lru_lock)->rlock: ...
tasklist_lock-W: ...
tasklist_lock-R: ...
rcu_node_1: ...
&(&dentry->d_lockref.lock)->rlock: ...
rcu_node_0: ...
# clear statistics
echo 0 > /proc/lock_stat
grep으로 class summary 줄만 뽑고 head를 적용하면 출력 정렬 상태에서 상위 contention lock을 빠르게 볼 수 있다. /proc/lock_stat에 0을 쓰면 누적 통계를 초기화한다.
Acquire와 release 사이의 측정 지점
lockstat.rst:5-42즉시 획득하면 __contended 경로를 거치지 않고 __acquired로 내려갑니다. 경쟁이 있으면 __contended에서 wait timer를 시작하고 __acquired에서 끝냅니다. 이후 __release까지가 hold time이며, acquire와 release hook은 같은 lock class의 통계로 합쳐집니다.
Lockstat는 lock function에 이미 들어 있는 lockdep hook과 class mapping을 재사용합니다. __contended에서 대기 시작을, __acquired에서 대기 종료와 보유 시작을, __release에서 보유 종료를 기록합니다. 수치는 lock instance가 아니라 class별로 모이며 read와 write state가 있으면 분리됩니다.