← Documents Documentation/locking/locktypes.rst GitHub 원문 ↗

Linux 6.18.37 · Locking

Kernel lock 종류와 중첩 규칙

Sleeping lock, CPU-local lock, spinning lock의 차이와 owner semantics, PREEMPT_RT 변환, lock nesting 순서를 설명합니다.

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

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

1. 요약·해설

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

세 가지 lock 범주

locktypes.rst:9-83

커널 lock은 sleeping lock, CPU-local lock, spinning lock의 세 범주로 나눌 수 있습니다. 이 구분은 lock을 기다릴 때 scheduler에 CPU를 넘길 수 있는지, 현재 CPU의 실행만 통제하는지, 다른 CPU가 lock을 놓을 때까지 busy-wait하는지를 기준으로 합니다.

범주대표 type획득 가능한 문맥기본 효과
Sleepingmutex, rt_mutex, semaphore, rw_semaphore, ww_mutexpreemptible task context경쟁 시 sleep 가능
CPU locallocal_lockper-CPU data를 다루는 지정 문맥일반 kernel에서는 preemption 또는 local IRQ 제어
Spinningraw_spinlock_t, bit spinlockatomic context 포함preemption을 막고 busy-wait
구성 의존spinlock_t, rwlock_t일반 kernel과 PREEMPT_RT에서 다름일반 kernel은 spinning, RT는 rt_mutex 기반

Spinning lock의 _bh() suffix는 softirq를, _irq()는 local hard IRQ를 막습니다. _irqsave()는 이전 IRQ disable 상태를 flags에 보존한 뒤 IRQ를 막고, unlock의 _irqrestore()가 그 상태를 복구합니다. 다른 CPU의 interrupt를 끄는 기능이 아니라 현재 CPU에서 같은 lock을 다시 요청하는 interrupt가 끼어드는 것을 막는 장치입니다.

Owner semantics, rt_mutex와 semaphore

locktypes.rst:86-130

Semaphore를 제외한 대부분의 lock은 strict owner semantics를 가집니다. 획득한 task가 직접 해제해야 하며, 다른 task가 대신 unlock하면 lock 구현과 lockdep이 추적하는 소유 관계가 깨집니다. rw_semaphore reader에는 특수한 non-owner release interface가 있지만 일반 규칙의 예외로만 사용해야 합니다.

rt_mutex는 waiter의 priority를 owner에게 상속시키는 PI mutex입니다. 높은 priority task가 낮은 priority owner의 lock을 기다릴 때 owner를 boost하여 중간 priority task가 owner를 무한히 밀어내는 상황을 막습니다. 하지만 preemption 또는 IRQ가 이미 disabled인 구간을 PI가 선점할 수는 없습니다.

Counting semaphore는 owner가 없으므로 누구의 priority를 올려야 할지 결정할 수 없습니다. PREEMPT_RT도 semaphore 구현을 PI lock으로 바꾸지 않습니다. 새 코드는 serialization에는 mutex를, event 대기에는 completion을 사용하여 두 의미를 분리하는 편이 낫습니다.

rw_semaphore의 fairness와 PREEMPT_RT

locktypes.rst:132-156

rw_semaphore는 여러 reader 또는 하나의 writer를 허용합니다. 일반 kernel 구현은 writer starvation을 막도록 공정성을 제공합니다. PREEMPT_RT에서는 별도의 rt_mutex 기반 구현으로 바뀌며 priority inheritance의 방향 때문에 reader와 writer의 starvation 특성이 비대칭이 됩니다.

Writer 하나는 자신을 기다리는 reader들의 priority를 상속할 수 있습니다. 반대로 여러 reader가 동시에 owner인 상태에서 높은 priority writer 하나의 priority를 모든 reader에게 정확히 전달하는 것은 불가능합니다. 따라서 선점된 저우선순위 reader가 high-priority writer를 오래 막는 경우가 남습니다.

local_lock과 per-CPU 보호 범위

locktypes.rst:158-227

local_lock은 preempt_disable()이나 local_irq_disable()로 보호하던 per-CPU critical section에 이름을 붙입니다. 일반 kernel에서는 다음과 같이 기존 primitive의 wrapper이지만, 이름과 lockdep map 덕분에 어떤 데이터가 어느 local scope로 보호되는지 검증할 수 있습니다.

local_lock API일반 kernel에서의 동작
local_lock / local_unlockpreempt_disable / preempt_enable
local_lock_irq / local_unlock_irqlocal_irq_disable / local_irq_enable
local_lock_irqsave / local_unlock_irqrestorelocal_irq_save / local_irq_restore

PREEMPT_RT에서는 local_lock이 per-CPU spinlock_t로 구현됩니다. 이때 lock은 task를 해당 CPU에 고정하고 동일한 per-CPU lock의 사용자를 직렬화하지만, 일반 kernel처럼 IRQ가 실제로 disabled되었다고 가정할 수 없습니다. softirq 전용 데이터에는 local_lock_nested_bh()를 사용하여 보호 범위를 명시합니다.

서로 다른 local_lock_t 두 개는 같은 보호 범위를 만들지 않습니다. 단순히 local_irq_save() 호출 두 곳을 각각 다른 local lock으로 치환하면 PREEMPT_RT에서 공통 하위 함수를 직렬화하지 못합니다.

raw_spinlock_t와 spinlock_t

locktypes.rst:229-316

raw_spinlock_t는 모든 kernel 구성에서 실제 spinning lock입니다. low-level interrupt 처리, hardware register access, scheduler와 같은 core atomic code에 한정해야 합니다. 획득하면 preemption이 disabled되므로 critical section은 짧고 절대로 sleep하지 않아야 합니다.

일반 kernel의 spinlock_t는 raw_spinlock_t와 같은 의미입니다. PREEMPT_RT의 spinlock_t는 rt_mutex 기반이며 경쟁 시 task가 잠들 수 있고 preemption을 끄지 않습니다. _irq와 _irqsave suffix도 CPU의 hard IRQ 상태를 바꾸지 않습니다. 다만 migration을 막아 task가 선점되더라도 per-CPU pointer가 다른 CPU의 데이터를 가리키지 않게 합니다.

RT spinlock 획득 중 task가 block되면 kernel은 기존 task state를 saved_state에 저장하고 내부 대기 상태를 TASK_UNINTERRUPTIBLE로 바꿉니다. 다른 wakeup이 들어오면 saved_state를 TASK_RUNNING으로 바꾸며, lock wakeup이 마지막에 saved_state를 복원합니다. 이 순서가 없으면 lock 대기 중 발생한 정상 wakeup을 잃을 수 있습니다.

