其他core的入口
文件:arch/arm64/kernel/head.S
secondary_entry:
在從bl31切到EL1上的Linux Kernel后:
第595行,在el2_setup中設置EL1和EL0為小端模式,然后將w0設置為BOOT_CPU_MODE_EL1,並返回
第596行,記錄cpuX的啟動模式到__boot_cpu_mode,目前是BOOT_CPU_MODE_EL1
secondary_startup:
第604行,__cpu_setup:
1、無效本地tlb
2、使能FP/ASIMD
3、設置mdscr_el1,在EL0訪問Debug Communication Channel寄存器時,陷入EL1
4、操作daif,使能debug中斷
5、設置pmuserenr_el0,當EL0訪問PMU寄存器時會陷入EL1
6、填充mair_el1,設置后面要用到的內存屬性索引,目前用到了6中內存屬性:
7、讀取sctlr_el1,修改后存入x0,后面配置mmu時會用到x0的值:x0= (sctrl_el1 & ~0xfcffffff)|0x34d5d91d,即將控制大小端的bit保留(因為之前在el2_setup里設置過了),其他位清零,然后設置新值,新值的含義如下:
從低位到高位依次說明:
0 |
M |
1:表示開啟EL1和EL0的stage1地址轉換機制,目前因為用不到虛擬化,所有只有stage1 |
1 |
A |
0:關閉地址對齊檢查,但是load/store exclusive和load-acquire/store-release除外 |
2 |
C |
1:EL0/1的data cache的控制不再由sctrl_el1控制,如果HCR_EL2.DC為1,那么EL0/1的data cache開啟。(所以,不開MMU,數據cache也可以開?) |
3 |
SA |
1 EL1上棧指針對齊檢查,需要16字節對齊 |
4 |
SA0 |
1:EL0上棧指針對齊檢查,需要16字節對齊 |
5 |
CP15BEN |
0:EL0運行在AArch32時,不能使用CP15DMB、CP15DSB以及CP15ISB |
7 |
ITD |
0:EL0運行在AArch32模式時,仍舊可以使用IT指令(IF-THEN)http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/Cjabicci.html |
8 |
SED |
1:EL0運行在AArch32時,不能使用SEDEND指令。這個指令是在ARMv6引入的,用於切換當前cpu的大小端,請參考:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/Cjacabbf.html |
9 |
UMA |
0:在EL0運行在AArch64上通過MRS或MSR指令訪問DAIF時會陷入EL1 |
12 |
I |
1:EL0/1的指令cache不再由sctrl_el1控制。如果HCR_EL2.DC為1,那么EL0/1的指令 cache開啟 |
14 |
DZE |
1:EL0運行AArch64時,如果執行DC ZVA指令(清除指令虛擬地址的data cache),會陷入EL1. |
15 |
UCT |
1:EL0運行AArch64時,訪問CTR_EL0時不會陷入EL1. 這個寄存器用於獲取當前系統的cache的架構信息 |
16 |
nTWI |
1: EL0上執行WFI時不會陷入EL1 |
18 |
nTWE |
1: EL0上執行WFE時不會陷入EL1 |
19 |
WXN |
0: 對EL1&0的內存訪問權限沒有影響。如果是1的話,在EL1&0下,如果某塊內存具備可寫權限,那么這塊內存就是XN(Execute Never) |
24 |
E0E |
控制EL0數據訪問的大小端控制,0小端,1大端 |
25 |
EE |
控制EL1以及EL1/0的table walk時的大小端,0小端,1大端 |
26 |
UCI |
1:EL0運行在AArch64時,如果執行DC CVAU/CIVAC/CVAC、IC IVAU時不會陷入EL1 |
8、設置TCR_EL1,用於建立1:1映射,目前配置的VA時48bit的:
[5:0] | T0SZ | 64-48=16 |
[21:16] | T1SZ | 64-48=16 |
[9:8] | IRGN0, 使用TTBR0_EL1進行內存訪問時的Inner cacheability | 1:Normal memory, Inner Write-Back Write-Allocate Cacheable |
[25:24] | IRGN1, 使用TTBR1_EL1進行內存訪問時的Inner cacheability | 1:Normal memory, Inner Write-Back Write-Allocate Cacheable |
[11:10] | ORGN0, 使用TTBR0_EL1進行內存訪問時的Outer cacheability | 1:Normal memory, Outer Write-Back Write-Allocate Cacheable |
[27:26] | ORGN1, 使用TTBR1_EL1進行內存訪問時的Outer cacheability | 1:Normal memory, Outer Write-Back Write-Allocate Cacheable |
[13:12] | SH0, 使用TTBR0_EL1進行內存訪問時的Shareability attribute | 3: Inner Shareable |
[29:28] | SH1, 使用TTBR1_EL1進行內存訪問時的Shareability attribute | 3: Inner Shareable |
[15:14] | TG0, 使用TTBR0_EL1時的頁大小 | 0:4KB |
[31:30] | TG1, 使用TTBR1_EL1時的頁大小 | 0:4KB |
[36] | AS, 表示ASID的大小 | 1:16bit |
[37] | TBI0, 在使用TTBR0_EL1做地址翻譯時,是否忽略虛擬地址的高字節,如果不忽略的話,會被用來當tagged地址 | 1:忽略高字節 |
[22] | A1, 選擇通過TTBR0_EL1或者TTBR1_EL1來定義ASID | 1:使用TTBR1_EL1.ASID來定義ASID |
[34:32] | IPS,物理地址大小 | 通過讀取ID_AA64MMFR0_EL1的獲取當前系統的Physical Address range,然后將值寫到這里,目前是48 |
第605行,__enable_mmu:
1、首先判斷當前系統硬件是否支持4KB的粒度,是的話繼續執行,否則stuck住
2、更新boot cpu的狀態信息,即將__early_cpu_boot_status的值設置為0
3、將idmap_pg_dir賦值給ttbr0_el1
4、將swapper_pg_dir賦值給ttbr1_el1
5、將之前填充好的x0的值賦值給sctlr_el1
6、將icache無效
此時,mmu已經開啟了,上面用到的兩個頁目錄的內容在cpu0啟動時已經填寫好了,其他的core直接用。
__secondary_switched:
第611行,設置CPUx的異常向量表基地址為vectors
第615~619,設置CPUx的棧指針的值,這里用到的secondary_data的內容是在CPU0啟動CPUx時賦值的。sp指向了idle task的棧頂地址,sp_el0指向idle task的task_struct首地址
第622,跳轉到secondary_start_kernel
secondary_start_kernel:


