← Documents Documentation/RCU/lockdep.rst GitHub 원문 ↗

Linux 6.18.37 · RCU

RCU와 lockdep 검사

RCU flavor별 held 상태와 CONFIG_PROVE_RCU accessor 및 list 순회 검사를 정리합니다.

Source pathDocumentation/RCU/lockdep.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

lockdep.rst:1-119

RCU flavor별 held 상태와 CONFIG_PROVE_RCU accessor 및 list 순회 검사를 정리합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 ========================
4 RCU and lockdep checking
5 ========================
6
7 All flavors of RCU have lockdep checking available, so that lockdep is
8 aware of when each task enters and leaves any flavor of RCU read-side
9 critical section. Each flavor of RCU is tracked separately (but note
10 that this is not the case in 2.6.32 and earlier). This allows lockdep's
11 tracking to include RCU state, which can sometimes help when debugging
12 deadlocks and the like.
13
14 In addition, RCU provides the following primitives that check lockdep's
15 state::
16
17 rcu_read_lock_held() for normal RCU.
18 rcu_read_lock_bh_held() for RCU-bh.
19 rcu_read_lock_sched_held() for RCU-sched.
20 rcu_read_lock_any_held() for any of normal RCU, RCU-bh, and RCU-sched.
21 srcu_read_lock_held() for SRCU.
22 rcu_read_lock_trace_held() for RCU Tasks Trace.
23
24 These functions are conservative, and will therefore return 1 if they
25 aren't certain (for example, if CONFIG_DEBUG_LOCK_ALLOC is not set).
26 This prevents things like WARN_ON(!rcu_read_lock_held()) from giving false
27 positives when lockdep is disabled.
28
29 In addition, a separate kernel config parameter CONFIG_PROVE_RCU enables
30 checking of rcu_dereference() primitives:
31
32 rcu_dereference(p):
33 Check for RCU read-side critical section.
34 rcu_dereference_bh(p):
35 Check for RCU-bh read-side critical section.
36 rcu_dereference_sched(p):
37 Check for RCU-sched read-side critical section.
38 srcu_dereference(p, sp):
39 Check for SRCU read-side critical section.
40 rcu_dereference_check(p, c):
41 Use explicit check expression "c" along with
42 rcu_read_lock_held(). This is useful in code that is
43 invoked by both RCU readers and updaters.
44 rcu_dereference_bh_check(p, c):
45 Use explicit check expression "c" along with
46 rcu_read_lock_bh_held(). This is useful in code that
47 is invoked by both RCU-bh readers and updaters.
48 rcu_dereference_sched_check(p, c):
49 Use explicit check expression "c" along with
50 rcu_read_lock_sched_held(). This is useful in code that
51 is invoked by both RCU-sched readers and updaters.
52 srcu_dereference_check(p, c):
53 Use explicit check expression "c" along with
54 srcu_read_lock_held(). This is useful in code that
55 is invoked by both SRCU readers and updaters.
56 rcu_dereference_raw(p):
57 Don't check. (Use sparingly, if at all.)
58 rcu_dereference_raw_check(p):
59 Don't do lockdep at all. (Use sparingly, if at all.)
60 rcu_dereference_protected(p, c):
61 Use explicit check expression "c", and omit all barriers
62 and compiler constraints. This is useful when the data
63 structure cannot change, for example, in code that is
64 invoked only by updaters.
65 rcu_access_pointer(p):
66 Return the value of the pointer and omit all barriers,
67 but retain the compiler constraints that prevent duplicating
68 or coalescing. This is useful when testing the
69 value of the pointer itself, for example, against NULL.
70
71 The rcu_dereference_check() check expression can be any boolean
72 expression, but would normally include a lockdep expression. For a
73 moderately ornate example, consider the following::
74
75 file = rcu_dereference_check(fdt->fd[fd],
76 lockdep_is_held(&files->file_lock) ||
77 atomic_read(&files->count) == 1);
78
79 This expression picks up the pointer "fdt->fd[fd]" in an RCU-safe manner,
80 and, if CONFIG_PROVE_RCU is configured, verifies that this expression
81 is used in:
82
83 1. An RCU read-side critical section (implicit), or
84 2. with files->file_lock held, or
85 3. on an unshared files_struct.
86
87 In case (1), the pointer is picked up in an RCU-safe manner for vanilla
88 RCU read-side critical sections, in case (2) the ->file_lock prevents
89 any change from taking place, and finally, in case (3) the current task
90 is the only task accessing the file_struct, again preventing any change
91 from taking place. If the above statement was invoked only from updater
92 code, it could instead be written as follows::
93
94 file = rcu_dereference_protected(fdt->fd[fd],
95 lockdep_is_held(&files->file_lock) ||
96 atomic_read(&files->count) == 1);
97
98 This would verify cases #2 and #3 above, and furthermore lockdep would
99 complain even if this was used in an RCU read-side critical section unless
100 one of these two cases held. Because rcu_dereference_protected() omits
101 all barriers and compiler constraints, it generates better code than do
102 the other flavors of rcu_dereference(). On the other hand, it is illegal
103 to use rcu_dereference_protected() if either the RCU-protected pointer
104 or the RCU-protected data that it points to can change concurrently.
105
106 Like rcu_dereference(), when lockdep is enabled, RCU list and hlist
107 traversal primitives check for being called from within an RCU read-side
108 critical section. However, a lockdep expression can be passed to them
109 as an additional optional argument. With this lockdep expression, these
110 traversal primitives will complain only if the lockdep expression is
111 false and they are called from outside any RCU read-side critical section.
112
113 For example, the workqueue for_each_pwq() macro is intended to be used
114 either within an RCU read-side critical section or with wq->mutex held.
115 It is thus implemented as follows::
116
117 #define for_each_pwq(pwq, wq)
118 list_for_each_entry_rcu((pwq), &(wq)->pwqs, pwqs_node,
119 lock_is_held(&(wq->mutex).dep_map))
120

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