그림 1. PREEMPT_RT spinlock 대기 중 task state 보존
Lock waiterOwner / wakeup path
01 state = TASK_INTERRUPTIBLE
02 lock() → block()
03 saved_state = INTERRUPTIBLE
04 state = UNINTERRUPTIBLE
05 schedule()waiter sleeps
06 lock available
07 state = saved_statelock wakeup

PREEMPT_RT의 spinlock_t는 경쟁 시 rt_mutex처럼 잠들 수 있습니다. 따라서 lock 내부 대기에는 TASK_UNINTERRUPTIBLE을 사용하되, 호출자가 미리 설정한 TASK_INTERRUPTIBLE을 saved_state에 보존해야 합니다. Lock을 얻을 수 있게 된 wakeup이 saved_state를 복원하면 호출자 관점의 task state 계약이 유지됩니다.

그림 2. lock과 무관한 wakeup이 먼저 온 경우
Lock waiterIndependent wakeups
01 state = TASK_INTERRUPTIBLE
02 lock() → block()
03 saved_state = INTERRUPTIBLE
04 state = UNINTERRUPTIBLE
05 schedule()non-lock wakeup
06 saved_state = RUNNINGblocked state는 아직 유지
07 lock wakeup
08 state = saved_state = RUNNING실제 실행 가능

Signal이나 일반 event wakeup이 lock 대기 중 들어와도 실제 state를 즉시 RUNNING으로 바꾸면 아직 lock을 얻지 못한 task를 깨울 수 있습니다. 그래서 일반 wakeup은 saved_state만 RUNNING으로 갱신합니다. 나중에 lock wakeup이 이를 복원하면서 실제 wakeup도 잃지 않고 lock 획득 순서도 지킵니다.

rwlock_t와 실시간 kernel

locktypes.rst:317-340

일반 kernel의 rwlock_t는 여러 spinning reader와 하나의 writer를 허용하며 spinlock suffix 규칙을 따릅니다. PREEMPT_RT에서는 rt_mutex 기반 구현으로 바뀌므로 spinlock_t의 semantic 변화가 그대로 적용됩니다.

Reader가 여러 명이면 writer의 priority를 모든 reader에게 전달할 수 없습니다. 따라서 저우선순위 reader가 선점된 상태에서 높은 priority writer가 굶을 수 있습니다. 이 특성 때문에 read-mostly 구조라는 이유만으로 rwlock_t를 선택해서는 안 되며 RCU, seqlock, rw_semaphore와 실제 access pattern을 비교해야 합니다.

PREEMPT_RT에서 local lock을 치환할 때

locktypes.rst:342-416

일반 kernel에서 local_lock_irq() 뒤에 raw_spin_lock()을 잡으면 local IRQ가 이미 disabled되어 있으므로 동작합니다. PREEMPT_RT에서 local_lock_irq()는 per-CPU spinlock_t일 뿐 IRQ를 끄지 않으므로 같은 코드는 깨집니다. 양쪽 구성에서 동일하게 동작해야 한다면 local_lock_irq() 안쪽에는 regular spin_lock()을 사용합니다.

/* 일반 kernel과 PREEMPT_RT 모두에서 허용되는 조합 */
local_lock_irq(&local_lock);
spin_lock(&lock);

/* 보호 대상 접근 */

spin_unlock(&lock);
local_unlock_irq(&local_lock);

하위 함수의 계약도 lockdep_assert_irqs_disabled()가 아니라 lockdep_assert_held(&local_lock)으로 표현해야 합니다. 필요한 조건은 IRQ flag 자체가 아니라 해당 데이터의 보호 lock을 보유했다는 사실이기 때문입니다.

per-CPU pointer와 spinlock_t의 조합

locktypes.rst:417-490

PREEMPT_RT의 spin_lock()은 preemptible context가 필요합니다. get_cpu_ptr()는 암묵적으로 preemption을 disable하므로 그 뒤에 spin_lock()을 호출하면 RT kernel에서 잘못된 조합이 됩니다. 데이터가 같은 CPU에 머물기만 하면 되는 경우 migrate_disable()과 this_cpu_ptr()을 사용합니다.

struct foo *p;

migrate_disable();
p = this_cpu_ptr(&var1);
spin_lock(&p->lock);
p->count += this_cpu_read(var2);
spin_unlock(&p->lock);
migrate_enable();

migrate_disable()은 CPU 이동만 막고 같은 CPU에서 다른 task가 preempt하여 동일 데이터를 재진입하는 것은 막지 않습니다. 재진입까지 막아야 한다면 local_lock을 사용해야 합니다. 일반 kernel에서는 preemption disable로, RT에서는 per-CPU spinlock으로 그 조건을 만족시킵니다.

RT에서 raw spinlock과 bit spinlock

locktypes.rst:491-525

raw_spinlock_t를 보유한 구간은 실제 atomic context입니다. PREEMPT_RT의 memory allocator는 fully preemptible하므로 raw lock 안에서 kmalloc()을 호출하는 식의 코드는 실패합니다. 반면 regular spinlock_t는 RT에서 preemption을 끄지 않으므로 같은 제한이 그대로 적용되지 않습니다.

Bit spinlock은 한 비트에 lock 상태를 저장하므로 rt_mutex 구조체로 치환할 공간이 없습니다. PREEMPT_RT에서도 raw spinning semantics가 유지됩니다. 필요한 경우 사용 지점의 conditional code로 regular spinlock_t를 별도로 두어야 합니다.

Lock 중첩 순서

locktypes.rst:528-556

같은 범주의 lock은 전체 lock ordering이 순환하지 않는 한 중첩할 수 있습니다. Sleeping lock은 CPU-local 또는 spinning lock 안쪽에 들어갈 수 없습니다. 반대로 CPU-local lock과 spinning lock은 sleeping lock 안쪽에 들어갈 수 있고, 실제 spinning lock은 모든 범주 안쪽의 가장 안쪽 단계에 둘 수 있습니다.

바깥쪽에서 안쪽 순서PREEMPT_RT를 포함한 type
1mutex, rt_mutex, semaphore, rw_semaphore 등 sleeping lock
2spinlock_t, rwlock_t, local_lock
3raw_spinlock_t, bit spinlock

