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

Linux 6.18.37 · Locking

Sequence counter와 seqlock

seqcount_t의 짝수·홀수 protocol, writer serialization, lock 연계형 counter, latch와 seqlock reader 세 종류를 설명합니다.

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

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

1. 요약·해설

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

Reader retry protocol

seqlock.rst:7-37

Sequence counter는 reader가 lock을 잡지 않고 일관된 snapshot을 얻는 reader-writer mechanism입니다. Writer가 드문 system time 같은 데이터에 적합합니다. Reader는 시작 counter와 종료 counter가 같은 짝수인지 확인하고, 다르거나 홀수이면 복사한 값을 버리고 다시 읽습니다.

Counter 상태의미Reader 동작
짝수 N완료된 안정 상태데이터를 복사하고 종료 시 N인지 확인
홀수 N+1writer가 갱신 중완료될 때까지 재시도
짝수 N+2읽는 동안 한 번의 write 완료기존 snapshot을 버리고 재시도

Writer는 시작과 끝에서 각각 counter를 증가시킵니다. Write-side critical section이 reader에 의해 interrupt된 채 counter가 홀수로 남으면 reader가 writer의 재실행을 기다리며 spin합니다. 특히 real-time reader가 writer를 영원히 선점하면 livelock이 되므로 writer context는 reader에 의해 중단되지 않도록 설계해야 합니다.

보호 데이터에 pointer가 들어 있고 writer가 그 object를 해제할 수 있다면 일반 seqcount를 사용할 수 없습니다. Reader가 pointer를 따라가는 동안 writer가 lifetime을 끝낼 수 있기 때문입니다.

Raw seqcount_t

seqlock.rst:41-90

seqcount_t 자체는 여러 writer를 직렬화하지 않습니다. 외부 lock으로 한 번에 하나의 writer만 진입하게 해야 하고 write section 동안 preemption을 막아야 합니다. Reader가 hardirq 또는 softirq에서 실행될 수 있으면 writer는 각각 IRQ 또는 bottom half도 막아야 합니다.

seqcount_t foo_seqcount;
seqcount_init(&foo_seqcount);

/* 외부 lock으로 writer 직렬화, preemption disabled */
write_seqcount_begin(&foo_seqcount);
foo.a = new_a;
foo.b = new_b;
write_seqcount_end(&foo_seqcount);

/* reader */
do {
    seq = read_seqcount_begin(&foo_seqcount);
    a = foo.a;
    b = foo.b;
} while (read_seqcount_retry(&foo_seqcount, seq));

Reader가 복사해야 할 데이터는 begin과 retry 사이에서 모두 읽어야 합니다. Retry가 false로 확인된 뒤 원본 구조체를 다시 참조하면 검증되지 않은 새 state가 섞입니다.

Lock 연계형 seqcount

seqlock.rst:94-141

seqcount_LOCKNAME_t는 writer 직렬화에 사용하는 lock pointer를 초기화 시 연결합니다. Lockdep이 켜져 있으면 write_seqcount_begin()에서 그 lock을 실제로 보유했는지 검증합니다. Lockdep이 꺼진 build에서는 연계 정보가 storage나 runtime overhead를 만들지 않습니다.

  • seqcount_spinlock_t
  • seqcount_raw_spinlock_t
  • seqcount_rwlock_t
  • seqcount_mutex_t
  • seqcount_ww_mutex_t
spinlock_t lock;
seqcount_spinlock_t seq;

spin_lock_init(&lock);
seqcount_spinlock_init(&seq, &lock);

spin_lock(&lock);
write_seqcount_begin(&seq);
/* update */
write_seqcount_end(&seq);
spin_unlock(&lock);

seqcount_latch_t와 NMI reader

seqlock.rst:146-158

seqcount_latch_t는 보호 데이터 복사본 두 개를 두고 embedded counter의 짝수·홀수 값으로 reader가 볼 copy를 전환하는 multiversion mechanism입니다. Reader가 write-side critical section 자체를 interrupt할 수 있어도 writer가 수정 중인 copy가 아닌 다른 copy를 읽을 수 있습니다.

대표 사용처는 NMI handler처럼 writer가 reader의 interrupt를 완전히 막을 수 없는 경우입니다. Memory 사용량과 두 copy 갱신 순서를 부담하는 대신 self-interrupt 상황에서도 안전한 snapshot을 제공합니다.

