티스토리 뷰

primary_switch

primary_switch

SYM_FUNC_START_LOCAL(__primary_switch)
    // vmlinux.lds.S 에 미리 위치가 정해져있는 reserved_pg_dir, init_idmap_pg_dir 을 x1, x2 인자로 설정
    // init_idmap_pg_dir 은 초기에 사용할 페이지 테이블
    // reserved_pg_dir 는 나중에 사용할 페이지 테이블
    adrp    x1, reserved_pg_dir
    adrp    x2, init_idmap_pg_dir
    bl    __enable_mmu

    adrp    x1, early_init_stack
    mov    sp, x1
    mov    x29, xzr
    mov    x0, x20                // pass the full boot status
    mov    x1, x21                // pass the FDT
    bl    __pi_early_map_kernel        // Map and relocate the kernel

    ldr    x8, =__primary_switched
    adrp    x0, KERNEL_START        // __pa(KERNEL_START)
    br    x8
SYM_FUNC_END(__primary_switch)

__enable_mmu

// 평생 멈춰있는 상태가 됨
SYM_FUNC_START_LOCAL(__no_granule_support)
    /* Indicate that this CPU can't boot and is stuck in the kernel */
    update_early_cpu_boot_status \
        CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x1, x2
1:
    wfe
    wfi
    b    1b
SYM_FUNC_END(__no_granule_support)
assembler.h


// 페이지 테이블의 물리 주소를 ttbr 레지스터에 쓸 수 있는 형식으로 변환
// [87:80] ← 주소의 상위 비트 (PA[50:43]) ← 물리 주소 일부
// [47:5]  ← 주소의 하위 비트 (PA[42:0]) ← 물리 주소 나머지
// [4:0]   ← 제어 비트 (ASID, CnP 등)
// BADDR[50:43] is TTBR0_EL1[87:80].
// BADDR[42:0] is TTBR0_EL1[47:5].

/*
 * Arrange a physical address in a TTBR register, taking care of 52-bit
 * addresses.
 *
 *     phys:    physical address, preserved
 *     ttbr:    returns the TTBR value
 */
    .macro    phys_to_ttbr, ttbr, phys
#ifdef CONFIG_ARM64_PA_BITS_52
    // https://developer.arm.com/documentation/ddi0601/2025-03/AArch64-Registers/TTBR0-EL1--Translation-Table-Base-Register-0--EL1-

    // 물리주소가 52 비트일때, 46 보다 큰 부분을 별도로 가져오고 싶어하는듯
    orr    \ttbr, \phys, \phys, lsr #46 // 2번째 인자를 46만큼 오른쪽 쉬프트 (logical shift right)
    and    \ttbr, \ttbr, #TTBR_BADDR_MASK_52 // BADDR = base address
#else
    mov    \ttbr, \phys
#endif
    .endm
  • TGRAN: Translation GRANule의 약자, 페이지 테이블에서 사용하는 페이지 크기(그레뉼 크기)
    .section ".idmap.text","a"
SYM_FUNC_START(__enable_mmu)
    mrs    x3, ID_AA64MMFR0_EL1 // 지난 포스팅에서 설명했던 메모리 피쳐 레지스터
    ubfx    x3, x3, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4 // TGRAN 값을 가져옴
    cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN
    b.lt    __no_granule_support
    cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX
    b.gt    __no_granule_support
    phys_to_ttbr x2, x2 // init_idmap_pg_dir
    // 유저/저레벨 커널 초기 매핑
    // 초기 부팅, 유저 프로세스 접근
    msr    ttbr0_el1, x2    // load TTBR0. ttbr0 에 값을 설정. 
    // 커널에서 사용할 공간 매핑
    load_ttbr1 x1, x1, x3 // ttbr1_el1 에 reserved_pg_dir 과 tgran 으로 설정

    set_sctlr_el1    x0 // 시스템 제어 레지스터 설정 (이전에 x0 은 설정되어있었음)

    ret
SYM_FUNC_END(__enable_mmu)

early_map_kernel