PREEMPT_RT에서 spinlock_t, rwlock_t, local_lock은 sleeping 계열 구현으로 변환되므로 raw_spinlock_t를 잡은 상태에서 획득할 수 없습니다. lockdep은 RT 여부와 관계없이 이 nesting 제약 위반을 보고합니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 .. _kernel_hacking_locktypes:
4
5 ==========================
6 Lock types and their rules
7 ==========================
8
9 Introduction
10 ============
11
12 The kernel provides a variety of locking primitives which can be divided
13 into three categories:
14
15 - Sleeping locks
16 - CPU local locks
17 - Spinning locks
18
19 This document conceptually describes these lock types and provides rules
20 for their nesting, including the rules for use under PREEMPT_RT.
21
22
23 Lock categories
24 ===============
25
26 Sleeping locks
27 --------------
28
29 Sleeping locks can only be acquired in preemptible task context.
30
31 Although implementations allow try_lock() from other contexts, it is
32 necessary to carefully evaluate the safety of unlock() as well as of
33 try_lock(). Furthermore, it is also necessary to evaluate the debugging
34 versions of these primitives. In short, don't acquire sleeping locks from
35 other contexts unless there is no other option.
36
37 Sleeping lock types:
38
39 - mutex
40 - rt_mutex
41 - semaphore
42 - rw_semaphore
43 - ww_mutex
44 - percpu_rw_semaphore
45
46 On PREEMPT_RT kernels, these lock types are converted to sleeping locks:
47
48 - local_lock
49 - spinlock_t
50 - rwlock_t
51
52
53 CPU local locks
54 ---------------
55
56 - local_lock
57
58 On non-PREEMPT_RT kernels, local_lock functions are wrappers around
59 preemption and interrupt disabling primitives. Contrary to other locking
60 mechanisms, disabling preemption or interrupts are pure CPU local
61 concurrency control mechanisms and not suited for inter-CPU concurrency
62 control.
63
64
65 Spinning locks
66 --------------
67
68 - raw_spinlock_t
69 - bit spinlocks
70
71 On non-PREEMPT_RT kernels, these lock types are also spinning locks:
72
73 - spinlock_t
74 - rwlock_t
75
76 Spinning locks implicitly disable preemption and the lock / unlock functions
77 can have suffixes which apply further protections:
78
79 =================== ====================================================
80 _bh() Disable / enable bottom halves (soft interrupts)
81 _irq() Disable / enable interrupts
82 _irqsave/restore() Save and disable / restore interrupt disabled state
83 =================== ====================================================
84
85
86 Owner semantics
87 ===============
88
89 The aforementioned lock types except semaphores have strict owner
90 semantics:
91
92 The context (task) that acquired the lock must release it.
93
94 rw_semaphores have a special interface which allows non-owner release for
95 readers.
96
97
98 rtmutex
99 =======
100
101 RT-mutexes are mutexes with support for priority inheritance (PI).
102
103 PI has limitations on non-PREEMPT_RT kernels due to preemption and
104 interrupt disabled sections.
105
106 PI clearly cannot preempt preemption-disabled or interrupt-disabled
107 regions of code, even on PREEMPT_RT kernels. Instead, PREEMPT_RT kernels
108 execute most such regions of code in preemptible task context, especially
109 interrupt handlers and soft interrupts. This conversion allows spinlock_t
110 and rwlock_t to be implemented via RT-mutexes.
111
112
113 semaphore
114 =========
115
116 semaphore is a counting semaphore implementation.
117
118 Semaphores are often used for both serialization and waiting, but new use
119 cases should instead use separate serialization and wait mechanisms, such
120 as mutexes and completions.
121
122 semaphores and PREEMPT_RT
123 ----------------------------
124
125 PREEMPT_RT does not change the semaphore implementation because counting
126 semaphores have no concept of owners, thus preventing PREEMPT_RT from
127 providing priority inheritance for semaphores. After all, an unknown
128 owner cannot be boosted. As a consequence, blocking on semaphores can
129 result in priority inversion.
130
131
132 rw_semaphore
133 ============
134
135 rw_semaphore is a multiple readers and single writer lock mechanism.
136
137 On non-PREEMPT_RT kernels the implementation is fair, thus preventing
138 writer starvation.
139
140 rw_semaphore complies by default with the strict owner semantics, but there
141 exist special-purpose interfaces that allow non-owner release for readers.
142 These interfaces work independent of the kernel configuration.
143
144 rw_semaphore and PREEMPT_RT
145 ---------------------------
146
147 PREEMPT_RT kernels map rw_semaphore to a separate rt_mutex-based
148 implementation, thus changing the fairness:
149
150 Because an rw_semaphore writer cannot grant its priority to multiple
151 readers, a preempted low-priority reader will continue holding its lock,
152 thus starving even high-priority writers. In contrast, because readers
153 can grant their priority to a writer, a preempted low-priority writer will
154 have its priority boosted until it releases the lock, thus preventing that
155 writer from starving readers.
156
157
158 local_lock
159 ==========
160
161 local_lock provides a named scope to critical sections which are protected
162 by disabling preemption or interrupts.
163
164 On non-PREEMPT_RT kernels local_lock operations map to the preemption and
165 interrupt disabling and enabling primitives:
166
167 =============================== ======================
168 local_lock(&llock) preempt_disable()
169 local_unlock(&llock) preempt_enable()
170 local_lock_irq(&llock) local_irq_disable()
171 local_unlock_irq(&llock) local_irq_enable()
172 local_lock_irqsave(&llock) local_irq_save()
173 local_unlock_irqrestore(&llock) local_irq_restore()
174 =============================== ======================
175
176 The named scope of local_lock has two advantages over the regular
177 primitives:
178
179 - The lock name allows static analysis and is also a clear documentation
180 of the protection scope while the regular primitives are scopeless and
181 opaque.
182
183 - If lockdep is enabled the local_lock gains a lockmap which allows to
184 validate the correctness of the protection. This can detect cases where
185 e.g. a function using preempt_disable() as protection mechanism is
186 invoked from interrupt or soft-interrupt context. Aside of that
187 lockdep_assert_held(&llock) works as with any other locking primitive.
188
189 local_lock and PREEMPT_RT
190 -------------------------
191
192 PREEMPT_RT kernels map local_lock to a per-CPU spinlock_t, thus changing
193 semantics:
194
195 - All spinlock_t changes also apply to local_lock.
196
197 local_lock usage
198 ----------------
199
200 local_lock should be used in situations where disabling preemption or
201 interrupts is the appropriate form of concurrency control to protect
202 per-CPU data structures on a non PREEMPT_RT kernel.
203
204 local_lock is not suitable to protect against preemption or interrupts on a
205 PREEMPT_RT kernel due to the PREEMPT_RT specific spinlock_t semantics.
206
207 CPU local scope and bottom-half
208 -------------------------------
209
210 Per-CPU variables that are accessed only in softirq context should not rely on
211 the assumption that this context is implicitly protected due to being
212 non-preemptible. In a PREEMPT_RT kernel, softirq context is preemptible, and
213 synchronizing every bottom-half-disabled section via implicit context results
214 in an implicit per-CPU "big kernel lock."
215
216 A local_lock_t together with local_lock_nested_bh() and
217 local_unlock_nested_bh() for locking operations help to identify the locking
218 scope.
219
220 When lockdep is enabled, these functions verify that data structure access
221 occurs within softirq context.
222 Unlike local_lock(), local_unlock_nested_bh() does not disable preemption and
223 does not add overhead when used without lockdep.
224
225 On a PREEMPT_RT kernel, local_lock_t behaves as a real lock and
226 local_unlock_nested_bh() serializes access to the data structure, which allows
227 removal of serialization via local_bh_disable().
228
229 raw_spinlock_t and spinlock_t
230 =============================
231
232 raw_spinlock_t
233 --------------
234
235 raw_spinlock_t is a strict spinning lock implementation in all kernels,
236 including PREEMPT_RT kernels. Use raw_spinlock_t only in real critical
237 core code, low-level interrupt handling and places where disabling
238 preemption or interrupts is required, for example, to safely access
239 hardware state. raw_spinlock_t can sometimes also be used when the
240 critical section is tiny, thus avoiding RT-mutex overhead.
241
242 spinlock_t
243 ----------
244
245 The semantics of spinlock_t change with the state of PREEMPT_RT.
246
247 On a non-PREEMPT_RT kernel spinlock_t is mapped to raw_spinlock_t and has
248 exactly the same semantics.
249
250 spinlock_t and PREEMPT_RT
251 -------------------------
252
253 On a PREEMPT_RT kernel spinlock_t is mapped to a separate implementation
254 based on rt_mutex which changes the semantics:
255
256 - Preemption is not disabled.
257
258 - The hard interrupt related suffixes for spin_lock / spin_unlock
259 operations (_irq, _irqsave / _irqrestore) do not affect the CPU's
260 interrupt disabled state.
261
262 - The soft interrupt related suffix (_bh()) still disables softirq
263 handlers.
264
265 Non-PREEMPT_RT kernels disable preemption to get this effect.
266
267 PREEMPT_RT kernels use a per-CPU lock for serialization which keeps
268 preemption enabled. The lock disables softirq handlers and also
269 prevents reentrancy due to task preemption.
270
271 PREEMPT_RT kernels preserve all other spinlock_t semantics:
272
273 - Tasks holding a spinlock_t do not migrate. Non-PREEMPT_RT kernels
274 avoid migration by disabling preemption. PREEMPT_RT kernels instead
275 disable migration, which ensures that pointers to per-CPU variables
276 remain valid even if the task is preempted.
277
278 - Task state is preserved across spinlock acquisition, ensuring that the
279 task-state rules apply to all kernel configurations. Non-PREEMPT_RT
280 kernels leave task state untouched. However, PREEMPT_RT must change
281 task state if the task blocks during acquisition. Therefore, it saves
282 the current task state before blocking and the corresponding lock wakeup
283 restores it, as shown below::
284
285 task->state = TASK_INTERRUPTIBLE
286 lock()
287 block()
288 task->saved_state = task->state
289 task->state = TASK_UNINTERRUPTIBLE
290 schedule()
291 lock wakeup
292 task->state = task->saved_state
293
294 Other types of wakeups would normally unconditionally set the task state
295 to RUNNING, but that does not work here because the task must remain
296 blocked until the lock becomes available. Therefore, when a non-lock
297 wakeup attempts to awaken a task blocked waiting for a spinlock, it
298 instead sets the saved state to RUNNING. Then, when the lock
299 acquisition completes, the lock wakeup sets the task state to the saved
300 state, in this case setting it to RUNNING::
301
302 task->state = TASK_INTERRUPTIBLE
303 lock()
304 block()
305 task->saved_state = task->state
306 task->state = TASK_UNINTERRUPTIBLE
307 schedule()
308 non lock wakeup
309 task->saved_state = TASK_RUNNING
310
311 lock wakeup
312 task->state = task->saved_state
313
314 This ensures that the real wakeup cannot be lost.
315
316
317 rwlock_t
318 ========
319
320 rwlock_t is a multiple readers and single writer lock mechanism.
321
322 Non-PREEMPT_RT kernels implement rwlock_t as a spinning lock and the
323 suffix rules of spinlock_t apply accordingly. The implementation is fair,
324 thus preventing writer starvation.
325
326 rwlock_t and PREEMPT_RT
327 -----------------------
328
329 PREEMPT_RT kernels map rwlock_t to a separate rt_mutex-based
330 implementation, thus changing semantics:
331
332 - All the spinlock_t changes also apply to rwlock_t.
333
334 - Because an rwlock_t writer cannot grant its priority to multiple
335 readers, a preempted low-priority reader will continue holding its lock,
336 thus starving even high-priority writers. In contrast, because readers
337 can grant their priority to a writer, a preempted low-priority writer
338 will have its priority boosted until it releases the lock, thus
339 preventing that writer from starving readers.
340
341
342 PREEMPT_RT caveats
343 ==================
344
345 local_lock on RT
346 ----------------
347
348 The mapping of local_lock to spinlock_t on PREEMPT_RT kernels has a few
349 implications. For example, on a non-PREEMPT_RT kernel the following code
350 sequence works as expected::
351
352 local_lock_irq(&local_lock);
353 raw_spin_lock(&lock);
354
355 and is fully equivalent to::
356
357 raw_spin_lock_irq(&lock);
358
359 On a PREEMPT_RT kernel this code sequence breaks because local_lock_irq()
360 is mapped to a per-CPU spinlock_t which neither disables interrupts nor
361 preemption. The following code sequence works perfectly correct on both
362 PREEMPT_RT and non-PREEMPT_RT kernels::
363
364 local_lock_irq(&local_lock);
365 spin_lock(&lock);
366
367 Another caveat with local locks is that each local_lock has a specific
368 protection scope. So the following substitution is wrong::
369
370 func1()
371 {
372 local_irq_save(flags); -> local_lock_irqsave(&local_lock_1, flags);
373 func3();
374 local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock_1, flags);
375 }
376
377 func2()
378 {
379 local_irq_save(flags); -> local_lock_irqsave(&local_lock_2, flags);
380 func3();
381 local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock_2, flags);
382 }
383
384 func3()
385 {
386 lockdep_assert_irqs_disabled();
387 access_protected_data();
388 }
389
390 On a non-PREEMPT_RT kernel this works correctly, but on a PREEMPT_RT kernel
391 local_lock_1 and local_lock_2 are distinct and cannot serialize the callers
392 of func3(). Also the lockdep assert will trigger on a PREEMPT_RT kernel
393 because local_lock_irqsave() does not disable interrupts due to the
394 PREEMPT_RT-specific semantics of spinlock_t. The correct substitution is::
395
396 func1()
397 {
398 local_irq_save(flags); -> local_lock_irqsave(&local_lock, flags);
399 func3();
400 local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock, flags);
401 }
402
403 func2()
404 {
405 local_irq_save(flags); -> local_lock_irqsave(&local_lock, flags);
406 func3();
407 local_irq_restore(flags); -> local_unlock_irqrestore(&local_lock, flags);
408 }
409
410 func3()
411 {
412 lockdep_assert_held(&local_lock);
413 access_protected_data();
414 }
415
416
417 spinlock_t and rwlock_t
418 -----------------------
419
420 The changes in spinlock_t and rwlock_t semantics on PREEMPT_RT kernels
421 have a few implications. For example, on a non-PREEMPT_RT kernel the
422 following code sequence works as expected::
423
424 local_irq_disable();
425 spin_lock(&lock);
426
427 and is fully equivalent to::
428
429 spin_lock_irq(&lock);
430
431 Same applies to rwlock_t and the _irqsave() suffix variants.
432
433 On PREEMPT_RT kernel this code sequence breaks because RT-mutex requires a
434 fully preemptible context. Instead, use spin_lock_irq() or
435 spin_lock_irqsave() and their unlock counterparts. In cases where the
436 interrupt disabling and locking must remain separate, PREEMPT_RT offers a
437 local_lock mechanism. Acquiring the local_lock pins the task to a CPU,
438 allowing things like per-CPU interrupt disabled locks to be acquired.
439 However, this approach should be used only where absolutely necessary.
440
441 A typical scenario is protection of per-CPU variables in thread context::
442
443 struct foo *p = get_cpu_ptr(&var1);
444
445 spin_lock(&p->lock);
446 p->count += this_cpu_read(var2);
447
448 This is correct code on a non-PREEMPT_RT kernel, but on a PREEMPT_RT kernel
449 this breaks. The PREEMPT_RT-specific change of spinlock_t semantics does
450 not allow to acquire p->lock because get_cpu_ptr() implicitly disables
451 preemption. The following substitution works on both kernels::
452
453 struct foo *p;
454
455 migrate_disable();
456 p = this_cpu_ptr(&var1);
457 spin_lock(&p->lock);
458 p->count += this_cpu_read(var2);
459
460 migrate_disable() ensures that the task is pinned on the current CPU which
461 in turn guarantees that the per-CPU access to var1 and var2 are staying on
462 the same CPU while the task remains preemptible.
463
464 The migrate_disable() substitution is not valid for the following
465 scenario::
466
467 func()
468 {
469 struct foo *p;
470
471 migrate_disable();
472 p = this_cpu_ptr(&var1);
473 p->val = func2();
474
475 This breaks because migrate_disable() does not protect against reentrancy from
476 a preempting task. A correct substitution for this case is::
477
478 func()
479 {
480 struct foo *p;
481
482 local_lock(&foo_lock);
483 p = this_cpu_ptr(&var1);
484 p->val = func2();
485
486 On a non-PREEMPT_RT kernel this protects against reentrancy by disabling
487 preemption. On a PREEMPT_RT kernel this is achieved by acquiring the
488 underlying per-CPU spinlock.
489
490
491 raw_spinlock_t on RT
492 --------------------
493
494 Acquiring a raw_spinlock_t disables preemption and possibly also
495 interrupts, so the critical section must avoid acquiring a regular
496 spinlock_t or rwlock_t, for example, the critical section must avoid
497 allocating memory. Thus, on a non-PREEMPT_RT kernel the following code
498 works perfectly::
499
500 raw_spin_lock(&lock);
501 p = kmalloc(sizeof(*p), GFP_ATOMIC);
502
503 But this code fails on PREEMPT_RT kernels because the memory allocator is
504 fully preemptible and therefore cannot be invoked from truly atomic
505 contexts. However, it is perfectly fine to invoke the memory allocator
506 while holding normal non-raw spinlocks because they do not disable
507 preemption on PREEMPT_RT kernels::
508
509 spin_lock(&lock);
510 p = kmalloc(sizeof(*p), GFP_ATOMIC);
511
512
513 bit spinlocks
514 -------------
515
516 PREEMPT_RT cannot substitute bit spinlocks because a single bit is too
517 small to accommodate an RT-mutex. Therefore, the semantics of bit
518 spinlocks are preserved on PREEMPT_RT kernels, so that the raw_spinlock_t
519 caveats also apply to bit spinlocks.
520
521 Some bit spinlocks are replaced with regular spinlock_t for PREEMPT_RT
522 using conditional (#ifdef'ed) code changes at the usage site. In contrast,
523 usage-site changes are not needed for the spinlock_t substitution.
524 Instead, conditionals in header files and the core locking implementation
525 enable the compiler to do the substitution transparently.
526
527
528 Lock type nesting rules
529 =======================
530
531 The most basic rules are:
532
533 - Lock types of the same lock category (sleeping, CPU local, spinning)
534 can nest arbitrarily as long as they respect the general lock ordering
535 rules to prevent deadlocks.
536
537 - Sleeping lock types cannot nest inside CPU local and spinning lock types.
538
539 - CPU local and spinning lock types can nest inside sleeping lock types.
540
541 - Spinning lock types can nest inside all lock types
542
543 These constraints apply both in PREEMPT_RT and otherwise.
544
545 The fact that PREEMPT_RT changes the lock category of spinlock_t and
546 rwlock_t from spinning to sleeping and substitutes local_lock with a
547 per-CPU spinlock_t means that they cannot be acquired while holding a raw
548 spinlock. This results in the following nesting ordering:
549
550 1) Sleeping locks
551 2) spinlock_t, rwlock_t, local_lock
552 3) raw_spinlock_t and bit spinlocks
553
554 Lockdep will complain if these constraints are violated, both in
555 PREEMPT_RT and otherwise.
556

