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

Linux 6.18.37 · RCU

Lockdep-RCU 경고 해석하기

RCU 보호 누락 경고를 읽고 protected accessor, reader critical section, rcu_access_pointer 중 올바른 해결책을 고르는 방법입니다.

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

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

1. 요약·해설

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

요약·해설

lockdep-splat.rst:1-115

RCU 보호 누락 경고를 읽고 protected accessor, reader critical section, rcu_access_pointer 중 올바른 해결책을 고르는 방법입니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 =================
4 Lockdep-RCU Splat
5 =================
6
7 Lockdep-RCU was added to the Linux kernel in early 2010
8 (http://lwn.net/Articles/371986/). This facility checks for some common
9 misuses of the RCU API, most notably using one of the rcu_dereference()
10 family to access an RCU-protected pointer without the proper protection.
11 When such misuse is detected, an lockdep-RCU splat is emitted.
12
13 The usual cause of a lockdep-RCU splat is someone accessing an
14 RCU-protected data structure without either (1) being in the right kind of
15 RCU read-side critical section or (2) holding the right update-side lock.
16 This problem can therefore be serious: it might result in random memory
17 overwriting or worse. There can of course be false positives, this
18 being the real world and all that.
19
20 So let's look at an example RCU lockdep splat from 3.0-rc5, one that
21 has long since been fixed::
22
23 =============================
24 WARNING: suspicious RCU usage
25 -----------------------------
26 block/cfq-iosched.c:2776 suspicious rcu_dereference_protected() usage!
27
28 other info that might help us debug this::
29
30 rcu_scheduler_active = 1, debug_locks = 0
31 3 locks held by scsi_scan_6/1552:
32 #0: (&shost->scan_mutex){+.+.}, at: [<ffffffff8145efca>]
33 scsi_scan_host_selected+0x5a/0x150
34 #1: (&eq->sysfs_lock){+.+.}, at: [<ffffffff812a5032>]
35 elevator_exit+0x22/0x60
36 #2: (&(&q->__queue_lock)->rlock){-.-.}, at: [<ffffffff812b6233>]
37 cfq_exit_queue+0x43/0x190
38
39 stack backtrace:
40 Pid: 1552, comm: scsi_scan_6 Not tainted 3.0.0-rc5 #17
41 Call Trace:
42 [<ffffffff810abb9b>] lockdep_rcu_dereference+0xbb/0xc0
43 [<ffffffff812b6139>] __cfq_exit_single_io_context+0xe9/0x120
44 [<ffffffff812b626c>] cfq_exit_queue+0x7c/0x190
45 [<ffffffff812a5046>] elevator_exit+0x36/0x60
46 [<ffffffff812a802a>] blk_cleanup_queue+0x4a/0x60
47 [<ffffffff8145cc09>] scsi_free_queue+0x9/0x10
48 [<ffffffff81460944>] __scsi_remove_device+0x84/0xd0
49 [<ffffffff8145dca3>] scsi_probe_and_add_lun+0x353/0xb10
50 [<ffffffff817da069>] ? error_exit+0x29/0xb0
51 [<ffffffff817d98ed>] ? _raw_spin_unlock_irqrestore+0x3d/0x80
52 [<ffffffff8145e722>] __scsi_scan_target+0x112/0x680
53 [<ffffffff812c690d>] ? trace_hardirqs_off_thunk+0x3a/0x3c
54 [<ffffffff817da069>] ? error_exit+0x29/0xb0
55 [<ffffffff812bcc60>] ? kobject_del+0x40/0x40
56 [<ffffffff8145ed16>] scsi_scan_channel+0x86/0xb0
57 [<ffffffff8145f0b0>] scsi_scan_host_selected+0x140/0x150
58 [<ffffffff8145f149>] do_scsi_scan_host+0x89/0x90
59 [<ffffffff8145f170>] do_scan_async+0x20/0x160
60 [<ffffffff8145f150>] ? do_scsi_scan_host+0x90/0x90
61 [<ffffffff810975b6>] kthread+0xa6/0xb0
62 [<ffffffff817db154>] kernel_thread_helper+0x4/0x10
63 [<ffffffff81066430>] ? finish_task_switch+0x80/0x110
64 [<ffffffff817d9c04>] ? retint_restore_args+0xe/0xe
65 [<ffffffff81097510>] ? __kthread_init_worker+0x70/0x70
66 [<ffffffff817db150>] ? gs_change+0xb/0xb
67
68 Line 2776 of block/cfq-iosched.c in v3.0-rc5 is as follows::
69
70 if (rcu_dereference(ioc->ioc_data) == cic) {
71
72 This form says that it must be in a plain vanilla RCU read-side critical
73 section, but the "other info" list above shows that this is not the
74 case. Instead, we hold three locks, one of which might be RCU related.
75 And maybe that lock really does protect this reference. If so, the fix
76 is to inform RCU, perhaps by changing __cfq_exit_single_io_context() to
77 take the struct request_queue "q" from cfq_exit_queue() as an argument,
78 which would permit us to invoke rcu_dereference_protected as follows::
79
80 if (rcu_dereference_protected(ioc->ioc_data,
81 lockdep_is_held(&q->queue_lock)) == cic) {
82
83 With this change, there would be no lockdep-RCU splat emitted if this
84 code was invoked either from within an RCU read-side critical section
85 or with the ->queue_lock held. In particular, this would have suppressed
86 the above lockdep-RCU splat because ->queue_lock is held (see #2 in the
87 list above).
88
89 On the other hand, perhaps we really do need an RCU read-side critical
90 section. In this case, the critical section must span the use of the
91 return value from rcu_dereference(), or at least until there is some
92 reference count incremented or some such. One way to handle this is to
93 add rcu_read_lock() and rcu_read_unlock() as follows::
94
95 rcu_read_lock();
96 if (rcu_dereference(ioc->ioc_data) == cic) {
97 spin_lock(&ioc->lock);
98 rcu_assign_pointer(ioc->ioc_data, NULL);
99 spin_unlock(&ioc->lock);
100 }
101 rcu_read_unlock();
102
103 With this change, the rcu_dereference() is always within an RCU
104 read-side critical section, which again would have suppressed the
105 above lockdep-RCU splat.
106
107 But in this particular case, we don't actually dereference the pointer
108 returned from rcu_dereference(). Instead, that pointer is just compared
109 to the cic pointer, which means that the rcu_dereference() can be replaced
110 by rcu_access_pointer() as follows::
111
112 if (rcu_access_pointer(ioc->ioc_data) == cic) {
113
114 Because it is legal to invoke rcu_access_pointer() without protection,
115 this change would also suppress the above lockdep-RCU splat.
116

3. 한국어 전문 번역

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

Lockdep-RCU splat과 진단 정보

1-66

Lockdep-RCU는 2010년 초 Linux에 추가되었으며, 적절한 보호 없이 `rcu_dereference()` 계열로 RCU 포인터에 접근하는 흔한 오용을 검사한다. 발견하면 suspicious RCU usage 경고와 stack trace를 출력한다.

보통 원인은 올바른 종류의 RCU read-side critical section 안에 있지 않거나, 해당 자료구조를 안정시키는 update-side lock을 잡지 않은 것이다. 이는 임의 memory overwrite로 이어질 수 있는 심각한 문제지만 실제 환경에서는 false positive도 가능하다.

예시의 Linux 3.0-rc5 경고는 `block/cfq-iosched.c:2776`의 `rcu_dereference_protected()` 사용을 지목하고, `rcu_scheduler_active`, `debug_locks`, 현재 task가 가진 세 lock과 전체 call trace를 제공한다. 문제 줄의 평범한 `rcu_dereference(ioc->ioc_data)`는 vanilla RCU reader 안이어야 한다고 선언하지만 trace는 그렇지 않음을 보여 준다.

Splat에서 먼저 볼 항목
출력의미
suspicious usage 위치잘못된 accessor 호출 지점
locks held대체 보호가 가능한 lock 후보
stack backtrace호출 경로와 객체 수명 문맥
RCU scheduler state검사와 실행 환경 상태

경고 위치와 실제 보호 상태를 대응시킨다.

.. SPDX-License-Identifier: GPL-2.0

=================
Lockdep-RCU Splat
=================

Lockdep-RCU was added to the Linux kernel in early 2010
(http://lwn.net/Articles/371986/).  This facility checks for some common
misuses of the RCU API, most notably using one of the rcu_dereference()
family to access an RCU-protected pointer without the proper protection.
When such misuse is detected, an lockdep-RCU splat is emitted.

The usual cause of a lockdep-RCU splat is someone accessing an
RCU-protected data structure without either (1) being in the right kind of
RCU read-side critical section or (2) holding the right update-side lock.
This problem can therefore be serious: it might result in random memory
overwriting or worse.  There can of course be false positives, this
being the real world and all that.

So let's look at an example RCU lockdep splat from 3.0-rc5, one that
has long since been fixed::

    =============================
    WARNING: suspicious RCU usage
    -----------------------------
    block/cfq-iosched.c:2776 suspicious rcu_dereference_protected() usage!

other info that might help us debug this::

    rcu_scheduler_active = 1, debug_locks = 0
    3 locks held by scsi_scan_6/1552:
    #0:  (&shost->scan_mutex){+.+.}, at: [<ffffffff8145efca>]
    scsi_scan_host_selected+0x5a/0x150
    #1:  (&eq->sysfs_lock){+.+.}, at: [<ffffffff812a5032>]
    elevator_exit+0x22/0x60
    #2:  (&(&q->__queue_lock)->rlock){-.-.}, at: [<ffffffff812b6233>]
    cfq_exit_queue+0x43/0x190

    stack backtrace:
    Pid: 1552, comm: scsi_scan_6 Not tainted 3.0.0-rc5 #17
    Call Trace:
    [<ffffffff810abb9b>] lockdep_rcu_dereference+0xbb/0xc0
    [<ffffffff812b6139>] __cfq_exit_single_io_context+0xe9/0x120
    [<ffffffff812b626c>] cfq_exit_queue+0x7c/0x190
    [<ffffffff812a5046>] elevator_exit+0x36/0x60
    [<ffffffff812a802a>] blk_cleanup_queue+0x4a/0x60
    [<ffffffff8145cc09>] scsi_free_queue+0x9/0x10
    [<ffffffff81460944>] __scsi_remove_device+0x84/0xd0
    [<ffffffff8145dca3>] scsi_probe_and_add_lun+0x353/0xb10
    [<ffffffff817da069>] ? error_exit+0x29/0xb0
    [<ffffffff817d98ed>] ? _raw_spin_unlock_irqrestore+0x3d/0x80
    [<ffffffff8145e722>] __scsi_scan_target+0x112/0x680
    [<ffffffff812c690d>] ? trace_hardirqs_off_thunk+0x3a/0x3c
    [<ffffffff817da069>] ? error_exit+0x29/0xb0
    [<ffffffff812bcc60>] ? kobject_del+0x40/0x40
    [<ffffffff8145ed16>] scsi_scan_channel+0x86/0xb0
    [<ffffffff8145f0b0>] scsi_scan_host_selected+0x140/0x150
    [<ffffffff8145f149>] do_scsi_scan_host+0x89/0x90
    [<ffffffff8145f170>] do_scan_async+0x20/0x160
    [<ffffffff8145f150>] ? do_scsi_scan_host+0x90/0x90
    [<ffffffff810975b6>] kthread+0xa6/0xb0
    [<ffffffff817db154>] kernel_thread_helper+0x4/0x10
    [<ffffffff81066430>] ? finish_task_switch+0x80/0x110
    [<ffffffff817d9c04>] ? retint_restore_args+0xe/0xe
    [<ffffffff81097510>] ? __kthread_init_worker+0x70/0x70
    [<ffffffff817db150>] ? gs_change+0xb/0xb

해결 1: Update-side lock을 조건으로 표현

67-93

실제로 `q->queue_lock`이 포인터를 안정시키고 있다면 reader critical section을 추가할 필요가 없다. 대신 `rcu_dereference_protected(ioc->ioc_data, lockdep_is_held(&q->queue_lock))`로 보호 조건을 lockdep에 알려야 한다.

예시에서는 held-lock 목록의 #2가 `->queue_lock`을 보여 주므로 이 조건이 참이고 경고가 사라진다. 이 선택은 단지 경고를 억제하는 주석이 아니라, 포인터와 대상 데이터가 해당 lock 아래에서 동시 변경되지 않는다는 실행 계약이다.

Protected accessor 선택
경고 지점 확인held lock이 객체를 안정시키는지 검증lockdep_is_held() 조건 작성rcu_dereference_protected()lockdep가 조건 확인

이미 가진 updater lock을 검증 가능한 조건으로 표현한다.


Line 2776 of block/cfq-iosched.c in v3.0-rc5 is as follows::

        if (rcu_dereference(ioc->ioc_data) == cic) {

This form says that it must be in a plain vanilla RCU read-side critical
section, but the "other info" list above shows that this is not the
case.  Instead, we hold three locks, one of which might be RCU related.
And maybe that lock really does protect this reference.  If so, the fix
is to inform RCU, perhaps by changing __cfq_exit_single_io_context() to
take the struct request_queue "q" from cfq_exit_queue() as an argument,
which would permit us to invoke rcu_dereference_protected as follows::

        if (rcu_dereference_protected(ioc->ioc_data,
                                      lockdep_is_held(&q->queue_lock)) == cic) {

With this change, there would be no lockdep-RCU splat emitted if this
code was invoked either from within an RCU read-side critical section
or with the ->queue_lock held.  In particular, this would have suppressed
the above lockdep-RCU splat because ->queue_lock is held (see #2 in the
list above).

On the other hand, perhaps we really do need an RCU read-side critical
section.  In this case, the critical section must span the use of the
return value from rcu_dereference(), or at least until there is some
reference count incremented or some such.  One way to handle this is to
add rcu_read_lock() and rcu_read_unlock() as follows::

해결 2: RCU reader로 실제 사용 구간 보호

94-107

포인터를 실제로 역참조하고 RCU의 수명 보장이 필요하다면 `rcu_read_lock()`을 추가해야 한다. Critical section은 `rcu_dereference()` 호출만 감싸는 것이 아니라 반환 포인터의 마지막 사용까지, 또는 reference count 같은 다른 수명 기법으로 handoff할 때까지 이어져야 한다.

예시는 lookup 뒤 `ioc->lock`을 잡고 `rcu_assign_pointer(ioc->ioc_data, NULL)`로 제거하는 전체 구간을 RCU read lock 안에 둔다. 이 변경은 경고뿐 아니라 객체 수명 race도 해결한다.

Reader 보호 범위
rcu_read_lock()rcu_dereference()반환 포인터 사용필요한 lock/refcount handoffrcu_read_unlock()

Accessor 호출보다 반환 포인터의 사용 수명이 기준이다.


        rcu_read_lock();
        if (rcu_dereference(ioc->ioc_data) == cic) {
                spin_lock(&ioc->lock);
                rcu_assign_pointer(ioc->ioc_data, NULL);
                spin_unlock(&ioc->lock);
        }
        rcu_read_unlock();

With this change, the rcu_dereference() is always within an RCU
read-side critical section, which again would have suppressed the
above lockdep-RCU splat.

But in this particular case, we don't actually dereference the pointer

해결 3: 값 비교만 하면 rcu_access_pointer()

108-115

이 사례는 반환 포인터를 역참조하지 않고 `cic`와 값만 비교한다. 그러므로 memory dependency와 reader 수명 보호가 필요한 `rcu_dereference()` 대신 보호 없이 호출 가능한 `rcu_access_pointer()`가 정확한 API다.

세 해결책은 서로 바꿔 쓸 경고 억제 요령이 아니다. 대상 데이터를 updater lock이 고정하면 protected accessor, RCU로 수명을 보호해야 하면 reader critical section, 포인터 값 자체만 검사하면 access accessor를 선택한다.

Splat 해결책 선택
상황해결
Update lock이 데이터 고정rcu_dereference_protected()
포인터를 역참조하고 수명 필요rcu_read_lock() + rcu_dereference()
NULL/동일성만 검사rcu_access_pointer()

코드가 실제로 요구하는 보호 의미에 따라 결정한다.

returned from rcu_dereference().  Instead, that pointer is just compared
to the cic pointer, which means that the rcu_dereference() can be replaced
by rcu_access_pointer() as follows::

        if (rcu_access_pointer(ioc->ioc_data) == cic) {

Because it is legal to invoke rcu_access_pointer() without protection,
this change would also suppress the above lockdep-RCU splat.