seqlock_t writer와 기본 reader

seqlock.rst:163-207

seqlock_t는 seqcount_t와 writer용 spinlock을 하나로 묶습니다. Writer serialization과 non-preemptibility를 primitive가 함께 제공합니다. Reader가 hardirq나 softirq에서 호출될 수 있으면 writer 쪽에서 IRQ 또는 bottom half를 disable하는 variant를 선택합니다.

static DEFINE_SEQLOCK(foo_seqlock);

write_seqlock(&foo_seqlock);
foo.a = new_a;
foo.b = new_b;
write_sequnlock(&foo_seqlock);

do {
    seq = read_seqbegin(&foo_seqlock);
    a = foo.a;
    b = foo.b;
} while (read_seqretry(&foo_seqlock, seq));

정상 sequence reader는 writer를 막지 않습니다. 대신 writer가 겹치면 필요한 횟수만큼 retry합니다. Read section이 길거나 write burst가 심하면 반복 비용이 커질 수 있으므로 복사 범위를 최소화해야 합니다.

Locking reader와 conditional reader

seqlock.rst:208-241

read_seqlock_excl()은 writer 또는 다른 locking reader와 배타적으로 동작합니다. rwlock의 shared read lock과 달리 locking reader도 한 명만 진입합니다. 재시도 없이 반드시 한 번에 읽어야 하는 느린 fallback에 사용할 수 있습니다.

int seq = 0; /* 짝수면 lockless 시도 */

do {
    read_seqbegin_or_lock(&foo_seqlock, &seq);
    snapshot = foo;
} while (need_seqretry(&foo_seqlock, seq));
done_seqretry(&foo_seqlock, seq);