3. 한국어 전문 번역

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

세 lock category와 owner semantics

1-95

Kernel locking primitive는 sleeping lock, CPU local lock, spinning lock의 세 category로 나눌 수 있다. 이 문서는 각 lock을 개념적으로 설명하고 PREEMPT_RT를 포함한 nesting rule을 제시한다.

CategoryLock type기본 규칙
Sleepingmutex, rt_mutex, semaphore, rw_semaphore, ww_mutex, percpu_rw_semaphorePreemptible task context에서만 획득
CPU locallocal_lockNon-RT에서는 preemption/IRQ disable wrapper이며 inter-CPU serialization을 제공하지 않음
Spinningraw_spinlock_t, bit spinlockPreemption을 암묵적으로 disable
Non-PREEMPT_RT에서 spinningspinlock_t, rwlock_traw spinning semantics
PREEMPT_RT에서 sleeping으로 변환local_lock, spinlock_t, rwlock_trt_mutex 기반 또는 per-CPU sleeping lock semantics

Sleeping lock 구현이 다른 context에서 try_lock()을 허용하더라도 try_lock뿐 아니라 unlock 안전성과 debugging version까지 검토해야 한다. 다른 선택지가 없다면 모를까 sleeping lock을 비-task context에서 획득하지 않는 것이 원칙이다.

