티스토리 뷰
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


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 설정
.endmscs_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 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- Quest2
- print shared_ptr class member variable
- 에러 위치 찾기
- red underline
- vrpit
- RVO
- 우리는 vr핏이라고 부릅니다
- vr핏
- 잘못된 빨간줄
- 카카오
- SuffixArray
- it's called a vrpit
- ad skip
- hole-punching
- 클래스 맴버 변수 출력하기
- Reciprocal n-body Collision Avoidance
- 봄날에 스케치
- Visual Studio
- chrome-extension
- set value
- mysql
- cockroach db
- C++
- Golang
- shared_from_this
- 코어 남기기
- boost
- 면접
- 영상 픽셀화 하기
- Obstacle Avoidance
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |
글 보관함