Conditional reader는 처음에는 짝수 marker로 lockless read를 시도합니다. 실패하여 홀수 marker를 받으면 다음 반복을 locking read로 전환합니다. Write activity 급증으로 lockless reader가 계속 retry하는 starvation을 제한하는 방식입니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 ======================================
4 Sequence counters and sequential locks
5 ======================================
6
7 Introduction
8 ============
9
10 Sequence counters are a reader-writer consistency mechanism with
11 lockless readers (read-only retry loops), and no writer starvation. They
12 are used for data that's rarely written to (e.g. system time), where the
13 reader wants a consistent set of information and is willing to retry if
14 that information changes.
15
16 A data set is consistent when the sequence count at the beginning of the
17 read side critical section is even and the same sequence count value is
18 read again at the end of the critical section. The data in the set must
19 be copied out inside the read side critical section. If the sequence
20 count has changed between the start and the end of the critical section,
21 the reader must retry.
22
23 Writers increment the sequence count at the start and the end of their
24 critical section. After starting the critical section the sequence count
25 is odd and indicates to the readers that an update is in progress. At
26 the end of the write side critical section the sequence count becomes
27 even again which lets readers make progress.
28
29 A sequence counter write side critical section must never be preempted
30 or interrupted by read side sections. Otherwise the reader will spin for
31 the entire scheduler tick due to the odd sequence count value and the
32 interrupted writer. If that reader belongs to a real-time scheduling
33 class, it can spin forever and the kernel will livelock.
34
35 This mechanism cannot be used if the protected data contains pointers,
36 as the writer can invalidate a pointer that the reader is following.
37
38
39 .. _seqcount_t:
40
41 Sequence counters (``seqcount_t``)
42 ==================================
43
44 This is the raw counting mechanism, which does not protect against
45 multiple writers. Write side critical sections must thus be serialized
46 by an external lock.
47
48 If the write serialization primitive is not implicitly disabling
49 preemption, preemption must be explicitly disabled before entering the
50 write side section. If the read section can be invoked from hardirq or
51 softirq contexts, interrupts or bottom halves must also be respectively
52 disabled before entering the write section.
53
54 If it's desired to automatically handle the sequence counter
55 requirements of writer serialization and non-preemptibility, use
56 :ref:`seqlock_t` instead.
57
58 Initialization::
59
60 /* dynamic */
61 seqcount_t foo_seqcount;
62 seqcount_init(&foo_seqcount);
63
64 /* static */
65 static seqcount_t foo_seqcount = SEQCNT_ZERO(foo_seqcount);
66
67 /* C99 struct init */
68 struct {
69 .seq = SEQCNT_ZERO(foo.seq),
70 } foo;
71
72 Write path::
73
74 /* Serialized context with disabled preemption */
75
76 write_seqcount_begin(&foo_seqcount);
77
78 /* ... [[write-side critical section]] ... */
79
80 write_seqcount_end(&foo_seqcount);
81
82 Read path::
83
84 do {
85 seq = read_seqcount_begin(&foo_seqcount);
86
87 /* ... [[read-side critical section]] ... */
88
89 } while (read_seqcount_retry(&foo_seqcount, seq));
90
91
92 .. _seqcount_locktype_t:
93
94 Sequence counters with associated locks (``seqcount_LOCKNAME_t``)
95 -----------------------------------------------------------------
96
97 As discussed at :ref:`seqcount_t`, sequence count write side critical
98 sections must be serialized and non-preemptible. This variant of
99 sequence counters associate the lock used for writer serialization at
100 initialization time, which enables lockdep to validate that the write
101 side critical sections are properly serialized.
102
103 This lock association is a NOOP if lockdep is disabled and has neither
104 storage nor runtime overhead. If lockdep is enabled, the lock pointer is
105 stored in struct seqcount and lockdep's "lock is held" assertions are
106 injected at the beginning of the write side critical section to validate
107 that it is properly protected.
108
109 For lock types which do not implicitly disable preemption, preemption
110 protection is enforced in the write side function.
111
112 The following sequence counters with associated locks are defined:
113
114 - ``seqcount_spinlock_t``
115 - ``seqcount_raw_spinlock_t``
116 - ``seqcount_rwlock_t``
117 - ``seqcount_mutex_t``
118 - ``seqcount_ww_mutex_t``
119
120 The sequence counter read and write APIs can take either a plain
121 seqcount_t or any of the seqcount_LOCKNAME_t variants above.
122
123 Initialization (replace "LOCKNAME" with one of the supported locks)::
124
125 /* dynamic */
126 seqcount_LOCKNAME_t foo_seqcount;
127 seqcount_LOCKNAME_init(&foo_seqcount, &lock);
128
129 /* static */
130 static seqcount_LOCKNAME_t foo_seqcount =
131 SEQCNT_LOCKNAME_ZERO(foo_seqcount, &lock);
132
133 /* C99 struct init */
134 struct {
135 .seq = SEQCNT_LOCKNAME_ZERO(foo.seq, &lock),
136 } foo;
137
138 Write path: same as in :ref:`seqcount_t`, while running from a context
139 with the associated write serialization lock acquired.
140
141 Read path: same as in :ref:`seqcount_t`.
142
143
144 .. _seqcount_latch_t:
145
146 Latch sequence counters (``seqcount_latch_t``)
147 ----------------------------------------------
148
149 Latch sequence counters are a multiversion concurrency control mechanism
150 where the embedded seqcount_t counter even/odd value is used to switch
151 between two copies of protected data. This allows the sequence counter
152 read path to safely interrupt its own write side critical section.
153
154 Use seqcount_latch_t when the write side sections cannot be protected
155 from interruption by readers. This is typically the case when the read
156 side can be invoked from NMI handlers.
157
158 Check `write_seqcount_latch()` for more information.
159
160
161 .. _seqlock_t:
162
163 Sequential locks (``seqlock_t``)
164 ================================
165
166 This contains the :ref:`seqcount_t` mechanism earlier discussed, plus an
167 embedded spinlock for writer serialization and non-preemptibility.
168
169 If the read side section can be invoked from hardirq or softirq context,
170 use the write side function variants which disable interrupts or bottom
171 halves respectively.
172
173 Initialization::
174
175 /* dynamic */
176 seqlock_t foo_seqlock;
177 seqlock_init(&foo_seqlock);
178
179 /* static */
180 static DEFINE_SEQLOCK(foo_seqlock);
181
182 /* C99 struct init */
183 struct {
184 .seql = __SEQLOCK_UNLOCKED(foo.seql)
185 } foo;
186
187 Write path::
188
189 write_seqlock(&foo_seqlock);
190
191 /* ... [[write-side critical section]] ... */
192
193 write_sequnlock(&foo_seqlock);
194
195 Read path, three categories:
196
197 1. Normal Sequence readers which never block a writer but they must
198 retry if a writer is in progress by detecting change in the sequence
199 number. Writers do not wait for a sequence reader::
200
201 do {
202 seq = read_seqbegin(&foo_seqlock);
203
204 /* ... [[read-side critical section]] ... */
205
206 } while (read_seqretry(&foo_seqlock, seq));
207
208 2. Locking readers which will wait if a writer or another locking reader
209 is in progress. A locking reader in progress will also block a writer
210 from entering its critical section. This read lock is
211 exclusive. Unlike rwlock_t, only one locking reader can acquire it::
212
213 read_seqlock_excl(&foo_seqlock);
214
215 /* ... [[read-side critical section]] ... */
216
217 read_sequnlock_excl(&foo_seqlock);
218
219 3. Conditional lockless reader (as in 1), or locking reader (as in 2),
220 according to a passed marker. This is used to avoid lockless readers
221 starvation (too much retry loops) in case of a sharp spike in write
222 activity. First, a lockless read is tried (even marker passed). If
223 that trial fails (odd sequence counter is returned, which is used as
224 the next iteration marker), the lockless read is transformed to a
225 full locking read and no retry loop is necessary::
226
227 /* marker; even initialization */
228 int seq = 0;
229 do {
230 read_seqbegin_or_lock(&foo_seqlock, &seq);
231
232 /* ... [[read-side critical section]] ... */
233
234 } while (need_seqretry(&foo_seqlock, seq));
235 done_seqretry(&foo_seqlock, seq);
236
237
238 API documentation
239 =================
240
241 .. kernel-doc:: include/linux/seqlock.h
242

