以k210为例

#### 起始代码

##### _start : libcpu/risc-v/k210/startup_gcc.S

- 不允许将machine级的中断和异常授权到其它特权级

- 设置mie为0，暂不允许使用中断，因为这个阶段中断例程还没有初始化好

- 初始化通用寄存器, mstatus寄存器, 浮点寄存器，这是为了指定每个寄存器的初始状态

- 设置gp, a0, sp寄存器，为当前cpu指定栈，这样就可以跳转到C代码去执行了。

  可以看到`__global_pointer$`定义在link.lds的data段，全局变量都会放在这里。

  可以看到`__stack_start__`的地址定义在link.lds的stack段，两个核各有4k大小的栈空间。栈顶的计算方法为`sp = (cpuid + 1) * 4096 + &__stack_start__`

- 如果当前核编号是0，说明是首次启动，调用函数`primary_cpu_entry`执行常规的启动流程

- 如果当前核编号非0，说明已启动完成，是要为第二个核准备运行环境，跳转到`secondary_cpu_entry`

#### 首次启动的流程

##### primary_cpu_entry : bsp/k210/driver/board.c

- 清空BSS段
- 调用函数entry

##### entry : src/components.c
- rt_hw_interrupt_disable关中断
- 调用`rtthread_startup`
##### rtthread_startup : src/components.c
- rt_hw_interrupt_disable关中断，就是把mstatus寄存器的MIE位清0。
- 执行一系统初始化函数
##### rt_hw_board_init : bsp/k210/drivers/board.c

初始化开发板

  - fpioa_init : sdk/lib/driver/fpioa.c，初始化现场可编程IO阵列。

    首先在系统控制器里使能fpioa时钟，即在系统控制器相应寄存器里里设置相关位。

    然后设置fpioa相关的寄存器的值，其中的io是和48个pin相关的数组，而tie里包含的en和val两个数组则与256个function有关。

  - dmac_init : sdk/lib/driver/dmac.c， 初始化DMA控制器。

    首先在系统控制器里使能dma时钟，然后设置dmac相关寄存器的值。数据结构dmac_reset看起来是对dmac->reset的缓存，数组结构intclear是对dmac->com_intclear的缓存，数据结构dmac_cfg是对dmac->cfg的缓存。

  - rt_hw_interrupt_init : libcpu/risc-v/k210/interrupt.c， 初始化硬件中断。

    首先，关闭当前核的所有中断，因为1个比特位对应一个中断信号，所以要计算出最大中断号对应多少个字(32位/字)。

    然后，设置所有中断源的优先级为0，数字越小优先级越高，这表明所有中断都设置为了最高优先级。

    然后，将当前核的中断所允许的优先级设为0。

    然后，初始化中断向量表irq_desc，都指向的是一个中断处理例程rt_hw_interrupt_handle，这个中断处理例程目前只是打印出中断号。

    最后，使能mie寄存器的机器级外部中断。

  - rt_hw_uart_init : bsp/k210/driver/drv_uart.c， 初始化uart。

    初始化uarths相关的寄存器。

  - rt_hw_tick_init : libcpu/risc-v/k210/tick.c， 设置计时器中断。

    设置寄存器clint->mtimecmp，然后使能计时器中断。

  - rt_hw_clint_ipi_enable : libcpu/risc-v/k210/interrupt.c，开启核间中断，即设置mie寄存器的MSIE位。

  - rt_console_set_device:  src/kservice.c，设置控制台设备，这样rt_kprintf的所有输出将被重定向到这台设备。

    rt_device_find来找到新的控制台设备，rt_device_close来关闭旧的控制台设备，rt_device_open来开启新的控制台设备。全局变量`_console_device`用来指示当前的控制台设备。

  - rt_system_heap_init : src/mem.c，初始化内存中的堆。

    就是指定一块内存给堆，堆的起始地址即这个堆的数据结构，也要初始化这个堆的数据结构。

  - rt_components_board_init : src/components.c，对开发板进行组件(components)初始化。

    `__rt_init_rti_board_start`指向了一系列初始化函数，依次执行这些初始化函数。
#####  rt_show_version : src/kservice.c

显示版本号

##### rt_system_timer_init : src/timer.c

把数组rt_timer_list的每个元素初始化为链表。

##### rt_system_scheduler_init : src/scheduler.c

初始化系统调度器。

在当前的代码中支持32个优先级，rt_thread_priority_table即为优先级的数组，每个数组元素都是双向链表中的一个结点。

结构体rt_cpus是关于cpu的数组，每个数组元素代表了一个cpu。其中，priority_table是当前cpu的优先级数组，irq_switch_flag是中断切换标志，current_priority是当前优先级(将被初始化为最低级)，current_thread当前线程，priority_group可能代表当前cpu的已就绪的优先级队列。

rt_defunct是一个链表结点，作用未知。

##### rt_system_signal_init : **src/signal.c**

使用rt_mp_create分一块内存给信号系统。

##### rt_application_init : src/componets.c

将main函数(bsp/k210/applications/main.c)初始化为进程。

使用rt_thread_create创建进程控制块。

使用rt_thread_startup初始化进程并将进程放入就绪队列里。

##### rt_system_timer_thread_init : src/timer.c

未定义宏RT_USING_TIMER_SOFT，所以本函数无效。

##### rt_thread_idle_init : src/idle.c

初始化idle进程。数组idle的每个数组元素代表了一个cpu的idle进程。

首先用rt_thread_init初始化某cpu的idle进程。

然后用rt_thread_control将此idle进程与某cpu绑定。

然后用rt_thread_startup初始化进程并将进程放入就绪队列里。

##### rt_hw_spin_lock : **libcpu/risc-v/k210/cpuport_smp.c**

持有锁_cpus_lock。

##### rt_system_scheduler_start : libcpu/risc-v/common/context_gcc.S

开始进程调度。

首先，通过`_get_highest_priority_thread`从就绪队列里选出来要执行的线程。

然后，设置目标进程的oncpu为当前运行的cpu。

然后，使用rt_schedule_remove_thread从就绪队列里删除目标进程。

然后，使用rt_hw_context_switch_to切换到目标进程。

#### 激活第二个核的流程

##### secondary_cpu_entry : libcpu/risc-v/k210/startup_gcc.S

在正常启动流程中，当准备激活第二个核的时候，应该是向`secondary_boot_flag`标志的内存里写入了`0xa55a`。如果`secondary_boot_flag`的内容是`0xa55a`，说明系统已准备好了来启动第二个核，则调用`secondary_cpu_c_start`。否则第二个核一直执行`j secondary_boot_flag`这个死循环等待着。

##### secondary_cpu_c_start : libcpu/risc-v/k210/cpuport_smp.c

持有锁`_cpus_lock`，然后rt_hw_scondary_interrupt_init为第二个核初始化中断控制器，rt_hw_tick_init和rt_hw_clint_ipi_enable使能第二个核的计数器中断和核间中断，然后rt_system_scheduler_start进行进程调度。

##### rt_hw_scondary_interrupt_init : libcpu/risc-v/k210/interrupt.c

为第二个核初始化中断控制器。