CPU에서 preemption 또는 interrupt를 disable하는 것은 순수한 CPU-local concurrency control이다. 다른 CPU와의 동시 접근을 직렬화하지 못한다.

Spinning lock suffix추가 보호
_bh()Bottom half, 즉 soft interrupt disable/enable
_irq()Interrupt disable/enable
_irqsave/restore()기존 interrupt-disabled state 저장 후 disable / 저장 state 복원

Semaphore를 제외한 앞의 lock type은 strict owner semantics를 갖는다. Lock을 획득한 context 또는 task가 직접 release해야 한다. rw_semaphore에는 reader를 위해 non-owner release를 허용하는 특별 interface가 있다.

rt_mutex, semaphore와 priority inheritance

98-129

RT-mutex는 priority inheritance(PI)를 지원하는 mutex다. Non-PREEMPT_RT kernel에서는 preemption-disabled와 interrupt-disabled section 때문에 PI에 한계가 있다.

PREEMPT_RT에서도 PI가 preemption-disabled 또는 interrupt-disabled code region을 preempt할 수는 없다. 대신 PREEMPT_RT는 interrupt handler와 soft interrupt를 포함한 이런 region 대부분을 preemptible task context에서 실행하도록 변환한다. 이 변환 덕분에 spinlock_t와 rwlock_t를 RT-mutex로 구현할 수 있다.

