3.4 双核并行
一、实验目的
本节课主要学习K210的核心0和核心1同时运行程序。
二、实验准备
1.实验元件
K210芯片中的核心0和核心1
2.元件特性
K210芯片搭载基于RISC-V ISA 的双核心64 位的高性能低功耗CPU,具备以下特性:
项目 | 内容 | 描述 |
核心数量 | 2核心 | 双核对等,各个核心具备独立FPU |
处理器位宽 | 64位 | 64 位CPU 位宽,为高性能算法计算提供位宽基础,计算带宽充足 |
标称频率 | 400Mhz | 频率可调,可通过调整PLL VCO 与分频进行变频 |
指令集扩展 | IMAFDC | 基于RISC-V 64 位IMAFDC (RV64GC),胜任通用任务 |
浮点处理单元 | 双精度 | 具备乘法器、除法器与平方根运算器,支持单精度、双精度的浮点计算 |
平台中断管理 | PLIC | 支持高级中断管理,支持64 个外部中断源路由到2 个核心 |
本地中断管理 | CLINT | 支持CPU 内置定时器中断与跨核心中断 |
指令缓存 | 32KIB*2 | 核心0 与核心1 各具有32 千字节的指令缓存,提升双核指令读取效能 |
数据缓存 | 32KIB*2 | 核心0 与核心1 各具有32 千字节的数据缓存,提升双核数据读取效能 |
片上SRAM | 8MIB | 共计8 兆字节的片上SRAM,2兆用于AI,6兆通用 |
FPU(浮点运算单元)是集成于CPU中的专用于浮点运算的处理器。K210核心0和核心1都具备独立的FPU,满足IEEE754-2008标准,计算流程以流水线方式进行,具备很强的运算能力,每个FPU都具备除法器和平方根运算器,支持单精度和双精度浮点硬件加速运算。
4.SDK中对应API功能
板级对应的头文件 bsp.h
bsp.h头文件是与平台相关的通用函数,核之间锁的相关操作。
提供获取当前运行程序的CPU核编号的接口以及启动第二个核的入口。
为用户提供以下接口:
• register_core1:向核心1注册函数,并启动核心1
• current_coreid:获取当前CPU的核心编号(0/1)
• read_cycle:获取CPU开机至今的时钟数。可以用使用这个函数精准的确定程序运行时钟。可以配合sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)计算运行的时间。
• spinlock_lock:自旋锁,不可嵌套,不建议在中断使用,中断中可以使用spinlock_trylock。
• spinlock_unlock:自旋锁解锁。
• spinlock_trylock:获取自旋锁,成功获取锁会返回0,失败返回-1。
• corelock_lock:获取核间锁,核之间互斥的锁,同核内该锁会嵌套,只有异核之间会阻塞。不建议在中断使用该函数,中断中可以使用corelock_trylock。
• corelock_trylock:获取核间锁,同核时锁会嵌套,异核时非阻塞。成功获取锁会返回0,失败返回-1。
• corelock_unlock:核间锁解锁。
• sys_register_putchar:注册系统输出回调函数,printf时会调用该函数。系统默认使用UART3,如果需要修改UART则调用uart_debug_init函数。
• sys_register_getchar:注册系统输入回调函数,scanf时会调用该函数。系统默认使用UART3,如果需要修改UART则调用uart_debug_init函数。
• sys_stdin_flush:清理stdin缓存。
• get_free_heap_size:获取空闲内存大小。
• printk:打印核心调试信息,用户不必理会。
三、实验原理
K210是双核CPU,那么什么是双核CPU呢?双核CPU是在一个CPU中拥有两个一样功能的处理器芯片,从而提高计算能力。K210的核心0和核心1都可以单独工作,系统默认使用核心0,如果需要使用核心1需要手动开启核心1的服务。
四、实验过程
1. 首先进入main函数,读取当前的CPU核心编号,然后通过printf函数打印出来,再注册核心1的启动函数为core1_main。
最后进入while(1)循环,每个1秒钟打印一次Core 0 is running。
2. core1_main函数已经被注册为核心1的入口函数,在这里可以像main函数一样使用,这里同样先读取当前运行的核心编号并打印出来。再进入while(1)循环,每隔0.5秒打印一次数据。这样和核心0的对比就是每秒都比核心0多打印一次信息。
3.编译调试,烧录运行
把本课程资料中的dual_core复制到SDK中的src目录下,然后进入build目录,运行以下命令编译。
cmake .. -DPROJ=dual_core -G "MinGW Makefiles"
make
编译完成后,在build文件夹下会生成dual_core.bin文件。
使用type-C数据线连接电脑与K210开发板,打开kflash,选择对应的设备,再将程序固件烧录到K210开发板上。
五、实验现象
烧录完成固件后,系统会弹出一个终端界面,如果没有弹出终端界面的可以打开串口助手显示调试内容。
打开电脑的串口助手,选择对应的K210开发板对应的串口号,波特率设置为115200,然后点击打开串口助手。注意还需要设置一下串口助手的DTR和RTS。在串口助手底部此时的4.DTR和7.RTS默认是红色的,点击4.DTR和7.RTS,都设置为绿色,然后按一下K210开发板的复位键。
从串口助手可以接收到核心0和核心1的欢迎语,然后两个核心都进入循环,只是核心0每1秒打印一次数据,核心1每0.5秒打印一次数据。
六、实验总结
1.系统的printf默认使用高速串口UARTHS(UART0)。
2.K210是双核心CPU,并且两个核心都可以单独运行。
3.K210的SDK默认是运行核心0,核心1需要手动注册开启。
附:API
对应的头文件 bsp.h
向1核注册函数,并启动1核。
int register_core1(core_function func, void *ctx)
参数名称 | 描述 | 输入输出 |
func | 向1核注册的函数 | 输入 |
ctx | 函数的参数,没有设置为NULL | 输入 |
返回值 | 描述 |
0 | 成功 |
非0 | 失败 |
获取当前CPU核编号。
#define current_coreid() read_csr(mhartid)
无。
当前所在CPU核的编号。
获取CPU开机至今的时钟数。可以用使用这个函数精准的确定程序运行时钟。可以配合sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)计算运行的时间。
#define read_cycle() read_csr(mcycle)
无。
开机至今的CPU时钟数。
自旋锁,不可嵌套,不建议在中断使用,中断中可以使用spinlock_trylock。
void spinlock_lock(spinlock_t *lock)
自旋锁,要使用全局变量。
无。
获取自旋锁,成功获取锁会返回0,失败返回-1。
int spinlock_trylock(spinlock_t *lock)
自旋锁,要使用全局变量。
返回值 | 描述 |
0 | 成功 |
非0 | 失败 |
自旋锁解锁。
void spinlock_unlock(spinlock_t *lock)
核间锁,要使用全局变量,参见举例。
无。
获取核间锁,核之间互斥的锁,同核内该锁会嵌套,只有异核之间会阻塞。不建议在中断使用该函数,中断中可以使用corelock_trylock。
void corelock_lock(corelock_t *lock)
核间锁,要使用全局变量,参见举例。
无。
获取核间锁,同核时锁会嵌套,异核时非阻塞。成功获取锁会返回0,失败返回-1。
corelock_trylock(corelock_t *lock)
核间锁,要使用全局变量,参见举例。
返回值 | 描述 |
0 | 成功 |
非0 | 失败 |
核间锁解锁。
void corelock_unlock(corelock_t *lock)
核间锁,要使用全局变量,参见举例。
无。
注册系统输入回调函数,scanf时会调用该函数。系统默认使用UART3,如果需要修改UART则调用uart_debug_init函数,具体请到uart章节查看该函数。
void sys_register_getchar(sys_getchar_t getchar);
参数名称 | 描述 | 输入输出 |
getchar | 回调函数 | 输入 |
无。
注册系统输出回调函数,printf时会调用该函数。系统默认使用UART3,如果需要修改UART则调用uart_debug_init函数,具体请到uart章节查看该函数。
void sys_register_putchar(sys_putchar_t putchar)
参数名称 | 描述 | 输入输出 |
putchar | 回调函数 | 输入 |
无。
清理stdin缓存。
无。
无。
获取空闲内存大小。
size_t get_free_heap_size(void)
无。
空闲内存大小。
/* 1核在0核第二次释放锁的时候才会获取到锁,通过读cycle计算时间 */
#include
#include "bsp.h"
#include
#include "sysctl.h"
corelock_t lock;
uint64_t get_time(void)
{
uint64_t v_cycle = read_cycle();
return v_cycle * 1000000 / sysctl_clock_get_freq(SYSCTL_CLOCK_CPU);
}
int core1_function(void *ctx)
{
uint64_t core = current_coreid();
printf("Core %ld Hello worldn", core);
while(1)
{
uint64_t start = get_time();
corelock_lock(&lock);
printf("Core %ld Hello worldn", core);
sleep(1);
corelock_unlock(&lock);
uint64_t stop = get_time();
printf("Core %ld lock time is %ld usn",core, stop - start);
usleep(10);
}
}
int main(void)
{
uint64_t core = current_coreid();
printf("Core %ld Hello worldn", core);
register_core1(core1_function, NULL);
while(1)
{
corelock_lock(&lock);
sleep(1);
printf("1> Core %ld sleep 1n", core);
corelock_lock(&lock);
sleep(2);
printf("2> Core %ld sleep 2n", core);
printf("2> Core unlockn");
corelock_unlock(&lock);
sleep(1);
printf("1> Core unlockn");
corelock_unlock(&lock);
usleep(10);
}
}
相关数据类型、数据结构定义如下:
· core_function:CPU核调用的函数。
· spinlock_t:自旋锁。
· corelock_t:核间锁。
CPU核调用的函数。
typedef int (*core_function)(void *ctx);
自旋锁。
typedef struct _spinlock
{
int lock;
} spinlock_t;
核间锁。
typedef struct _corelock
{
spinlock_t lock;
int count;
int core;
} corelock_t;