asmlinkage void __init early_map_kernel(u64 boot_status, void *fdt)
{
    static char const chosen_str[] __initconst = "/chosen";
    u64 va_base, pa_base = (u64)&_text; // 텍스트 영역
    u64 kaslr_offset = pa_base % MIN_KIMG_ALIGN; // 물리주소에서 커널이미지 align 만큼 모듈러
    int root_level = 4 - CONFIG_PGTABLE_LEVELS; // 페이지테이블 시작 레벨
    int va_bits = VA_BITS;
    int chosen;

    map_fdt((u64)fdt);
    // init 영역에서 fdt 영역을 물리주소 그대로 접근하기위해 임시로 매핑?
    // fdt 를 init idmap 으로 매핑

    /* Clear BSS and the initial page tables */
    memset(__bss_start, 0, (u64)init_pg_end - (u64)__bss_start); // 초기화 되지않은 전역 변수를 0으로 클리어. 그리고 initial page 도 연속적이기 때문에 한번에 같이 맴셋함

    /* Parse the command line for CPU feature overrides */
    chosen = fdt_path_offset(fdt, chosen_str); // fdt 에서 chosen 을 검색
    init_feature_override(boot_status, fdt, chosen); // chosen 노드의 값으로 cpu 값 설정

    // Config 에 따라 va bits 설정
    if (IS_ENABLED(CONFIG_ARM64_64K_PAGES) && !cpu_has_lva()) {
        va_bits = VA_BITS_MIN;
    } else if (IS_ENABLED(CONFIG_ARM64_LPA2) && !cpu_has_lpa2()) {
        va_bits = VA_BITS_MIN;
        root_level++;
    }

    if (va_bits > VA_BITS_MIN)
        sysreg_clear_set(tcr_el1, TCR_T1SZ_MASK, TCR_T1SZ(va_bits));

    /*
     * The virtual KASLR displacement modulo 2MiB is decided by the
     * physical placement of the image, as otherwise, we might not be able
     * to create the early kernel mapping using 2 MiB block descriptors. So
     * take the low bits of the KASLR offset from the physical address, and
     * fill in the high bits from the seed.
     */
    if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
        u64 kaslr_seed = kaslr_early_init(fdt, chosen);

        if (kaslr_seed && kaslr_requires_kpti())
            arm64_use_ng_mappings = true;

        kaslr_offset |= kaslr_seed & ~(MIN_KIMG_ALIGN - 1);
    }

    if (IS_ENABLED(CONFIG_ARM64_LPA2) && va_bits > VA_BITS_MIN) // large page
        remap_idmap_for_lpa2();

    va_base = KIMAGE_VADDR + kaslr_offset;
    map_kernel(kaslr_offset, va_base - pa_base, root_level);
}

primary_switched

init_task 란

  • 모든 프로세스의 조상이 되는 task
  • include/linux/sched/task.h 에 전역으로 선언
  • init_task.c 에서 값을 설정함
struct task_struct init_task __aligned(L1_CACHE_BYTES) = {
#ifdef CONFIG_THREAD_INFO_IN_TASK
    .thread_info    = INIT_THREAD_INFO(init_task),
    .stack_refcount    = REFCOUNT_INIT(1),
#endif
    .__state    = 0,
    .stack        = init_stack,

__primalry_switched

으아ㅏㅏ
https://www.sciencedirect.com/science/article/pii/S2666281722000798

SYM_FUNC_START_LOCAL(__primary_switched)
    adr_l    x4, init_task
    init_cpu_task x4, x5, x6

    adr_l    x8, vectors            // load VBAR_EL1 with virtual. 인터럽트 벡터 테이블 로드
    msr    vbar_el1, x8            // vector table address. Vector Base Address Register for Exception Level 1
    isb

