이 이야기의 시작은 Thomas Gleixner가 preemt_count()관련한 패치 [2] 를 올리면서다. 총 13개의 패치를 살펴보면 1번 2번에서 CONFIG_ARCH_NO_PREEMPT이건 아니건 CONFIG_PREEMPT_COUNT가 선택되도록 하고 또한 CONFIG_PREEMPTION 선택과 상관없이 CONFIG_PREEMPT_COUNT가 매번 y가 되도록 바꾼다.
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1161,7 +1161,7 @@ config PROVE_LOCKING
select DEBUG_RWSEMS
select DEBUG_WW_MUTEX_SLOWPATH
select DEBUG_LOCK_ALLOC
- select PREEMPT_COUNT if !ARCH_NO_PREEMPT
+ select PREEMPT_COUNT
select TRACE_IRQFLAGS
default n
help
@@ -1323,7 +1323,6 @@ config DEBUG_ATOMIC_SLEEP
bool "Sleep inside atomic section checking"
select PREEMPT_COUNT
depends on DEBUG_KERNEL
- depends on !ARCH_NO_PREEMPT
help
If you say Y here, various routines which may sleep will become very
noisy if they are called inside atomic sections: when a spinlock is
--- a/kernel/Kconfig.preempt
+++ b/kernel/Kconfig.preempt
@@ -75,8 +75,7 @@ config PREEMPT_RT
endchoice
config PREEMPT_COUNT
- bool
+ def_bool y
config PREEMPTION
bool
- select PREEMPT_COUNT
이후 3~12번까지 PREEMPT_COUNT와 관련한 코드를 모두 삭제한 후에 마지막 13번에서 CONFIG_PREEMPT_COUNT를 없애버린다.
--- a/kernel/Kconfig.preempt
+++ b/kernel/Kconfig.preempt
@@ -74,8 +74,5 @@ config PREEMPT_RT
endchoice
-config PREEMPT_COUNT
- def_bool y
-
config PREEMPTION
bool
결국 간단히 말하면 PREEMPT_COUNT를 선택 옵션으로 두지 않고 디폴트 on시키겠다는 뜻.
gfp = preemptible() ? GFP_KERNEL : GFP_ATOMIC;
이런 패치를 만든 이유는 시스템에 따라 CONFIG_PREEMPT_COUNT=n 이면 preemptible()이 항상 false를 리턴하여 시스템 전체가 GFP_ATOMIC으로 allocation을 하는 경우가 발생해서라고 밝히고 있다.
첫번째 이야기: preempt_count()
리눅스와 같은 멀티태스킹 시스템에서는 더 높은 우선순위를 가진 다른 프로세스나 인터럽트같은 이벤트가 현재 돌고 있는 프로세스를 언제든 선점할 수 있다. 그러므로 커널은 현재 돌고있는 프로세스가 선점 가능한지 아니면 불가능한지에 대한 정보를 트래킹할 필요가 있는데 이런 일을 하는 것이 preempt_count이고 preempt_count() 함수로 값을 확인한다.
static __always_inline int preempt_count(void)
{
return READ_ONCE(current_thread_info()->preempt_count);
}
preempt_count는 여러개의 비트필드로 나뉘어 정보를 담고 있는데, reschedule이 필요한지를 알려주는 bit, Non-maskable 인터럽트 카운트, 하드웨어 인터럽트 카운트, 소프트웨어 인터럽트 카운트, preempt-disable 카운트가 있다.
Preempt-disable count는 LSB 8비트로 preempt_disable()함수로 카운터값을 증가 preempt_enable()로 감소시킨다.
#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
#define preempt_enable() \
do { \
barrier(); \
if (unlikely(preempt_count_dec_and_test())) \
__preempt_schedule(); \
} while (0)
preempt_count()가 0이 아니라는 뜻은 현재 프로세스가 선점 불가능하다는 것이고, 이것은 명시적으로 선점불가능하게 했거나 또는 현재 인터럽트 처리구간에 있다라고 할 수 있다.
"reschedule needed" 비트는 더 우선순위가 높은 프로세스에게 cpu를 양보할 필요가 있다라는 뜻이고 당연하게도 preempt_count()가 0일때 이 비트가 설정될 수가 없다. 0이면 선점가능하므로 reschedule이 발생하기 때문.
두번째 이야기: 선점 기능이 없는 옛날 옛적 시절 시스템들
preemption이 초기 리눅스 커널 시절부터 있던 것이 아니었다. 내 기억이 맞다면 버전 2.6에서 선점커널 기능이 들어갔던 것 같다. 현재 코드는 과거의 유물 코드들과 같이 존재해서 선점기능을 제공하지 않는 아키텍쳐의 코드들은 CONFIG_PREEMPT_COUNT가 n 이고 이럴 경우 모든 preemption 관련 함수들이 false를 리턴하거나 아무것도 하지 않게 된다. preempt_count()는 항상 0을 리턴하고 preemptible()은 항상 false를 리턴한다. 선점기능이 없는 시스템이 preemption관련 정보를 트래킹하는데 드는 오버헤드를 줄일 수 있고 코드사이즈가 줄어드니 이렇게 하는 것이 앞뒤가 맞고 문제가 없어 보인다. 그러나, in_atomic()함수를 사용하는데 있어 문제가 발생한다.
in_atomic()은 프로세스가 atomic구간에 있는지 확인하는 함수다.
#define in_atomic() (preempt_count() != 0)
(보통의) 선점기능을 사용하는 시스템에서는 preempt_disable()을 호출한 후 in_atomic()을 호출하면 (preempt_count() != 0) 이 참이 되고 in_atomic()이 true를 리턴한다. 하지만, 선점기능을 끈 시스템에서는 preempt_disable()을 호출하더라도 아무 기능을 하지 않으므로 in_atomic()은 항상 false를 리턴한다. 스핀락 구간이면 실제로 atomic구간이라 in_atomic()이 true를 리턴해야 하지만 false를 리턴하는 문제가 발생하는 것이다.
Thomas는 패치에서 개발자들이 이 문제의 원인을 정확히 이해하지 못해서 다양한 방식으로 workaround를 만들어 해결하려고 한것이 또다른 골칫거리라고 이야기한다. 한편 선점기능이 없는 시스템에서는 Thomas의 패치로 선점기능 코드가 추가되어 커널사이즈가 늘고 속도가 전보다 느려질 것을 우려할 수 있는데, 이런 문제를 누가 제기할까봐 미리 테스트를 했나보다. "did not reveal any measurable impact"
별차이가 없다고...
[4] 리누스는 별 차이 없다는 말이 믿기 어려웠을 것 같다. 선점기능이 없을때 스핀락이 더 빨랐다고 이야기를 하는거 보면. [5] 토마스가 preemptible()을 사용하는 몇가지 코드를 예로 들면서 (x86 GP handler와 scatterlist) 방어에 나섰는데 그게 먹혔나보다. [6] 코드 자체는 더 심플해진 건 인정한다고.
세번째 이야기: preempt_count 사용
앞으로의 방향성에 관련한 이야기라 정보가 될 만한 것은 별로 없고 무엇이 옳다 무엇이 그러다 식의 토의만 있다. 개인적으로 이런 토의는 좋아하지 않지만 봐야할 것이라는 것은 인정. 원문 보길.
네번째 이야기: 하이 메모리에 대한 의문
선점이 가능한지 불가능한지 상황에 따라 메모리 allocation모드를 바꾸는 crypto 코드에 대해서 [11] Ard Biesheuvel이 코드의 진짜 목적은 가능한 kmap_atomic()을 사용하는 상황을 피하기 위함이라고 이유를 설명한다.
chacha20poly1305_crypt_sg_inplace(), which does
flags = SG_MITER_TO_SG;
if (!preemptible())
flags |= SG_MITER_ATOMIC;
kmap()과 kmap_atomic()은 하이메모리가 설정된 시스템에서 사용하는 api인데 [12], 간단히 요약해서 말하면, 32bit 시스템의 주소공간은 4GB가 한계인데 이를 커널영역과 유저영역으로 나눌 경우 대개 1G:3G 또는 2G:2G로 나누어 썼었다. 1:3일 경우 커널 영역 1G안에 모든 걸 매핑 시켜야 하는데 만약 시스템 메모리가 2G이거나 3G이면 커널 주소공간안에 물리메모리를 1:1 매핑하는 것은 불가능하다. 그러므로, 최대한 물리메모리를 1:1로 매핑하고 남은 영역은 하이메모리 영역으로 두고 필요할 때 마다 kmap()또는 kmap_atomic()으로 가용한 커널 주소공간을 임시로 하이메모리인 물리주소와 매핑을 시켜 사용했다. 개인적으로 arm같은 32bit 시스템을 사용하지 않은지 꽤 되었지만 여전히 다양한 32bit 시스템에선 이 하이메모리 기능을 사용하고 있다.
include/linux/highmem.h:
static inline void *kmap_atomic(struct page *page)
{
preempt_disable();
pagefault_disable();
return page_address(page);
}
내 생각에도 실제로 kmap()보다는 kmap_atomic()을 개발자들이 더 선호하는 것은 맞는 것 같다. Ard 본인은 kmap()을 최대한 더 쓰고 kmap_atomic()을 최대한 덜 쓰려고 preemptible()을 사용했다라고 설명한다. 추가적으로 kmap()을 더 선호하는 이유에 대해서 밝혔는데 위 kmap_atomic()함수에서 보듯이 64bit 시스템에서도 preempt_disable()을 불러서 선점불가능으로 만들어 버려 WireGuard VPN같은 코드가 대부분 선점 불가능 상태로 돌아가게 된다는 것이다.
토마스가 kmap_atomic()을 선점가능하게 만들어버리자(rt linux에서는 이미 이렇게 하고 있다고 한다.)는 제안[14]에 리누스는 아예 하이메모리 디자인을 없애버릴 생각이라고 응답[15].
"It's not that 32-bit is irrelevant, it's that 32-bit with large amounts of memory is irrelevant"
링크
[1] 2020/09/18 lwn.net 원문: "Four short stories about preempt_count()" lwn.net/Articles/831678/
[2] Thomas Gleixner's patch: lwn.net/ml/linux-mm/20200914204209.256266093@linutronix.de/
[3] preemt_count() code: elixir.bootlin.com/linux/v5.8.9/source/include/asm-generic/preempt.h#L9
[4] Linus Torvalds: lwn.net/ml/linux-mm/CAHk-=win80rdof8Pb=5k6gT9j_v+hz-TQzKPVastZDvBe9RimQ@mail.gmail.com/
[5] Thomas Gleixner: lwn.net/ml/linux-mm/871rj4owfn.fsf@nanos.tec.linutronix.de/
[6] Linus Torvalds: lwn.net/ml/linux-mm/871rj4owfn.fsf@nanos.tec.linutronix.de/
[7] Linus Torvalds: lwn.net/ml/linux-mm/CAHk-=wht7kAeyR5xEW2ORj7m0hibVxZ3t+2ie8vNHLQfdbN2_g@mail.gmail.com/
[8] Daniel Vetter: lwn.net/ml/linux-mm/CAKMK7uHAk9-Vy2cof0ws=DrcD52GHiCDiyHbjLd19CgpBU2rKQ@mail.gmail.com/
[9] Paul McKenney: lwn.net/ml/linux-mm/20200916152956.GV29330@paulmck-ThinkPad-P72/
[10] Linus Torvalds: lwn.net/ml/linux-mm/CAHk-=wjsMycgMHJrCmeetR3r+K5bpSRtmVWfd8iaoQCYd_VYAg@mail.gmail.com/
[11] Ard Biesheuvel: lwn.net/ml/linux-mm/CAMj1kXHrDU50D08TwLfzz2hCK+8+C7KGPF99PphXtsOYZ-ff1g@mail.gmail.com/
[12] An end to high memory?: lwn.net/Articles/813201/
[13] Linus Torvalds: lwn.net/ml/linux-mm/CAHk-=wir6LZ=4gHt8VDdASv=TmEMjEUONuzbt=s+DyXPCvxaBA@mail.gmail.com/
[14] Thomas Gleixner: lwn.net/ml/linux-mm/87een35woz.fsf@nanos.tec.linutronix.de/
[15] Linus Torvalds: lwn.net/ml/linux-mm/CAHk-=wg_koVS=8bYurGCZ8zs=zDH5cOKVzFdoj4AkUWEW9mKjA@mail.gmail.com/
[16] more story: lore.kernel.org/linux-mm/20200919091751.011116649@linutronix.de/