3. 한국어 전문 번역

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

Sequence counter의 consistency protocol

1-27

Sequence counter는 lockless reader가 read-only retry loop를 돌고 writer starvation이 없는 reader-writer consistency mechanism이다. System time처럼 write가 드문 data에서 reader가 일관된 snapshot을 원하고 update와 겹치면 다시 읽을 수 있을 때 사용한다.

Reader는 critical section 시작에서 sequence count가 even인지 확인하고 data 전체를 section 안에서 복사한 뒤 끝에서 같은 count를 다시 읽는다. Count가 바뀌었으면 snapshot이 update와 겹친 것이므로 재시도한다.

Writer는 critical section 시작과 끝에서 sequence count를 증가시킨다. Update 중에는 odd라 reader가 진행하지 않고, 끝나면 다시 even이 되어 reader가 consistent snapshot을 얻을 수 있다.

Writer non-preemptibility와 pointer 금지

29-36

Sequence counter write-side critical section은 read-side section에 의해 preempt되거나 interrupt되어서는 안 된다. Odd count를 남긴 writer가 중단되면 reader는 scheduler tick 전체를 spin한다. Real-time reader가 writer보다 높은 priority라면 writer가 다시 실행되지 못해 kernel livelock이 될 수 있다.

보호 data가 pointer를 포함하면 writer가 reader가 따라가는 pointer를 invalidate할 수 있으므로 이 mechanism을 사용할 수 없다.

Raw seqcount_t와 외부 writer lock

39-56

seqcount_t는 raw counting mechanism이며 여러 writer를 직렬화하지 않는다. Write-side critical section은 반드시 외부 lock으로 serialize해야 한다.

그 lock이 preemption을 암묵적으로 disable하지 않으면 write section 전에 명시적으로 disable한다. Reader가 hardirq 또는 softirq에서 실행될 수 있다면 writer는 각각 interrupt 또는 bottom half도 disable해야 한다. Writer serialization과 non-preemptibility를 자동으로 다루려면 seqlock_t를 사용한다.

seqcount_t 초기화와 read/write path

58-89
/* dynamic */
seqcount_t foo_seqcount;
seqcount_init(&foo_seqcount);

/* static */
static seqcount_t foo_seqcount = SEQCNT_ZERO(foo_seqcount);

/* C99 struct init */
struct {
    .seq = SEQCNT_ZERO(foo.seq),
} foo;

/* writer: serialized, preemption disabled */
write_seqcount_begin(&foo_seqcount);
/* update snapshot fields */
write_seqcount_end(&foo_seqcount);

/* reader */
do {
    seq = read_seqcount_begin(&foo_seqcount);
    /* copy all protected fields */
} while (read_seqcount_retry(&foo_seqcount, seq));

seqcount_LOCKNAME_t와 lockdep 연계

92-121

