Egloos | Log-in
F/OSS study
F/OSS study
[Linux] SMP initialization
Linux: 2.6.37-rc4
arch: x86_64


커널 초기화 과정의 마지막에 다다르면 start_kernel() 함수는 rest_init()을 호출하고
여기서 첫 번째 프로세스인 init 프로세스를 생성하는데 init 프로세스는
사용자 공간의 init을 실행하기 전에 커널에서 수행할 작업을 처리하기 위해 kernel_init()
함수에서부터 실행되며 (사실 커널 개발자들은 여기서 실행하는 대부분의 코드가 사용자 공간에서
처리되어야 한다고 생각한다.) 이 과정에서 SMP 머신의 다른 CPU (AP)들을 동작시키기 위해
smp_init() 함수가 호출된다.

사실 SMP 환경에서도 부팅 과정에서는 오직 하나의 CPU (BSP) 만이 동작하여 초기화를 수행하며
시스템이 어느 정도 준비가 되면 다른 CPU (AP)들을 깨워서 동작시키게 된다.

smp_init() 함수는 BSP가 실행하는 것으로 다음과 같이 loop를 돌며 AP를 하나씩 깨우게 된다.

init/main.c:
    /* FIXME: This should be done in userspace --RR */
    for_each_present_cpu(cpu) {
        if (num_online_cpus() >= setup_max_cpus)
            break;
        if (!cpu_online(cpu))
            cpu_up(cpu);
    }

시스템 상에 존재하는 CPU의 상태 정보는 다음과 같은 4개의 비트맵 (cpumask_t)으로 관리한다.
  • cpu_possible_mask - 해당 비트에 대한 CPU가 존재할 수 있다.
  • cpu_present_mask - 해당 비트에 대한 CPU가 존재한다.
  • cpu_online_mask - 해당 비트에 대한 CPU가 존재하며 스케줄러가 이를 관리한다.
  • cpu_active_mask - 해당 비트에 대한 CPU가 존재하며 task migration 시 이를 이용할 수 있다.

참고로 CPU hotplug가 활성화되지 않은 환경이라면 present == possible이고 active == oneline이다.
따라서 위의 코드는 시스템에 존재하는 모든 CPU에 대해 loop를 돌며
해당 CPU가 아직 online 상태가 아니라면 cpu_up() 함수를 호출한다.

x86에서 이는 결국 native_cpu_up()을 거쳐 do_boot_cpu() 함수로 이어진다.
이 함수는 먼저 깨어날 AP에서 수행될 idle 태스크를 생성하고 (do_idle_fork)
AP의 bootstrap을 위한 trampoline을 준비한 후에 (setup_trampoline)
local APIC를 통해 IPI를 보내서 실제로 AP를 깨운다. (wakeup_secondary_cpu_via_init)

trampoline이 필요한 이유는 AP가 깨어난 직후에는 real mode로 동작하기 때문이다.
따라서 간략한 초기화를 수행하여 protected mode를 거쳐 long mode까지 진입하기 위한 코드가
필요한 것이다. 이는 arch/x86/kernel/trampoline_64.S에 어셈블리어로 작성되어 있으며
trampoline_data에서부터 시작하는 코드이지만 STARTUP IPI를 전달할 때는 코드의 시작 주소가
4KB 단위로 정렬되어 있어야 하므로 trampoline_base 위치로 복사되며
이 때 cs 레지스터는 코드의 시작 위치로 ip 레지스터는 0으로 각각 설정되므로
심볼 참조 시에 r_base 심볼을 이용하여 상대 주소를 생성하도록 하고 있다.

trampoline 코드의 마지막 부분에서는 secondary_startup_64로 이동하게 되는데
이는 BSP의 초기화 코드를 공유하는 것이다. 여기서는 현재 CPU의 gdt, rip, rsp, gs 레지스터를 설정하며
이는 각각 early_gdt_descr, initial_code, stack_start, initial_gs에 해당하는데
이 정보들은 do_boot_cpu()에서 해당 CPU에 해당하는 정보로 이미 변경해 둔 상태이다.

initial_code는 start_secondary() 함수의 주소로 지정되는데
이 함수는 먼저 cpu_init() 함수를 호출하여 필요한 초기화를 마저 수행하고
현재 CPU를 cpu_online_mask에 추가한 뒤 인터럽트를 활성화하고
cpu_idle()을 호출하여 실제로 idle 태스크로서의 작업을 수행한다.

