MIPS Linux内核编译构建环境的搭建

217 篇文章 29 订阅
156 篇文章 16 订阅

AI的端侧应用离不开各种算力平台,目前形成了CPU+NPU,CPU+FPGA以及CPU+GPU的几类算力搭配,他们各有优势,也各有弱点。先看它们的共同点,从组合名字可以看出,那就是他们都依赖CPU,这是因为无论NPU,FPGA还是GPU,它们的架构特点决定了在流水线pipeline,逻辑控制方面要弱于CPU,它们要么作为专用算力加速器,没有完备的指令集系统,比如GPU有简单的流水线,可以完成取指,解码执行的操作,它的流水线适合做大量的SIMD并行数据计算,比如DP4a指令(4元素矢量点积),Intel、AMD、NVIDIA基本所有的GPU都支持它,但是无法完成复杂的执行流以及逻辑控制。另外还有一些没有取指流水等机制,无法自举执行指令,必须依赖CPU打配合,有点类似于FPU或者NEON单元,比如NPU就是这个样子,这类算力无法独立完成取指令执行的操作,运行依赖于CPU不断的喂指令,在设计中,必须配合CPU一起用。

关于流水线独立这一点,就连谷歌的TPU也不例外,TPU类似于FPU,而非可以自行取指令的GPU就是证明:

之前对与RISCV Vector指令,MIPS的FPU以及ARM的NEON都有过一些分析,但当时对AI没有太多概念,对AI如何使用这些单元提供的算力缺乏认识,分析完了感觉没有结果。现在对AI有了一些了解,尤其其中的算子实现对异构算力的设计需求有了一些自己的看法,这样反过来从需求方倒推,对ISA指令集的设计理解的会更加深刻。 所以觉得有必要结合AI,对一些主流的CPU ISA指令集特点做一个总结,包括最早接触的MIPS,Xlinux(FPGA)做过验证,之后是ARM,然后RISCV和DSP(Xtensa),ARM和RISCV也都跑过Altera FPGA。这里面RISCV是最精简的,软件上可以认为是MIPS的一个子集(可能Vector向量指令的区别会大一些),可以放在一起分析,感觉这是一个比较大的主题和工作量,万事开头难,先从重温MIPS开始,之后逐渐切入其它ISA,横向对比,纵向深挖,分析它们各自对AI加速提供的各类ISA级的机制以及背后的思考,个人的一些看法,可能不对,期待被打脸。

首先从搭建MIPS开发环境说起

MIPS编译工具链下载

MIPS官网:MIPS

比较滑稽的是,在MIPS官网主页上,一幅标题赫然写着:

We’re taking RISC-V to new heights(我们将RISC-V推向新的高度),以MIPS多年积累的技术实力,相信这个新“高度”不难实现。但是市场是残酷的,只见新人笑,哪闻旧人哭,曾经的产业一哥如今沦落到要为新近晚辈执鞭随镫,不禁让人喟叹。

MIPS不但是这样说的,还是这样做的,MIPS已经发布它的的首颗RISCV ISA CPU IP eVocore

更奇葩的是MIPS官网的副标题竟然是elevating riscv,几年前MIPS就已经宣布放弃MIPS拥抱RISCV,一代旗手沦落至此,令人唏嘘。

顺着导航,来到Developer Tools – MIPS,可以看到开发工具和开发模式。

现在有多没落,曾经就有多辉煌,从高端的64位MIPS服务器,到低端32位MCU级控制器,都可以看到MIPS的身影。

之后来到编译器下载链接:MIPS Compilers – MIPS

MIPS支持LLVM工具开发,不过我们这里用GCC

 我们将bare metal和Linux用户态工具链都下载下来,由于我们要编译的是Linux内核,究竟使用哪种工具链关系不大,任何一个都可以,我们解压:Codescape.GNU.Tools.Package.2019.02-05.for.MIPS.IMG.Linux.CentOS-6.x86_64.tar.gz

之后导出环境变量:

export PATH=$PATH:/home/caozilong/mips/mips-img-linux-gnu/2019.02-05/bin

之后,在任何一个目录,都可以直接输入mips-img-linux-gnu-gcc访问GCC了。

下载最新的Linux内核代码:

从kernel.org下载并解压:

编译

make ARCH=mips CROSS_COMPILE=mips-img-linux-gnu- malta_defconfig 
make ARCH=mips CROSS_COMPILE=mips-img-linux-gnu- menuconfig 
make -j4 ARCH=mips CROSS_COMPILE=mips-img-linux-gnu-

编译结果:

 反编译

mips-img-linux-gnu-objdump -d vmlinux>vmlinux.dis

从运行地址也可以看出,内核虚拟地址从0X80000000地址开始,这也是MIPS ISA地址分布的特征。

32位MIPS处理器将地址空间划分为四段,从低地址到搞地址,各段的特性如下:

1.用户模式,监管模式和核心模式都可以访问,用户模式下只能访问该段空间,需要通过MMU映射,其CACHE算法由页表中相应字段规定,在LINUX操作系统中,该段存放用户程序,动态连接库,程序堆栈等等。

2.kseg0,只能由核心模式访问,该段地址不通过MMU进行地址映射,其CACHE算法由CONFIG控制寄存器的K0域决定,KSEG0直接映射至低512MB物理地址空间,映射方法为抹去最高三位,在LINUX 系统中,该段用来存放内核代码,数据,异常入口。

3.kseg1,只能由和新模式访问,该段地址不通过MMU进行地址映射,也不通过CACHE进行缓存,KSEG1同样映射至低512M物理地址空间,映射方法为抹去高3位,KSEG1和KSEG0映射到相同的物理地址,但KSEG1的访问无需依赖CACHE,因此MIPS处理器程序计数器的复位地址0xbfc00000就落在KSEG1段,完成CACHE初始化等工作后,在跳转到KSEG0执行,可以利用CACHE提高访问速度,在LINUX操作系统中,该段存放启动ROM和IO寄存器等。

4.kseg2,由于核心模式和监管模式访问,通过MMU进行映射,CACHE 算法由页表决定,LINUX操作系统中,该段用于存放动态分配的内核数据。

编译完成,下一步准备用busybox构建一个小的文件系统,之后用qemu模拟器进行启动,就可以愉快的跑MIPS ISA了。

MIPS处理器复位

处理器的第一条指令,实际上是由复位信号控制的,但受限于各种其他因素,复位信号并没有对处理器内部的所有部分进行控制,例如TLB, CACHE等等。

MIPS处理器复位后的第一条指令将固定从地址0xbfc00000位置获取,这个过程是由处理器执行指针寄存器,也就是PC,被硬件复位为0xbfc00000而决定的。

对于MIPS来说,0xbfc00000这个虚拟地址属于直接映射且不经CACHE缓存的地址段,直接对应于物理地址的0x1fc00000,正是因为这个地址是直接映射的,不需要使用TLB转换,也不经过CACHE缓存,不需要使用CACHE结构,所以硬件上没有必要对TLB,CACHE这两个结构进行初始化,只需由硬件提供机制,软件在启动过程中在对其进行初始化即可。

环境搭建写到这里,下面简要分析一下MIPS版的Linux内核,从调度入手:

save_some会调用get_saved_sp从kernelsp结构中获取current对应的内核栈指针,而且是在 判断prev状态为user mode的时候才会这样做,这说明什么? 说明从用户空间通过系统调用进来的时刻,内核栈一定是空的,等待新来的客人。但在内核中睡眠的用户态线程,当前处于内核态,它们不需要这样的方式获取栈,判断依据是CP0_STATUS寄存器的CU0 BIT。

arch/mips/kernel/r4k_switch.S,相当于arm下的__switch_to,kernelsp维护者当前进程的内核态堆栈指针,铁打的营盘,流水的兵,任何用户态进程需要内核服务的时候,一猛子扎下来都要嫖一下kernelsp,发生点故事后再退回去,不留下一点云彩,所以用户态resume进来的时候,内核栈一定是空的,注意系统调用而非中断。