RCU flavor별 held 검사

1-24

Lockdep는 각 task가 모든 RCU flavor의 read-side critical section에 들어가고 나오는 시점을 안다. Normal RCU, RCU-bh, RCU-sched, SRCU, Tasks Trace를 따로 추적하므로 RCU 상태를 포함한 deadlock 분석이 가능하다.

`rcu_read_lock_held()`, `rcu_read_lock_bh_held()`, `rcu_read_lock_sched_held()`, `rcu_read_lock_any_held()`, `srcu_read_lock_held()`, `rcu_read_lock_trace_held()`가 해당 상태를 확인한다. 이 함수들은 보수적이어서 `CONFIG_DEBUG_LOCK_ALLOC`이 꺼져 확신할 수 없으면 1을 반환한다. Lockdep 비활성 상태에서 `WARN_ON(!rcu_read_lock_held())` 같은 false positive를 막기 위해서다.

Held-state API
API확인 대상
rcu_read_lock_held()Normal RCU
rcu_read_lock_bh_held()RCU-bh
rcu_read_lock_sched_held()RCU-sched
rcu_read_lock_any_held()앞 세 flavor 중 하나
srcu_read_lock_held()SRCU domain
rcu_read_lock_trace_held()Tasks Trace RCU

각 reader flavor의 lockdep 상태를 질의한다.

.. SPDX-License-Identifier: GPL-2.0

========================
RCU and lockdep checking
========================

All flavors of RCU have lockdep checking available, so that lockdep is
aware of when each task enters and leaves any flavor of RCU read-side
critical section.  Each flavor of RCU is tracked separately (but note
that this is not the case in 2.6.32 and earlier).  This allows lockdep's
tracking to include RCU state, which can sometimes help when debugging
deadlocks and the like.

In addition, RCU provides the following primitives that check lockdep's
state::

        rcu_read_lock_held() for normal RCU.
        rcu_read_lock_bh_held() for RCU-bh.
        rcu_read_lock_sched_held() for RCU-sched.
        rcu_read_lock_any_held() for any of normal RCU, RCU-bh, and RCU-sched.
        srcu_read_lock_held() for SRCU.
        rcu_read_lock_trace_held() for RCU Tasks Trace.