Semaphore는 counting semaphore 구현이다. Serialization과 waiting 두 목적으로 자주 쓰였지만 새 use case는 mutex와 completion처럼 serialization mechanism과 wait mechanism을 분리해야 한다.

PREEMPT_RT는 semaphore 구현을 바꾸지 않는다. Counting semaphore에는 owner 개념이 없어 priority inheritance를 제공할 수 없기 때문이다. 알 수 없는 owner의 priority는 boost할 수 없다. 따라서 semaphore에서 block하면 priority inversion이 생길 수 있다.

rw_semaphore의 fairness와 PREEMPT_RT

132-155

rw_semaphore는 여러 reader 또는 한 writer를 허용하는 lock mechanism이다. Non-PREEMPT_RT 구현은 fair하므로 writer starvation을 막는다. 기본적으로 strict owner semantics를 따르지만 reader용 non-owner release special-purpose interface가 있으며 kernel configuration과 무관하게 동작한다.

PREEMPT_RT는 rw_semaphore를 별도의 rt_mutex 기반 구현으로 mapping하여 fairness를 바꾼다. rw_semaphore writer는 여러 reader에게 자기 priority를 넘겨줄 수 없다. 따라서 preempt된 low-priority reader가 lock을 계속 보유하면 high-priority writer도 starvation될 수 있다.

반대 방향에서는 reader들이 writer에게 priority를 전달할 수 있다. Preempt된 low-priority writer는 lock을 release할 때까지 priority가 boost되므로 그 writer 때문에 reader가 starvation되는 일을 막는다.

local_lock의 named CPU-local scope

158-205

local_lock은 preemption 또는 interrupt disable로 보호하는 critical section에 이름 있는 scope를 제공한다. Non-PREEMPT_RT에서 operation은 다음 primitive로 mapping된다.

local_lock APINon-PREEMPT_RT mapping
local_lock(&llock)preempt_disable()
local_unlock(&llock)preempt_enable()
local_lock_irq(&llock)local_irq_disable()
local_unlock_irq(&llock)local_irq_enable()
local_lock_irqsave(&llock)local_irq_save()
local_unlock_irqrestore(&llock)local_irq_restore()

이름 있는 scope에는 두 장점이 있다. Lock name은 static analysis에 사용할 수 있고 보호 범위를 분명히 문서화한다. 일반 preemption·IRQ primitive는 이름과 scope가 없어 어떤 data를 보호하는지 알기 어렵다.

Lockdep를 enable하면 local_lock에 lockmap이 생겨 보호가 올바른지 검증할 수 있다. 예를 들어 preempt_disable()로 보호하는 function이 interrupt 또는 soft-interrupt context에서 불리는 오류를 찾는다. lockdep_assert_held(&llock)도 다른 locking primitive와 같은 방식으로 사용할 수 있다.

PREEMPT_RT는 local_lock을 per-CPU spinlock_t로 mapping하므로 spinlock_t의 모든 의미 변화가 local_lock에도 적용된다. Local_lock은 non-RT에서 per-CPU data structure를 보호하기 위해 preemption 또는 interrupt disable이 적절한 경우에 사용한다. RT에서는 spinlock_t semantics 때문에 preemption이나 interrupt 자체를 막는 용도로 사용할 수 없다.

