티스토리 뷰
7.3 Secondary Booting
boot cpu 로 커널을 초기화 한 후 non-boot cpu 를 초기화
7.3.1 ARM: SMP 오퍼레이션
- bootcpu 가 smp cpu 를 init 후 boot_secondary 호출
ARM 의 SMP 오퍼레이션
- cpu_kill, cpu_die, cpu_can_disable, cpu_disable
7.3.2 AEM64: cpu 오퍼레이션
- ARM64 커널을 활용하는 플랫폼은 부팅을 위해 펌웨어나 다른 인터페이스가 필요한 경우가 생겨, SMP 오퍼레이션을 사용하지 않는다.
- 기존 ARM 의 smp_operations 을 사용하지 않고 cpu_operations 구조체를 사용한다.
ARM64 의 cpu operation 종류
디바이스 트리에따라 결정됨. cpu_operations 구조체에는 SMP 부팅 정보가 들어가게됨
fdt 의 enable-method 이름이 spin-table, psci 로 나뉨
spin-table 의 경우 bootcpu 를 제외한 나머지 cpu 가 폴링하는 주소를 갖고있어야 하기 때문에, cpu-release-addr 이 정의되어있어야 함.
- psci: 하이퍼바이저나 펌웨어를 통해 수행
- spin-table: pc 에 secondary boot 를 지정하여 부팅
7-7 arch/arm64/kernel/cpu_ops.c cpu_get_ops
cpu_operations 찾기.
dt : spin-table 우선 / acpi : psci
static const struct cpu_operations * __init cpu_get_ops(const char *name)
{
const struct cpu_operations **ops;
ops = acpi_disabled ? dt_supported_cpu_ops : acpi_supported_cpu_ops;
while (*ops++) {
if (!strcmp(name, (*ops)->name)) return *ops;
}
}
7-8 kernel/cpu.c cpu_read_bootcpu_ops, 7-9 kernel/cpu_ops.c cpu_read_ops
- 0 번 cpu 의 ops 를 반환
- cpu 의 ops 를 반환
7.3.3 PSCI(Power State Coordination Interface)
- 하이퍼바이저 또는 보안 모니터상 구동되는 펌웨어가 구현할 수 있는 프로토콜. 전원 상태를 조정
- OS 와 SPF(Secure Platform Firmware) 사이 다른 권한 수준에서 작동하는 소프트웨어 간 cpu 전원 관리를 가상화
SMC(Secure Monitor Call) 호출 규칙
- 보안 모니터 호출 : EL3 으로 이동
- 하이퍼 바이저 호출 : EL2 로 이동 (SPF 가 없는 플랫폼에 적합)
PSCI 통신 인터페이스
device tree 의 enable-method 에 따라 인터페이스가 갈림
const struct cpu_operations smp_spin_table_ops // 전원 관리 기능이 없음
const struct cpu_operations cpu_psci_ops
7-10 arch/arm64/kernel/setup.c psci_dt_init
device tree 를 읽고, 초기화 함수를 등록 & 호출
7.3.4 ARM64 non-boot cpu 부팅
7-11 arch/arm64/kernel/smp.c smp_init_cpus
PSCI is a low-level firmware interface for power management on ARM systems.
ACPI is a higher-level hardware abstraction standard, commonly used in ARM64 servers.
ACPI 사용하지 않을때
static void __init of_parse_and_init_cpus(void)
{
struct device_node *dn = NULL;
while ((dn = of_find_node_by_type(dn, "cpu"))) {
u64 hwid = of_get_cpu_mpidr(dn); // Multiprocessor Affinity Register 로 id 획득
if (is_mpidr_duplicate(cpu_count, hwid)) {goto next;} // 중복 체크
if (hwid == cpu_logical_map(0)) { // 부팅 cpu 스킵
continue;
}
if (cpu_count >= NR_CPUS) goto next;
pr_debug("cpu logical map 0x%llx\n", hwid);
cpu_logical_map(cpu_count) = hwid; // i 번 인덱스에 id 를 등록
next:
cpu_count++;
}
}
ACPI 를 사용할때
- SoC(System on a chip 완전 구동이 가능한 제품과 시스템이 한 개의 칩에 들어 있는 것) 의 언터럽트 사용
7-12 arch/arm64/kernel/smp.c smp_cpu_setup
- cpu ops 등록과 possible map 에 등록
7-13 kernel/cpu.c boot_cpu_state_init
- boot cpu 을 CPUHP_ONLINE 상태로 초기화화 (HP = Hot Plug)
7-14 arch/arm64/kernel/smp.c smp_prepare_boot_cpu
- 부팅중이거나 온라인 상태가 될때 각 cpu 별로 feature 레지스터가 필요함
- non-boot-cpu 의 데이터가 boot cpu 데이터로 복사될 염려가 있음. __cpuinfo_store_cpu 가 현재 cpu 의 정보를 덮어씌우기 때문
- 따라서 boot_cpu_data 라는 전역변수로 boot cpu 의 feature 레지스터를 관리
cpu 의 속성을 기록해둔 정도로 알고 가자
cpu feature register : read-only registers that provide information about the capabilities and features of a CPU
TPIDRPRW : Thread Process ID Register PL1 Privileged Read/Write
Privilege Level
PL0: 유저 / PL1: 커널 / PL2: 하이퍼바이저 / PL3: 보안 모니터
void __init cpuinfo_store_boot_cpu(void)
{
struct cpuinfo_arm64 *info = &per_cpu(cpu_data, 0);
__cpuinfo_store_cpu(info);
boot_cpu_data = *info;
init_cpu_features(&boot_cpu_data);
}
static inline void set_my_cpu_offset(unsigned long off)
{
asm volatile("msr tpidr_el1, %0" :: "r" (off) : "memory");
}
void __init smp_prepare_boot_cpu(void)
{
cpuinfo_store_boot_cpu();
set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
}
7-15 arch/arm64/kernel/smp.c smp_prepare_cpus
void __init smp_prepare_cpus(unsigned int max_cpus)
{
init_cpu_topology() // cpu_topology[cpu all] 초기화
smp_store_cpu_info(smp_processor_id()); // cpu topolgy 구조체 설정
for_each_possible_cpu(cpu) {
err = cpu_ops[cpu]->cpu_prepare(cpu);
set_cpu_present(cpu, true);
}
}
7-16 kernel/smp.c smp_init
void __init smp_init(void)
{
unsigned int cpu;
idle_threads_init(); // 부트 cpu 를 제외한 각 cpu Idle thread 생성
cpuhp_threads_init(); // 7-17 cpu 당 핫플러그를 위한 쓰레드 초기화
for_each_present_cpu(cpu) {
if (num_online_cpus() >= setup_max_cpus)
break;
if (!cpu_online(cpu)) // cpu 활성화
cpu_up(cpu);
}
smp_announce(); // 7-18 온라인 cpu 출력
smp_cpus_done(setup_max_cpus); 7-19
7-19 arch/arm64/kernel/smp.c smp_cpus_done
void __init smp_cpus_done(unsigned int max_cpus)
{
pr_info("SMP: Total of %d processors activated.\n", num_online_cpus());
setup_cpu_features();
hyp_mode_check();
apply_alternatives_all(); // boot cpu 를 제외한 모든 온라인 cpu 를 중지 why?
}
void __init apply_alternatives_all(void)
{
/* better not try code patching on a live SMP system */
stop_machine(__apply_alternatives_multi_stop, NULL, cpu_online_mask);
}
7-20 init/main.c do_pre_smp_initcalls
__initcalls_start ~ __initcall0_start 사이의 함수를 실행. smp 용 initcall 이라고 함
7-21 kernel/cpu.c cpu_up
int cpu_up(unsigned int cpu)
{
return do_cpu_up(cpu, CPUHP_ONLINE);
}
7-22 kernel/cpu.c do_cpu_up
static int do_cpu_up(unsigned int cpu, enum cpuhp_state target)
{
if (!cpu_possible(cpu)) {return -EINVAL;}
err = try_online_node(cpu_to_node(cpu));
if (err) return err;
cpu_maps_update_begin(); // mutex lock. cpu_online_mask 뼌경을 위함
if (cpu_hotplug_disabled) {
err = -EBUSY;
goto out;
}
err = _cpu_up(cpu, 0, target); // 7-23
out:
cpu_maps_update_done(); // unlock
return err;
}
7-23 kernel/cpu.c _cpu_up
static int _cpu_up(unsigned int cpu, int tasks_frozen, enum cpuhp_state target)
{
struct cpuhp_cpu_state *st = per_cpu_ptr(&cpuhp_state, cpu);
if (st->state == CPUHP_OFFLINE) { // offline 인 경우 idle 쓰레드로 지정
idle = idle_thread_get(cpu);
if (IS_ERR(idle)) {
ret = PTR_ERR(idle);
goto out;
}
}
st->target = target;
if (st->state > CPUHP_BRINGUP_CPU) { // 이미 브링업 된 상태
ret = cpuhp_kick_ap_work(cpu);
if (ret)
goto out;
}
target = min((int)target, CPUHP_BRINGUP_CPU); // cpu up 을 수행하기 위해 목표값(target) 을 최소 bring up 으로 설정
ret = cpuhp_up_callbacks(cpu, st, cpuhp_bp_states, target);
out:
cpu_hotplug_done();
return ret;
}
7-24 cpuhp_up_callbacks
현재상태부터 target 상태까지 단계별로 필요한 콜백 호출,
실패할 경우 이전 단계로 돌려 재수행 살 수 있도록 undo 를 반복
static int cpuhp_up_callbacks(unsigned int cpu, struct cpuhp_cpu_state *st,
struct cpuhp_step *steps, enum cpuhp_state target)
{
enum cpuhp_state prev_state = st->state;
int ret = 0;
// state
// OFFLINE -> CREATE_THREADS ..
while (st->state < target) {
struct cpuhp_step *step;
st->state++;
step = steps + st->state;
ret = cpuhp_invoke_callback(cpu, st->state, step->startup); // startup 함수 실행
if (ret) { // 에러 발생시
st->target = prev_state; // 이전 상태까지 내려가면서
undo_cpu_up(cpu, st, steps); // teardown 함수 실행
break;
}
}
return ret;
}
static struct cpuhp_step cpuhp_bp_states[] = {
[CPUHP_OFFLINE] = {
.name = "offline",
.startup = NULL,
.teardown = NULL,
},
#ifdef CONFIG_SMP
[CPUHP_CREATE_THREADS]= {
.name = "threads:create",
.startup = smpboot_create_threads,
.teardown = NULL,
.cant_stop = true,
},
/*
* Preparatory and dead notifiers. Will be replaced once the notifiers
* are converted to states.
*/
[CPUHP_NOTIFY_PREPARE] = {
.name = "notify:prepare",
.startup = notify_prepare,
.teardown = notify_dead,
.skip_onerr = true,
.cant_stop = true,
},
/* Kicks the plugged cpu into life */
[CPUHP_BRINGUP_CPU] = {
.name = "cpu:bringup",
.startup = bringup_cpu,
.teardown = NULL,
.cant_stop = true,
},
/*
* Handled on controll processor until the plugged processor manages
* this itself.
*/
[CPUHP_TEARDOWN_CPU] = {
.name = "cpu:teardown",
.startup = NULL,
.teardown = takedown_cpu,
.cant_stop = true,
},
#endif
};
7-25 kernel/cpu.c bringup_cpu
static int bringup_cpu(unsigned int cpu)
{
struct task_struct *idle = idle_thread_get(cpu);
int ret;
/* Arch-specific enabling code. */
ret = __cpu_up(cpu, idle); // 7-26
if (ret) {
cpu_notify(CPU_UP_CANCELED, cpu); // 실패처리. raw_notifier_head 의 call chain
return ret;
}
ret = bringup_wait_for_ap(cpu); // per-cpu 로 관리되는 hp_state 가 odone 이 될때까지 대기 (kernel/sched/completion)
BUG_ON(!cpu_online(cpu));
return ret;
}
7-26 arch/arm64/kernel/smp.c __cpu_up
// secondary cpu 에게 필요한 secondary_data 구조체 (arm64 용)
// * @stack - sp for the secondary CPU
// * @status - Result passed back from the secondary CPU to
struct secondary_data {
void *stack;
long status;
};
int __cpu_up(unsigned int cpu, struct task_struct *idle)
{
secondary_data.stack = task_stack_page(idle) + THREAD_START_SP;
update_cpu_boot_status(CPU_MMU_OFF); // secondary_data.status = val
__flush_dcache_area(&secondary_data, sizeof(secondary_data));
ret = boot_secondary(cpu, idle); // 7-27
if (ret == 0) {
wait_for_completion_timeout(&cpu_running, msecs_to_jiffies(1000)); // 잠시 대기
if (!cpu_online(cpu)) {
pr_crit("CPU%u: failed to come online\n", cpu);
ret = -EIO;
}
} else {
pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
}
secondary_data.stack = NULL; // stack 비워주기
status = READ_ONCE(secondary_data.status); // 메모리에서 직접 read
if (ret && status) {
// 정상적으로 켜지지 않은경우 에러로그 출력이나 panic
}
return ret;
}
7-22 arch/arm64/kernel/smp.c boot_secondary
cpu_ops 의 cpu_boot 실행
secondary_entry : secondary boot 되는 cpu 의 진입점. hp 세팅을 마치고 running 상태로 만드는 듯 하다.
static int boot_secondary(unsigned int cpu, struct task_struct *idle)
{
if (cpu_ops[cpu]->cpu_boot)
return cpu_ops[cpu]->cpu_boot(cpu);
return -EOPNOTSUPP;
}
// arch/arm64/kernel/psci.c
static int cpu_psci_cpu_boot(unsigned int cpu)
{
// of_parse_and_init_cpus() 에서 cpu 의 hwid 를 미리 구했었음.
int err = psci_ops.cpu_on(cpu_logical_map(cpu), __pa(secondary_entry)); // cpu 물리 Id 와 secondary_entry 함수 시작 주소
if (err)
pr_err("failed to boot CPU%d (%d)\n", cpu, err);
return err;
}
// drivers/firmwarew/psci.c
static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point)
{
int err;
u32 fn;
fn = psci_function_id[PSCI_FN_CPU_ON]; // 함수 포인터 주소
err = invoke_psci_fn(fn, cpuid, entry_point, 0);
return psci_to_linux_errno(err);
}
7-23 arch/arm64/kernel.smp.c secondary_start_kernel
asmlinkage void secondary_start_kernel(void)
{
struct mm_struct *mm = &init_mm;
unsigned int cpu = smp_processor_id();
atomic_inc(&mm->mm_count); // secondary cpu mm 을 init_mm 으로 설정
current->active_mm = mm;
set_my_cpu_offset(per_cpu_offset(smp_processor_id())); // per-cpu offset 설정. tpidr_el1
cpu_uninstall_idmap(); // ttbr0_el1 의 idmap 을 제거하고 active_mm 의 pgd 를 재정의 하기 위해 초기화 & 플러시
preempt_disable(); // 선점 불가 상태로 변경
cpuinfo_store_cpu(); // cpu info 초기화
notify_cpu_starting(cpu); // hp 를 AP_ONLINE 까지 변경하며 콜백
smp_store_cpu_info(cpu); // MPIDR 의 토폴로지등 갱신
update_cpu_boot_status(CPU_BOOT_SUCCESS); // 상태 변경
set_cpu_online(cpu, true);
complete(&cpu_running);
local_dbg_enable(); // cpu 에서 신호 받을수 있도록 설정
local_irq_enable();
local_async_enable();
cpu_startup_entry(CPUHP_AP_ONLINE_IDLE); hp 상태를 AP_ONLINE_IDLE 로 변경하여 cpu_idle_loop 를 수행
}
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
250329 인터럽트 개념과 핸들러 등록 (8.1~8.3) (0) | 2025.03.29 |
---|---|
250317 SMP 를 위한 커널지원 & cpu topology(7.1~7.2) (0) | 2025.03.17 |
250308 스케줄링 엔티티의 실행시간 관리하기 & 스케줄러 초기화 (6.3~6.4) (2) | 2025.03.08 |
250301 CFS 태스크 선택, 제거, 삽입 (6.3.1~6.3.4) (0) | 2025.03.01 |
250222 6.2.6 태스크 깨우기 (ttwu) (0) | 2025.02.22 |
- Total
- Today
- Yesterday
- 카카오
- ad skip
- Reciprocal n-body Collision Avoidance
- it's called a vrpit
- 영상 픽셀화 하기
- shared_from_this
- vr핏
- RVO
- Golang
- 코어 남기기
- vrpit
- mysql
- chrome-extension
- C++
- 봄날에 스케치
- set value
- 우리는 vr핏이라고 부릅니다
- 클래스 맴버 변수 출력하기
- Visual Studio
- hole-punching
- boost
- cockroach db
- 에러 위치 찾기
- Quest2
- 면접
- SuffixArray
- Obstacle Avoidance
- print shared_ptr class member variable
- red underline
- 잘못된 빨간줄
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |