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

Linux 6.18.37 · Locking

Spinlock 사용 원리와 interrupt 규칙

spin_lock_irqsave(), reader-writer spinlock, process context 전용 lock과 same-CPU interrupt deadlock을 실제 코드로 설명합니다.

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

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

1. 요약·해설

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

기본 spinlock과 irqsave

spinlocks.rst:1-48
static DEFINE_SPINLOCK(xxx_lock);

unsigned long flags;

spin_lock_irqsave(&xxx_lock, flags);
/* shared data를 다루는 critical section */
spin_unlock_irqrestore(&xxx_lock, flags);

spin_lock_irqsave()는 현재 CPU의 IRQ 상태를 flags에 저장하고 local IRQ를 disable한 뒤 global spinlock을 획득합니다. Local IRQ disable은 같은 CPU의 interrupt handler가 이 lock을 다시 요청하는 것을 막고, spinlock 자체는 다른 CPU와 critical section을 직렬화합니다.

보호 규칙은 shared variable을 만지는 모든 경로에서 동일해야 합니다. 한 함수만 lock을 잡고 다른 함수가 lock 없이 같은 field를 읽거나 쓰면 critical section은 성립하지 않습니다. Lock을 데이터 구조와 함께 설계하고, 각 access path의 context를 전부 확인해야 합니다.

spin_lock은 acquire operation, spin_unlock은 release operation을 제공합니다. 하지만 lock 밖의 lockless access까지 자동으로 올바르게 만드는 것은 아닙니다. 그런 access에는 별도의 memory-ordering 설계가 필요합니다.

Reader-writer spinlock

spinlocks.rst:52-93
rwlock_t xxx_lock = __RW_LOCK_UNLOCKED(xxx_lock);
unsigned long flags;

read_lock_irqsave(&xxx_lock, flags);
/* 읽기 전용 critical section */
read_unlock_irqrestore(&xxx_lock, flags);

write_lock_irqsave(&xxx_lock, flags);
/* 배타적 읽기와 쓰기 */
write_unlock_irqrestore(&xxx_lock, flags);

rwlock은 여러 reader를 동시에 허용하지만 writer는 배타적으로 진입합니다. 연결 리스트를 변경하지 않고 검색하는 reader가 많을 때 사용할 수 있습니다. 다만 단순 spinlock보다 atomic memory operation이 많으므로 read-side가 짧으면 오히려 손해일 수 있습니다.

Read lock을 잡은 채 write lock으로 upgrade할 수 없습니다. 실행 중 한 번이라도 변경할 가능성이 있다면 시작부터 write lock을 잡아야 합니다. 새로운 read-mostly 설계에서는 RCU가 더 적합한지 먼저 검토해야 합니다.

Process context 전용 spinlock과 deadlock

spinlocks.rst:97-140

보호 대상이 interrupt handler에서 절대 접근되지 않고 process context에서만 사용된다면 spin_lock()과 spin_unlock()을 사용할 수 있습니다. IRQ mask를 변경하지 않으므로 irqsave variant보다 비용이 작습니다. 이 선택은 호출 graph 전체에서 interrupt access가 없다는 근거가 있을 때만 안전합니다.

spin_lock(&lock);
/* 여기서 같은 CPU의 IRQ 발생 */
    interrupt_handler() {
        spin_lock(&lock); /* owner를 중단시킨 채 영원히 대기 */
    }

같은 CPU의 handler가 이미 보유한 lock을 요청하면 lock owner인 interrupted context가 다시 실행될 수 없어 deadlock이 됩니다. 다른 CPU의 interrupt가 같은 lock에서 도는 것은 owner CPU의 진행을 막지 않으므로 owner가 결국 unlock할 수 있습니다. 그래서 irqsave는 모든 CPU가 아니라 local IRQ만 막으면 충분합니다.

정적·동적 초기화

spinlocks.rst:146-165
static DEFINE_SPINLOCK(static_lock);
static DEFINE_RWLOCK(static_rwlock);

spinlock_t dynamic_lock;
rwlock_t dynamic_rwlock;

static int __init xxx_init(void)
{
    spin_lock_init(&dynamic_lock);
    rwlock_init(&dynamic_rwlock);
    return 0;
}