CPU-local softirq scope를 명시하는 nested_bh

207-227

Softirq context에서만 접근하는 per-CPU variable이라도 그 context가 non-preemptible이라 자동 보호된다고 가정해서는 안 된다. PREEMPT_RT에서는 softirq context가 preemptible이며, bottom-half-disabled section 전체를 암묵적으로 동기화하면 per-CPU big kernel lock과 같은 결과가 된다.

local_lock_t와 local_lock_nested_bh()·local_unlock_nested_bh()를 사용하면 실제 locking scope를 식별할 수 있다. Lockdep가 켜져 있으면 data structure access가 softirq context 안에서 이루어지는지 검증한다.

local_unlock_nested_bh()는 local_lock()과 달리 preemption을 disable하지 않으며 lockdep가 없을 때 overhead를 추가하지 않는다. PREEMPT_RT에서는 local_lock_t가 real lock으로 동작하고 local_unlock_nested_bh()가 data access를 직렬화하므로 local_bh_disable()에 의한 serialization을 제거할 수 있다.

raw_spinlock_t와 spinlock_t의 기본 차이

229-276

raw_spinlock_t는 PREEMPT_RT를 포함한 모든 kernel에서 strict spinning lock이다. Real critical core code, low-level interrupt handling, hardware state에 안전하게 접근하기 위해 preemption 또는 interrupt disable이 반드시 필요한 곳에서만 사용한다. Critical section이 극히 작아 RT-mutex overhead를 피하려는 경우에도 제한적으로 쓸 수 있다.

spinlock_t 의미는 PREEMPT_RT 여부에 따라 달라진다. Non-PREEMPT_RT에서는 raw_spinlock_t에 mapping되어 완전히 같은 semantics를 갖는다.

PREEMPT_RT에서는 spinlock_t가 rt_mutex 기반 별도 구현으로 mapping된다. Preemption을 disable하지 않으며 spin_lock/spin_unlock의 _irq, _irqsave/_irqrestore suffix도 CPU interrupt-disabled state를 바꾸지 않는다.

_bh suffix는 여전히 softirq handler를 disable한다. Non-RT는 preemption을 disable해 이 효과를 얻지만 PREEMPT_RT는 preemption을 유지하면서 per-CPU lock으로 serialization한다. 이 lock은 softirq handler를 disable하고 task preemption에 의한 reentrancy도 막는다.

그 밖의 spinlock_t semantics는 유지된다. Lock을 가진 task는 migrate하지 않는다. Non-RT는 preemption disable로 migration을 막고 PREEMPT_RT는 migration만 disable하여 task가 preempt되더라도 per-CPU variable pointer가 유효하도록 보장한다.

PREEMPT_RT spinlock 대기와 task state 보존

278-314

Spinlock 획득 전후의 task state도 모든 kernel configuration에서 보존해야 한다. Non-PREEMPT_RT는 task state를 건드리지 않는다. 하지만 PREEMPT_RT에서는 rt_mutex 기반 spinlock 획득 중 task가 block할 수 있어 state를 바꿔야 한다. Block 전에 current state를 saved_state에 저장하고 lock wakeup이 이를 복원한다.

그림 1. Lock wakeup이 saved_state를 복원하는 순서
Waiting taskLock wakeup
01 task->state = TASK_INTERRUPTIBLE
02 lock() → block()
03 saved_state = TASK_INTERRUPTIBLE
04 state = TASK_UNINTERRUPTIBLE → schedule()
05 lock available
06 task->state = task->saved_state

TASK_INTERRUPTIBLE 상태에서 lock을 기다리면 scheduler block을 위해 TASK_UNINTERRUPTIBLE로 잠시 바꾼다. Lock이 가능해지면 wakeup path가 original saved_state를 되돌린다.

일반 wakeup은 보통 task state를 무조건 RUNNING으로 바꾼다. 그러나 spinlock을 기다리며 block된 task에 그렇게 하면 lock이 가능하기 전에 실행될 수 있어 안 된다. 따라서 non-lock wakeup은 실제 state가 아니라 saved_state를 RUNNING으로 바꾼다. Lock 획득이 끝나면 lock wakeup이 saved_state를 current state에 복원한다.

그림 2. 일반 wakeup을 잃지 않고 lock 대기를 계속하는 순서
Waiting taskWakeup paths
01 state = TASK_INTERRUPTIBLE
02 lock() → block()
03 saved_state = TASK_INTERRUPTIBLE
04 state = TASK_UNINTERRUPTIBLE → schedule()
05 non-lock wakeup: saved_state = TASK_RUNNING
06 lock wakeup: state = saved_state
07 state = TASK_RUNNING

일반 wakeup은 task를 즉시 runnable로 만들지 않고 saved_state에 TASK_RUNNING을 기록한다. Lock wakeup이 뒤에 이 값을 복원하므로 실제 wakeup event가 사라지지 않는다.

이 two-stage state update 덕분에 실제 wakeup event가 lock wait에 가려져 유실되지 않는다.

rwlock_t와 PREEMPT_RT fairness

317-339

rwlock_t는 여러 reader 또는 한 writer를 허용한다. Non-PREEMPT_RT에서는 spinning lock으로 구현되어 spinlock_t suffix rule이 적용되고 fair implementation이 writer starvation을 막는다.

PREEMPT_RT는 rwlock_t를 별도 rt_mutex 기반 구현으로 mapping한다. Spinlock_t의 모든 변화가 rwlock_t에도 적용된다.

Writer는 여러 reader에게 priority를 전달할 수 없으므로 preempt된 low-priority reader가 lock을 계속 보유해 high-priority writer를 starvation시킬 수 있다. 반대로 reader는 writer에게 priority를 전달할 수 있어 preempt된 low-priority writer는 release할 때까지 boost되고 reader starvation을 막는다.

PREEMPT_RT에서 local_lock 변환 시 주의점

342-415

Non-PREEMPT_RT에서 다음 sequence는 local_lock_irq()가 interrupt를 disable하므로 기대대로 동작하며 raw_spin_lock_irq(&lock)와 같다.

local_lock_irq(&local_lock);
raw_spin_lock(&lock);

/* non-RT에서 동등 */
raw_spin_lock_irq(&lock);

PREEMPT_RT에서는 local_lock_irq()가 interrupt나 preemption을 disable하지 않는 per-CPU spinlock_t로 mapping되어 이 sequence가 깨진다. 다음처럼 일반 spin_lock을 nesting하면 RT와 non-RT 모두 올바르다.

