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)에서 하는 역할을 그림으로 표현해 보겠습니다.

smpalt table 을 이용한 up 용 명령어 패치.png

__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 )

  1. 만약 CONFIG_SMP 가 설정되어 빌드되었고, 실행 시에도 SMP 환경이 맞다면 -> (1) 의 instruction 만 수행
  2. CONFIG_SMP 가 설정되었고, 실행 시에는 UP 환경이라면 -> (1)의 instruction 자리에 (2) 의 명령어로 교체하여 수행
  3. CONFIG_SMP 가 설정되어 있지 않고, 실행 시에도 UP 환경이 맞다면 -> (2) 의 instruction 만 수행
  4. CONFIG_SMP 가 설정되어 있지 않고, 실행 시에는 SMP 환경이라면 -> (2) 의 instruction 만 수행 (즉, UP 로 동작)

You may also like...

5 1 vote
Article Rating
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
부끄럼틀

와! 감사합니다. 한참 고민했었어요!

1
0
Would love your thoughts, please comment.x
()
x