CPUHP_AP_IDLE_DEAD | ||
CPUHP_AP_OFFLINE | ||
CPUHP_AP_SCHED_STARTING | sched_cpu_starting | |
CPUHP_AP_RCUTREE_DYING | ||
CPUHP_AP_IRQ_GIC_STARTING | gic_starting_cpu | 動態注冊的 |
CPUHP_AP_ARM_VFP_STARTING | ||
CPUHP_AP_ARM64_DEBUG_MONITORS_STARTING | ||
CPUHP_AP_PERF_ARM_HW_BREAKPOINT_STARTING | ||
CPUHP_AP_PERF_ARM_STARTING | ||
CPUHP_AP_ARM_ARCH_TIMER_STARTING | arch_timer_starting_cpu | 動態注冊 |
CPUHP_AP_ARM_GLOBAL_TIMER_STARTING | ||
CPUHP_AP_DUMMY_TIMER_STARTING | ||
CPUHP_AP_ARM_CORESIGHT_STARTING | ||
CPUHP_AP_ARM64_ISNDEP_STARTING | ||
CPUHP_AP_SMPCFD_DYING | ||
CPUHP_AP_ONLINE |

第270,設置secondary_data.status為CPU_BOOT_SUCCESS,后面cpu0醒來后,會根據這個標志判斷cpuX是否啟動正常
第271,設置__cpu_online_mask中當前cpu對應的bit,后面cpu0醒來后,會根據這個標志判斷cpuX是否啟動正常
第272,通知cpu0,可以繼續往下運行了,此時cpu0在arch\arm64\kernel\smp.c中調用完boot_secondary后,緊接着wait_for_completion_timeout(&cpu_running, msecs_to_jiffies(1000))
第274,開啟本地中斷
第275,使能SERROR中斷
第280,執行cpu_startup_entry:

第374,執行do_idle
CPU0啟動以及PSCI初始化
設備樹


start_kernel:


下面進入rest_init:
在這個函數里先后創建個內核線程kernel_init和kthreadd,然后在kernel_init中會wait_for_completion(&kthreadd_done),當主流程准備就緒后,設置system_state為SYSTEM_SCHEDULING,
然后主流程會complete(&kthreadd_done),在主流程的最后,會執行cpu_startup_entry(CPUHP_ONLINE),在前面非boot cpu的啟動分析中,最后也會執行cpu_startup_entry,不過它傳遞的cpu狀態是CPUHP_AP_ONLINE_IDLE
下面重點分析kernel_init:
在執行這個內核線程是,使用的仍然是cpu0,因為當前系統中也只有cpu0.

第1058,smp_prepare_cpus,首先調用init_cpu_topology,解析當前/cpus節點以及下面的cpu-map子節點,填充cpu_topology[cpu],得到系統cpu的拓撲關系,然后
調用set_sched_topology設置sched_domain_topology為arm64_topology。如果沒有從設備樹里cpu-map獲得拓撲關系,smp_prepare_cpus會調用store_cpu_topology來
設置cpu_topology[0]的內容。最后,這個函數會遍歷當前所有cpu,設置per_cpu變量cpu_number為cpu編號,然后cpu_ops[cpu]->cpu_prepare(cpu),如果成功的話,
設置set_cpu_present(cpu, true)
第1064,do_pre_smp_initcalls:執行__initcall_start到__initcall0_start之間的回調函數
第1067,smp_init:這個函數就是cpu0喚醒其他core的地方,重點分析
smp_init:

第574~579,遍歷cpu,如果cpu沒有online(cpu_online_mask),那么調用cpu_on(cpu)
下面重點分析cpu_on:



4、如果待處理cpu的當前狀態大於CPUHP_BRINGUP_CPU,那么會執行cpuhp_kick_ap_work(cpu)。
5、下面的喚醒步驟分兩個階段,首先從OFFLINE到CPUHP_BRINGUP_CPU,然后剩下從CPUHP_BRINGUP_CPU到CPUHP_ONLINE則通過AP hotplug thread進行,這個線程就是上面創建的"cpuhp/0",
它的task_struct存放在per_cpu變量cpuhp_state.thread中
下面是從OFFLINE->CPUHP_BRINGUP_CPU:

下面分析cpuhp_up_callbacks:

在獲得cpuhp_step結構后,先判斷該step是否支持multi_instance,如果不支持的話,對於bringup操作,會回調step->startup.single(cpu),否則回調step->teardown.single(cpu).對於支持multi_instance的step,會遍歷step->list,對於bringup,回調step->startup.multi(cpu,node),其中node就是從step->list遍歷得到的,否則回調step->teardown.multi(cpu,node)。
下面是cpuhp_bp_states中涉及到的回調:
CPUHP_CREATE_THREADS | smpboot_create_threads | 遍歷hotplug_threads,對沒有創建task的 |
CPUHP_PERF_PREPARE | perf_event_init_cpu | |
CPUHP_WORKQUEUE_PREP | workqueue_prepare_cpu | |
CPUHP_HRTIMERS_PREPARE | hrtimers_prepare_cpu | |
CPUHP_SMPCFD_PREPARE | smpcfd_prepare_cpu | |
CPUHP_RELAY_PREPARE | relay_prepare_cpu | |
CPUHP_SLAB_PREPARE | slab_prepare_cpu | |
CPUHP_RCUTREE_PREP | rcutree_prepare_cpu | |
CPUHP_TIMERS_PREPARE | timers_prepare_cpu | |
CPUHP_BRINGUP_CPU | bringup_cpu |
bringup_cpu:

psci_ops.cpu_on(cpu_logical_map(cpu), __pa_symbol(secondary_entry))
cpu_on是在drivers\firmware\psci.c賦值的:


5、如果上面返回成功,那么cpu0調用wait_for_completion_timeout(&cpu_running,msecs_to_jiffies(1000)); 根據前面的分析,當cpuX醒來之后,會complete這里的cpu_running
6、調用cpu_online(cpu)檢查cpuX是否成功online
7、讀取READ_ONCE(secondary_data.status),然后判斷cpuX是否正常
第533,bringup_wait_for_ap(cpu):
1、調用wait_for_ap_thread(st, true),因為是bringup,所以會wait_for_completion(&st->done_up)。在cpuX喚醒后,執行idle任務之前會complete該變量
2、stop_machine_unpark(cpu);
3、kthread_unpark(st->thread),這里的st->thread是"cpuhp/0",是在cpuhp_threads_init中初始化的
4、如果st->target小於等於CPUHP_AP_ONLINE_IDLE,那么這個函數的任務就完成了,但是對於這里,target的值是CPUHP_ONLINE
5、調用cpuhp_kick_ap(st, st->target),這個函數進一步調用__cpuhp_kick_ap(st):

cpuhp_thread_fun:

1、比較cpuX的當前狀態和目標狀態,如果小於目標狀態,那么設置st->should為true,這樣這個函數返回后,還會被smpboot_thread_fn繼續回調
2、調用st->result = cpuhp_invoke_callback(cpu, state, bringup, st->node, &st->last);
3、如果上個函數返回失敗,設置st->should_run = false
4、如果st->should_run為false,那么調用complete_ap_thread(st, bringup),如果是bringup,這個函數會complete(&st->done_up)。即:在正常情況下,
如果當前狀態達不到target狀態,那么st->should_run一直為true,只有達到target,才會complete(&st->done_up)。別忘了,此時cpu0還在wait呢
第二階段從CPUHP_AP_ONLINE_IDLE->CPUHP_ONLINE,下面的函數會被調用,這些函數位於cpuhp_ap_states中:
CPUHP_AP_SMPBOOT_THREADS | smpboot_unpark_threads | |
CPUHP_AP_IRQ_AFFINITY_ONLINE | irq_affinity_online_cpu | |
CPUHP_AP_PERF_ONLINE | perf_event_init_cpu | |
CPUHP_AP_WORKQUEUE_ONLINE | workqueue_online_cpu | |
CPUHP_AP_RCUTREE_ONLINE | rcutree_online_cpu | |
CPUHP_AP_ACTIVE | sched_cpu_activate | |
CPUHP_ONLINE | NULL |
SMP啟動log
下面是多核啟動時的log,有助於我們理解上面的分析過程:(log中<cpu_id 進程name:進程id>)
[ 0.117936] <0 swapper/0:1> smp: Bringing up secondary CPUs ... [ 0.118893] <0 swapper/0:1> smpboot_create_threads, cpu: 1 [ 0.140551] <0 swapper/0:1> perf_event_init_cpu, cpu: 1 [ 0.140958] <0 swapper/0:1> workqueue_prepare_cpu, cpu: 1 [ 0.149409] <0 swapper/0:1> hrtimers_prepare_cpu, cpu: 1 [ 0.149555] <0 swapper/0:1> smpcfd_prepare_cpu, cpu: 1 [ 0.149780] <0 swapper/0:1> rcutree_prepare_cpu, cpu: 1 [ 0.150696] <0 swapper/0:1> timers_prepare_cpu, cpu: 1 [ 0.150999] <0 swapper/0:1> bringup_cpu, cpu: 1 [ 0.152518] <1 swapper/1:0> Detected PIPT I-cache on CPU1 [ 0.153478] <1 swapper/1:0> sched_cpu_starting, cpu: 1 [ 0.153575] <1 swapper/1:0> GICv3: CPU1: found redistributor 1 region 0:0x00000000080c0000 [ 0.153849] <1 swapper/1:0> CPU1: cluster 0 core 1 thread -1 mpidr 0x00000080000001 [ 0.153955] <1 swapper/1:0> CPU1: Booted secondary processor [410fd083] [ 0.157031] <1 cpuhp/1:13> smpboot_unpark_threads, cpu: 1 [ 0.158684] <1 cpuhp/1:13> irq_affinity_online_cpu, cpu: 1 [ 0.158999] <1 cpuhp/1:13> perf_event_init_cpu, cpu: 1 [ 0.159236] <1 cpuhp/1:13> workqueue_online_cpu, cpu: 1 [ 0.160831] <1 cpuhp/1:13> rcutree_online_cpu, cpu: 1 [ 0.161322] <1 cpuhp/1:13> sched_cpu_activate, cpu: 1 [ 0.162781] <0 swapper/0:1> smpboot_create_threads, cpu: 2 [ 0.185357] <0 swapper/0:1> perf_event_init_cpu, cpu: 2 [ 0.185528] <0 swapper/0:1> workqueue_prepare_cpu, cpu: 2 [ 0.193720] <0 swapper/0:1> hrtimers_prepare_cpu, cpu: 2 [ 0.193829] <0 swapper/0:1> smpcfd_prepare_cpu, cpu: 2 [ 0.193951] <0 swapper/0:1> rcutree_prepare_cpu, cpu: 2 [ 0.194451] <0 swapper/0:1> timers_prepare_cpu, cpu: 2 [ 0.194583] <0 swapper/0:1> bringup_cpu, cpu: 2 [ 0.194853] <2 swapper/2:0> Detected PIPT I-cache on CPU2 [ 0.194970] <2 swapper/2:0> sched_cpu_starting, cpu: 2 [ 0.195027] <2 swapper/2:0> GICv3: CPU2: found redistributor 2 region 0:0x00000000080e0000 [ 0.195198] <2 swapper/2:0> CPU2: cluster 0 core 2 thread -1 mpidr 0x00000080000002 [ 0.195269] <2 swapper/2:0> CPU2: Booted secondary processor [410fd083] [ 0.195899] <2 cpuhp/2:18> smpboot_unpark_threads, cpu: 2 [ 0.196294] <2 cpuhp/2:18> irq_affinity_online_cpu, cpu: 2 [ 0.196426] <2 cpuhp/2:18> perf_event_init_cpu, cpu: 2 [ 0.196594] <2 cpuhp/2:18> workqueue_online_cpu, cpu: 2 [ 0.197099] <2 cpuhp/2:18> rcutree_online_cpu, cpu: 2 [ 0.197325] <2 cpuhp/2:18> sched_cpu_activate, cpu: 2 [ 0.197811] <0 swapper/0:1> smpboot_create_threads, cpu: 3 [ 0.220870] <0 swapper/0:1> perf_event_init_cpu, cpu: 3 [ 0.221065] <0 swapper/0:1> workqueue_prepare_cpu, cpu: 3 [ 0.229439] <0 swapper/0:1> hrtimers_prepare_cpu, cpu: 3 [ 0.229550] <0 swapper/0:1> smpcfd_prepare_cpu, cpu: 3 [ 0.229675] <0 swapper/0:1> rcutree_prepare_cpu, cpu: 3 [ 0.230062] <0 swapper/0:1> timers_prepare_cpu, cpu: 3 [ 0.230195] <0 swapper/0:1> bringup_cpu, cpu: 3 [ 0.230439] <3 swapper/3:0> Detected PIPT I-cache on CPU3 [ 0.230549] <3 swapper/3:0> sched_cpu_starting, cpu: 3 [ 0.230604] <3 swapper/3:0> GICv3: CPU3: found redistributor 3 region 0:0x0000000008100000 [ 0.230767] <3 swapper/3:0> CPU3: cluster 0 core 3 thread -1 mpidr 0x00000080000003 [ 0.230907] <3 swapper/3:0> CPU3: Booted secondary processor [410fd083] [ 0.231723] <3 cpuhp/3:23> smpboot_unpark_threads, cpu: 3 [ 0.232132] <3 cpuhp/3:23> irq_affinity_online_cpu, cpu: 3 [ 0.232266] <3 cpuhp/3:23> perf_event_init_cpu, cpu: 3 [ 0.232439] <3 cpuhp/3:23> workqueue_online_cpu, cpu: 3 [ 0.232941] <3 cpuhp/3:23> rcutree_online_cpu, cpu: 3 [ 0.233179] <3 cpuhp/3:23> sched_cpu_activate, cpu: 3 [ 0.233572] <0 swapper/0:1> smp: Brought up 1 node, 4 CPUs [ 0.233680] <0 swapper/0:1> SMP: Total of 4 processors activated.