seqcount_LOCKNAME_t는 writer serialization에 쓰는 lock을 초기화 시 sequence counter와 연결한다. Lockdep은 write-side critical section이 올바른 lock으로 보호되는지 검증할 수 있다.

Lockdep이 꺼져 있으면 association은 storage와 runtime overhead가 없는 NOOP다. 켜져 있으면 struct seqcount에 lock pointer를 저장하고 write section 시작에 lock-held assertion을 삽입한다. Lock type이 preemption을 암묵적으로 막지 않으면 write function이 이를 보장한다.

  • seqcount_spinlock_t
  • seqcount_raw_spinlock_t
  • seqcount_rwlock_t
  • seqcount_mutex_t
  • seqcount_ww_mutex_t

Sequence counter read/write API는 plain seqcount_t와 모든 seqcount_LOCKNAME_t variant를 같은 방식으로 받는다.

Associated-lock sequence counter 초기화

123-141
/* dynamic */
seqcount_LOCKNAME_t foo_seqcount;
seqcount_LOCKNAME_init(&foo_seqcount, &lock);

/* static */
static seqcount_LOCKNAME_t foo_seqcount =
    SEQCNT_LOCKNAME_ZERO(foo_seqcount, &lock);

/* C99 struct init */
struct {
    .seq = SEQCNT_LOCKNAME_ZERO(foo.seq, &lock),
} foo;

LOCKNAME은 지원하는 lock 이름으로 바꾼다. Write path는 associated serialization lock을 획득한 context에서 plain seqcount_t와 같은 API를 쓰고 read path도 동일하다.

seqcount_latch_t와 NMI reader

144-158

seqcount_latch_t는 embedded seqcount_t의 even/odd 값을 이용해 보호 data 사본 두 개 사이를 전환하는 multiversion concurrency-control mechanism이다. Reader가 자기 write-side critical section을 interrupt해도 안전하게 읽을 수 있다.

NMI handler에서 read side가 호출되는 경우처럼 reader interruption으로부터 writer를 보호할 수 없을 때 사용한다. 자세한 protocol은 write_seqcount_latch() 문서를 확인한다.

seqlock_t의 embedded spinlock

161-185

seqlock_t는 seqcount_t mechanism에 writer serialization과 non-preemptibility를 위한 spinlock을 embedded한다. Reader가 hardirq나 softirq에서 실행될 수 있다면 writer는 각각 interrupt 또는 bottom half를 disable하는 variant를 사용한다.

/* dynamic */
seqlock_t foo_seqlock;
seqlock_init(&foo_seqlock);

/* static */
static DEFINE_SEQLOCK(foo_seqlock);

/* C99 struct init */
struct {
    .seql = __SEQLOCK_UNLOCKED(foo.seql)
} foo;

seqlock_t write path

187-193
write_seqlock(&foo_seqlock);
/* write-side critical section */
write_sequnlock(&foo_seqlock);

Reader 1: lockless retry loop

195-206

일반 sequence reader는 writer를 block하지 않는다. Writer가 진행 중이거나 sequence가 바뀌면 재시도하며 writer는 reader를 기다리지 않는다.

do {
    seq = read_seqbegin(&foo_seqlock);
    /* read-side critical section */
} while (read_seqretry(&foo_seqlock, seq));

Reader 2: exclusive locking reader

208-217

Locking reader는 writer 또는 다른 locking reader가 진행 중이면 기다리고, 자신이 진행 중일 때 writer 진입도 막는다. rwlock_t와 달리 reader 사이에도 exclusive라 한 번에 하나만 획득한다.

read_seqlock_excl(&foo_seqlock);
/* read-side critical section */
read_sequnlock_excl(&foo_seqlock);

Reader 3: retry 폭증 시 locking으로 전환

219-235

Write activity가 급증해 lockless reader가 계속 retry하며 starvation되는 것을 막는 hybrid 방식이다. Even marker로 lockless read를 먼저 시도한다. 실패해 odd sequence가 다음 marker가 되면 다음 iteration은 full locking read로 전환되어 retry가 필요 없다.

int seq = 0; /* even marker */
do {
    read_seqbegin_or_lock(&foo_seqlock, &seq);
    /* read-side critical section */
} while (need_seqretry(&foo_seqlock, seq));
done_seqretry(&foo_seqlock, seq);

API 원본 위치

238-241

Sequence counter와 sequential lock 전체 kernel-doc은 include/linux/seqlock.h에서 생성된다.