local_lock_irq(&local_lock);
spin_lock(&lock);

또 다른 함정은 local_lock마다 고유한 protection scope가 있다는 점이다. 두 caller가 같은 func3()의 protected data에 접근하면서 서로 다른 local_lock_1과 local_lock_2를 사용하면 RT에서 두 caller를 직렬화하지 못한다. local_lock_irqsave()가 interrupt를 실제로 disable하지 않으므로 lockdep_assert_irqs_disabled()도 fail한다.

/* 잘못된 변환: 서로 다른 scope */
func1()
{
  local_lock_irqsave(&local_lock_1, flags);
  func3();
  local_unlock_irqrestore(&local_lock_1, flags);
}

func2()
{
  local_lock_irqsave(&local_lock_2, flags);
  func3();
  local_unlock_irqrestore(&local_lock_2, flags);
}

func3()
{
  lockdep_assert_irqs_disabled();
  access_protected_data();
}

올바른 변환은 두 caller가 같은 local_lock을 사용하고 callee가 lockdep_assert_held()로 그 lock을 확인하는 것이다.

func1()
{
  local_lock_irqsave(&local_lock, flags);
  func3();
  local_unlock_irqrestore(&local_lock, flags);
}

func2()
{
  local_lock_irqsave(&local_lock, flags);
  func3();
  local_unlock_irqrestore(&local_lock, flags);
}

func3()
{
  lockdep_assert_held(&local_lock);
  access_protected_data();
}

IRQ disable, per-CPU pointer와 RT spinlock nesting

417-488

Non-PREEMPT_RT에서 local_irq_disable() 뒤 spin_lock()을 호출하는 sequence는 spin_lock_irq()와 같다. rwlock_t와 _irqsave suffix도 마찬가지다.

local_irq_disable();
spin_lock(&lock);

/* non-RT에서 동등 */
spin_lock_irq(&lock);

PREEMPT_RT에서는 RT-mutex가 fully preemptible context를 요구하므로 첫 sequence가 깨진다. 대신 spin_lock_irq() 또는 spin_lock_irqsave()와 대응 unlock을 사용한다. IRQ disable과 locking을 분리해야 한다면 local_lock으로 task를 CPU에 pin한 뒤 per-CPU interrupt-disabled lock 등을 획득할 수 있지만 반드시 필요한 경우에만 사용한다.

Thread context에서 per-CPU variable을 보호하는 다음 code는 non-RT에서는 맞지만 RT에서는 get_cpu_ptr()가 암묵적으로 preemption을 disable하기 때문에 p->lock을 획득할 수 없다.

struct foo *p = get_cpu_ptr(&var1);

spin_lock(&p->lock);
p->count += this_cpu_read(var2);

두 kernel에서 모두 동작하려면 migration만 disable하고 task는 preemptible하게 유지한다. migrate_disable()은 task를 current CPU에 pin하여 var1과 var2의 per-CPU access가 같은 CPU에 남게 한다.

struct foo *p;

migrate_disable();
p = this_cpu_ptr(&var1);
spin_lock(&p->lock);
p->count += this_cpu_read(var2);

그러나 migrate_disable()은 preempting task의 reentrancy를 막지 못한다. Function이 per-CPU data를 읽은 뒤 preempt될 수 있고 다른 task가 같은 data에 다시 들어올 수 있는 경우에는 local_lock을 사용해야 한다.

func()
{
  struct foo *p;

  local_lock(&foo_lock);
  p = this_cpu_ptr(&var1);
  p->val = func2();
}

Non-RT에서는 local_lock이 preemption을 disable해 reentrancy를 막고 PREEMPT_RT에서는 underlying per-CPU spinlock을 획득해 같은 보호를 제공한다.

RT에서 raw_spinlock과 bit spinlock의 atomic 제약

491-525

raw_spinlock_t를 획득하면 preemption과 경우에 따라 interrupt도 disable된다. 따라서 critical section은 일반 spinlock_t 또는 rwlock_t를 추가로 획득해서는 안 되고 memory allocation 같은 operation도 피해야 한다.

다음 code는 non-PREEMPT_RT에서는 동작하지만 fully preemptible memory allocator를 truly atomic context에서 호출할 수 없는 PREEMPT_RT에서는 실패한다.

raw_spin_lock(&lock);
p = kmalloc(sizeof(*p), GFP_ATOMIC);

PREEMPT_RT에서 일반 non-raw spinlock은 preemption을 disable하지 않으므로 해당 lock을 가진 상태에서 allocator를 호출하는 것은 허용된다.

spin_lock(&lock);
p = kmalloc(sizeof(*p), GFP_ATOMIC);

Bit 하나에는 RT-mutex를 담을 수 없어 PREEMPT_RT가 bit spinlock을 대체할 수 없다. 따라서 bit spinlock semantics는 그대로이며 raw_spinlock_t의 주의점이 모두 적용된다.

일부 bit spinlock은 사용 위치의 #ifdef code로 PREEMPT_RT에서 일반 spinlock_t로 바꾼다. 반면 spinlock_t 자체의 대체는 사용 위치를 고칠 필요가 없다. Header와 core locking implementation의 conditional이 compiler가 투명하게 substitution하도록 만든다.

Lock category nesting 순서

528-555
  • 같은 category의 lock끼리는 일반 lock ordering rule을 지켜 deadlock을 막는 한 자유롭게 nesting할 수 있다.
  • Sleeping lock은 CPU-local lock 또는 spinning lock 안에 nesting할 수 없다.
  • CPU-local lock과 spinning lock은 sleeping lock 안에 nesting할 수 있다.
  • Spinning lock은 모든 lock type 안에 nesting할 수 있다.

이 제약은 PREEMPT_RT와 non-RT 모두에 적용된다. PREEMPT_RT에서는 spinlock_t와 rwlock_t category가 spinning에서 sleeping으로 바뀌고 local_lock이 per-CPU spinlock_t가 되므로 raw spinlock을 가진 상태에서 이들을 획득할 수 없다.

Nesting levelPREEMPT_RT에서 바깥쪽부터 안쪽 순서
1Sleeping locks
2spinlock_t, rwlock_t, local_lock
3raw_spinlock_t, bit spinlock

PREEMPT_RT 여부와 관계없이 이 제약을 위반하면 lockdep가 경고한다.