These functions are conservative, and will therefore return 1 if they

CONFIG_PROVE_RCU accessor 검사

25-72

`CONFIG_PROVE_RCU`는 `rcu_dereference()` 계열이 알맞은 reader 안에서 호출되는지 검사한다. Normal, bh, sched, SRCU 변형은 각각 대응하는 read lock을 요구한다. `_check(p, c)` 변형은 reader 상태 또는 명시한 boolean 조건 `c` 중 하나가 참이면 허용되므로 reader와 updater가 공유하는 코드에 맞는다.

`rcu_dereference_raw()`는 보호 검사를 하지 않고 `rcu_dereference_raw_check()`는 lockdep 자체를 수행하지 않으므로 아주 드물게만 써야 한다. `rcu_dereference_protected(p, c)`는 명시 조건만 확인하고 barrier와 compiler constraint도 생략한다. Update-only 코드처럼 자료구조가 변할 수 없을 때 더 좋은 코드를 만들지만 동시 변경 가능성이 있으면 불법이다.

`rcu_access_pointer(p)`는 pointer value를 반환하면서 barrier를 생략하되 compiler가 load를 복제하거나 합치는 것은 막는다. 포인터를 역참조하지 않고 `NULL` 또는 다른 주소와 비교하는 용도다.

RCU accessor의 검증 강도
계열Lockdep 검사대표 용도
rcu_dereference()해당 RCU reader일반 역참조
rcu_dereference_check()Reader 또는 조건 creader/updater 공유
rcu_dereference_protected()조건 c만변경 불가 updater 경로
rcu_access_pointer()보호 불필요포인터 값 검사
raw 계열없음 또는 최소정말 표현 불가능한 경로

보호 방식과 필요한 ordering을 정확히 표현한다.

aren't certain (for example, if CONFIG_DEBUG_LOCK_ALLOC is not set).
This prevents things like WARN_ON(!rcu_read_lock_held()) from giving false
positives when lockdep is disabled.

In addition, a separate kernel config parameter CONFIG_PROVE_RCU enables
checking of rcu_dereference() primitives:

        rcu_dereference(p):
                Check for RCU read-side critical section.
        rcu_dereference_bh(p):
                Check for RCU-bh read-side critical section.
        rcu_dereference_sched(p):
                Check for RCU-sched read-side critical section.
        srcu_dereference(p, sp):
                Check for SRCU read-side critical section.
        rcu_dereference_check(p, c):
                Use explicit check expression "c" along with
                rcu_read_lock_held().  This is useful in code that is
                invoked by both RCU readers and updaters.
        rcu_dereference_bh_check(p, c):
                Use explicit check expression "c" along with
                rcu_read_lock_bh_held().  This is useful in code that
                is invoked by both RCU-bh readers and updaters.
        rcu_dereference_sched_check(p, c):
                Use explicit check expression "c" along with
                rcu_read_lock_sched_held().  This is useful in code that
                is invoked by both RCU-sched readers and updaters.
        srcu_dereference_check(p, c):
                Use explicit check expression "c" along with
                srcu_read_lock_held().  This is useful in code that
                is invoked by both SRCU readers and updaters.
        rcu_dereference_raw(p):
                Don't check.  (Use sparingly, if at all.)
        rcu_dereference_raw_check(p):
                Don't do lockdep at all.  (Use sparingly, if at all.)
        rcu_dereference_protected(p, c):
                Use explicit check expression "c", and omit all barriers
                and compiler constraints.  This is useful when the data
                structure cannot change, for example, in code that is
                invoked only by updaters.
        rcu_access_pointer(p):
                Return the value of the pointer and omit all barriers,
                but retain the compiler constraints that prevent duplicating
                or coalescing.  This is useful when testing the
                value of the pointer itself, for example, against NULL.

The rcu_dereference_check() check expression can be any boolean
expression, but would normally include a lockdep expression.  For a

복합 보호 조건 표현

73-103

