arch/arm/kernel/head.S (4) – ALT_SMP 와 ALT_UP 의 동작원리
이는 pv_table 과 유사한 패치 개념을 사용하기 때문에 이전 포스팅의 pv_table 을 정확히 이해했다면 수월하게 이해할 수 있습니다.
ALT_SMP 와 ALT_UP 매크로는 SMP 용으로 빌드된 커널이 UP(Uni-processor)에서 실행될 때, SMP 용 명령어를 UP 용으로 패치하여 사용할 수 있도록 한 것입니다.
ALT_ 접두어는 Alternative 를 의미합니다.
SMP 나 UP 중에서 하나 선택하여 사용한다는 의미로 Alternative 를 사용한 것입니다.
그래서, 커널코드를 보면 아시겠지만 ALT_SMP 와 ALT_UP 매크로는 아래와 같이 항상 붙어다닙니다.
ALT_SMP(orr r1, r1, #TTB_FLAGS_SMP) ALT_UP(orr r1, r1, #TTB_FLAGS_UP)
이전 포스팅에서 __pv_stub 매크로는 pv_table section 을 사용한다고 말씀드렸는데, ALT_SMP 와 ALT_UP 매크로는 altsmp section (.init.smpalt)을 이용합니다.
Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .head.text PROGBITS c0008000 008000 0001c0 00 AX 0 0 4 [ 2] .text PROGBITS c00081c0 0081c0 36125c 00 AX 0 0 64 [ 3] .rodata PROGBITS c036a000 36a000 0e7c68 00 A 0 0 64 ... [20] .init.smpalt PROGBITS c04b6a04 4bea04 0001f0 00 A 0 0 4 [21] .init.pv_table PROGBITS c04b6bf4 4bebf4 0002f8 00 A 0 0 1 [22] .init.data PROGBITS c04b6ef0 4beef0 00ea14 00 WA 0 0 8 [23] .data..percpu PROGBITS c04c6000 4ce000 001d00 00 WA 0 0 64 [24] .data PROGBITS c04c8000 4d0000 038260 00 WA 0 0 64 [25] __tracepoint_str PROGBITS c0500260 508260 00000c 00 WA 0 0 4 [26] .bss NOBITS c0500280 50826c 041d18 00 WA 0 0 64
section 정보를 보시면 .init.pv_table section 과 붙어있네요.
그러면, ALT_SMP 와 ALT_UP 매크로를 한번 볼까요 ?
(매크로는 precompile 시에 항상 이를 사용한 자리에 그대로 코드가 붙는다는건 모두 아실 것입니다.)
매크로가 사용한 자리에 그대로 붙어버리므로 실제로는 ALT_SMP 와 ALT_UP 코드는 분리된 것이 아니고 그냥 연속적으로 나오는 코드일 뿐이라는걸 기억해야 합니다.
#ifdef CONFIG_SMP #define ALT_SMP(instr...) \ 9998: instr /* * Note: if you get assembler errors from ALT_UP() when building with * CONFIG_THUMB2_KERNEL, you almost certainly need to use * ALT_SMP( W(instr) ... ) */ #define ALT_UP(instr...) \ .pushsection ".alt.smp.init", "a" ;\ .long 9998b ;\ 9997: instr ;\ .if . - 9997b != 4 ;\ .error "ALT_UP() content must assemble to exactly 4 bytes";\ .endif ;\ .popsection #define ALT_UP_B(label) \ .equ up_b_offset, label - 9998b ;\ .pushsection ".alt.smp.init", "a" ;\ .long 9998b ;\ W(b) . + up_b_offset ;\ .popsection #else #define ALT_SMP(instr...) #define ALT_UP(instr...) instr #define ALT_UP_B(label) b label #endif
위의 코드를 보시면 CONFIG_SMP 가 define 되어 있다면 위쪽의 무언가 복잡한 매크로를 사용하고, 그렇지 않으면 아래의 간단한 매크로를 사용함을 알 수 있습니다.
이는 빌드할 때 CONFIG_SMP 를 config 파일에 정의했다면 위쪽 코드를 사용하고 아니면 아래쪽 코드를 사용한다는 말인데요. CONFIG_SMP 설정을 안했다는 것은 사용자가 UP 용으로 이미지를 빌드한 것이므로 ALT_SMP 에서는 아무것도 하지 않고, ALT_UP 에서 UP 용 instr 만 수행한다는 말입니다.
즉, UP 용으로 빌드된 커널은 수행 시에 ALT_SMP 와 ALT_UP 가 나란히 나와도 ALT_UP 에 주어진 instruction 만 수행한다는 말입니다. 생각해보면 당연한 것입니다.
우리는 SMP 용으로 빌드하고 UP 환경에서 실행하는 것을 가정하고 분석을 해야만 해당 매크로를 이해할 수 있으므로 #ifdef CONFIG_SMP 부분을 분석하도록 합니다.
분석하기 전에 일단 먼저 판단해야 하는 것이 실행시점의 환경이 UP 라는 것을 어떻게 판단할까입니다.
우리는 kernel/head.S 의 __fixup_smp 과정을 통해 UP 인지를 걸러내는 로직을 분석하였었습니다.
__fixup_smp: and r3, r9, #0x000f0000 @ architecture version teq r3, #0x000f0000 @ CPU ID supported? bne __fixup_smp_on_up @ no, assume UP
위 코드를 보면 UP 에서 실행하고 있는 것이 맞다면 __fixup_smp_on_up 을 수행하는 것을 볼 수 있습니다.
__fixup_smp_on_up 이 위쪽에서 살펴보았던 __fixup_pv_table 의 역할과 비슷한 역할을 수행합니다. 이 부분은 아래쪽에서 살펴보겠습니다.
그러면, 매크로를 한 line 씩 살펴보겠습니다.
#define ALT_SMP(instr...) 9998: instr
위는 code section 에 SMP 용 instruction 을 추가하는 것입니다.
#define ALT_UP(instr...) \ .pushsection ".alt.smp.init", "a" ;\ .long 9998b
위에서는 이전의 section 을 section stack 에 push 하고 .alt.smp.init section 을 사용합니다.
altsmp section 에 9998 label instruction 의 주소를 넣어둡니다.
요것이 위쪽에서 code section 에 추가했던 9998 label 의 SMP 용 instruction 이죠.
즉, table entry 의 첫번째 값으로는 SMP 용 명령어의 주소를 넣어두는 것입니다.
9997: instr ;\ .if . - 9997b != 4 ;\ .error "ALT_UP() content must assemble to exactly 4 bytes";\ .endif ;\ .popsection
위를 보면 이번에는 9997 label 에 ALT_UP 로 전달된 instruction 이 저장됩니다.
그런데, 현재 사용하고 있는 section 은 altsmp section 이므로, 위쪽에서 SMP 용 instruction 주소를 넣어둔 자리 바로 뒤로 UP 용 instruction 이 저장됩니다.
즉, altsmp 의 각 entry 의 구성요소는 (SMP 용 instruction 의 주소 + UP 용 instruction)이 된다는 것을 알 수 있습니다.
(매크로 중간의 error 검사는 UP 용 패치 명령어의 자리의 4 byte align 검사를 위한 것입니다.)
CONFIG_SMP 설정 후 빌드 시에 이 매크로드을 통과하면 pv_table 에서와 마찬가지로 code section 에는 instruction 하나가 추가되고, altsmp table 에는 (SMP 용 instruction 의 주소 + UP 용 instruction)쌍들이 쭈르륵 들어가 있겠네요.
그러면, __fixup_smp (__fixup_smp_on_up)에서 하는 역할을 그림으로 표현해 보겠습니다.
__fixup_smp 는 altsmp table 을 돌면서 첫번째의 smp instruction 주소값이 가리키는 자리에 저장된 SMP 용 명령어를 두번째의 UP 용 명령어로 교체해 버립니다.
즉, 이게 위의 ALT_SMP 에서 생성했던 9998 label 자리입니다.
결국 실행할 때는 이렇게 교체된 UP 용 명령어가 수행되겠네요. (9998 label 자리의 교체된 UP 용 명령어)
결국 위의 모든 과정을 정리해보면 다음과 같습니다.
- 빌드 과정에서 ALT_SMP 와 ALT_UP 를 이용하여 altsmp table 에 SMP 용 instruction 주소와 UP 용 instruction 을 쌓는다.
- __fixup_smp 에서 altsmp table 을 통해 instruction 을 SMP 용에서 UP 용으로 패치한다.
- ALT_SMP 와 ALT_UP 의 실행 시점에 실제로는 UP 용 명령어를 실행한다.
실행 시점을 기준으로 생각해보면...
(1) ALT_SMP( smp instruction )
(2) ALT_UP( up instruction )
- 만약 CONFIG_SMP 가 설정되어 빌드되었고, 실행 시에도 SMP 환경이 맞다면 -> (1) 의 instruction 만 수행
- CONFIG_SMP 가 설정되었고, 실행 시에는 UP 환경이라면 -> (1)의 instruction 자리에 (2) 의 명령어로 교체하여 수행
- CONFIG_SMP 가 설정되어 있지 않고, 실행 시에도 UP 환경이 맞다면 -> (2) 의 instruction 만 수행
- CONFIG_SMP 가 설정되어 있지 않고, 실행 시에는 SMP 환경이라면 -> (2) 의 instruction 만 수행 (즉, UP 로 동작)
와! 감사합니다. 한참 고민했었어요!