요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
=====================
Theory of operation
=====================
:Author: Sebastian Andrzej Siewior <[email protected]>
Preface
=======
PREEMPT_RT transforms the Linux kernel into a real-time kernel. It achieves
this by replacing locking primitives, such as spinlock_t, with a preemptible
and priority-inheritance aware implementation known as rtmutex, and by enforcing
the use of threaded interrupts. As a result, the kernel becomes fully
preemptible, with the exception of a few critical code paths, including entry
code, the scheduler, and low-level interrupt handling routines.
This transformation places the majority of kernel execution contexts under the
control of the scheduler and significantly increasing the number of preemption
points. Consequently, it reduces the latency between a high-priority task
becoming runnable and its actual execution on the CPU.
Scheduling
==========
The core principles of Linux scheduling and the associated user-space API are
documented in the man page sched(7)
`sched(7) <https://man7.org/linux/man-pages/man7/sched.7.html>`_.
By default, the Linux kernel uses the SCHED_OTHER scheduling policy. Under
this policy, a task is preempted when the scheduler determines that it has
consumed a fair share of CPU time relative to other runnable tasks. However,
the policy does not guarantee immediate preemption when a new SCHED_OTHER task
becomes runnable. The currently running task may continue executing.
This behavior differs from that of real-time scheduling policies such as
SCHED_FIFO. When a task with a real-time policy becomes runnable, the
scheduler immediately selects it for execution if it has a higher priority than
the currently running task. The task continues to run until it voluntarily
yields the CPU, typically by blocking on an event.
Sleeping spin locks
===================
The various lock types and their behavior under real-time configurations are
described in detail in Documentation/locking/locktypes.rst.
In a non-PREEMPT_RT configuration, a spinlock_t is acquired by first disabling
preemption and then actively spinning until the lock becomes available. Once
the lock is released, preemption is enabled. From a real-time perspective,
this approach is undesirable because disabling preemption prevents the
scheduler from switching to a higher-priority task, potentially increasing
latency.
To address this, PREEMPT_RT replaces spinning locks with sleeping spin locks
that do not disable preemption. On PREEMPT_RT, spinlock_t is implemented using
rtmutex. Instead of spinning, a task attempting to acquire a contended lock
disables CPU migration, donates its priority to the lock owner (priority
inheritance), and voluntarily schedules out while waiting for the lock to
become available.
Disabling CPU migration provides the same effect as disabling preemption, while
still allowing preemption and ensuring that the task continues to run on the
same CPU while holding a sleeping lock.
Priority inheritance
====================
Lock types such as spinlock_t and mutex_t in a PREEMPT_RT enabled kernel are
implemented on top of rtmutex, which provides support for priority inheritance
(PI). When a task blocks on such a lock, the PI mechanism temporarily
propagates the blocked task’s scheduling parameters to the lock owner.
For example, if a SCHED_FIFO task A blocks on a lock currently held by a
SCHED_OTHER task B, task A’s scheduling policy and priority are temporarily
inherited by task B. After this inheritance, task A is put to sleep while
waiting for the lock, and task B effectively becomes the highest-priority task
in the system. This allows B to continue executing, make progress, and
eventually release the lock.
Once B releases the lock, it reverts to its original scheduling parameters, and
task A can resume execution.
Threaded interrupts
===================
Interrupt handlers are another source of code that executes with preemption
disabled and outside the control of the scheduler. To bring interrupt handling
under scheduler control, PREEMPT_RT enforces threaded interrupt handlers.
With forced threading, interrupt handling is split into two stages. The first
stage, the primary handler, is executed in IRQ context with interrupts disabled.
Its sole responsibility is to wake the associated threaded handler. The second
stage, the threaded handler, is the function passed to request_irq() as the
interrupt handler. It runs in process context, scheduled by the kernel.
From waking the interrupt thread until threaded handling is completed, the
interrupt source is masked in the interrupt controller. This ensures that the
device interrupt remains pending but does not retrigger the CPU, allowing the
system to exit IRQ context and handle the interrupt in a scheduled thread.
By default, the threaded handler executes with the SCHED_FIFO scheduling policy
and a priority of 50 (MAX_RT_PRIO / 2), which is midway between the minimum and
maximum real-time priorities.
If the threaded interrupt handler raises any soft interrupts during its
execution, those soft interrupt routines are invoked after the threaded handler
completes, within the same thread. Preemption remains enabled during the
execution of the soft interrupt handler.
Summary
=======
By using sleeping locks and forced-threaded interrupts, PREEMPT_RT
significantly reduces sections of code where interrupts or preemption is
disabled, allowing the scheduler to preempt the current execution context and
switch to a higher-priority task.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
PREEMPT_RT 동작 이론
1-23SPDX 라이선스 식별자는 GPL-2.0입니다.
동작 이론
저자: Sebastian Andrzej Siewior <[email protected]>
서문
`PREEMPT_RT`는 Linux kernel을 real-time kernel로 변환합니다. `spinlock_t` 같은 locking primitive를 preempt 가능하고 priority inheritance를 인식하는 `rtmutex` 구현으로 바꾸고 threaded interrupt 사용을 강제하여 이를 달성합니다.
그 결과 entry code, scheduler, low-level interrupt handling routine 같은 일부 critical code path를 제외한 kernel 전체를 preempt할 수 있게 됩니다.
이 변환은 kernel execution context 대부분을 scheduler의 제어 아래 두고 preemption point 수를 크게 늘립니다. 따라서 높은 priority task가 runnable 상태가 된 시점부터 CPU에서 실제로 실행될 때까지의 latency를 줄입니다.
Scheduling policy
24-41Scheduling
Linux scheduling의 핵심 원리와 관련 user-space API는 `sched(7)` man page에 문서화되어 있습니다.
기본적으로 Linux kernel은 `SCHED_OTHER` scheduling policy를 사용합니다. 이 policy에서는 scheduler가 다른 runnable task와 비교해 현재 task가 공정한 CPU time 몫을 소비했다고 판단할 때 task를 preempt합니다.
그러나 새로운 `SCHED_OTHER` task가 runnable이 되는 즉시 preemption한다고 보장하지 않으므로 현재 task가 계속 실행될 수 있습니다.
이 동작은 `SCHED_FIFO` 같은 real-time scheduling policy와 다릅니다. Real-time policy를 사용하는 task가 runnable이 되고 현재 task보다 priority가 높으면 scheduler가 즉시 그 task를 실행 대상으로 선택합니다. 선택된 task는 보통 event를 기다리며 block하는 방식으로 CPU를 자발적으로 양보할 때까지 계속 실행합니다.
Sleep 가능한 spin lock
42-64Sleep 가능한 spin lock
여러 lock type과 real-time configuration에서의 동작은 `Documentation/locking/locktypes.rst`에 자세히 설명되어 있습니다.
Non-`PREEMPT_RT` configuration에서 `spinlock_t`를 얻을 때는 먼저 preemption을 끄고 lock을 사용할 수 있을 때까지 능동적으로 spin합니다. Lock이 해제되면 preemption을 다시 켭니다.
Real-time 관점에서 이 방식은 바람직하지 않습니다. Preemption을 끄면 scheduler가 더 높은 priority task로 전환할 수 없어 latency가 늘어날 수 있기 때문입니다.
이를 해결하기 위해 `PREEMPT_RT`는 spinning lock을 preemption을 끄지 않는 sleep 가능한 spin lock으로 바꿉니다. `PREEMPT_RT`의 `spinlock_t`는 `rtmutex`로 구현됩니다.
경합 중인 lock을 얻으려는 task는 spin하는 대신 CPU migration을 끄고 자신의 priority를 lock owner에게 기부하는 priority inheritance를 수행하며, lock을 사용할 수 있을 때까지 자발적으로 schedule out합니다.
CPU migration을 비활성화하면 preemption을 끄는 것과 같은 CPU 고정 효과를 얻으면서도 preemption은 허용할 수 있습니다. 따라서 task는 sleep 가능한 lock을 보유한 동안 같은 CPU에서 계속 실행됩니다.
Priority inheritance
65-82Priority inheritance
`PREEMPT_RT` kernel의 `spinlock_t`와 `mutex_t` 같은 lock type은 priority inheritance(PI)를 지원하는 `rtmutex` 위에 구현됩니다. Task가 이 lock에서 block하면 PI 메커니즘이 block된 task의 scheduling parameter를 lock owner에게 일시적으로 전파합니다.
예를 들어 `SCHED_FIFO` task A가 `SCHED_OTHER` task B가 보유한 lock에서 block하면 A의 scheduling policy와 priority를 B가 일시적으로 상속합니다. 그 뒤 A는 lock을 기다리며 sleep하고 B는 사실상 system에서 가장 높은 priority task가 됩니다.
이 덕분에 B가 계속 실행하고 진행하여 마침내 lock을 해제할 수 있습니다. B가 lock을 해제하면 원래 scheduling parameter로 돌아가고 task A가 실행을 재개할 수 있습니다.
Threaded interrupt
83-109Threaded interrupt
Interrupt handler도 preemption disabled 상태이며 scheduler 제어 밖에서 실행되는 code의 원천입니다. `PREEMPT_RT`는 interrupt 처리를 scheduler 제어 아래 두기 위해 threaded interrupt handler를 강제합니다.
강제 threading에서는 interrupt 처리를 두 단계로 나눕니다. 첫 단계인 primary handler는 interrupt가 꺼진 IRQ context에서 실행되며, associated threaded handler를 깨우는 일만 담당합니다.
두 번째 단계인 threaded handler는 `request_irq()`에 interrupt handler로 전달한 function입니다. 이 handler는 kernel이 schedule하는 process context에서 실행됩니다.
Interrupt thread를 깨운 시점부터 threaded 처리가 끝날 때까지 interrupt controller에서 interrupt source를 mask합니다. 따라서 device interrupt는 pending 상태로 남지만 CPU를 다시 trigger하지 않으며, system은 IRQ context에서 빠져나와 scheduled thread에서 interrupt를 처리할 수 있습니다.
기본적으로 threaded handler는 `SCHED_FIFO` scheduling policy와 50의 priority, 즉 `MAX_RT_PRIO / 2`로 실행합니다. 이는 real-time priority 최솟값과 최댓값의 중간입니다.
Threaded interrupt handler가 실행 중 soft interrupt를 발생시키면 해당 soft interrupt routine은 threaded handler가 끝난 뒤 같은 thread 안에서 호출됩니다. Soft interrupt handler를 실행하는 동안에도 preemption은 활성화된 상태로 유지됩니다.
요약
110-116요약
`PREEMPT_RT`는 sleep 가능한 lock과 강제 threaded interrupt를 사용하여 interrupt 또는 preemption이 비활성화되는 code section을 크게 줄입니다. 그 결과 scheduler가 현재 execution context를 preempt하고 더 높은 priority task로 전환할 수 있습니다.
요약과 해설
theory.rst:1-116`PREEMPT_RT`의 핵심은 scheduler가 제어할 수 없는 실행 구간을 줄이는 것입니다. 일반 spin lock을 `rtmutex` 기반 sleeping lock으로 바꾸고 interrupt handler 대부분을 scheduled thread로 이동합니다.
Priority inheritance는 높은 priority task가 낮은 priority lock owner를 기다릴 때 owner를 일시적으로 승격하여 priority inversion을 줄이고 lock 해제를 앞당깁니다.
Threaded interrupt는 짧은 primary handler와 process-context threaded handler로 나뉘며, 후자는 기본 `SCHED_FIFO` priority 50으로 실행됩니다. 이 구조가 높은 priority task의 실질적인 wakeup latency를 낮춥니다.