`rcu_dereference_check(fdt->fd[fd], lockdep_is_held(&files->file_lock) || atomic_read(&files->count) == 1)`은 세 가지 합법 경로를 표현한다. Normal RCU reader 안이거나, `files->file_lock`을 잡았거나, `files_struct`를 현재 task 하나만 공유하는 경우다.

첫 경우에는 RCU가 포인터 publish/subscribe와 수명을 보호한다. 둘째는 lock이 변경을 막고, 셋째는 다른 접근자가 없어 변경 경쟁 자체가 없다. Update-only 코드라면 같은 두 명시 조건을 `rcu_dereference_protected()`에 넘겨 barrier와 compiler 제약을 줄일 수 있다.

Protected 변형은 RCU reader 안에 있다는 이유만으로 통과하지 않는다. 명시 조건 둘 중 하나가 참이어야 하며, pointer 또는 pointed-to data가 동시에 바뀔 수 있다면 사용할 수 없다.

복합 lockdep 조건
RCU reader인가?아니면 file_lock held인가?아니면 files->count == 1인가?하나라도 참이면 안전모두 거짓이면 splat

하나의 accessor가 여러 합법 호출 문맥을 문서화하고 검증한다.

moderately ornate example, consider the following::

        file = rcu_dereference_check(fdt->fd[fd],
                                     lockdep_is_held(&files->file_lock) ||
                                     atomic_read(&files->count) == 1);

This expression picks up the pointer "fdt->fd[fd]" in an RCU-safe manner,
and, if CONFIG_PROVE_RCU is configured, verifies that this expression
is used in:

1.        An RCU read-side critical section (implicit), or
2.        with files->file_lock held, or
3.        on an unshared files_struct.

In case (1), the pointer is picked up in an RCU-safe manner for vanilla
RCU read-side critical sections, in case (2) the ->file_lock prevents
any change from taking place, and finally, in case (3) the current task
is the only task accessing the file_struct, again preventing any change
from taking place.  If the above statement was invoked only from updater
code, it could instead be written as follows::

        file = rcu_dereference_protected(fdt->fd[fd],
                                         lockdep_is_held(&files->file_lock) ||
                                         atomic_read(&files->count) == 1);

This would verify cases #2 and #3 above, and furthermore lockdep would
complain even if this was used in an RCU read-side critical section unless
one of these two cases held.  Because rcu_dereference_protected() omits
all barriers and compiler constraints, it generates better code than do
the other flavors of rcu_dereference().  On the other hand, it is illegal
to use rcu_dereference_protected() if either the RCU-protected pointer

RCU list/hlist 순회 검사

104-119

RCU list와 hlist 순회 macro도 lockdep가 켜지면 RCU reader 안에서 호출되는지 확인한다. 추가 optional lockdep expression을 넘기면 reader 밖이어도 그 조건이 참일 때 허용한다.

Workqueue의 `for_each_pwq()`는 RCU reader 안이거나 `wq->mutex`가 잡힌 상태에서 쓸 수 있다. 따라서 `list_for_each_entry_rcu(..., lock_is_held(&(wq->mutex).dep_map))`로 정의해 두 보호 경로를 모두 코드에 기록한다.

RCU 순회 허용 조건
list_for_each_entry_rcu()RCU reader 상태 확인또는 optional lockdep 조건 확인둘 다 거짓이면 경고참이면 안전한 순회

순회 macro 자체가 call-site의 보호 계약을 검사한다.

or the RCU-protected data that it points to can change concurrently.

Like rcu_dereference(), when lockdep is enabled, RCU list and hlist
traversal primitives check for being called from within an RCU read-side
critical section.  However, a lockdep expression can be passed to them
as an additional optional argument.  With this lockdep expression, these
traversal primitives will complain only if the lockdep expression is
false and they are called from outside any RCU read-side critical section.

For example, the workqueue for_each_pwq() macro is intended to be used
either within an RCU read-side critical section or with wq->mutex held.
It is thus implemented as follows::

        #define for_each_pwq(pwq, wq)
                list_for_each_entry_rcu((pwq), &(wq)->pwqs, pwqs_node,
                                        lock_is_held(&(wq->mutex).dep_map))