cpu_init() 함수는 CPU에 관련된 여러 정보들 (TSS, IST, GDT, IDT, ...)을 설정하는데
여기서 percpu 데이터에 접근하기 위한 gs 레지스터 정보를 설정한다.
(사실은 trampoline에서 호출한 secondary_startup_64 루틴에서 이미 initial_gs를 설정했지만
다른 코드 (xen, lguest, ...)와의 호환성을 위해 여기서 다시 한 번 설정하는 것이다.)

x86_64 아키텍처에서 segment descriptor는 (IA-32와의 호환성을 위해) base/limit 필드의 값을
최대 4GB까지만 표현할 수 있으므로 64비트 주소 공간을 제대로 활용할 수 없었는데 fs와 gs 레지스터의 경우
IA32_FS_BASE & IA32_GS_BASE 및 IA32_KERNEL_GS_BASE MSR을 통해 이를 활용할 수 있으며
특히 gs 레지스터의 경우에는 percpu 영역의 시작 주소를 저장하는 용도로 사용되고 있다.
참고로 x86 (IA-32) 아키텍처의 경우에는 GDT 내에 하나의 descriptor를 지정하여 percpu 영역의
시작 주소를 저장하고 fs 레지스터를 통해 이 descriptor에 접근하도록 되어 있다.

cpu_init()에서 호출한 switch_to_new_gdt() 함수는 다음과 같이 load_percpu_segment()를 호출한다.

arch/x86/kernel/cpu/common.c:
void load_percpu_segment(int cpu)
{
#ifdef CONFIG_X86_32
    loadsegment(fs, __KERNEL_PERCPU);
#else
    loadsegment(gs, 0);
    wrmsrl(MSR_GS_BASE, (unsigned long)per_cpu(irq_stack_union.gs_base, cpu));
#endif
    load_stack_canary_segment();
}

irq_stack_union은 인터럽트 처리 시 사용할 스택 공간을 할당해 둔 것으로 percpu 영역의 제일 처음에
존재한다. (PER_CPU_FIRST) 이는 또한 union 타입이므로 스택과 stack protector를 위한 정보를
공유하도록 되어 있으며 gs_base라는 배열을 통해 gs 레지스터의 시작 위치임을 명시적으로 알려준다.

arch/x86/include/asm/processor.h:
union irq_stack_union {
    char irq_stack[IRQ_STACK_SIZE];
    /*
     * GCC hardcodes the stack canary as %gs:40.  Since the
     * irq_stack is the object at %gs:0, we reserve the bottom
     * 48 bytes of the irq stack for the canary.
     */
    struct {
        char gs_base[40];
        unsigned long stack_canary;
    };
};

DECLARE_PER_CPU_FIRST(union irq_stack_union, irq_stack_union);
DECLARE_INIT_PER_CPU(irq_stack_union);

이렇게 percpu 데이터 접근을 위한 gs 레지스터가 설정되고나면 언제 어디서든 smp_processor_id()
매크로를 이용하여 현재 CPU 번호를 알 수 있게 된다.

by namhyung | 2010/12/02 06:17 | Kernel | 트랙백(1) | 덧글(2)
트랙백 주소 : http://studyfoss.egloos.com/tb/5444259
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Tracked from at 2014/03/11 00:38

제목 : pure garcinia cambogia
line3...more

Commented by Murali at 2013/03/27 14:00
Thanks Namhyung. Nice article on SMP boot of x86 arch.
Commented by ioopoi at 2014/11/11 20:36
사용자 공간의 init을 실행하기 전에 커널에서 수행할 작업을 처리하기 위해 kernel_init()
함수에서부터 실행되며 (사실 커널 개발자들은 여기서 실행하는 대부분의 코드가 사용자 공간에서
처리되어야 한다고 생각한다.) 이 과정에서 SMP 머신의 다른 CPU (AP)들을 동작시키기 위해
smp_init() 함수가 호출된다.
statement of diversity
http://www.diversitystatement.net/why-choose-professional-assistance-with-writing-a-statement-of-diversity/

:         :

:

비공개 덧글

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

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

최근 등록된 덧글
http://serbaserbiinfoterkini56..
by cakra at 09/22
informsi yang bagus dan b..
by cakra at 09/16
informsi yang bagus dan be..
by pordanaia at 08/05
최근 등록된 트랙백
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