    stp    x29, x30, [sp, #-16]! // x29 = pt_reg.stackframe. sp = pt_reg. (이전 값을 지정, sp 는 프레임 포인터를 가리키게됨)
    mov    x29, sp  // x29 에는 실제 스택 프레임 포인터값을 지정

    // *(__fdt_pointer + x5) = x21; 
    // x5 는 per_cpu_offset. x21 은 fdt_pointer
    // cpu 별로 fdt 주소를 저장?
    str_l    x21, __fdt_pointer, x5        // Save FDT pointer 

    adrp    x4, _text            // Save the offset between // _text 섹션 시작 주소(가상)를
    sub    x4, x4, x0            // the kernel virtual and  // 커널 시작 물리주소에서 뺌. x0 = __pa(KERNEL_START)
    str_l    x4, kimage_voffset, x5        // physical mappings // cpu 별로 voffset 을 저장

    mov    x0, x20
    bl    set_cpu_boot_mode_flag // boot mode 설정. 부팅된 el 에 설정. cold 인지 hot plug 인지 등

#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
    bl    kasan_early_init // kernel address anitizer 검사 & 초기화
#endif
    mov    x0, x20
    // 내부적으로 hvc 콜
    bl    finalise_el2            // Prefer VHE if possible (Virtualization host extensions)
    ldp    x29, x30, [sp], #16    // x29 = sp, x30 = sp + 8, sp += 16
    bl    start_kernel
    ASM_BUG()
SYM_FUNC_END(__primary_switched)

init_cpu_task

STP <Xt1>, <Xt2>, [<SP>, #<imm>]!
sp += imm;
*sp = xt1;
*(sp + 1) xt2;

STP <Xt1>, <Xt2> [<SP>, #<imm>]
*(sp + imm) = xt1;
*(xp + imm) = xt2;
    /*
     * Initialize CPU registers with task-specific and cpu-specific context.
     *
     * Create a final frame record at task_pt_regs(current)->stackframe, so
     * that the unwinder can identify the final frame record of any task by
     * its location in the task stack. We reserve the entire pt_regs space
     * for consistency with user tasks and kthreads.
     */
    .macro    init_cpu_task tsk, tmp1, tmp2
    // arm64 에서 설계는 sp_el0 를 유저모드의 스택 포인터로 사용하라고 설계했지만
    // 리눅스 커널에서는 현재 실행중인 task (current) 를 가리키도록 사용함
    msr    sp_el0, \tsk 

    // DEFINE(TSK_STACK,        offsetof(struct task_struct, stack));
    // task_struct 의 stack 위치 offset
    // tmp1 = [task 주소] + [stack 의 offset] 에 있는 값을 read
    // 즉, stack 의 주소값
    ldr    \tmp1, [\tsk, #TSK_STACK]

    // THREAD_SIZE: 프로세스의 스택 사이즈
    // stack 을 높은곳에서부터 아래로 쌓기 때문에 더함
    // 스택에는 레지스터값도 사용하기 때문에, 미리 Reg 만큼 쌓아줌
    add    sp, \tmp1, #THREAD_SIZE
    sub    sp, sp, #PT_REGS_SIZE // sp = pt_reg 의 시작주소

    // DEFINE(S_STACKFRAME,        offsetof(struct pt_regs, stackframe));
    // pt_regs 의 stackframe[0, 1] 을 0으로 설정
    // 를 따로 관리하고 있음
    stp    xzr, xzr, [sp, #S_STACKFRAME]
    add    x29, sp, #S_STACKFRAME // x29 = &pt_reg.stackframe

    scs_load_current // scs_sp 에 스택 포인터를 불러옴

    adr_l    \tmp1, __per_cpu_offset // tmp1 = &__per_cpu_offset
    // DEFINE(TSK_TI_CPU,        offsetof(struct task_struct, thread_info.cpu));
    ldr    w\tmp2, [\tsk, #TSK_TI_CPU] // tmp2 = cpu 번호 로드
    ldr    \tmp1, [\tmp1, \tmp2, lsl #3] // cpu 번호를 3만큼 left shift. 왜냐면 offset 이 8바이트이기 때문.
    set_this_cpu_offset \tmp1 // tpidr(Thread ID Register) 레지스터에 cpu 설정
    .endm

scs_load_current

  • scs: Shadow Call Stack support
  • ShadowCallStack is an instrumentation pass, currently only implemented for aarch64 and RISC-V, that protects programs against return address overwrite
  • 함수 호출시 반환 주소를 보호된 공간에 저장하는 기술
assembler.h

    .macro    get_current_task, rd
    mrs    \rd, sp_el0
    .endm
#ifdef CONFIG_SHADOW_CALL_STACK
    // x18 is used for SCS stack pointer
    // alias
    scs_sp    .req    x18

    .macro scs_load_current
    get_current_task scs_sp // 현재 task 를 x18 에 불러오기
    // DEFINE(TSK_TI_SCS_SP,        offsetof(struct task_struct, thread_info.scs_sp));
    // 별도 공간(task.thread_info.scs_sp) 에 저장해두었던 sp 를 scs_sp 에 불러옴
    ldr    scs_sp, [scs_sp, #TSK_TI_SCS_SP]
    .endm

    .macro scs_save tsk
    str    scs_sp, [\tsk, #TSK_TI_SCS_SP]
    .endm
#else
    .macro scs_load_current
    .endm

    .macro scs_save tsk
    .endm
#endif /* CONFIG_SHADOW_CALL_STACK */

pt_regs

  • 커널 스택 영역에 존재하며, 인터럽트, 시스템 콜, 컨텍스트 스위치 등 특수한 상황에서 레지스터를 저장
struct pt_regs {
    union {
        struct user_pt_regs user_regs;
        struct {
            u64 regs[31];
            u64 sp;
            u64 pc;
            u64 pstate;
        };
    };
    u64 orig_x0;
#ifdef __AARCH64EB__
    u32 unused2;
    s32 syscallno;
#else
    s32 syscallno;
    u32 unused2;
#endif
    u64 sdei_ttbr1;
    /* Only valid when ARM64_HAS_GIC_PRIO_MASKING is enabled. */
    u64 pmr_save;
    u64 stackframe[2];

    /* Only valid for some EL1 exceptions. */
    u64 lockdep_hardirqs;
    u64 exit_rcu;
};

head.S 에서 init/main.c 의 start_kernel 로 진입

#0  0xffff800081ce08e0 in start_kernel () at init/main.c:901
#1  0xffff800081ce9e40 in __primary_switched () at arch/arm64/kernel/head.S:246

'개발 > arm64 linux 6 분석' 카테고리의 다른 글

jump_label (setup_arch)  (0) 2025.05.31
start_kernel 2 setup_arch  (0) 2025.05.24
start_kernel 1  (0) 2025.05.24
proc.S __cpu_setup  (0) 2025.05.10
250503 head.S  (2) 2025.05.03
댓글