arch/mips/kernel/scall32-o32.S,相当于arm下的SVC异常入口,STI开中断,是宏定义。

 安装异常处理向量入口:

kernelsp的定义:

页目录基址寄存器

MIPS区别于ARM,RISCV和X86架构的一个重要不同点是,MIPS没有页目录基址寄存器,就拿其他三种架构来说,ARM有TTBR0,TTBR1寄存器,RISCV有SATP寄存器,X86有CR3寄存器。除了MIPS之外,还有另外一种架构也没有硬件基址寄存器,它就是OpenRISC架构。

我们拿出证据,关键就在switch_mm的实现:

ARM,check_and_switch_context将会进行TTBRX的设置。

X86,switch mm的调用栈将会调用write_cr3将新的页目录设置进硬件寄存器。

RISCV的switch_mm实现更直接,就在函数体中直接设置了SATB寄存器。

反观MIPS和openRISC:

MIPS:

openrisc: 两种架构都没有全局寄存器来存储PGD。

为什么呢?仔细分析这两类架构,我们可以看到两类架构的明显区别,MIPS和OPENRISC都是TLB软件重填的,而其他架构都是硬件重填TLB。

由于需要硬件来重填TLB,所以,必须要开出硬件寄存器,让硬件可以根据寄存器的信息去做page walk,但是软件重填就不需要了,记录在一个某个地方,在TLBREFILL 异常中访问这个位置就可以了。

从图中代码中可以看到,openRISC将下个进程的PGD放进了current_pgd这个每CPU变量中(TTBR和CR3以及SATP也都是每CPU寄存器),而在TLBREFILL异常中,将会读取current_pgd,获取当前页目录基地址

dtlb refill handler:

 itlb refill hander:

MIPS 遵循同样的套路,在switch_mm中将下个进程的PGD记录在内存中:

和OPENRISC不同的是MIPS的TLB重填函数是由函数生成的。

关于TLB软硬件refill和基址寄存器的内在逻辑,请教了社区MIPS专家,理解大抵是对的。

而pgd_current,恰好就是switch_mm函数中,调用的tlbmiss_handler_setup_pgd函数设置的,只是比较隐晦的是,tlbmiss_handler_setup_pgd也是由代码动态生成的。至此,逻辑闭环。

MIPS ASID:

用一个不太恰当的比喻,任务切换的过程就是给CPU“洗脑”的过程。既然是洗脑,下一个执行的任务就要从头开始记忆,是不是之前的记忆都没有用了呢,显然不是,有的时候,具备记忆移植或者迁移的能力的CPU,执行效率会更高,ASID就是做这个的。MIPS的ASID空间只有8位,也就是允许同时256个进程共享TLB,如果发生overflow,就需要刷新TLB开始新的cycle.

处理ASID overflow的数据结构,每个version表示一个asid cycle。

ASID切换逻辑的cmodel仿真代码:

#include <stdio.h>
#include <stdlib.h>

unsigned int cpu_cache = 0;
int flush_tlb_cnt = 0;

void switch_mm(unsigned int next_id)
{
    if((next_id ^ cpu_cache) & 0xffffff00)
    {
        if(!((cpu_cache += 256)&0x000000ff))
        {
             printf("%s line %d, flush tlb %d times.\n", __func__, __LINE__, ++ flush_tlb_cnt);
        }
        next_id = cpu_cache;
    }

    printf("%s line %d. next_id = 0x%08x.\n", __func__, __LINE__, next_id);
}

int main(void)
{
    unsigned int next_id = 0;

    for (next_id = 0; next_id < 1000; next_id ++)
        switch_mm(next_id);

    switch_mm(666);
    return 0;
}

上面说的PTW(Page Table Walker)应该就是这里的了吧,HTW官方文档表示hardware table walker,表示硬件页表遍历器。

所以较新的MIPS也是有存放PGD的硬件寄存器的。


结束!

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papaofdoudou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值