정적 object에는 DEFINE_SPINLOCK() 또는 DEFINE_RWLOCK()을 사용하고 동적 수명 object에는 spin_lock_init()과 rwlock_init()을 호출합니다. 초기화가 끝나기 전에 lock을 공개하거나, 사용 중인 lock storage를 재초기화해서는 안 됩니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 ===============
2 Locking lessons
3 ===============
4
5 Lesson 1: Spin locks
6 ====================
7
8 The most basic primitive for locking is spinlock::
9
10 static DEFINE_SPINLOCK(xxx_lock);
11
12 unsigned long flags;
13
14 spin_lock_irqsave(&xxx_lock, flags);
15 ... critical section here ..
16 spin_unlock_irqrestore(&xxx_lock, flags);
17
18 The above is always safe. It will disable interrupts _locally_, but the
19 spinlock itself will guarantee the global lock, so it will guarantee that
20 there is only one thread-of-control within the region(s) protected by that
21 lock. This works well even under UP also, so the code does _not_ need to
22 worry about UP vs SMP issues: the spinlocks work correctly under both.
23
24 NOTE! Implications of spin_locks for memory are further described in:
25
26 Documentation/memory-barriers.txt
27
28 (5) ACQUIRE operations.
29
30 (6) RELEASE operations.
31
32 The above is usually pretty simple (you usually need and want only one
33 spinlock for most things - using more than one spinlock can make things a
34 lot more complex and even slower and is usually worth it only for
35 sequences that you **know** need to be split up: avoid it at all cost if you
36 aren't sure).
37
38 This is really the only really hard part about spinlocks: once you start
39 using spinlocks they tend to expand to areas you might not have noticed
40 before, because you have to make sure the spinlocks correctly protect the
41 shared data structures **everywhere** they are used. The spinlocks are most
42 easily added to places that are completely independent of other code (for
43 example, internal driver data structures that nobody else ever touches).
44
45 NOTE! The spin-lock is safe only when you **also** use the lock itself
46 to do locking across CPU's, which implies that EVERYTHING that
47 touches a shared variable has to agree about the spinlock they want
48 to use.
49
50 ----
51
52 Lesson 2: reader-writer spinlocks.
53 ==================================
54
55 If your data accesses have a very natural pattern where you usually tend
56 to mostly read from the shared variables, the reader-writer locks
57 (rw_lock) versions of the spinlocks are sometimes useful. They allow multiple
58 readers to be in the same critical region at once, but if somebody wants
59 to change the variables it has to get an exclusive write lock.
60
61 NOTE! reader-writer locks require more atomic memory operations than
62 simple spinlocks. Unless the reader critical section is long, you
63 are better off just using spinlocks.
64
65 The routines look the same as above::
66
67 rwlock_t xxx_lock = __RW_LOCK_UNLOCKED(xxx_lock);
68
69 unsigned long flags;
70
71 read_lock_irqsave(&xxx_lock, flags);
72 .. critical section that only reads the info ...
73 read_unlock_irqrestore(&xxx_lock, flags);
74
75 write_lock_irqsave(&xxx_lock, flags);
76 .. read and write exclusive access to the info ...
77 write_unlock_irqrestore(&xxx_lock, flags);
78
79 The above kind of lock may be useful for complex data structures like
80 linked lists, especially searching for entries without changing the list
81 itself. The read lock allows many concurrent readers. Anything that
82 **changes** the list will have to get the write lock.
83
84 NOTE! RCU is better for list traversal, but requires careful
85 attention to design detail (see Documentation/RCU/listRCU.rst).
86
87 Also, you cannot "upgrade" a read-lock to a write-lock, so if you at _any_
88 time need to do any changes (even if you don't do it every time), you have
89 to get the write-lock at the very beginning.
90
91 NOTE! We are working hard to remove reader-writer spinlocks in most
92 cases, so please don't add a new one without consensus. (Instead, see
93 Documentation/RCU/rcu.rst for complete information.)
94
95 ----
96
97 Lesson 3: spinlocks revisited.
98 ==============================
99
100 The single spin-lock primitives above are by no means the only ones. They
101 are the most safe ones, and the ones that work under all circumstances,
102 but partly **because** they are safe they are also fairly slow. They are slower
103 than they'd need to be, because they do have to disable interrupts
104 (which is just a single instruction on a x86, but it's an expensive one -
105 and on other architectures it can be worse).
106
107 If you have a case where you have to protect a data structure across
108 several CPU's and you want to use spinlocks you can potentially use
109 cheaper versions of the spinlocks. IFF you know that the spinlocks are
110 never used in interrupt handlers, you can use the non-irq versions::
111
112 spin_lock(&lock);
113 ...
114 spin_unlock(&lock);
115
116 (and the equivalent read-write versions too, of course). The spinlock will
117 guarantee the same kind of exclusive access, and it will be much faster.
118 This is useful if you know that the data in question is only ever
119 manipulated from a "process context", ie no interrupts involved.
120
121 The reasons you mustn't use these versions if you have interrupts that
122 play with the spinlock is that you can get deadlocks::
123
124 spin_lock(&lock);
125 ...
126 <- interrupt comes in:
127 spin_lock(&lock);
128
129 where an interrupt tries to lock an already locked variable. This is ok if
130 the other interrupt happens on another CPU, but it is _not_ ok if the
131 interrupt happens on the same CPU that already holds the lock, because the
132 lock will obviously never be released (because the interrupt is waiting
133 for the lock, and the lock-holder is interrupted by the interrupt and will
134 not continue until the interrupt has been processed).
135
136 (This is also the reason why the irq-versions of the spinlocks only need
137 to disable the _local_ interrupts - it's ok to use spinlocks in interrupts
138 on other CPU's, because an interrupt on another CPU doesn't interrupt the
139 CPU that holds the lock, so the lock-holder can continue and eventually
140 releases the lock).
141
142 Linus
143
144 ----
145
146 Reference information:
147 ======================
148
149 For dynamic initialization, use spin_lock_init() or rwlock_init() as
150 appropriate::
151
152 spinlock_t xxx_lock;
153 rwlock_t xxx_rw_lock;
154
155 static int __init xxx_init(void)
156 {
157 spin_lock_init(&xxx_lock);
158 rwlock_init(&xxx_rw_lock);
159 ...
160 }
161
162 module_init(xxx_init);
163
164 For static initialization, use DEFINE_SPINLOCK() / DEFINE_RWLOCK() or
165 __SPIN_LOCK_UNLOCKED() / __RW_LOCK_UNLOCKED() as appropriate.
166

3. 한국어 전문 번역

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

Lesson 1: 기본 spinlock

1-22
static DEFINE_SPINLOCK(xxx_lock);

unsigned long flags;

spin_lock_irqsave(&xxx_lock, flags);
/* critical section */
spin_unlock_irqrestore(&xxx_lock, flags);

이 형태는 항상 안전하다. Local CPU의 interrupt를 disable하고 spinlock 자체가 모든 CPU에 걸친 배타성을 보장하므로, 해당 lock이 보호하는 구간에는 하나의 execution context만 들어갈 수 있다. UP와 SMP에서 모두 올바르게 동작하므로 code가 두 configuration을 별도로 처리할 필요가 없다.

Memory ordering과 lock 범위

24-48

Spinlock의 memory ordering 의미는 Documentation/memory-barriers.txt의 ACQUIRE operation과 RELEASE operation 절에서 자세히 설명한다.

대부분의 경우 한 자료 구조에는 하나의 spinlock만 사용하는 방식이 단순하다. 여러 spinlock을 쓰면 복잡성과 비용이 늘어나므로 보호 구간을 반드시 나눠야 한다고 확신하는 경우에만 고려한다.

Spinlock을 도입할 때 어려운 점은 공유 자료 구조가 사용되는 모든 지점을 찾아 같은 규칙으로 보호하는 일이다. 다른 code와 완전히 독립적인 driver 내부 자료 구조처럼 접근 지점이 제한된 곳에 추가하는 것이 가장 쉽다.

CPU 사이의 동기화가 안전하려면 공유 변수를 만지는 모든 code가 어떤 spinlock을 사용할지 합의하고 실제로 그 lock을 획득해야 한다.

Lesson 2: reader-writer spinlock

52-77

공유 변수를 대부분 읽기만 하는 자연스러운 access pattern이라면 reader-writer spinlock인 rwlock이 유용할 수 있다. 여러 reader가 동시에 critical region에 들어갈 수 있지만 변수를 바꾸려는 writer는 배타적인 write lock을 획득해야 한다.

Reader-writer lock은 단순 spinlock보다 atomic memory operation을 더 많이 수행한다. Reader critical section이 길지 않다면 일반 spinlock이 더 낫다.

rwlock_t xxx_lock = __RW_LOCK_UNLOCKED(xxx_lock);

unsigned long flags;

read_lock_irqsave(&xxx_lock, flags);
/* read-only critical section */
read_unlock_irqrestore(&xxx_lock, flags);

write_lock_irqsave(&xxx_lock, flags);
/* exclusive read/write critical section */
write_unlock_irqrestore(&xxx_lock, flags);

rwlock 사용 조건과 제한

79-93

여러 task가 linked list entry를 검색하지만 list 자체는 자주 바꾸지 않는 복잡한 자료 구조에서 이 lock이 유용할 수 있다. Read lock은 여러 reader를 허용하고 list를 변경하는 code는 write lock을 획득한다.

List 순회에는 RCU가 더 적합하지만 설계를 세심하게 해야 한다. 자세한 내용은 Documentation/RCU/listRCU.rst를 참고한다.

Read lock을 write lock으로 upgrade할 수는 없다. 어떤 실행에서든 변경할 가능성이 있다면 시작부터 write lock을 획득해야 한다.

Kernel은 대부분의 reader-writer spinlock을 제거하는 방향으로 작업 중이다. Consensus 없이 새 rwlock을 추가하지 말고 Documentation/RCU/rcu.rst의 대안을 검토한다.

Lesson 3: interrupt를 막지 않는 variant

97-119

앞의 단일 spinlock primitive는 모든 상황에서 동작해 가장 안전하지만 그만큼 느리다. Interrupt disable은 x86에서 명령 하나이더라도 비용이 크고 다른 architecture에서는 더 비쌀 수 있다.

여러 CPU가 공유하는 자료를 보호하되 해당 spinlock을 interrupt handler에서 절대로 사용하지 않는다고 확신한다면 interrupt를 disable하지 않는 더 저렴한 variant를 사용할 수 있다.

spin_lock(&lock);
/* ... */
spin_unlock(&lock);

배타성은 동일하게 보장되며 더 빠르다. 해당 자료가 interrupt와 무관하고 process context에서만 변경되는 경우에 적합하다. Reader-writer variant에도 같은 원칙이 적용된다.

같은 CPU의 interrupt가 만드는 deadlock

121-140
spin_lock(&lock);
/* ... */
    /* interrupt arrives */
    spin_lock(&lock);

Interrupt handler도 같은 lock을 사용한다면 non-irq variant를 사용해서는 안 된다. Lock을 보유한 CPU에 interrupt가 들어와 handler가 같은 lock을 기다리면 deadlock이 된다. Handler가 끝나야 원래 code가 계속 실행해 lock을 해제할 수 있는데 handler는 그 lock을 기다리고 있기 때문이다.

이 때문에 irq variant는 local interrupt만 disable하면 충분하다. 다른 CPU의 interrupt가 같은 spinlock을 기다리더라도 lock owner가 실행 중인 CPU를 중단시키지는 않으므로 owner는 계속 실행해 결국 lock을 해제할 수 있다.

동적 및 정적 초기화

142-165

이 문서의 locking lesson은 Linus가 작성했다. 동적 초기화에는 spin_lock_init()과 rwlock_init()을 사용한다.

spinlock_t xxx_lock;
rwlock_t xxx_rw_lock;

static int __init xxx_init(void)
{
    spin_lock_init(&xxx_lock);
    rwlock_init(&xxx_rw_lock);
    /* ... */
}

module_init(xxx_init);

정적 초기화에는 상황에 맞게 DEFINE_SPINLOCK(), DEFINE_RWLOCK(), __SPIN_LOCK_UNLOCKED(), __RW_LOCK_UNLOCKED()를 사용한다.