Egloos | Log-in
F/OSS study
F/OSS study
[Linux] alternative instructions - runtime code update
Linux : 2.6.30


alternative()는 x86에서 memory barrier를 구현할 때 사용되는 기법으로
커널 부팅 시에 (즉, runtime에!) machine에서 지원한다면 특정 부분의 코드를 더 좋은 성능의 코드로 변경한다.

사용되는 예는 다음과 같다. (arch/x86/include/asm/system.h)

#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)

기본적으로는 SSE2 instruction set을 지원하지 않는 구식 machine을 지원하기 위해
예전부터 사용하던 lock prefix를 이용하여 memory 접근을 serialize한다.
하지만 SSE2 instruction set을 지원한다면 mfence 명령을 이용하도록 코드를 수정할 것이다.

alternative() 매크로는 다음과 같이 정의되어 있다.

#define alternative(oldinstr, newinstr, feature)            \
    asm volatile ("661:\n\t" oldinstr "\n662:\n"            \
              ".section .altinstructions,\"a\"\n"        \
              _ASM_ALIGN "\n"                    \
              _ASM_PTR "661b\n"        /* label */        \
              _ASM_PTR "663f\n"        /* new instruction */    \
              "     .byte %c0\n"        /* feature bit */    \
              "     .byte 662b-661b\n"    /* sourcelen */        \
              "     .byte 664f-663f\n"    /* replacementlen */    \
              ".previous\n"                    \
              ".section .altinstr_replacement,\"ax\"\n"        \
              "663:\n\t" newinstr "\n664:\n"  /* replacement */    \
              ".previous" :: "i" (feature) : "memory")

oldinstr 부분은 원래대로 저장된다. (661 ~ 662)
그리고는 .altinstructions 섹션이 나오는데 이는 아래에서 볼 alt_instr 구조체와 같다.
다음은 .altinstr_replacement 섹션으로 newinstr이 저장된다. (663 ~ 664)
alt_instr 구조체는 다음과 같이 정의한다.

struct alt_instr {
    u8 *instr;        /* original instruction */
    u8 *replacement;
    u8  cpuid;        /* cpuid bit set for replacement */
    u8  instrlen;        /* length of original instruction */
    u8  replacementlen;    /* length of new instruction, <= instrlen */
    u8  pad1;
#ifdef CONFIG_X86_64
    u32 pad2;
#endif
};

각 섹션은 링커 스크립트에서 볼 수 있듯이 text segment로 합쳐지지 않고 별도로 존재한다.

  . = ALIGN(4);
  .altinstructions : AT(ADDR(.altinstructions) - LOAD_OFFSET) {
      __alt_instructions = .;
    *(.altinstructions)
    __alt_instructions_end = .;
  }
  .altinstr_replacement : AT(ADDR(.altinstr_replacement) - LOAD_OFFSET) {
    *(.altinstr_replacement)
  }

.altinstructions 섹션은 alt_Instr 구조체 만이 모여있기 때문에
섹션의 첫 주소(__alt_instructions)를 구조체의 포인터로 변경하여 접근할 수 있다.
이를 이용하여 코드를 patch하는 부분은 다음과 같다. (불필요한 부분은 생략)

/* Replace instructions with better alternatives for this CPU type.
   This runs before SMP is initialized to avoid SMP problems with
   self modifying code. This implies that assymetric systems where
   APs have less capabilities than the boot processor are not handled.
   Tough. Make sure you disable such features by hand. */

void apply_alternatives(struct alt_instr *start, struct alt_instr *end)
{
    struct alt_instr *a;
    char insnbuf[MAX_PATCH_LEN];

    for (a = start; a < end; a++) {
        u8 *instr = a->instr;

        if (!boot_cpu_has(a->cpuid))
            continue;

        memcpy(insnbuf, a->replacement, a->replacementlen);
        add_nops(insnbuf + a->replacementlen,
             a->instrlen - a->replacementlen);
        text_poke_early(instr, insnbuf, a->instrlen);
    }
}

함수의 인자로 주어지는 start와 end는 당연히 __alt_instructions와 __alt_instructions_end로 설정된다.
(그럼 왜 굳이 인자로 받을까? 그 이유는 module을 loading하는 경우에도 동일하게 적용할 수 있기 때문이다.)
각각의 alternative instruction에 대해 머신의 지원 여부를 확인하고 (cpuid)
가능하다면 replacement 부분의 instruction을 원래의 instr 부분에 덮어쓴다. (memcpy)
이 때 replacement 코드의 크기가 다를 수 있기 때문에
add_nops() 함수를 실행하여 남는 부분은 nop로 채운다.
(사실은 최소한의 instruction을 수행하도록 실제 nop가 아닌 다른 instruction을 수행할 수도 있다.)
그리고는 커널의 코드를 업데이트한다. (text_poke_early)
사실 아직 어떻게 (read only 영역일) text segment의 내용을 바꿀 수 있는지는 확실히 이해하지 못했다.

alternative() mechanism은 다른 architecture에서도 사용할 수 있으리라고 생각되지만
현재는 x86의 memory barrier 관련 부분에만 사용되고 있다.


=== 참고 ===
 * http://lwn.net/Articles/29599/

by namhyung | 2009/10/01 18:46 | Kernel | 트랙백 | 덧글(2)
트랙백 주소 : http://studyfoss.egloos.com/tb/5129119
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by namhyung at 2010/03/16 02:08
apply_alternatives()가 호출되는 시점에서 커널 코드는 writable하므로 단순한 memcpy()로 업데이트가 가능하다.
이 후 init 스레드가 init_post()를 호출하는 과정에서 쓰기가 금지된다. (mark_rodata_ro)

나중에 모듈 로드시 alternative instruction을 포함한다면 비슷한 방식으로 코드를 업데이트 하는데
이 때는 text_poke()를 실행하여 먼저 해당 페이지를 fixmap 방식으로 매핑해서 writable하게 만든 후 업데이트를 수행한다.
Commented by 프로그라머 at 2014/02/17 21:23
이렇게 좋은 글이 한글로 있다는 것에 천지신명께 감사를 합니다

:         :

:

비공개 덧글

◀ 이전 페이지 다음 페이지 ▶

카테고리
General
Application
System
Kernel
Book
Tips
태그
binutils algorithm x86 vcs synchronization kernel memory gcc glibc elf sed script build scm awk emacs compiler git CAaQA3 linux block-layer SMP documentation patch perf blktrace CARM bash C computer-architecture
전체보기
이글루 파인더

최근 등록된 덧글
1번은 call-stack 금방 확인이 ..
by 혁 at 11/13
해당 문법에 대한 자세한 가이드 같..
by ㅇㄷㅎ at 11/07
perf에 대한 설명 감사드립니다! ..
by flavono123 at 10/24
최근 등록된 트랙백
Tod's Ferrari Homme
by Tods Pas Cher,Kodak did ..
Mocassin Femme
by Mocassins Homme, I got so..
natural garcinia cambogia
by
rss

skin by jiinny


X