多核支持
在实验1中我们已经介绍,在QEMU模拟的树莓派中,所有CPU核心在开机时会被同时启动。在引导时这些核心会被分为两种类型。一个指定的CPU核心会引导整个操作系统和初始化自身,被称为CPU主核(primary CPU)。其他的CPU核心只初始化自身即可,被称为CPU从核(backup CPU)。CPU核心仅在系统引导时有所区分,在其他阶段,每个CPU核心都是被相同对待的。
思考题 1
阅读
Lab1
中的汇编代码kernel/arch/aarch64/boot/raspi3/init/start.S。说明ChCore是如何选定主CPU,并 阻塞其他其他CPU的执行的。
在Lab1之中,我们已经详细的讨论了这一点,这里仅简单复述
主核恒为cpu 0, 在start.S之中我们比较当前cpu id和0,如果是0核就跳进primary执行init_c
从核先是循环等待bss cleared, 再循环等待smp enable
主核在init_c初始化uart之后就用sev指令唤醒其他核,之后进入start kernel,初始化cpu内核栈、清空页表和TLB设置后进入main, 在main之中初始化了锁,uart,cpu info 内存管理mm, 内核页表kernel pagetable, 调度器等之后enable smp,此时其他核通过secondary init初始化自己的cpu info和kernel stack之后让出cpu, 进入调度,之后由主核负责创建第一个用户态线程,完毕后全部进入调度
Q: 可以看到,此时从cpu还没有等待的线程,调度给谁呢?
A: 调度给自己,并且会有idle优化,这是仿照linux做的优化(ref: https://www.cnblogs.com/doitjust/p/13307378.html),以policy_rr调度策略为例
struct thread *rr_sched_choose_thread(void)
{
unsigned int cpuid = smp_get_cpu_id();
struct thread *thread = NULL;
if (!list_empty(&(rr_ready_queue_meta[cpuid].queue_head))) {
lock(&(rr_ready_queue_meta[cpuid].queue_lock));
again:
if (list_empty(&(rr_ready_queue_meta[cpuid].queue_head))) {
unlock(&(rr_ready_queue_meta[cpuid].queue_lock));
goto out;
}
/*
* When the thread is just moved from another cpu and
* the kernel stack is used by the origina core, try
* to find another thread.
*/
if (!(thread = find_runnable_thread(
&(rr_ready_queue_meta[cpuid].queue_head)))) {
unlock(&(rr_ready_queue_meta[cpuid].queue_lock));
goto out;
}
BUG_ON(__rr_sched_dequeue(thread));
if (thread_is_exiting(thread) || thread_is_exited(thread)) {
/* Thread need to exit. Set the state to TE_EXITED */
thread_set_exited(thread);
goto again;
}
unlock(&(rr_ready_queue_meta[cpuid].queue_lock));
return thread;
}
out:
return &idle_threads[cpuid];
}
注意到在等待队列为空的时候,会进入out,返回一个idle_thread
它的ctx在idle_thread_routine处,这个函数是体系结构相关的,旨在防止cpu空转降低功耗
/* Arch-dependent func declaraions, which are defined in assembly files */
extern void idle_thread_routine(void);
在arm架构中是wfi指令,让cpu进入低功耗状态,在某些版本中的实现是关闭几乎所有的时钟
BEGIN_FUNC(idle_thread_routine)
idle: wfi
b idle
END_FUNC(idle_thread_routine)
阅读汇编代码
kernel/arch/aarch64/boot/raspi3/init/start.S
, init_c.c以及kernel/arch/aarch64/main.c,解释用于阻塞其他CPU核心的secondary_boot_flag是物理地址还是虚拟地址?是如何传入函数enable_smp_cores
中,又是如何赋值的(考虑虚拟地址/物理地址 )?
物理地址,在启用内核页表映射前就使用了secondary_boot_flag
secondary_boot_flag实际上是作为kernel .data 段的一个地址被加载的
// kernel/arch/aarch64/boot/raspi3/init/init_c.c
/*
* Initialize these varibles in order to make them not in .bss section.
* So, they will have concrete initial value even on real machine.
*
* Non-primary CPUs will spin until they see the secondary_boot_flag becomes
* non-zero which is set in kernel (see enable_smp_cores).
*
* The secondary_boot_flag is initilized as {NOT_BSS, 0, 0, ...}.
*/
#define NOT_BSS (0xBEEFUL)
long secondary_boot_flag[PLAT_CPU_NUMBER] = {NOT_BSS}; // 0xBEEFUL
volatile u64 clear_bss_flag = NOT_BSS;
而在mmu.c初始化kernel pagetable启用mmc之后,变成了虚拟地址
所以在enable_smp_cores之中再次使用时需要一次phys_to_virt转换
void enable_smp_cores(paddr_t boot_flag)
{
int i = 0;
long *secondary_boot_flag;
/* Set current cpu status */
cpu_status[smp_get_cpu_id()] = cpu_run;
secondary_boot_flag = (long *)phys_to_virt(boot_flag);
for (i = 0; i < PLAT_CPU_NUM; i++) {
secondary_boot_flag[i] = 1;
flush_dcache_area((u64) secondary_boot_flag,
(u64) sizeof(u64) * PLAT_CPU_NUM);
asm volatile ("dsb sy");
while (cpu_status[i] == cpu_hang)
;
kinfo("CPU %d is active\n", i);
}
/* wait all cpu to boot */
kinfo("All %d CPUs are active\n", PLAT_CPU_NUM);
init_ipi_data();
}